* [PATCH v4 1/2] f2fs: fix to off-by-one issue in f2fs_zero_post_eof_page()
@ 2026-08-09 1:19 Chao Yu
2026-08-09 1:19 ` [PATCH v4 2/2] f2fs: fix to zero post-EOF data when extending file size Chao Yu
0 siblings, 1 reply; 3+ messages in thread
From: Chao Yu @ 2026-08-09 1:19 UTC (permalink / raw)
To: jaegeuk; +Cc: linux-f2fs-devel, linux-kernel, Chao Yu, stable
Otherwise, it will drop one more page after new_size which is not
necessary.
Cc: stable@kernel.org
Fixes: ba8dac350faf ("f2fs: fix to zero post-eof page")
Signed-off-by: Chao Yu <chao@kernel.org>
---
v4:
- no changes
fs/f2fs/file.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index b99d9cdf9ba7..be319a2e9830 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -50,7 +50,7 @@ static void f2fs_zero_post_eof_page(struct inode *inode,
if (lock)
filemap_invalidate_lock(inode->i_mapping);
/* zero or drop pages only in range of [old_size, new_size] */
- truncate_inode_pages_range(inode->i_mapping, old_size, new_size);
+ truncate_inode_pages_range(inode->i_mapping, old_size, new_size - 1);
if (lock)
filemap_invalidate_unlock(inode->i_mapping);
}
--
2.49.0
^ permalink raw reply related [flat|nested] 3+ messages in thread* [PATCH v4 2/2] f2fs: fix to zero post-EOF data when extending file size 2026-08-09 1:19 [PATCH v4 1/2] f2fs: fix to off-by-one issue in f2fs_zero_post_eof_page() Chao Yu @ 2026-08-09 1:19 ` Chao Yu 2026-08-10 8:52 ` [f2fs-dev] " Zhiguo Niu 0 siblings, 1 reply; 3+ messages in thread From: Chao Yu @ 2026-08-09 1:19 UTC (permalink / raw) To: jaegeuk; +Cc: linux-f2fs-devel, linux-kernel, Chao Yu, stable 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, meanwhile, tagging the inode with FI_ZERO_POST_EOF, so that following checkpoint() and fsync() can persist the page contain 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> --- v4: - fix to receive return value of f2fs_zero_post_eof_page() w/ @err rather than @ret in f2fs_vm_page_mkwrite(). fs/f2fs/file.c | 100 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 84 insertions(+), 16 deletions(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index be319a2e9830..446d2328f975 100644 --- a/fs/f2fs/file.c +++ b/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); @@ -1175,8 +1224,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) @@ -1185,6 +1238,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) @@ -1296,7 +1350,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; @@ -1583,7 +1639,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); @@ -1591,6 +1649,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; @@ -1712,7 +1771,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; @@ -1847,7 +1908,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) { @@ -1865,6 +1928,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) @@ -1907,7 +1971,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); @@ -5023,8 +5089,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; } -- 2.49.0 ^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [f2fs-dev] [PATCH v4 2/2] f2fs: fix to zero post-EOF data when extending file size 2026-08-09 1:19 ` [PATCH v4 2/2] f2fs: fix to zero post-EOF data when extending file size Chao Yu @ 2026-08-10 8:52 ` Zhiguo Niu 0 siblings, 0 replies; 3+ messages in thread From: Zhiguo Niu @ 2026-08-10 8:52 UTC (permalink / raw) To: Chao Yu; +Cc: jaegeuk, stable, linux-kernel, linux-f2fs-devel Chao Yu via Linux-f2fs-devel <linux-f2fs-devel@lists.sourceforge.net> 于2026年8月9日周日 09:21写道: > > 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, meanwhile, tagging the inode > with FI_ZERO_POST_EOF, so that following checkpoint() and fsync() can persist Hi Chao, It looks like this version doesn't have this flag "FI_ZERO_POST_EOF"? thanks! > the page contain 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> > --- > v4: > - fix to receive return value of f2fs_zero_post_eof_page() w/ @err rather than > @ret in f2fs_vm_page_mkwrite(). > fs/f2fs/file.c | 100 +++++++++++++++++++++++++++++++++++++++++-------- > 1 file changed, 84 insertions(+), 16 deletions(-) > > diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c > index be319a2e9830..446d2328f975 100644 > --- a/fs/f2fs/file.c > +++ b/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); > @@ -1175,8 +1224,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) > @@ -1185,6 +1238,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) > @@ -1296,7 +1350,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; > @@ -1583,7 +1639,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); > @@ -1591,6 +1649,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; > @@ -1712,7 +1771,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; > @@ -1847,7 +1908,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) { > @@ -1865,6 +1928,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) > @@ -1907,7 +1971,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); > > @@ -5023,8 +5089,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; > } > > -- > 2.49.0 > > > > _______________________________________________ > Linux-f2fs-devel mailing list > Linux-f2fs-devel@lists.sourceforge.net > https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel ^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-08-10 8:53 UTC | newest] Thread overview: 3+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-09 1:19 [PATCH v4 1/2] f2fs: fix to off-by-one issue in f2fs_zero_post_eof_page() Chao Yu 2026-08-09 1:19 ` [PATCH v4 2/2] f2fs: fix to zero post-EOF data when extending file size Chao Yu 2026-08-10 8:52 ` [f2fs-dev] " Zhiguo Niu
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox