From: Jan Prusakowski via Linux-f2fs-devel <linux-f2fs-devel@lists.sourceforge.net>
To: Jaegeuk Kim <jaegeuk@kernel.org>, Chao Yu <chao@kernel.org>
Cc: linux-kernel@vger.kernel.org, linux-f2fs-devel@lists.sourceforge.net
Subject: [f2fs-dev] [PATCH] f2fs: fix stale extent cache in inode page after truncation
Date: Mon, 19 Oct 2026 13:02:41 +0000 [thread overview]
Message-ID: <20261019130241.3751829-1-jprusakowski@google.com> (raw)
During generic/475 (fsstress with dm-error), orphan inode recovery on
remount can fail in sanity_check_extent_cache() with a stale on-disk
extent (ri->i_ext) whose blocks have already been invalidated in the SIT
bitmap:
F2FS-fs (dm-0): Inconsistent error blkaddr:212341, sit bitmap:0
F2FS-fs (dm-0): sanity_check_extent_cache: inode (ino=14f2) extent info
[212341, 109, 1] is incorrect, run fsck to fix
This happens for two reasons:
1. In f2fs_drop_inode(), when an unlinked inode has I_SYNC set by a
concurrent __writeback_single_inode(), f2fs_destroy_extent_node() only
frees the rbtree nodes while leaving et->largest intact and without
setting FI_NO_EXTENT. Concurrent writeback in f2fs_write_inode() ->
f2fs_update_inode_page() can race with f2fs_truncate() and write a
stale et->largest into ri->i_ext after blocks have been invalidated in
the SIT bitmap. Call f2fs_drop_extent_tree() instead so FI_NO_EXTENT
is set and et->largest.len is zeroed before truncation.
2. During truncation (including f2fs_evict_inode() -> f2fs_delete_inode()
-> f2fs_truncate() where I_FREEING is set and f2fs_pre_evict_inode()
has already destroyed the in-memory extent_tree),
f2fs_truncate_data_blocks_range() clears ri->i_addr[] directly in the
inode cached block (marking ientry dirty) and invalidates the blocks
in the SIT bitmap, but does not call f2fs_update_inode() to
synchronize ri->i_ext in ientry. Because igrab() returns NULL for an
I_FREEING inode, both f2fs_sync_inode_meta() and flush_dirty_inode()
skip the inode if a checkpoint or node writeback occurs before
f2fs_remove_inode_page() completes, persisting ientry with truncated
ri->i_addr[] and a stale ri->i_ext.
Fix this by:
- Calling f2fs_drop_extent_tree() instead of f2fs_destroy_extent_node()
in f2fs_drop_inode().
- Updating the inode cached block via f2fs_update_inode() in
f2fs_truncate_data_blocks_range() when truncating blocks in the inode
cached block, as well as in f2fs_do_truncate_blocks() and
f2fs_truncate_inode_blocks() when FI_DIRTY_INODE is set.
Fixes: 3e72f721390d ("f2fs: use extent_cache by default")
Assisted-by: LLM
Signed-off-by: Jan Prusakowski <jprusakowski@google.com>
---
fs/f2fs/file.c | 4 ++++
fs/f2fs/node.c | 5 +++++
fs/f2fs/super.c | 2 +-
3 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index 3b5ad281b518..b7a404d0efc6 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -796,6 +796,8 @@ void f2fs_truncate_data_blocks_range(struct dnode_of_data *dn, int count)
f2fs_update_read_extent_cache_range(dn, fofs, 0, len);
f2fs_update_age_extent_cache_range(dn, fofs, len);
dec_valid_block_count(sbi, dn->inode, nr_free);
+ if (IS_INODE(sbi, dn->node_entry))
+ f2fs_update_inode(dn->inode, dn->node_entry);
}
dn->ofs_in_node = ofs;
@@ -885,6 +887,7 @@ int f2fs_do_truncate_blocks(struct inode *inode, u64 from, bool lock)
f2fs_update_time(sbi, REQ_TIME);
f2fs_drop_extent_tree(inode);
+ f2fs_update_inode(inode, ientry);
f2fs_put_cache(ientry, true);
goto out;
@@ -892,6 +895,7 @@ int f2fs_do_truncate_blocks(struct inode *inode, u64 from, bool lock)
if (f2fs_has_inline_data(inode)) {
f2fs_truncate_inline_inode(inode, ientry, from);
+ f2fs_update_inode(inode, ientry);
f2fs_put_cache(ientry, true);
truncate_page = true;
goto out;
diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c
index 424e95ca5878..27ccc175c282 100644
--- a/fs/f2fs/node.c
+++ b/fs/f2fs/node.c
@@ -1315,6 +1315,11 @@ int f2fs_truncate_inode_blocks(struct inode *inode, pgoff_t from)
nofs += err;
}
fail:
+ if (is_inode_flag_set(inode, FI_DIRTY_INODE)) {
+ f2fs_lock_cache(entry);
+ f2fs_update_inode(inode, entry);
+ f2fs_unlock_cache(entry);
+ }
f2fs_put_cache(entry, false);
trace_f2fs_truncate_inode_blocks_exit(inode, err);
return err > 0 ? 0 : err;
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
index 773b12ea29c0..acefc1378a6a 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -1888,7 +1888,7 @@ static int f2fs_drop_inode(struct inode *inode)
spin_unlock(&inode->i_lock);
/* should remain fi->extent_tree for writepage */
- f2fs_destroy_extent_node(inode);
+ f2fs_drop_extent_tree(inode);
sb_start_intwrite(inode->i_sb);
f2fs_i_size_write(inode, 0);
--
2.56.0.176.g3575e65682-goog
_______________________________________________
Linux-f2fs-devel mailing list
Linux-f2fs-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel
reply other threads:[~2026-10-19 13:02 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261019130241.3751829-1-jprusakowski@google.com \
--to=linux-f2fs-devel@lists.sourceforge.net \
--cc=chao@kernel.org \
--cc=jaegeuk@kernel.org \
--cc=jprusakowski@google.com \
--cc=linux-kernel@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.