Re: [f2fs-dev] [PATCH v2] f2fs: evict: truncate page cache before clear_inode
From: kth5965
Date: Tue Mar 31 2026 - 11:27:07 EST
Sorry for the late reply.
I worked in that direction, and the fix ended up touching a bit more than I first expected.
One part that grew a bit was the empty/no-data inline conversion side.
To handle that case cleanly, I ended up consolidating part of the inline-state teardown so the no-data path and the normal inline-conversion cleanup follow the same path.
This also lets f2fs_convert_inline_inode() avoid grabbing folio #0 in the empty inline case.
With the current version, the reproducer no longer reaches the late clear_inode() BUG and instead fails earlier with Failed to read root inode.
If you think that cleanup change should be trimmed or split out, please let me know.
The current diff is below.
diff --git a/fs/f2fs/inline.c b/fs/f2fs/inline.c
index 0a1052d5ee62..89e8a0fbb923 100644
--- a/fs/f2fs/inline.c
+++ b/fs/f2fs/inline.c
@@ -53,8 +53,8 @@ bool f2fs_sanity_check_inline_data(struct inode *inode, struct folio *ifolio)
if (!f2fs_has_inline_data(inode))
return false;
- if (inode_has_blocks(inode, ifolio))
- return false;
+ if (!f2fs_exist_data(inode) && inode_has_blocks(inode, ifolio))
+ return true;
if (!support_inline_data(inode))
return true;
@@ -142,6 +142,17 @@ int f2fs_read_inline_data(struct inode *inode, struct folio *folio)
return 0;
}
+static void f2fs_clear_inline_inode(struct dnode_of_data *dn)
+{
+ f2fs_folio_wait_writeback(dn->inode_folio, NODE, true, true);
+ clear_inode_flag(dn->inode, FI_DATA_EXIST);
+ stat_dec_inline_inode(dn->inode);
+ clear_inode_flag(dn->inode, FI_INLINE_DATA);
+ set_raw_inline(dn->inode, F2FS_INODE(dn->inode_folio));
+ folio_mark_dirty(dn->inode_folio);
+ folio_clear_f2fs_inline(dn->inode_folio);
+}
+
int f2fs_convert_inline_folio(struct dnode_of_data *dn, struct folio *folio)
{
struct f2fs_io_info fio = {
@@ -157,8 +168,10 @@ int f2fs_convert_inline_folio(struct dnode_of_data *dn, struct folio *folio)
struct node_info ni;
int dirty, err;
- if (!f2fs_exist_data(dn->inode))
- goto clear_out;
+ if (!f2fs_exist_data(dn->inode)) {
+ f2fs_clear_inline_inode(dn);
+ goto out;
+ }
err = f2fs_reserve_block(dn, 0);
if (err)
return err;
@@ -206,10 +219,8 @@ int f2fs_convert_inline_folio(struct dnode_of_data *dn, struct folio *folio)
/* clear inline data and flag after data writeback */
f2fs_truncate_inline_inode(dn->inode, dn->inode_folio, 0);
- folio_clear_f2fs_inline(dn->inode_folio);
-clear_out:
- stat_dec_inline_inode(dn->inode);
- clear_inode_flag(dn->inode, FI_INLINE_DATA);
+ f2fs_clear_inline_inode(dn);
+out:
f2fs_put_dnode(dn);
return 0;
}
@@ -219,7 +230,7 @@ int f2fs_convert_inline_inode(struct inode *inode)
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
struct dnode_of_data dn;
struct f2fs_lock_context lc;
- struct folio *ifolio, *folio;
+ struct folio *ifolio, *folio = NULL;
int err = 0;
if (f2fs_hw_is_readonly(sbi) || f2fs_readonly(sbi->sb))
return -EROFS;
@@ -232,9 +243,13 @@ int f2fs_convert_inline_inode(struct inode *inode)
if (err)
return err;
- folio = f2fs_grab_cache_folio(inode->i_mapping, 0, false);
- if (IS_ERR(folio))
- return PTR_ERR(folio);
+ if (f2fs_exist_data(inode)) {
+ folio = f2fs_grab_cache_folio(inode->i_mapping, 0, false);
+ if (IS_ERR(folio))
+ return PTR_ERR(folio);
+ }
+
+ set_new_dnode(&dn, inode, NULL, NULL, 0);
f2fs_lock_op(sbi, &lc);
@@ -249,11 +264,11 @@ int f2fs_convert_inline_inode(struct inode *inode)
if (f2fs_has_inline_data(inode))
err = f2fs_convert_inline_folio(&dn, folio);
- f2fs_put_dnode(&dn);
out:
+ f2fs_put_dnode(&dn);
f2fs_unlock_op(sbi, &lc);
-
- f2fs_folio_put(folio, true);
+ if (folio)
+ f2fs_folio_put(folio, true);
if (!err)
f2fs_balance_fs(sbi, dn.node_changed);