mirror of
https://github.com/torvalds/linux.git
synced 2026-09-24 06:24:02 +02:00
f2fs: fix to zero post-EOF data when extending file size
generic/794 4s ... - output mismatch (see /share/git/fstests/results//generic/794.out.bad)
--- tests/generic/794.out 2026-06-12 08:46:32.766426241 +0800
+++ /share/git/fstests/results//generic/794.out.bad 2026-07-05 18:32:55.000000000 +0800
@@ -1,4 +1,16 @@
QA output created by 794
append_write
+FAIL: non-zero data in gap [4080,4096) after shutdown+remount
+000000 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a >ZZZZZZZZZZZZZZZZ<
+*
+001000
truncate_up
...
(Run 'diff -u /share/git/fstests/tests/generic/794.out /share/git/fstests/results//generic/794.out.bad' to see the entire diff)
Ran: generic/794
Failures: generic/794
Failed 1 of 1 tests
Steps of generic/794:
1. write 4096 bytes to file w/ 0x5a
2. use fiemap to get PBA of first block in file
3. truncate file to 4080
4. umount; write 4096 bytes to file w/ 0x5a directly via PBA; mount
5. extend filesize via
a) append 4096 from offset 4096, or
b) truncate 8192, or
c) fallocate 4096 from offset 4096
6. verify the gap is zeroed in memory [4080,4096)
7. sync range 4096 from offset 4096; shutdown -f (flush meta before shutdown)
8. umount; mount; verify [4080,4096) is zeroed or not.
When extending file size (e.g. via truncate, fallocate, or write) across an
unaligned EOF boundary, we need to ensure that post-EOF data in the partial
page is zeroed out in pagecache and marked dirty, then writeback the cache to
persist zeroed data before committing inode w/ updated i_size.
This help to prevent stale disk data beyond the previous EOF from being exposed
after remounting or crash recovery.
Since f2fs is a LFS filesystem, we only support direct write via PBA in pinfile,
and pinfile has section-aligned filesize, so in Android, there should no problem,
but for other usage in different environment, let's fix this w/ fsync_mode=strict
mount option.
Cc: stable@kernel.org
Signed-off-by: Chao Yu <chao@kernel.org>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
This commit is contained in:
parent
b2205d3cfd
commit
5eced87b7d
100
fs/f2fs/file.c
100
fs/f2fs/file.c
|
|
@ -36,16 +36,52 @@
|
|||
#include <trace/events/f2fs.h>
|
||||
#include <uapi/linux/f2fs.h>
|
||||
|
||||
static void f2fs_zero_post_eof_page(struct inode *inode,
|
||||
loff_t new_size, bool lock)
|
||||
static int fill_zero(struct inode *inode, pgoff_t index,
|
||||
loff_t start, loff_t len);
|
||||
|
||||
static int do_zero_post_eof_page(struct inode *inode, loff_t new_size)
|
||||
{
|
||||
loff_t old_size = i_size_read(inode);
|
||||
unsigned int offset, len;
|
||||
pgoff_t index;
|
||||
int err;
|
||||
|
||||
offset = old_size & (PAGE_SIZE - 1);
|
||||
|
||||
if (!offset)
|
||||
return 0;
|
||||
|
||||
len = min_t(loff_t, PAGE_SIZE - offset, new_size - old_size);
|
||||
index = old_size >> PAGE_SHIFT;
|
||||
|
||||
if (f2fs_has_inline_data(inode)) {
|
||||
/* data post eof should be always zero */
|
||||
if (new_size <= MAX_INLINE_DATA(inode))
|
||||
return 0;
|
||||
err = f2fs_convert_inline_inode(inode);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
err = fill_zero(inode, index, offset, len);
|
||||
if (err)
|
||||
return err;
|
||||
return filemap_write_and_wait_range(inode->i_mapping,
|
||||
old_size, old_size + len - 1);
|
||||
}
|
||||
|
||||
static int f2fs_zero_post_eof_page(struct inode *inode,
|
||||
loff_t new_size, bool lock, bool writeback)
|
||||
{
|
||||
loff_t old_size = i_size_read(inode);
|
||||
bool strict =
|
||||
F2FS_OPTION(F2FS_I_SB(inode)).fsync_mode == FSYNC_MODE_STRICT;
|
||||
|
||||
if (old_size >= new_size)
|
||||
return;
|
||||
return 0;
|
||||
|
||||
if (mapping_empty(inode->i_mapping))
|
||||
return;
|
||||
if (!strict && mapping_empty(inode->i_mapping))
|
||||
return 0;
|
||||
|
||||
if (lock)
|
||||
filemap_invalidate_lock(inode->i_mapping);
|
||||
|
|
@ -53,6 +89,16 @@ static void f2fs_zero_post_eof_page(struct inode *inode,
|
|||
truncate_inode_pages_range(inode->i_mapping, old_size, new_size - 1);
|
||||
if (lock)
|
||||
filemap_invalidate_unlock(inode->i_mapping);
|
||||
|
||||
if (!writeback || !strict)
|
||||
return 0;
|
||||
/*
|
||||
* In fsync_mode=strict, when we expand an unaligned EOF size, we
|
||||
* should zero post EOF data and writeback the data immediately,
|
||||
* so that it can avoid exposing stale data after metadata flush
|
||||
* and POR.
|
||||
*/
|
||||
return do_zero_post_eof_page(inode, new_size);
|
||||
}
|
||||
|
||||
static vm_fault_t f2fs_filemap_fault(struct vm_fault *vmf)
|
||||
|
|
@ -132,7 +178,10 @@ static vm_fault_t f2fs_vm_page_mkwrite(struct vm_fault *vmf)
|
|||
|
||||
f2fs_bug_on(sbi, f2fs_has_inline_data(inode));
|
||||
|
||||
f2fs_zero_post_eof_page(inode, (folio->index + 1) << PAGE_SHIFT, true);
|
||||
err = f2fs_zero_post_eof_page(inode,
|
||||
(folio->index + 1) << PAGE_SHIFT, true, false);
|
||||
if (err)
|
||||
goto out_pagefault;
|
||||
|
||||
file_update_time(vmf->vma->vm_file);
|
||||
filemap_invalidate_lock_shared(inode->i_mapping);
|
||||
|
|
@ -189,7 +238,7 @@ static vm_fault_t f2fs_vm_page_mkwrite(struct vm_fault *vmf)
|
|||
|
||||
out_sem:
|
||||
filemap_invalidate_unlock_shared(inode->i_mapping);
|
||||
|
||||
out_pagefault:
|
||||
sb_end_pagefault(inode->i_sb);
|
||||
out:
|
||||
ret = vmf_fs_error(err);
|
||||
|
|
@ -1182,8 +1231,12 @@ int f2fs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
|
|||
f2fs_down_write(&fi->i_gc_rwsem[WRITE]);
|
||||
filemap_invalidate_lock(inode->i_mapping);
|
||||
|
||||
if (attr->ia_size > old_size)
|
||||
f2fs_zero_post_eof_page(inode, attr->ia_size, false);
|
||||
if (attr->ia_size > old_size) {
|
||||
err = f2fs_zero_post_eof_page(inode,
|
||||
attr->ia_size, false, true);
|
||||
if (err)
|
||||
goto err_out;
|
||||
}
|
||||
truncate_setsize(inode, attr->ia_size);
|
||||
|
||||
if (attr->ia_size <= old_size)
|
||||
|
|
@ -1192,6 +1245,7 @@ int f2fs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
|
|||
* do not trim all blocks after i_size if target size is
|
||||
* larger than i_size.
|
||||
*/
|
||||
err_out:
|
||||
filemap_invalidate_unlock(inode->i_mapping);
|
||||
f2fs_up_write(&fi->i_gc_rwsem[WRITE]);
|
||||
if (err)
|
||||
|
|
@ -1303,7 +1357,9 @@ static int f2fs_punch_hole(struct inode *inode, loff_t offset, loff_t len)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
f2fs_zero_post_eof_page(inode, offset + len, true);
|
||||
ret = f2fs_zero_post_eof_page(inode, offset + len, true, false);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pg_start = ((unsigned long long) offset) >> PAGE_SHIFT;
|
||||
pg_end = ((unsigned long long) offset + len) >> PAGE_SHIFT;
|
||||
|
|
@ -1590,7 +1646,9 @@ static int f2fs_do_collapse(struct inode *inode, loff_t offset, loff_t len)
|
|||
f2fs_down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
|
||||
filemap_invalidate_lock(inode->i_mapping);
|
||||
|
||||
f2fs_zero_post_eof_page(inode, offset + len, false);
|
||||
ret = f2fs_zero_post_eof_page(inode, offset + len, false, false);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
|
||||
f2fs_lock_op(sbi, &lc);
|
||||
f2fs_drop_extent_tree(inode);
|
||||
|
|
@ -1598,6 +1656,7 @@ static int f2fs_do_collapse(struct inode *inode, loff_t offset, loff_t len)
|
|||
ret = __exchange_data_block(inode, inode, end, start, nrpages - end, true);
|
||||
f2fs_unlock_op(sbi, &lc);
|
||||
|
||||
out_unlock:
|
||||
filemap_invalidate_unlock(inode->i_mapping);
|
||||
f2fs_up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
|
||||
return ret;
|
||||
|
|
@ -1719,7 +1778,9 @@ static int f2fs_zero_range(struct inode *inode, loff_t offset, loff_t len,
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
f2fs_zero_post_eof_page(inode, offset + len, true);
|
||||
ret = f2fs_zero_post_eof_page(inode, offset + len, true, false);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pg_start = ((unsigned long long) offset) >> PAGE_SHIFT;
|
||||
pg_end = ((unsigned long long) offset + len) >> PAGE_SHIFT;
|
||||
|
|
@ -1854,7 +1915,9 @@ static int f2fs_insert_range(struct inode *inode, loff_t offset, loff_t len)
|
|||
f2fs_down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
|
||||
filemap_invalidate_lock(mapping);
|
||||
|
||||
f2fs_zero_post_eof_page(inode, offset + len, false);
|
||||
ret = f2fs_zero_post_eof_page(inode, offset + len, false, false);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
truncate_pagecache(inode, offset);
|
||||
|
||||
while (!ret && idx > pg_start) {
|
||||
|
|
@ -1872,6 +1935,7 @@ static int f2fs_insert_range(struct inode *inode, loff_t offset, loff_t len)
|
|||
idx + delta, nr, false);
|
||||
f2fs_unlock_op(sbi, &lc);
|
||||
}
|
||||
out_unlock:
|
||||
filemap_invalidate_unlock(mapping);
|
||||
f2fs_up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
|
||||
if (ret)
|
||||
|
|
@ -1914,7 +1978,9 @@ static int f2fs_expand_inode_data(struct inode *inode, loff_t offset,
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
f2fs_zero_post_eof_page(inode, offset + len, true);
|
||||
err = f2fs_zero_post_eof_page(inode, offset + len, true, true);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
f2fs_balance_fs(sbi, true);
|
||||
|
||||
|
|
@ -5285,8 +5351,10 @@ static ssize_t f2fs_write_checks(struct kiocb *iocb, struct iov_iter *from)
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
f2fs_zero_post_eof_page(inode,
|
||||
iocb->ki_pos + iov_iter_count(from), true);
|
||||
err = f2fs_zero_post_eof_page(inode,
|
||||
iocb->ki_pos + iov_iter_count(from), true, true);
|
||||
if (err)
|
||||
return err;
|
||||
return count;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user