[f2fs-dev] [RFC PATCH v2 1/3] fsck: add inline extent format support

Yongpeng Yang <[email protected]>
Newsgroups net.sourceforge.lists.linux-f2fs-devel
Message-ID <[email protected]>
From: Yongpeng Yang <[email protected]>

Add inline extent format recognition and handling to fsck.f2fs.

The inline extent format repurposes the inode's direct block address
area (i_addr[]) to store compact extent entries, identified by dual
magic numbers (0xF2F5EF00, 0xFFFFFFFD). This patch enables fsck to
correctly parse, validate, and repair inodes using this format.

Changes:
- iextent.h/iextent.c: core extent operations including binary search
  lookup, insert with merge/split, truncate, and format conversion
  between inline extent and direct block formats
- node.h: update datablock_addr() to query inline extent first
- node.c: update set_data_blkaddr() to maintain inline extent format
- mount.c: handle inline extent in block address update path
- fsck.c: add sanity check for inline extent integrity
- dump.c: use datablock_addr() for consistent block address access

Signed-off-by: Yongpeng Yang <[email protected]>
---
v2:
- Rename __is_extent_mergeable to __is_iextent_mergeable.
- Remove NULL_ADDR caching support from merge/split logic.
- Unify fofs range check to "fofs >= direct_blocks".
- Fix f2fs_iext_sanity_check overlap detection (use <= instead of <).
- Fix f2fs_iext_truncate_from_blkaddr write ordering to match kernel.
- Refactor datablock_addr/set_data_blkaddr to reduce #ifdef nesting.
- Code style fixes to comply with 80-column limit.
---
 fsck/dump.c    |   4 +-
 fsck/fsck.c    |  39 ++-
 fsck/iextent.c | 787 +++++++++++++++++++++++++++++++++++++++++++++++++
 fsck/iextent.h | 100 +++++++
 fsck/mount.c   |  67 ++++-
 fsck/node.c    |  31 ++
 fsck/node.h    |  16 +
 7 files changed, 1029 insertions(+), 15 deletions(-)
 create mode 100644 fsck/iextent.c
 create mode 100644 fsck/iextent.h

diff --git a/fsck/dump.c b/fsck/dump.c
index c68dc6e..d7ecfa8 100644
--- a/fsck/dump.c
+++ b/fsck/dump.c
@@ -547,8 +547,8 @@ static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
 
 	/* check data blocks in inode */
 	for (i = 0; i < addr_per_inode; i++, ofs++)
-		dump_data_blk(sbi, ofs * F2FS_BLKSIZE, le32_to_cpu(
-			node_blk->i.i_addr[get_extra_isize(node_blk) + i]), type);
+		dump_data_blk(sbi, ofs * F2FS_BLKSIZE,
+			datablock_addr(node_blk, i), type);
 
 	/* check node blocks in inode */
 	for (i = 0; i < 5; i++) {
diff --git a/fsck/fsck.c b/fsck/fsck.c
index 4fca4dd..cbc1cfe 100644
--- a/fsck/fsck.c
+++ b/fsck/fsck.c
@@ -274,7 +274,7 @@ static int is_valid_summary(struct f2fs_sb_info *sbi, struct f2fs_summary *sum,
 
 		if (ofs + ofs_in_node >= DEF_ADDRS_PER_INODE)
 			goto out;
-		target_blk_addr = node_blk->i.i_addr[ofs + ofs_in_node];
+		target_blk_addr = datablock_addr(node_blk, ofs_in_node);
 	} else {
 		if (ofs_in_node >= DEF_ADDRS_PER_BLOCK)
 			goto out;
@@ -562,6 +562,14 @@ static int sanity_check_nid(struct f2fs_sb_info *sbi, u32 nid,
 				p10);
 		}
 	}
+
+#ifdef HAVE_INLINE_EXTENT
+	if (IS_INODE(node_blk) &&
+			f2fs_iext_support_inline_extent(node_blk) &&
+			f2fs_iext_sanity_check(node_blk))
+		return -EINVAL;
+#endif
+
 	return 0;
 }
 
@@ -1211,7 +1219,7 @@ check_next:
 	}
 
 	for (idx = 0; idx < addrs; idx++, child.pgofs++) {
-		block_t blkaddr = le32_to_cpu(node_blk->i.i_addr[ofs + idx]);
+		block_t blkaddr = datablock_addr(node_blk, idx);
 
 		/* check extent info */
 		check_extent_info(&child, blkaddr, 0);
@@ -1247,14 +1255,20 @@ check_next:
 				&child, (i_blocks == *blk_cnt),
 				ftype, nid, idx, ni->version,
 				node_blk);
-		if (blkaddr != le32_to_cpu(node_blk->i.i_addr[ofs + idx]))
+		if (blkaddr != datablock_addr(node_blk, idx))
 			need_fix = 1;
 		if (!ret) {
 			*blk_cnt = *blk_cnt + 1;
 			if (cur_qtype != -1 && blkaddr != NEW_ADDR)
 				qf_last_blkofs[cur_qtype] = child.pgofs;
 		} else if (c.fix_on) {
-			node_blk->i.i_addr[ofs + idx] = NULL_ADDR;
+			struct dnode_of_data dn;
+
+			dn.inode_blk = node_blk;
+			dn.node_blk = node_blk;
+			dn.data_blkaddr = NULL_ADDR;
+			dn.ofs_in_node = idx;
+			set_data_blkaddr(&dn);
 			need_fix = 1;
 			FIX_MSG("[0x%x] i_addr[%d] = NULL_ADDR", nid, ofs + idx);
 		}
@@ -1498,6 +1512,18 @@ skip_dot_fix:
 	}
 
 	if (need_fix && f2fs_dev_is_writable()) {
+#ifdef HAVE_INLINE_EXTENT
+		/*
+		 * Truncate inline extent area to direct blocks boundary.
+		 * This removes any extent entries beyond ADDRS_PER_INODE,
+		 * ensuring inline extent doesn't reference indirect node
+		 * blocks.
+		 */
+		ASSERT(IS_INODE(node_blk));
+		if (f2fs_iext_support_inline_extent(node_blk))
+			f2fs_iext_truncate_from_blkaddr(node_blk,
+					ADDRS_PER_INODE(&node_blk->i));
+#endif
 		ret = update_block(sbi, node_blk, &ni->blk_addr, NULL);
 		ASSERT(ret >= 0);
 	}
@@ -3366,7 +3392,7 @@ static void fsck_disconnect_file(struct f2fs_sb_info *sbi, nid_t ino,
 	struct f2fs_node *node;
 	struct node_info ni;
 	nid_t nid;
-	int ofs, i, err;
+	int i, err;
 
 	node = calloc(F2FS_BLKSIZE, 1);
 	ASSERT(node);
@@ -3397,9 +3423,8 @@ static void fsck_disconnect_file(struct f2fs_sb_info *sbi, nid_t ino,
 	if (!(node->i.i_inline & (F2FS_INLINE_DATA | F2FS_INLINE_DENTRY))) {
 		u32 addrs = ADDRS_PER_INODE(&node->i);
 
-		ofs = get_extra_isize(node);
 		for (i = 0; i < addrs; i++) {
-			block_t addr = le32_to_cpu(node->i.i_addr[ofs + i]);
+			block_t addr = datablock_addr(node, i);
 			if (!addr)
 				continue;
 			release_block_cnt(sbi, dealloc);
diff --git a/fsck/iextent.c b/fsck/iextent.c
new file mode 100644
index 0000000..af73ae0
--- /dev/null
+++ b/fsck/iextent.c
@@ -0,0 +1,787 @@
+/**
+ * iextent.c
+ *
+ * Copyright (c) 2026 Xiaomi Technology Co., Ltd.
+ *             http://www.mi.com/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include "fsck.h"
+#include "node.h"
+#include "iextent.h"
+
+#ifdef HAVE_INLINE_EXTENT
+
+/* Global iext_info variable */
+struct f2fs_iext_info g_iext_info = {0};
+
+int f2fs_iext_sanity_check(struct f2fs_node *node_blk)
+{
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)blkaddr_in_node(node_blk);
+	struct f2fs_extent *ix;
+	int inline_extents = EXT_ENTRY_COUNT(eh);
+	int k;
+
+	if (!S_ISREG(le16_to_cpu(node_blk->i.i_mode)))
+		return -EINVAL;
+
+	if (inline_extents == 0)
+		return 0;
+
+	ix = EXT_FIRST_INDEX(eh);
+	for (k = 0; k < EXT_ENTRY_COUNT(eh); k++, ix++) {
+		if (F2FS_EXT_LEN(ix) == 0)
+			return -EINVAL;
+		if (F2FS_EXT_LOGICAL_START(ix) > UINT_MAX - F2FS_EXT_LEN(ix))
+			return -EINVAL;
+		if (F2FS_EXT_PHYSICAL_START(ix) != NEW_ADDR &&
+			F2FS_EXT_PHYSICAL_START(ix) >
+			UINT_MAX - F2FS_EXT_LEN(ix))
+			return -EINVAL;
+		if (k == 0)
+			continue;
+		if (F2FS_EXT_LOGICAL_START(ix)
+				<= F2FS_EXT_LOGICAL_END(&ix[-1]))
+			return -EINVAL;
+	}
+	return 0;
+}
+
+/*
+ * Borrowed from ext4_ext_binsearch_idx in kernel.
+ *
+ * __iext_binsearch_idx:
+ * binary search for the closest index of the given block
+ * the header must be checked before calling this
+ */
+static int __iext_binsearch_idx(struct f2fs_node *node_blk, block_t block)
+{
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)blkaddr_in_node(node_blk);
+	struct f2fs_extent *r, *l, *m;
+	int index = 0;
+
+	if (EXT_ENTRY_COUNT(eh) == 0)
+		return -1;
+
+	l = EXT_FIRST_INDEX(eh) + 1;
+	r = EXT_LAST_INDEX(eh);
+
+	while (l <= r) {
+		m = l + (r - l) / 2;
+
+		if (block < le32_to_cpu(m->fofs))
+			r = m - 1;
+		else
+			l = m + 1;
+	}
+	if (c.dbg_lv > 2) {
+		struct f2fs_extent *chix, *ix;
+		int k;
+
+		chix = ix = EXT_FIRST_INDEX(eh);
+		for (k = 0; k < EXT_ENTRY_COUNT(eh); k++, ix++) {
+			if (k != 0 && le32_to_cpu(ix->fofs) <=
+			    le32_to_cpu(ix[-1].fofs)) {
+				DBG(2, "k=%d, ix=0x%p, first=0x%p",
+				       k, ix, EXT_FIRST_INDEX(eh));
+				DBG(2, "%u <= %u",
+				       le32_to_cpu(ix->fofs),
+				       le32_to_cpu(ix[-1].fofs));
+			}
+			ASSERT(!(k && le32_to_cpu(ix->fofs)
+					   <= le32_to_cpu(ix[-1].fofs)));
+			if (block < le32_to_cpu(ix->fofs))
+				break;
+			chix = ix;
+		}
+		ASSERT(chix == l - 1);
+	}
+
+	index = l - EXT_FIRST_INDEX(eh) - 1;
+
+	return index;
+}
+
+static void __ext_add_one_entry(struct f2fs_iext_header *eh, int index,
+			struct f2fs_extent *ext)
+{
+	int inline_extents = EXT_ENTRY_COUNT(eh);
+
+	ASSERT(index >= 0 && index <= inline_extents);
+
+	memmove(&eh->exts[index + 1], &eh->exts[index],
+			(inline_extents - index) * sizeof(struct f2fs_extent));
+	memcpy(&eh->exts[index], ext, sizeof(struct f2fs_extent));
+
+	eh->cnt = cpu_to_le32(inline_extents + 1);
+}
+
+static void __ext_del_one_entry(struct f2fs_iext_header *eh, int index)
+{
+	int inline_extents = EXT_ENTRY_COUNT(eh);
+
+	memmove(&eh->exts[index], &eh->exts[index + 1],
+			(inline_extents - index - 1) *
+			sizeof(struct f2fs_extent));
+
+	eh->cnt = cpu_to_le32(inline_extents - 1);
+}
+
+enum {
+	F2FS_EXT_HIT_LEFT = -1,
+	F2FS_EXT_HIT_MID = 0,
+	F2FS_EXT_HIT_RIGHT = 1,
+};
+
+static int __is_iextent_hit(struct f2fs_extent *ext, block_t fofs)
+{
+	block_t start = F2FS_EXT_LOGICAL_START(ext);
+	block_t end = F2FS_EXT_LOGICAL_END(ext);
+
+	if (fofs < start)
+		return F2FS_EXT_HIT_LEFT;
+	if (fofs > end)
+		return F2FS_EXT_HIT_RIGHT;
+	return F2FS_EXT_HIT_MID;
+}
+
+static bool __is_iextent_mergeable(struct f2fs_extent *left,
+			struct f2fs_extent *right)
+{
+	block_t left_blk_start = F2FS_EXT_PHYSICAL_START(left);
+	block_t right_blk_start = F2FS_EXT_PHYSICAL_START(right);
+
+	if (F2FS_EXT_LOGICAL_END(left) + 1 != F2FS_EXT_LOGICAL_START(right))
+		return false;
+
+	/* NEW_ADDR always can merge. */
+	if (left_blk_start == right_blk_start) {
+		/*
+		 * In fallocate context, FALLOC_FL_COLLAPSE_RANGE mode might
+		 * cause `left_blk_start == right_blk_start` and not equal
+		 * NEW_ADDR.
+		 */
+		if (left_blk_start == NEW_ADDR)
+			return true;
+		return false;
+	}
+	if (left_blk_start == NULL_ADDR ||
+			left_blk_start == NEW_ADDR ||
+			right_blk_start == NULL_ADDR ||
+			right_blk_start == NEW_ADDR)
+		return false;
+	if (F2FS_EXT_PHYSICAL_END(left) + 1 != F2FS_EXT_PHYSICAL_START(right))
+		return false;
+
+	return true;
+}
+
+/*
+ * 1. all single extent insert operation must check mergeable at first even for
+ * split case.
+ * 2. all -ENOSPC error will delete the old mapping except for case which will
+ * split extent into 2 separate extent and insert new one.
+ * 3. index point to 0th extent or extent which first fofs is bigger than @fofs.
+ * 4. delete old mapping before insert new one even for extent which len is 1.
+ */
+
+static int __iext_insert_idx(struct f2fs_node *node_blk,
+				block_t fofs, block_t blkaddr)
+{
+	int max_inline_extents = MAX_INLINE_EXTENTS(&node_blk->i);
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)blkaddr_in_node(node_blk);
+	int inline_extents = EXT_ENTRY_COUNT(eh);
+	int index = 0, left = -1, right = -1, ret = 0, hit;
+	int merge_bits = 0, max_split_cnt = 0;
+	block_t fofs_start, fofs_end, blk_start;
+	unsigned int len;
+	struct f2fs_extent *last_ext;
+	struct f2fs_extent split;
+	struct f2fs_extent ext = {
+		.fofs = cpu_to_le32(fofs),
+		.blk_addr = cpu_to_le32(blkaddr),
+		.len = cpu_to_le32(1),
+	};
+
+	ASSERT(EXT_ENTRY_COUNT(eh) <= MAX_INLINE_EXTENTS(&node_blk->i));
+
+	/* 1. insert empty inline extent area. */
+	if (inline_extents == 0) {
+		ASSERT(max_inline_extents > 0);
+		INLINE_EXT_STAT_INC(&g_iext_info, overwrite_ext_count);
+		goto add_entry;
+	}
+
+	/* 2. try merge with last extent. */
+	last_ext = EXT_LAST_INDEX(eh);
+	if (__is_iextent_mergeable(last_ext, &ext)) {
+		last_ext->len = cpu_to_le32(F2FS_EXT_LEN(last_ext) + 1);
+		INLINE_EXT_STAT_INC(&g_iext_info, fast_right_merge_count);
+		goto out;
+	}
+
+	if (max_inline_extents == inline_extents &&
+			fofs > F2FS_EXT_LOGICAL_END(last_ext)) {
+		INLINE_EXT_STAT_INC(&g_iext_info, drop_insert_new_ext_cnt);
+		ret = -E2BIG;
+		goto out;
+	}
+	/* 3. search extent. */
+	index = __iext_binsearch_idx(node_blk, fofs);
+
+	fofs_start = F2FS_EXT_LOGICAL_START(&eh->exts[index]);
+	fofs_end = F2FS_EXT_LOGICAL_END(&eh->exts[index]);
+	blk_start = F2FS_EXT_PHYSICAL_START(&eh->exts[index]);
+	len = F2FS_EXT_LEN(&eh->exts[index]);
+
+	/* 4. check extent hit. */
+	hit = __is_iextent_hit(&eh->exts[index], fofs);
+	switch (hit) {
+	case F2FS_EXT_HIT_LEFT:
+		/* only if fofs < eh->exts[0].fofs. */
+		ASSERT(index == 0);
+		right = index;
+		break;
+	case F2FS_EXT_HIT_RIGHT:
+		left = index;
+		if (left + 1 < inline_extents)
+			right = left + 1;
+		break;
+	case F2FS_EXT_HIT_MID:
+		max_split_cnt = 0;
+
+		/* 5. overwrite extent. */
+		if (len == 1) {
+			__ext_del_one_entry(eh, index);
+			inline_extents = EXT_ENTRY_COUNT(eh);
+			/*
+			 * just overwrite for single extent, other cases may
+			 * cause merge.
+			 */
+			if (inline_extents == 0) {
+				/*
+				 * blkaddr can be NULL_ADDR, so need to check
+				 * before update.
+				 */
+				INLINE_EXT_STAT_INC(&g_iext_info,
+						overwrite_ext_count);
+				goto add_entry;
+			}
+			if (index < inline_extents)
+				right = index;
+			if (index - 1 >= 0)
+				index = left = index - 1;
+			/*
+			 * index may not change but extent content has already
+			 * changed.
+			 */
+			fofs_start = F2FS_EXT_LOGICAL_START(&eh->exts[index]);
+			fofs_end = F2FS_EXT_LOGICAL_END(&eh->exts[index]);
+			blk_start = F2FS_EXT_PHYSICAL_START(&eh->exts[index]);
+			len = F2FS_EXT_LEN(&eh->exts[index]);
+			break;
+		}
+		/* 6. split extent. */
+		if (blk_start == NEW_ADDR && blk_start == blkaddr)
+			goto out;
+		if (blk_start != NEW_ADDR &&
+				blk_start + fofs - fofs_start == blkaddr)
+			goto out;
+		if (fofs_start == fofs || fofs_end == fofs)
+			max_split_cnt = 1;
+		else
+			max_split_cnt = 2;
+		switch (max_split_cnt) {
+		case 1:
+			/*
+			 * Corner cases:
+			 * 1. actual split count maybe 0 or 1, because @ext
+			 * might merge with another extent.
+			 * 2. truncate fofs at first even though ext can not
+			 * merge and no space, because ext will add to inline
+			 * extent area or may out of range of inline extent
+			 * area.
+			 */
+			eh->exts[index].len = cpu_to_le32(
+					F2FS_EXT_LEN(&eh->exts[index]) - 1);
+			eh->exts[index].fofs = cpu_to_le32(fofs == fofs_start ?
+					fofs_start + 1 : fofs_start);
+			if (blk_start != NEW_ADDR)
+				eh->exts[index].blk_addr = cpu_to_le32(
+						fofs == fofs_start ?
+						blk_start + 1 : blk_start);
+			/* add ext at index or 'index + 1' */
+			if (fofs == fofs_end) {
+				INLINE_EXT_STAT_INC(&g_iext_info,
+						split_right_count);
+				/* may be can merge with the right of index */
+				if (index + 1 < inline_extents)
+					right = index + 1;
+				else {
+					index++; /* insert to next position. */
+					goto add_entry;
+				}
+			} else {
+				INLINE_EXT_STAT_INC(&g_iext_info,
+						split_left_count);
+				/* may be can merge with the left of index */
+				if (index - 1 >= 0)
+					index = left = index - 1;
+				else
+					goto add_entry;
+			}
+			fofs_start = F2FS_EXT_LOGICAL_START(&eh->exts[index]);
+			fofs_end = F2FS_EXT_LOGICAL_END(&eh->exts[index]);
+			blk_start = F2FS_EXT_PHYSICAL_START(&eh->exts[index]);
+			len = F2FS_EXT_LEN(&eh->exts[index]);
+			break;
+		case 2:
+			if (inline_extents + max_split_cnt >
+					max_inline_extents) {
+				ret = -ENOSPC;
+				goto out;
+			}
+			eh->exts[index].len = cpu_to_le32(fofs - fofs_start);
+
+			split.fofs = cpu_to_le32(fofs + 1);
+			split.len = cpu_to_le32(fofs_end - fofs);
+			if (blk_start != NEW_ADDR)
+				split.blk_addr = cpu_to_le32(blk_start +
+						fofs - fofs_start + 1);
+			else
+				split.blk_addr = cpu_to_le32(blk_start);
+			__ext_add_one_entry(eh, index + 1, &split);
+			inline_extents = EXT_ENTRY_COUNT(eh);
+			/* add ext between index and 'index + 1' */
+			index++;
+			INLINE_EXT_STAT_INC(&g_iext_info, split_mid_count);
+			goto add_entry;
+		default:
+			ASSERT(0);
+		}
+		break;
+	default:
+		ASSERT(0);
+	}
+	/* 7. try to merge extent. */
+	if (left >= 0 && __is_iextent_mergeable(&eh->exts[left], &ext))
+		merge_bits |= 1;
+	if (right >= 0 && __is_iextent_mergeable(&ext, &eh->exts[right]))
+		merge_bits |= (1 << 1);
+	switch (merge_bits) {
+	case 1:
+		eh->exts[left].len = cpu_to_le32(
+				F2FS_EXT_LEN(&eh->exts[left]) + 1);
+		INLINE_EXT_STAT_INC(&g_iext_info, right_merge_count);
+		goto out;
+	case 2:
+		eh->exts[right].len = cpu_to_le32(
+				F2FS_EXT_LEN(&eh->exts[right]) + 1);
+		eh->exts[right].fofs = cpu_to_le32(fofs);
+		eh->exts[right].blk_addr = cpu_to_le32(blkaddr);
+		INLINE_EXT_STAT_INC(&g_iext_info, left_merge_count);
+		goto out;
+	case 3:
+		eh->exts[left].len = cpu_to_le32(F2FS_EXT_LEN(&eh->exts[left])
+					+ 1 + F2FS_EXT_LEN(&eh->exts[right]));
+		__ext_del_one_entry(eh, right);
+		INLINE_EXT_STAT_INC(&g_iext_info, del_ext_count);
+		INLINE_EXT_STAT_INC(&g_iext_info, lr_merge_count);
+		goto out;
+	default:
+		/* no extent can merge. */
+		ASSERT(merge_bits == 0);
+	}
+	/* insert ext after found index. always false for split case. */
+	if (fofs > fofs_start)
+		index++;
+	INLINE_EXT_STAT_INC(&g_iext_info, insert_new_ext_count);
+add_entry:
+	/*
+	 * Just bypass insert new NULL_ADDR, because the old blkaddr can not
+	 * be NULL_ADDR, and inserting need to delete it at first.
+	 * This will bypass all newly NULL_ADDR insert operation.
+	 */
+	if (blkaddr == NULL_ADDR) {
+		/* ret = 0; delete succeed. */
+		goto out;
+	}
+	if (inline_extents + 1 > max_inline_extents) {
+		ret = -ENOSPC;
+		goto out;
+	}
+	/* 8. insert as new extent. */
+	INLINE_EXT_STAT_INC(&g_iext_info, add_ext_count);
+	__ext_add_one_entry(eh, index, &ext);
+out:
+	ASSERT(EXT_ENTRY_COUNT(eh) <= max_inline_extents);
+	return ret;
+}
+
+/* Lookup whether fofs is exists in inline extent area. */
+static int __iext_lookup_data_blkaddr(struct f2fs_node *node_blk,
+				block_t fofs, block_t *blkaddr,
+				unsigned int *len)
+{
+	int index, hit;
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)blkaddr_in_node(node_blk);
+	int inline_extents = EXT_ENTRY_COUNT(eh);
+	block_t blk_start, fofs_start;
+
+	if (inline_extents == 0)
+		return -ENOENT;
+	index = __iext_binsearch_idx(node_blk, fofs);
+	hit = __is_iextent_hit(&eh->exts[index], fofs);
+	if (hit != F2FS_EXT_HIT_MID)
+		return -ENOENT;
+	blk_start = F2FS_EXT_PHYSICAL_START(&eh->exts[index]);
+	fofs_start = F2FS_EXT_LOGICAL_START(&eh->exts[index]);
+	if (blk_start == NEW_ADDR) {
+		*blkaddr = blk_start;
+		if (len)
+			*len = 1;
+	} else {
+		*blkaddr = blk_start + fofs - fofs_start;
+		if (len)
+			*len = F2FS_EXT_LEN(&eh->exts[index]) -
+					(fofs - fofs_start);
+	}
+	return 0;
+}
+
+int f2fs_iext_lookup_blkaddr(struct f2fs_node *node_blk,
+					block_t fofs, block_t *blkaddr,
+					unsigned int *len)
+{
+	int error = 0;
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)blkaddr_in_node(node_blk);
+	struct f2fs_extent *last_ext = EXT_LAST_INDEX(eh);
+
+	if (EXT_ENTRY_COUNT(eh) == 0 || F2FS_EXT_LOGICAL_END(last_ext) < fofs)
+		error = -ENOENT;
+	else
+		error = __iext_lookup_data_blkaddr(node_blk, fofs,
+						blkaddr, len);
+
+	/* return NULL_ADDR for direct blocks. */
+	if (error && fofs < ADDRS_PER_INODE(&node_blk->i)) {
+		*blkaddr = NULL_ADDR;
+		error = 0;
+		if (len)
+			*len = 1;
+	}
+	return error;
+}
+
+/*
+ * Exceptions:
+ * 1. inline extent area is full, blkaddr is overlap with EXT_LAST_INDEX()
+ *    1.1 blkaddr hit the right/left most or middle blk of EXT_LAST_INDEX()
+ *    1.2 the above cases is similar with extent merge
+ * 2. inline extent area is full, blkaddr is less than EXT_LAST_INDEX()
+ *    2.1 remove EXT_LAST_INDEX() and try again.
+ * 3. inline extent area is full, blkaddr is bigger than EXT_LAST_INDEX(), just
+ *    drop it.
+ *
+ * The above exceptions will be handled twice at most, because 1 insert
+ * operation may cause 2 extent insert operations at most.
+ *
+ * This function inform whether need to mark inode as dirty through return
+ * value.
+ */
+
+int f2fs_iext_update_data_blkaddr(struct f2fs_node *node_blk,
+					block_t fofs, block_t blkaddr)
+{
+	int error = 0, retry_cnt = 2, hit;
+	int direct_blocks = ADDRS_PER_INODE(&node_blk->i);
+	unsigned int len;
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)blkaddr_in_node(node_blk);
+	struct f2fs_extent *last_ext;
+	block_t fofs_start;
+
+retry:
+	ASSERT(retry_cnt >= 0);
+	ASSERT(EXT_ENTRY_COUNT(eh) <= MAX_INLINE_EXTENTS(&node_blk->i));
+	error = __iext_insert_idx(node_blk, fofs, blkaddr);
+
+	if (!error)
+		return 0;
+	/*
+	 * fofs doesn't exists in inline extent area and bigger than last
+	 * fofs. Caller only need to give up insert.
+	 */
+	if (error == -E2BIG && fofs >= direct_blocks)
+		return F2FS_IEXT_INSERT_DROP;
+	/* evict the last extent and try to insert again. */
+	/* must be after __iext_insert_idx, which might remove extent. */
+	last_ext = EXT_LAST_INDEX(eh);
+	ASSERT(last_ext);
+	if (!last_ext)
+		return F2FS_IEXT_INSERT_CONVERT;
+	fofs_start = F2FS_EXT_LOGICAL_START(last_ext);
+	len = F2FS_EXT_LEN(last_ext);
+	hit = __is_iextent_hit(last_ext, fofs);
+	switch (hit) {
+	case F2FS_EXT_HIT_LEFT:
+		/*
+		 * if last extent cover direct blocks, we must interrupt and
+		 * trigger format conversion.
+		 */
+		if (fofs_start < direct_blocks) {
+			error = F2FS_IEXT_INSERT_CONVERT;
+			INLINE_EXT_STAT_INC(&g_iext_info,
+					trigger_ext_format_convert);
+			goto out;
+		}
+		/*
+		 * whether fofs exceed ADDRS_PER_INODE or not, just delete the
+		 * lastest extent and try again.
+		 */
+		INLINE_EXT_STAT_INC(&g_iext_info, evict_last_ext_cnt);
+		__ext_del_one_entry(eh, EXT_ENTRY_COUNT(eh) - 1);
+		break;
+	case F2FS_EXT_HIT_RIGHT:
+		/*
+		 * don't need to cache it in inline extent area and old mapping
+		 * has already removed.
+		 */
+		if (fofs >= direct_blocks) {
+			INLINE_EXT_STAT_INC(&g_iext_info,
+					drop_insert_new_ext_cnt);
+			error = F2FS_IEXT_INSERT_REMOVED;
+			goto out;
+		}
+		INLINE_EXT_STAT_INC(&g_iext_info, trigger_ext_format_convert);
+		error = F2FS_IEXT_INSERT_CONVERT;
+		goto out;
+	case F2FS_EXT_HIT_MID:
+		/*
+		 * if `len == 1` then {fofs, blkaddr, 1} already overwrite old
+		 * extent.
+		 */
+		ASSERT(len > 1);
+		/*
+		 * truncate [fofs + 1, fofs_start + len - 1] and replace
+		 * last_ext with {fofs, blkaddr, 1}.
+		 * `fofs == fofs_start` will never happen, because
+		 * __iext_insert_idx will truncate fofs.
+		 *
+		 * truncate [fofs, fofs_start + len - 1] when fofs are out of
+		 * range of [0, direct_blocks).
+		 */
+		if (fofs > fofs_start && fofs >= direct_blocks) {
+			last_ext->len = cpu_to_le32(fofs - fofs_start);
+			INLINE_EXT_STAT_INC(&g_iext_info,
+					truncate_last_ext_cnt);
+			if (EXT_ENTRY_COUNT(eh) ==
+					MAX_INLINE_EXTENTS(&node_blk->i)) {
+				error = F2FS_IEXT_INSERT_REMOVED;
+				goto out;
+			}
+		} else {
+			error = F2FS_IEXT_INSERT_CONVERT;
+			goto out;
+		}
+		break;
+	}
+	ASSERT(EXT_ENTRY_COUNT(eh) <= MAX_INLINE_EXTENTS(&node_blk->i));
+	retry_cnt--;
+	goto retry;
+out:
+	return error;
+}
+
+static int __iext_convert_to_inline_extent(struct f2fs_node *direct_node,
+				       struct f2fs_node *extent_node)
+{
+	int i = 0, error = 0, direct_blocks = ADDRS_PER_INODE(&direct_node->i);
+	__le32 *arr = (__le32 *)blkaddr_in_node(direct_node);
+
+	for (; i < direct_blocks; i++) {
+		error = __iext_insert_idx(extent_node, i,
+					le32_to_cpu(arr[i]));
+		if (error)
+			return error;
+	}
+
+	return 0;
+}
+
+bool f2fs_iext_convert_to_inline_extent(struct f2fs_node *node_blk)
+{
+	bool updated = false;
+	int ret;
+	struct f2fs_node *convert_node;
+
+	if (f2fs_iext_support_inline_extent(node_blk))
+		goto out;
+
+	/* Allocate temporary buffer for conversion */
+	convert_node = calloc(F2FS_BLKSIZE, 1);
+	ASSERT(convert_node);
+
+	memcpy(convert_node, node_blk, F2FS_BLKSIZE);
+	f2fs_iext_init_inline_extent(node_blk);
+	ret = __iext_convert_to_inline_extent(convert_node, node_blk);
+	if (ret)
+		memcpy(node_blk, convert_node, F2FS_BLKSIZE);
+	else
+		updated = true;
+	free(convert_node);
+
+out:
+	return updated;
+}
+
+static void __iext_convert_to_direct_blocks(struct f2fs_node *direct_node,
+					struct f2fs_node *extent_node)
+{
+	int i = 0, direct_blocks = ADDRS_PER_INODE(&direct_node->i);
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)blkaddr_in_node(extent_node);
+	int inline_extents = EXT_ENTRY_COUNT(eh);
+	int j, len;
+	struct f2fs_extent *ext;
+	block_t fofs_start = 0, blk_start;
+	__le32 *arr = (__le32 *)blkaddr_in_node(direct_node);
+
+	/* set default to NULL_ADDR. */
+	memset(arr, 0, sizeof(__le32) * direct_blocks);
+	for (; fofs_start < direct_blocks && i < inline_extents; i++) {
+		ext = &eh->exts[i];
+		len = F2FS_EXT_LEN(ext);
+		fofs_start = F2FS_EXT_LOGICAL_START(ext);
+		blk_start = F2FS_EXT_PHYSICAL_START(ext);
+		for (j = 0; j < len && fofs_start < direct_blocks; j++) {
+			if (blk_start == NEW_ADDR)
+				arr[fofs_start++] = cpu_to_le32(blk_start);
+			else
+				arr[fofs_start++] = cpu_to_le32(blk_start + j);
+		}
+	}
+}
+
+void f2fs_iext_convert_to_direct_blocks(struct f2fs_node *node_blk)
+{
+	struct f2fs_node *convert_node;
+
+	/* Allocate temporary buffer for conversion */
+	convert_node = calloc(F2FS_BLKSIZE, 1);
+	ASSERT(convert_node);
+
+	memcpy(convert_node, node_blk, F2FS_BLKSIZE);
+	__iext_convert_to_direct_blocks(node_blk, convert_node);
+	free(convert_node);
+}
+
+/* truncate extent starting from @fofs. */
+void f2fs_iext_truncate_from_blkaddr(struct f2fs_node *node_blk, block_t fofs)
+{
+	int index, hit;
+	unsigned int len;
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)blkaddr_in_node(node_blk);
+	int inline_extents = EXT_ENTRY_COUNT(eh);
+	struct f2fs_extent *ext;
+	block_t fofs_start;
+
+	if (inline_extents == 0)
+		return;
+	index = __iext_binsearch_idx(node_blk, fofs);
+
+	ext = &eh->exts[index];
+
+	hit = __is_iextent_hit(ext, fofs);
+	switch (hit) {
+	case F2FS_EXT_HIT_LEFT:
+		ASSERT(index == 0);
+		eh->cnt = cpu_to_le32(0);
+		break;
+	case F2FS_EXT_HIT_RIGHT:
+		eh->cnt = cpu_to_le32(index + 1);
+		break;
+	case F2FS_EXT_HIT_MID:
+		fofs_start = F2FS_EXT_LOGICAL_START(ext);
+		len = F2FS_EXT_LEN(ext);
+
+		if (len == 1 || fofs == fofs_start) {
+			eh->cnt = cpu_to_le32(index);
+			break;
+		}
+		ext->len = cpu_to_le32(fofs - fofs_start);
+		eh->cnt = cpu_to_le32(index + 1);
+		break;
+	default:
+		ASSERT(0);
+	}
+}
+
+void f2fs_iext_init_inline_extent(struct f2fs_node *node_blk)
+{
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)blkaddr_in_node(node_blk);
+
+	eh->magic1 = cpu_to_le32(F2FS_IEXT_FORMAT_MAGIC1);
+	eh->magic2 = cpu_to_le32(F2FS_IEXT_FORMAT_MAGIC2);
+	eh->cnt = 0;
+}
+
+/* Check if node block supports inline extent format */
+bool f2fs_iext_support_inline_extent(struct f2fs_node *node_blk)
+{
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)blkaddr_in_node(node_blk);
+
+	return (le32_to_cpu(eh->magic1) == F2FS_IEXT_FORMAT_MAGIC1 &&
+		le32_to_cpu(eh->magic2) == F2FS_IEXT_FORMAT_MAGIC2);
+}
+
+block_t f2fs_iext_last_fofs(struct f2fs_node *node_blk)
+{
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)blkaddr_in_node(node_blk);
+
+	if (EXT_ENTRY_COUNT(eh) == 0)
+		return 0;
+
+	return F2FS_EXT_LOGICAL_END(EXT_LAST_INDEX(eh));
+}
+
+void f2fs_iext_show_stat(void)
+{
+	DBG(3, "Inline extent statistics:\n");
+	DBG(3, "  - left_merge_count: %llu\n", g_iext_info.left_merge_count);
+	DBG(3, "  - right_merge_count: %llu\n", g_iext_info.right_merge_count);
+	DBG(3, "  - fast_right_merge_count: %llu\n",
+			g_iext_info.fast_right_merge_count);
+	DBG(3, "  - lr_merge_count: %llu\n", g_iext_info.lr_merge_count);
+	DBG(3, "  - split_left_count: %llu\n", g_iext_info.split_left_count);
+	DBG(3, "  - split_right_count: %llu\n", g_iext_info.split_right_count);
+	DBG(3, "  - split_mid_count: %llu\n", g_iext_info.split_mid_count);
+	DBG(3, "  - insert_new_ext_count: %llu\n",
+			g_iext_info.insert_new_ext_count);
+	DBG(3, "  - overwrite_ext_count: %llu\n",
+			g_iext_info.overwrite_ext_count);
+	DBG(3, "  - add_ext_count: %llu\n", g_iext_info.add_ext_count);
+	DBG(3, "  - del_ext_count: %llu\n", g_iext_info.del_ext_count);
+	DBG(3, "  - evict_last_ext_cnt: %llu\n",
+			g_iext_info.evict_last_ext_cnt);
+	DBG(3, "  - truncate_last_ext_cnt: %llu\n",
+			g_iext_info.truncate_last_ext_cnt);
+	DBG(3, "  - drop_insert_new_ext_cnt: %llu\n",
+			g_iext_info.drop_insert_new_ext_cnt);
+	DBG(3, "  - trigger_ext_format_convert: %llu\n",
+			g_iext_info.trigger_ext_format_convert);
+}
+
+#endif /* HAVE_INLINE_EXTENT */
diff --git a/fsck/iextent.h b/fsck/iextent.h
new file mode 100644
index 0000000..45d2ee0
--- /dev/null
+++ b/fsck/iextent.h
@@ -0,0 +1,100 @@
+/**
+ * iextent.h
+ *
+ * Copyright (c) 2026 Xiaomi Technology Co., Ltd.
+ *             http://www.mi.com/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef __IEXTENT_H__
+#define __IEXTENT_H__
+
+#include "fsck.h"
+
+#ifdef HAVE_INLINE_EXTENT
+
+#define F2FS_IEXT_FORMAT_MAGIC1	0xF2F5EF00 /* F2FS Extent Format. */
+/* Use invalid data blkaddr as inline-extent identifier. */
+#define F2FS_IEXT_FORMAT_MAGIC2	(-3U)
+struct f2fs_iext_header {
+	__le32 magic1;		/* magic1 # to identify extent format. */
+	/* magic2 # cannot be a valid blkaddr in non-inline-extent format.*/
+	__le32 magic2;
+	__le32 cnt;		/* total # of extents. */
+	struct f2fs_extent exts[];
+};
+
+/* Extent macros - match kernel definition */
+#define MAX_INLINE_EXTENTS(inode)	((ADDRS_PER_INODE(inode) * \
+			sizeof(__le32) - sizeof(struct f2fs_iext_header)) / \
+			sizeof(struct f2fs_extent))
+
+#define EXT_FIRST_INDEX(__hdr__) ((__hdr__)->exts)
+#define EXT_ENTRY_COUNT(__hdr__) (le32_to_cpu((__hdr__)->cnt))
+#define EXT_LAST_INDEX(__hdr__) \
+			(EXT_ENTRY_COUNT(__hdr__) ? \
+			&(__hdr__)->exts[EXT_ENTRY_COUNT(__hdr__) - 1] : \
+			NULL)
+
+/* Use blk_addr field name as defined in f2fs_fs.h */
+#define F2FS_EXT_LEN(ext) (le32_to_cpu((ext)->len))
+#define F2FS_EXT_LOGICAL_START(ext) (le32_to_cpu((ext)->fofs))
+#define F2FS_EXT_LOGICAL_END(ext) \
+			((F2FS_EXT_LOGICAL_START(ext) + F2FS_EXT_LEN(ext)) - 1)
+#define F2FS_EXT_PHYSICAL_START(ext) (le32_to_cpu((ext)->blk_addr))
+#define F2FS_EXT_PHYSICAL_END(ext) \
+			((F2FS_EXT_PHYSICAL_START(ext) + F2FS_EXT_LEN(ext)) - 1)
+
+struct f2fs_iext_info {
+	/* stat for inline extent code coverage. */
+	unsigned long long left_merge_count;
+	unsigned long long right_merge_count;
+	unsigned long long fast_right_merge_count;
+	unsigned long long lr_merge_count;
+	unsigned long long split_left_count;
+	unsigned long long split_right_count;
+	unsigned long long split_mid_count;
+	unsigned long long insert_new_ext_count;
+	unsigned long long overwrite_ext_count;
+	unsigned long long add_ext_count;
+	unsigned long long del_ext_count;
+	unsigned long long evict_last_ext_cnt;
+	unsigned long long truncate_last_ext_cnt;
+	unsigned long long drop_insert_new_ext_cnt;
+	unsigned long long trigger_ext_format_convert;
+};
+
+enum {
+	/* insert extent correctly */
+	F2FS_IEXT_INSERT_NORMAL = 0,
+	/* inline area cannot cover all direct blocks */
+	F2FS_IEXT_INSERT_CONVERT,
+	/* didn't modify inode page, no space and fofs is too big */
+	F2FS_IEXT_INSERT_DROP,
+	/* modified inode buffer, but didn't insert due to no space */
+	F2FS_IEXT_INSERT_REMOVED,
+};
+
+#define INLINE_EXT_STAT_INC(iext_info, stat) \
+	do { \
+		(iext_info)->stat++; \
+	} while (0)
+
+int f2fs_iext_sanity_check(struct f2fs_node *node_blk);
+int f2fs_iext_lookup_blkaddr(struct f2fs_node *node_blk, block_t fofs,
+			      block_t *blkaddr, unsigned int *len);
+int f2fs_iext_update_data_blkaddr(struct f2fs_node *node_blk, block_t fofs,
+				   block_t blkaddr);
+bool f2fs_iext_convert_to_inline_extent(struct f2fs_node *node_blk);
+void f2fs_iext_convert_to_direct_blocks(struct f2fs_node *node_blk);
+void f2fs_iext_truncate_from_blkaddr(struct f2fs_node *node_blk, block_t fofs);
+void f2fs_iext_init_inline_extent(struct f2fs_node *node_blk);
+bool f2fs_iext_support_inline_extent(struct f2fs_node *node_blk);
+block_t f2fs_iext_last_fofs(struct f2fs_node *node_blk);
+void f2fs_iext_show_stat(void);
+
+#endif /* HAVE_INLINE_EXTENT */
+
+#endif /* __IEXTENT_H__ */
diff --git a/fsck/mount.c b/fsck/mount.c
index 2f4e6c9..975fc8f 100644
--- a/fsck/mount.c
+++ b/fsck/mount.c
@@ -10,6 +10,9 @@
  */
 #include "fsck.h"
 #include "node.h"
+#ifdef HAVE_INLINE_EXTENT
+#include "iextent.h"
+#endif
 #include "xattr.h"
 #include "quota.h"
 #include <locale.h>
@@ -359,7 +362,7 @@ void print_inode_info(struct f2fs_sb_info *sbi,
 		if (i + ofs >= DEF_ADDRS_PER_INODE)
 			break;
 
-		blkaddr = le32_to_cpu(inode->i_addr[i + ofs]);
+		blkaddr = datablock_addr(node, i);
 
 		if (blkaddr == 0x0)
 			continue;
@@ -2512,6 +2515,7 @@ void update_data_blkaddr(struct f2fs_sb_info *sbi, nid_t nid,
 	struct node_info ni;
 	block_t oldaddr, startaddr, endaddr;
 	bool node_blk_alloced = false;
+	bool write_inode = false;
 	int ret;
 
 	if (node_blk == NULL) {
@@ -2539,7 +2543,41 @@ void update_data_blkaddr(struct f2fs_sb_info *sbi, nid_t nid,
 	if (IS_INODE(node_blk)) {
 		int ofs = get_extra_isize(node_blk);
 
-		oldaddr = le32_to_cpu(node_blk->i.i_addr[ofs + ofs_in_node]);
+		oldaddr = datablock_addr(node_blk, ofs_in_node);
+#ifdef HAVE_INLINE_EXTENT
+		if (f2fs_iext_support_inline_extent(node_blk)) {
+			int err = 0;
+			int direct_blocks = ADDRS_PER_INODE(&node_blk->i);
+			block_t fofs;
+
+			fofs = start_bidx_of_node(ofs_of_node(node_blk),
+					node_blk) + ofs_in_node;
+			ASSERT(fofs == ofs_in_node);
+
+			/* Truncate all blocks after direct_blocks position */
+			if (f2fs_iext_last_fofs(node_blk) >= direct_blocks)
+				f2fs_iext_truncate_from_blkaddr(node_blk,
+					direct_blocks);
+
+			if (fofs >= direct_blocks)
+				ASSERT(0);
+			err = f2fs_iext_update_data_blkaddr(node_blk, fofs,
+					newaddr);
+			DBG(1, "update iext mapping fofs=%u, newaddr: %u\n",
+					fofs, newaddr);
+			if (err) {
+				DBG(1, "Converting from inline extent to direct blocks at fofs=%u\n",
+						fofs);
+				f2fs_iext_convert_to_direct_blocks(node_blk);
+				/*
+				 * For non-inode nodes or non-inline-extent
+				 * format, use direct addressing.
+				 */
+				node_blk->i.i_addr[ofs + ofs_in_node] =
+						cpu_to_le32(newaddr);
+			}
+		} else
+#endif
 		node_blk->i.i_addr[ofs + ofs_in_node] = cpu_to_le32(newaddr);
 		if (node_blk_alloced) {
 			ret = update_inode(sbi, node_blk, &ni.blk_addr);
@@ -2566,6 +2604,17 @@ void update_data_blkaddr(struct f2fs_sb_info *sbi, nid_t nid,
 		}
 		ret = dev_read_block(node_blk, ni.blk_addr);
 		ASSERT(ret >= 0);
+#ifdef HAVE_INLINE_EXTENT
+		if (f2fs_iext_support_inline_extent(node_blk) &&
+				f2fs_iext_last_fofs(node_blk) >=
+				ADDRS_PER_INODE(&node_blk->i)) {
+			DBG(1, "through dnode update iext mapping newaddr: %u\n",
+				newaddr);
+			f2fs_iext_truncate_from_blkaddr(node_blk,
+					ADDRS_PER_INODE(&node_blk->i));
+			write_inode = true;
+		}
+#endif
 	}
 
 	/* check extent cache entry */
@@ -2573,12 +2622,13 @@ void update_data_blkaddr(struct f2fs_sb_info *sbi, nid_t nid,
 	endaddr = startaddr + le32_to_cpu(node_blk->i.i_ext.len);
 	if (oldaddr >= startaddr && oldaddr < endaddr) {
 		node_blk->i.i_ext.len = 0;
-
-		/* update inode block */
-		if (node_blk_alloced)
-			ASSERT(update_inode(sbi, node_blk, &ni.blk_addr) >= 0);
+		write_inode = true;
 	}
 
+	/* update inode block */
+	if (write_inode && node_blk_alloced)
+		ASSERT(update_inode(sbi, node_blk, &ni.blk_addr) >= 0);
+
 	if (node_blk_alloced)
 		free(node_blk);
 }
@@ -4268,6 +4318,7 @@ out:
 		if (check_nat_bits(sbi, sb, cp) && c.fix_on)
 			write_nat_bits(sbi, sb, cp, sbi->cur_cp);
 	}
+
 	return 0;
 }
 
@@ -4299,6 +4350,10 @@ void f2fs_do_umount(struct f2fs_sb_info *sbi)
 
 	free(sbi->ckpt);
 	free(sbi->raw_super);
+
+#ifdef HAVE_INLINE_EXTENT
+	f2fs_iext_show_stat();
+#endif
 }
 
 #ifdef WITH_ANDROID
diff --git a/fsck/node.c b/fsck/node.c
index 8d4479c..356a81a 100644
--- a/fsck/node.c
+++ b/fsck/node.c
@@ -100,6 +100,37 @@ void set_data_blkaddr(struct dnode_of_data *dn)
 	struct f2fs_node *node_blk = dn->node_blk;
 	unsigned int ofs_in_node = dn->ofs_in_node;
 
+#ifdef HAVE_INLINE_EXTENT
+	if (f2fs_iext_support_inline_extent(dn->inode_blk)) {
+		int ret = 0;
+		int direct_blocks = ADDRS_PER_INODE(&dn->inode_blk->i);
+		block_t fofs;
+
+		fofs = start_bidx_of_node(ofs_of_node(node_blk),
+					dn->node_blk) + ofs_in_node;
+
+		/* Truncate all blocks after direct_blocks position */
+		if (f2fs_iext_last_fofs(dn->inode_blk) >= direct_blocks) {
+			f2fs_iext_truncate_from_blkaddr(dn->inode_blk,
+					direct_blocks);
+			dn->idirty = 1;
+		}
+
+		if (fofs < direct_blocks) {
+			ret = f2fs_iext_update_data_blkaddr(dn->inode_blk, fofs,
+					dn->data_blkaddr);
+			if (!ret) {
+				dn->idirty = 1;
+				return;
+			}
+			DBG(1, "Converting from inline extent to direct blocks at fofs=%u\n",
+			    fofs);
+			f2fs_iext_convert_to_direct_blocks(dn->inode_blk);
+		}
+		/* For non-inode nodes or non-inline-extent format, fallback. */
+	}
+#endif
+
 	addr_array = blkaddr_in_node(node_blk);
 	addr_array[ofs_in_node] = cpu_to_le32(dn->data_blkaddr);
 	if (dn->node_blk != dn->inode_blk)
diff --git a/fsck/node.h b/fsck/node.h
index f3e2c46..023d636 100644
--- a/fsck/node.h
+++ b/fsck/node.h
@@ -17,6 +17,9 @@
 #define _NODE_H_
 
 #include "fsck.h"
+#ifdef HAVE_INLINE_EXTENT
+#include "iextent.h"
+#endif
 
 static inline bool IS_INODE(struct f2fs_node *node)
 {
@@ -64,6 +67,19 @@ static inline block_t datablock_addr(struct f2fs_node *node_page,
 	__le32 *addr_array;
 
 	ASSERT(node_page);
+
+#ifdef HAVE_INLINE_EXTENT
+	if (IS_INODE(node_page) && f2fs_iext_support_inline_extent(node_page)) {
+		block_t blkaddr;
+
+		/* Query inline extent, node offset is equal to file's offset */
+		if (f2fs_iext_lookup_blkaddr(node_page, offset,
+					&blkaddr, NULL) == 0)
+			return blkaddr;
+		/* If not found in inline extent, it's unexpected. */
+		ASSERT(0);
+	}
+#endif
 	addr_array = blkaddr_in_node(node_page);
 	return le32_to_cpu(addr_array[offset]);
 }
-- 
2.43.0



_______________________________________________
Linux-f2fs-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.