gfs2: Fix end-of-file handling in gfs2_page_mkwrite

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>
This commit is contained in:
Andreas Gruenbacher 2019-11-06 14:09:25 +00:00
parent f53056c430
commit 184b4e6085

View 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_inode *ip = GFS2_I(inode);
struct gfs2_sbd *sdp = GFS2_SB(inode); struct gfs2_sbd *sdp = GFS2_SB(inode);
struct gfs2_alloc_parms ap = { .aflags = 0, }; struct gfs2_alloc_parms ap = { .aflags = 0, };
unsigned long last_index; u64 offset = page_offset(page);
u64 pos = page_offset(page);
unsigned int data_blocks, ind_blocks, rblocks; unsigned int data_blocks, ind_blocks, rblocks;
struct gfs2_holder gh; struct gfs2_holder gh;
unsigned int length;
loff_t size; loff_t size;
int ret; int ret;
@ -436,20 +436,39 @@ static vm_fault_t gfs2_page_mkwrite(struct vm_fault *vmf)
if (ret) if (ret)
goto out; goto out;
gfs2_size_hint(vmf->vma->vm_file, pos, PAGE_SIZE);
gfs2_holder_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &gh); gfs2_holder_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &gh);
ret = gfs2_glock_nq(&gh); ret = gfs2_glock_nq(&gh);
if (ret) if (ret)
goto out_uninit; 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 */ /* Update file times before taking page lock */
file_update_time(vmf->vma->vm_file); 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(GLF_DIRTY, &ip->i_gl->gl_flags);
set_bit(GIF_SW_PAGED, &ip->i_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); lock_page(page);
if (!PageUptodate(page) || page->mapping != inode->i_mapping) { if (!PageUptodate(page) || page->mapping != inode->i_mapping) {
ret = -EAGAIN; ret = -EAGAIN;
@ -462,7 +481,7 @@ static vm_fault_t gfs2_page_mkwrite(struct vm_fault *vmf)
if (ret) if (ret)
goto out_unlock; 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; ap.target = data_blocks + ind_blocks;
ret = gfs2_quota_lock_check(ip, &ap); ret = gfs2_quota_lock_check(ip, &ap);
if (ret) if (ret)
@ -483,13 +502,6 @@ static vm_fault_t gfs2_page_mkwrite(struct vm_fault *vmf)
goto out_trans_fail; goto out_trans_fail;
lock_page(page); 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; ret = -EAGAIN;
/* If truncated, we must retry the operation, we may have raced /* If truncated, we must retry the operation, we may have raced
* with the glock demotion code. * 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)) if (gfs2_is_stuffed(ip))
ret = gfs2_unstuff_dinode(ip, page); ret = gfs2_unstuff_dinode(ip, page);
if (ret == 0) if (ret == 0)
ret = gfs2_allocate_page_backing(page, PAGE_SIZE); ret = gfs2_allocate_page_backing(page, length);
out_trans_end: out_trans_end:
if (ret) if (ret)