]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
gfs2: Fix end-of-file handling in gfs2_page_mkwrite
authorAndreas Gruenbacher <agruenba@redhat.com>
Wed, 6 Nov 2019 14:09:25 +0000 (14:09 +0000)
committerAndreas Gruenbacher <agruenba@redhat.com>
Thu, 7 Nov 2019 20:02:35 +0000 (21:02 +0100)
When the filesystem block size is smaller than the page size, the last
page may contain blocks that lie entirely beyond the end of the file.
Make sure to only allocate blocks that lie at least partially in the
file.  Allocating blocks beyond that isn't useful, and what's more, they
will not be zeroed out and may end up containing random data.

With that change in place, make sure we'll still always unstuff stuffed
inodes: iomap_writepage and iomap_writepages currently can't handle
stuffed files.

In addition, simplify and move the end-of-file check further to the top
in gfs2_page_mkwrite to avoid weird side effects like unstuffing when
we're not.

Fixes xfstest generic/263.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
fs/gfs2/file.c

index 30b857017fd3fe5a2d46fd71d3ad09f4118214f5..92524a946d038887db95762e13efbbc6e15edb1c 100644 (file)
@@ -423,10 +423,10 @@ static vm_fault_t gfs2_page_mkwrite(struct vm_fault *vmf)
        struct gfs2_inode *ip = GFS2_I(inode);
        struct gfs2_sbd *sdp = GFS2_SB(inode);
        struct gfs2_alloc_parms ap = { .aflags = 0, };
-       unsigned long last_index;
-       u64 pos = page_offset(page);
+       u64 offset = page_offset(page);
        unsigned int data_blocks, ind_blocks, rblocks;
        struct gfs2_holder gh;
+       unsigned int length;
        loff_t size;
        int ret;
 
@@ -436,20 +436,39 @@ static vm_fault_t gfs2_page_mkwrite(struct vm_fault *vmf)
        if (ret)
                goto out;
 
-       gfs2_size_hint(vmf->vma->vm_file, pos, PAGE_SIZE);
-
        gfs2_holder_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &gh);
        ret = gfs2_glock_nq(&gh);
        if (ret)
                goto out_uninit;
 
+       /* Check page index against inode size */
+       size = i_size_read(inode);
+       if (offset >= size) {
+               ret = -EINVAL;
+               goto out_unlock;
+       }
+
        /* Update file times before taking page lock */
        file_update_time(vmf->vma->vm_file);
 
+       /* page is wholly or partially inside EOF */
+       if (offset > size - PAGE_SIZE)
+               length = offset_in_page(size);
+       else
+               length = PAGE_SIZE;
+
+       gfs2_size_hint(vmf->vma->vm_file, offset, length);
+
        set_bit(GLF_DIRTY, &ip->i_gl->gl_flags);
        set_bit(GIF_SW_PAGED, &ip->i_flags);
 
-       if (!gfs2_write_alloc_required(ip, pos, PAGE_SIZE)) {
+       /*
+        * iomap_writepage / iomap_writepages currently don't support inline
+        * files, so always unstuff here.
+        */
+
+       if (!gfs2_is_stuffed(ip) &&
+           !gfs2_write_alloc_required(ip, offset, length)) {
                lock_page(page);
                if (!PageUptodate(page) || page->mapping != inode->i_mapping) {
                        ret = -EAGAIN;
@@ -462,7 +481,7 @@ static vm_fault_t gfs2_page_mkwrite(struct vm_fault *vmf)
        if (ret)
                goto out_unlock;
 
-       gfs2_write_calc_reserv(ip, PAGE_SIZE, &data_blocks, &ind_blocks);
+       gfs2_write_calc_reserv(ip, length, &data_blocks, &ind_blocks);
        ap.target = data_blocks + ind_blocks;
        ret = gfs2_quota_lock_check(ip, &ap);
        if (ret)
@@ -483,13 +502,6 @@ static vm_fault_t gfs2_page_mkwrite(struct vm_fault *vmf)
                goto out_trans_fail;
 
        lock_page(page);
-       ret = -EINVAL;
-       size = i_size_read(inode);
-       last_index = (size - 1) >> PAGE_SHIFT;
-       /* Check page index against inode size */
-       if (size == 0 || (page->index > last_index))
-               goto out_trans_end;
-
        ret = -EAGAIN;
        /* If truncated, we must retry the operation, we may have raced
         * with the glock demotion code.
@@ -502,7 +514,7 @@ static vm_fault_t gfs2_page_mkwrite(struct vm_fault *vmf)
        if (gfs2_is_stuffed(ip))
                ret = gfs2_unstuff_dinode(ip, page);
        if (ret == 0)
-               ret = gfs2_allocate_page_backing(page, PAGE_SIZE);
+               ret = gfs2_allocate_page_backing(page, length);
 
 out_trans_end:
        if (ret)