[PATCH v2 11/11] f2fs: describe node tree geometry dynamically
From: Xinping Zhang
Date: Sat Aug 29 2026 - 13:53:32 EST
The file indexing tree boundaries separating direct, indirect, and
double-indirect node blocks depend on the number of data block addresses
and node IDs contained in each node block.
Parameterize index boundary macros (NODE_DIR1_BLOCK,
NODE_DIR2_BLOCK, NODE_IND1_BLOCK, NODE_IND2_BLOCK, and NODE_DIND_BLOCK) and
address capacity helpers (DEF_ADDRS_PER_BLOCK, NIDS_PER_BLOCK,
cur_addrs_per_inode, addrs_per_page, and addrs_per_folio) with
struct f2fs_sb_info *sbi.
Update file mapping, block allocation, truncate, garbage collection,
and recovery paths to compute indexing tree offsets from the runtime
geometry.
Signed-off-by: Kelvin Zhang <zhangxp1998@xxxxxxxxx>
---
fs/f2fs/f2fs.h | 19 +++++++++--
fs/f2fs/gc.c | 10 +++---
fs/f2fs/node.c | 74 +++++++++++++++++++++--------------------
fs/f2fs/node.h | 18 ++++++----
fs/f2fs/super.c | 6 ++--
include/linux/f2fs_fs.h | 18 ++--------
6 files changed, 77 insertions(+), 68 deletions(-)
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index f83cdab66f54..e1f33ac24c6e 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -602,8 +602,12 @@ static inline int update_sits_in_cursum(struct
f2fs_journal *journal, int i)
#define DEF_INLINE_RESERVED_SIZE 1
static inline int get_extra_isize(struct inode *inode);
static inline int get_inline_xattr_addrs(struct inode *inode);
+static inline unsigned int cur_addrs_per_inode(struct inode *inode)
+{
+ return DEF_ADDRS_PER_INODE(inode) - get_extra_isize(inode);
+}
#define MAX_INLINE_DATA(inode) (sizeof(__le32) * \
- (CUR_ADDRS_PER_INODE(inode) - \
+ (cur_addrs_per_inode(inode) - \
get_inline_xattr_addrs(inode) - \
DEF_INLINE_RESERVED_SIZE))
@@ -2257,11 +2261,19 @@ static inline struct f2fs_sb_info
*F2FS_P_SB(struct page *page)
#define SIT_ENTRY_PER_BLOCK(sbi) ((sbi)->sit_entries_per_block)
#define NAT_ENTRY_PER_BLOCK(sbi) ((sbi)->nat_entries_per_block)
#define DEF_ADDRS_PER_INODE_SBI(sbi) ((sbi)->addrs_per_inode)
+#define DEF_ADDRS_PER_BLOCK(sbi) ((sbi)->addrs_per_block)
+#define NIDS_PER_BLOCK(sbi) ((sbi)->nids_per_block)
#define F2FS_ORPHANS_PER_BLOCK(sbi) ((sbi)->orphans_per_block)
#define GET_ORPHAN_BLOCKS(sbi, n) DIV_ROUND_UP((n), \
F2FS_ORPHANS_PER_BLOCK(sbi))
#define CP_CHKSUM_OFFSET(sbi) (F2FS_BLKSIZE(sbi) - sizeof(__le32))
+#define NODE_DIR1_BLOCK(sbi) (DEF_ADDRS_PER_INODE_SBI(sbi) + 1)
+#define NODE_DIR2_BLOCK(sbi) (DEF_ADDRS_PER_INODE_SBI(sbi) + 2)
+#define NODE_IND1_BLOCK(sbi) (DEF_ADDRS_PER_INODE_SBI(sbi) + 3)
+#define NODE_IND2_BLOCK(sbi) (DEF_ADDRS_PER_INODE_SBI(sbi) + 4)
+#define NODE_DIND_BLOCK(sbi) (DEF_ADDRS_PER_INODE_SBI(sbi) + 5)
+
static inline struct f2fs_orphan_footer *
f2fs_orphan_footer(void *orphan_block, struct f2fs_sb_info *sbi)
{
@@ -3650,8 +3662,9 @@ static inline bool
f2fs_need_compress_data(struct inode *inode)
static inline unsigned int addrs_per_page(struct inode *inode,
bool is_inode)
{
- unsigned int addrs = is_inode ? (CUR_ADDRS_PER_INODE(inode) -
- get_inline_xattr_addrs(inode)) : DEF_ADDRS_PER_BLOCK;
+ unsigned int addrs = is_inode ? (cur_addrs_per_inode(inode) -
+ get_inline_xattr_addrs(inode)) :
+ DEF_ADDRS_PER_BLOCK(F2FS_I_SB(inode));
if (f2fs_compressed_file(inode))
return ALIGN_DOWN(addrs, F2FS_I(inode)->i_cluster_size);
diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c
index 299d5310e597..b17e7ce58f2c 100644
--- a/fs/f2fs/gc.c
+++ b/fs/f2fs/gc.c
@@ -1123,7 +1123,8 @@ static int gc_node_segment(struct f2fs_sb_info *sbi,
*/
block_t f2fs_start_bidx_of_node(unsigned int node_ofs, struct inode *inode)
{
- unsigned int indirect_blks = 2 * NIDS_PER_BLOCK + 4;
+ struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
+ unsigned int indirect_blks = 2 * NIDS_PER_BLOCK(sbi) + 4;
unsigned int bidx;
if (node_ofs == 0)
@@ -1132,11 +1133,12 @@ block_t f2fs_start_bidx_of_node(unsigned int
node_ofs, struct inode *inode)
if (node_ofs <= 2) {
bidx = node_ofs - 1;
} else if (node_ofs <= indirect_blks) {
- int dec = (node_ofs - 4) / (NIDS_PER_BLOCK + 1);
+ int dec = (node_ofs - 4) / (NIDS_PER_BLOCK(sbi) + 1);
bidx = node_ofs - 2 - dec;
} else {
- int dec = (node_ofs - indirect_blks - 3) / (NIDS_PER_BLOCK + 1);
+ int dec = (node_ofs - indirect_blks - 3) /
+ (NIDS_PER_BLOCK(sbi) + 1);
bidx = node_ofs - 5 - dec;
}
@@ -1179,7 +1181,7 @@ static bool is_alive(struct f2fs_sb_info *sbi,
struct f2fs_summary *sum,
max_addrs = DEF_ADDRS_PER_INODE_SBI(sbi);
} else {
base = 0;
- max_addrs = DEF_ADDRS_PER_BLOCK;
+ max_addrs = DEF_ADDRS_PER_BLOCK(sbi);
}
if (base + ofs_in_node >= max_addrs) {
diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c
index 8d16715a3aae..68a95d703c4a 100644
--- a/fs/f2fs/node.c
+++ b/fs/f2fs/node.c
@@ -691,7 +691,7 @@ static void f2fs_ra_node_pages(struct folio
*parent, int start, int n)
/* Then, try readahead for siblings of the desired node */
end = start + n;
- end = min(end, (int)NIDS_PER_BLOCK);
+ end = min_t(int, end, NIDS_PER_BLOCK(sbi));
for (i = start; i < end; i++) {
nid = get_nid(parent, i, false);
f2fs_ra_node_page(sbi, nid);
@@ -702,9 +702,11 @@ static void f2fs_ra_node_pages(struct folio
*parent, int start, int n)
pgoff_t f2fs_get_next_page_offset(struct dnode_of_data *dn, pgoff_t pgofs)
{
+ struct f2fs_sb_info *sbi = F2FS_I_SB(dn->inode);
const long direct_index = ADDRS_PER_INODE(dn->inode);
const long direct_blks = ADDRS_PER_BLOCK(dn->inode);
- const long indirect_blks = ADDRS_PER_BLOCK(dn->inode) * NIDS_PER_BLOCK;
+ const long indirect_blks = ADDRS_PER_BLOCK(dn->inode) *
+ NIDS_PER_BLOCK(sbi);
unsigned int skipped_unit = ADDRS_PER_BLOCK(dn->inode);
int cur_level = dn->cur_level;
int max_level = dn->max_level;
@@ -714,7 +716,7 @@ pgoff_t f2fs_get_next_page_offset(struct
dnode_of_data *dn, pgoff_t pgofs)
return pgofs + 1;
while (max_level-- > cur_level)
- skipped_unit *= NIDS_PER_BLOCK;
+ skipped_unit *= NIDS_PER_BLOCK(sbi);
switch (dn->max_level) {
case 3:
@@ -740,11 +742,13 @@ pgoff_t f2fs_get_next_page_offset(struct
dnode_of_data *dn, pgoff_t pgofs)
static int get_node_path(struct inode *inode, long block,
int offset[4], unsigned int noffset[4])
{
+ struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
const long direct_index = ADDRS_PER_INODE(inode);
const long direct_blks = ADDRS_PER_BLOCK(inode);
- const long dptrs_per_blk = NIDS_PER_BLOCK;
- const long indirect_blks = ADDRS_PER_BLOCK(inode) * NIDS_PER_BLOCK;
- const long dindirect_blks = indirect_blks * NIDS_PER_BLOCK;
+ const long dptrs_per_blk = NIDS_PER_BLOCK(sbi);
+ const long indirect_blks = ADDRS_PER_BLOCK(inode) *
+ NIDS_PER_BLOCK(sbi);
+ const long dindirect_blks = indirect_blks * NIDS_PER_BLOCK(sbi);
int n = 0;
int level = 0;
@@ -756,7 +760,7 @@ static int get_node_path(struct inode *inode, long block,
}
block -= direct_index;
if (block < direct_blks) {
- offset[n++] = NODE_DIR1_BLOCK;
+ offset[n++] = NODE_DIR1_BLOCK(sbi);
noffset[n] = 1;
offset[n] = block;
level = 1;
@@ -764,7 +768,7 @@ static int get_node_path(struct inode *inode, long block,
}
block -= direct_blks;
if (block < direct_blks) {
- offset[n++] = NODE_DIR2_BLOCK;
+ offset[n++] = NODE_DIR2_BLOCK(sbi);
noffset[n] = 2;
offset[n] = block;
level = 1;
@@ -772,7 +776,7 @@ static int get_node_path(struct inode *inode, long block,
}
block -= direct_blks;
if (block < indirect_blks) {
- offset[n++] = NODE_IND1_BLOCK;
+ offset[n++] = NODE_IND1_BLOCK(sbi);
noffset[n] = 3;
offset[n++] = block / direct_blks;
noffset[n] = 4 + offset[n - 1];
@@ -782,7 +786,7 @@ static int get_node_path(struct inode *inode, long block,
}
block -= indirect_blks;
if (block < indirect_blks) {
- offset[n++] = NODE_IND2_BLOCK;
+ offset[n++] = NODE_IND2_BLOCK(sbi);
noffset[n] = 4 + dptrs_per_blk;
offset[n++] = block / direct_blks;
noffset[n] = 5 + dptrs_per_blk + offset[n - 1];
@@ -792,7 +796,7 @@ static int get_node_path(struct inode *inode, long block,
}
block -= indirect_blks;
if (block < dindirect_blks) {
- offset[n++] = NODE_DIND_BLOCK;
+ offset[n++] = NODE_DIND_BLOCK(sbi);
noffset[n] = 5 + (dptrs_per_blk * 2);
offset[n++] = block / indirect_blks;
noffset[n] = 6 + (dptrs_per_blk * 2) +
@@ -1054,6 +1058,7 @@ static int truncate_dnode(struct dnode_of_data *dn)
static int truncate_nodes(struct dnode_of_data *dn, unsigned int nofs,
int ofs, int depth)
{
+ struct f2fs_sb_info *sbi = F2FS_I_SB(dn->inode);
struct dnode_of_data rdn = *dn;
struct folio *folio;
struct f2fs_node *rn;
@@ -1063,7 +1068,7 @@ static int truncate_nodes(struct dnode_of_data
*dn, unsigned int nofs,
int i, ret;
if (dn->nid == 0)
- return NIDS_PER_BLOCK + 1;
+ return NIDS_PER_BLOCK(sbi) + 1;
trace_f2fs_truncate_nodes_enter(dn->inode, dn->nid, dn->data_blkaddr);
@@ -1074,11 +1079,11 @@ static int truncate_nodes(struct dnode_of_data
*dn, unsigned int nofs,
return PTR_ERR(folio);
}
- f2fs_ra_node_pages(folio, ofs, NIDS_PER_BLOCK);
+ f2fs_ra_node_pages(folio, ofs, NIDS_PER_BLOCK(sbi));
rn = F2FS_NODE(folio);
if (depth < 3) {
- for (i = ofs; i < NIDS_PER_BLOCK; i++, freed++) {
+ for (i = ofs; i < NIDS_PER_BLOCK(sbi); i++, freed++) {
child_nid = le32_to_cpu(rn->in.nid[i]);
if (child_nid == 0)
continue;
@@ -1090,16 +1095,16 @@ static int truncate_nodes(struct dnode_of_data
*dn, unsigned int nofs,
dn->node_changed = true;
}
} else {
- child_nofs = nofs + ofs * (NIDS_PER_BLOCK + 1) + 1;
- for (i = ofs; i < NIDS_PER_BLOCK; i++) {
+ child_nofs = nofs + ofs * (NIDS_PER_BLOCK(sbi) + 1) + 1;
+ for (i = ofs; i < NIDS_PER_BLOCK(sbi); i++) {
child_nid = le32_to_cpu(rn->in.nid[i]);
if (child_nid == 0) {
- child_nofs += NIDS_PER_BLOCK + 1;
+ child_nofs += NIDS_PER_BLOCK(sbi) + 1;
continue;
}
rdn.nid = child_nid;
ret = truncate_nodes(&rdn, child_nofs, 0, depth - 1);
- if (ret == (NIDS_PER_BLOCK + 1)) {
+ if (ret == (NIDS_PER_BLOCK(sbi) + 1)) {
if (set_nid(folio, i, 0, false))
dn->node_changed = true;
child_nofs += ret;
@@ -1156,10 +1161,12 @@ static int truncate_partial_nodes(struct
dnode_of_data *dn,
nid[i + 1] = get_nid(folios[i], offset[i + 1], false);
}
- f2fs_ra_node_pages(folios[idx], offset[idx + 1], NIDS_PER_BLOCK);
+ f2fs_ra_node_pages(folios[idx], offset[idx + 1],
+ NIDS_PER_BLOCK(F2FS_I_SB(dn->inode)));
/* free direct nodes linked to a partial indirect node */
- for (i = offset[idx + 1]; i < NIDS_PER_BLOCK; i++) {
+ for (i = offset[idx + 1];
+ i < NIDS_PER_BLOCK(F2FS_I_SB(dn->inode)); i++) {
child_nid = get_nid(folios[idx], i, false);
if (!child_nid)
continue;
@@ -1193,7 +1200,9 @@ static int truncate_partial_nodes(struct
dnode_of_data *dn,
}
/*
- * All the block addresses of data and nodes should be nullified.
+ * All the node blocks actually belong to the inode will be released.
+ * If the level is 0, we will simply truncate the dnode,
+ * or else we should do dynamic truncate for the node pointers with the depth.
*/
int f2fs_truncate_inode_blocks(struct inode *inode, pgoff_t from)
{
@@ -1240,10 +1249,10 @@ int f2fs_truncate_inode_blocks(struct inode
*inode, pgoff_t from)
err = truncate_partial_nodes(&dn, offset, level);
if (err < 0 && err != -ENOENT)
goto fail;
- nofs += 1 + NIDS_PER_BLOCK;
+ nofs += 1 + NIDS_PER_BLOCK(sbi);
break;
case 3:
- nofs = 5 + 2 * NIDS_PER_BLOCK;
+ nofs = 5 + 2 * NIDS_PER_BLOCK(sbi);
if (!offset[level - 1])
goto skip_partial;
err = truncate_partial_nodes(&dn, offset, level);
@@ -1257,23 +1266,16 @@ int f2fs_truncate_inode_blocks(struct inode
*inode, pgoff_t from)
skip_partial:
while (cont) {
dn.nid = get_nid(folio, offset[0], true);
- switch (offset[0]) {
- case NODE_DIR1_BLOCK:
- case NODE_DIR2_BLOCK:
+ if (offset[0] == NODE_DIR1_BLOCK(sbi) ||
+ offset[0] == NODE_DIR2_BLOCK(sbi)) {
err = truncate_dnode(&dn);
- break;
-
- case NODE_IND1_BLOCK:
- case NODE_IND2_BLOCK:
+ } else if (offset[0] == NODE_IND1_BLOCK(sbi) ||
+ offset[0] == NODE_IND2_BLOCK(sbi)) {
err = truncate_nodes(&dn, nofs, offset[1], 2);
- break;
-
- case NODE_DIND_BLOCK:
+ } else if (offset[0] == NODE_DIND_BLOCK(sbi)) {
err = truncate_nodes(&dn, nofs, offset[1], 3);
cont = 0;
- break;
-
- default:
+ } else {
BUG();
}
if (err == -ENOENT) {
diff --git a/fs/f2fs/node.h b/fs/f2fs/node.h
index 32125f8fc753..66898288b961 100644
--- a/fs/f2fs/node.h
+++ b/fs/f2fs/node.h
@@ -354,17 +354,18 @@ static inline bool is_recoverable_dnode(const
struct folio *folio)
*/
static inline bool IS_DNODE(const struct folio *node_folio)
{
+ struct f2fs_sb_info *sbi = F2FS_F_SB(node_folio);
unsigned int ofs = ofs_of_node(node_folio);
if (f2fs_has_xattr_block(ofs))
return true;
- if (ofs == 3 || ofs == 4 + NIDS_PER_BLOCK ||
- ofs == 5 + 2 * NIDS_PER_BLOCK)
+ if (ofs == 3 || ofs == 4 + NIDS_PER_BLOCK(sbi) ||
+ ofs == 5 + 2 * NIDS_PER_BLOCK(sbi))
return false;
- if (ofs >= 6 + 2 * NIDS_PER_BLOCK) {
- ofs -= 6 + 2 * NIDS_PER_BLOCK;
- if (!((long int)ofs % (NIDS_PER_BLOCK + 1)))
+ if (ofs >= 6 + 2 * NIDS_PER_BLOCK(sbi)) {
+ ofs -= 6 + 2 * NIDS_PER_BLOCK(sbi);
+ if (!((long)ofs % (NIDS_PER_BLOCK(sbi) + 1)))
return false;
}
return true;
@@ -372,13 +373,15 @@ static inline bool IS_DNODE(const struct folio
*node_folio)
static inline int set_nid(struct folio *folio, int off, nid_t nid, bool i)
{
+ struct f2fs_sb_info *sbi = F2FS_F_SB(folio);
struct f2fs_node *rn = F2FS_NODE(folio);
__le32 *inode_nids = F2FS_INODE_NIDS(folio);
f2fs_folio_wait_writeback(folio, NODE, true, true);
if (i)
- inode_nids[off - NODE_DIR1_BLOCK] = cpu_to_le32(nid);
+ inode_nids[off - NODE_DIR1_BLOCK(sbi)] =
+ cpu_to_le32(nid);
else
rn->in.nid[off] = cpu_to_le32(nid);
return folio_mark_dirty(folio);
@@ -388,9 +391,10 @@ static inline nid_t get_nid(const struct folio
*folio, int off, bool i)
{
struct f2fs_node *rn = F2FS_NODE(folio);
const __le32 *inode_nids = F2FS_INODE_NIDS(folio);
+ int nid_index = off - NODE_DIR1_BLOCK(F2FS_F_SB(folio));
if (i)
- return le32_to_cpu(inode_nids[off - NODE_DIR1_BLOCK]);
+ return le32_to_cpu(inode_nids[nid_index]);
return le32_to_cpu(rn->in.nid[off]);
}
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
index ca6403a0ed88..f6c492f76f0c 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -3899,17 +3899,17 @@ loff_t max_file_blocks(struct f2fs_sb_info
*sbi, struct inode *inode)
if (inode && f2fs_compressed_file(inode))
leaf_count = ADDRS_PER_BLOCK(inode);
else
- leaf_count = DEF_ADDRS_PER_BLOCK;
+ leaf_count = DEF_ADDRS_PER_BLOCK(sbi);
/* two direct node blocks */
result += (leaf_count * 2);
/* two indirect node blocks */
- leaf_count *= NIDS_PER_BLOCK;
+ leaf_count *= NIDS_PER_BLOCK(sbi);
result += (leaf_count * 2);
/* one double indirect node block */
- leaf_count *= NIDS_PER_BLOCK;
+ leaf_count *= NIDS_PER_BLOCK(sbi);
result += leaf_count;
/*
diff --git a/include/linux/f2fs_fs.h b/include/linux/f2fs_fs.h
index f08b87602729..7badd5fd0070 100644
--- a/include/linux/f2fs_fs.h
+++ b/include/linux/f2fs_fs.h
@@ -271,27 +271,15 @@ struct node_footer {
} __packed;
/* Address Pointers in an Inode */
-#define DEF_ADDRS_PER_INODE ((PAGE_SIZE - OFFSET_OF_END_OF_I_EXT \
- - SIZE_OF_I_NID \
- - sizeof(struct node_footer)) / sizeof(__le32))
-#define CUR_ADDRS_PER_INODE(inode) (DEF_ADDRS_PER_INODE - \
- get_extra_isize(inode))
+#define DEF_ADDRS_PER_INODE(inode) \
+ ((i_blocksize(inode) - OFFSET_OF_END_OF_I_EXT - SIZE_OF_I_NID - \
+ sizeof(struct node_footer)) / sizeof(__le32))
#define DEF_NIDS_PER_INODE 5 /* Node IDs in an Inode */
#define ADDRS_PER_INODE(inode) addrs_per_page(inode, true)
/* Address Pointers in a Direct Block */
-#define DEF_ADDRS_PER_BLOCK ((PAGE_SIZE - sizeof(struct node_footer))
/ sizeof(__le32))
#define ADDRS_PER_BLOCK(inode) addrs_per_page(inode, false)
-/* Node IDs in an Indirect Block */
-#define NIDS_PER_BLOCK ((PAGE_SIZE - sizeof(struct node_footer)) /
sizeof(__le32))
-
#define ADDRS_PER_PAGE(folio, inode) (addrs_per_page(inode, IS_INODE(folio)))
-#define NODE_DIR1_BLOCK (DEF_ADDRS_PER_INODE + 1)
-#define NODE_DIR2_BLOCK (DEF_ADDRS_PER_INODE + 2)
-#define NODE_IND1_BLOCK (DEF_ADDRS_PER_INODE + 3)
-#define NODE_IND2_BLOCK (DEF_ADDRS_PER_INODE + 4)
-#define NODE_DIND_BLOCK (DEF_ADDRS_PER_INODE + 5)
-
#define F2FS_INLINE_XATTR 0x01 /* file inline xattr flag */
#define F2FS_INLINE_DATA 0x02 /* file inline data flag */
#define F2FS_INLINE_DENTRY 0x04 /* file inline dentry flag */
--
2.53.0