From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B936C1F8755 for ; Sun, 9 Aug 2026 01:19:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786238363; cv=none; b=NDun99PYOYQDkyBiAcwsJSJVnczzh3lTJTaG1ywYTy1qtFIArYRhwJd99zPbctMcQf6rNfd/CuubWr1SwJhFQ6H/0anzoAjYfOu2xWY0rrFqe+RxB1ciNTOivA0gA61RGbvUAV026t0kJ2uXhi8/XrOB77fUR0vaZCYNR8vukb4= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786238363; c=relaxed/simple; bh=o3ZM1p8e0OgCRHHcC7RAQnJHQe07UNQOHahYFsY76r4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=KmCod7v1VhNrZRoTVaizwOJ9cipaI1Z6YV09D6+b84mfgUBcQ7pfq4rD3Cq9g1KKbivpX0oKhSpI7RGI59wz2nm3yLKgR/iCsg34xB394zUmfftoN8d+/790EaUdWZJhE5bE/TJ+Ygkc7m8S4TMFVU68ibrpjzrl+BDS21FNXHs= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Ct1xf1YB; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Ct1xf1YB" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 55B3C1F00A3D; Sun, 9 Aug 2026 01:19:20 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1786238361; bh=961aaWN/im4Z7DBF4zaZ/SbT5+y0jXH7B0a7wAN6gb0=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=Ct1xf1YBMWSmQ3iZrtgt6dYMPX231MWRKi/0GWwiXodGqI7NyAsMTVv7+sPRIwznl Rek4c4ygf2udG5CRHtE2Mf9rMqb1XFvn4Tai29puLtTOMGeJeXjm9tYCZ+Gkh9VrUX RAqBqqn02Qaama+bJmlR5F7JR4gQCZtm9uYS9xLRp2EvvoZgk0/L2R6TbeLCXtnvnG 7GsWM3N8o0sP0n1fGyBz+C00+yMH6AgR/nzYVrm8KE5SV9oFDJuLhEyFFtTY/HtE7M Em0UL3owS81JTuSxW7cjJOoq5/fNXmdP8wJqOuI0SRbm6NA7zMEnnQuRp4va4c27+C 3nFHS0gb+VXyQ== From: Chao Yu To: jaegeuk@kernel.org Cc: linux-f2fs-devel@lists.sourceforge.net, linux-kernel@vger.kernel.org, Chao Yu , stable@kernel.org Subject: [PATCH v4 2/2] f2fs: fix to zero post-EOF data when extending file size Date: Sun, 9 Aug 2026 09:19:14 +0800 Message-ID: <20260809011914.565936-2-chao@kernel.org> X-Mailer: git-send-email 2.55.0.654.g21b8a5bc05-goog In-Reply-To: <20260809011914.565936-1-chao@kernel.org> References: <20260809011914.565936-1-chao@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 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 --- 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 #include -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