[PATCH] erofs-utils: support ztailpacking with encoded extents

Saksham aghi.saksham5 at gmail.com
Tue Mar 31 05:29:11 AEDT 2026


Currently, ztailpacking (inline pclusters for compressed files) is only
supported for the legacy compact and full index layouts. When the
encoded extents layout (a.k.a. non-compact indexes, Layout 2) is used,
the mkfs process explicitly skips the encoded extents path if
ztailpacking is enabled (idata_size > 0), and there is a TODO in
z_erofs_write_indexes to address this.

This patch implements support for ztailpacking within the encoded
extents framework. The encoded extents layout is more flexible and
robust, especially for 48-bit physical addresses or complex extent
mappings. Supporting ztailpacking here allows for better storage
efficiency even when using advanced features.

Key changes:
1.  lib/compress.c:
    - z_erofs_write_extents() is updated to correctly track physical
      continuity (pend) when inlined extents are encountered. An inlined
      extent breaks the contiguous physical block sequence, so pend is
      reset to EROFS_NULL_ADDR.
    - z_erofs_write_indexes() now allows the encoded extents path even if
      idata_size is non-zero. It still falls back to legacy indexes if
      the encoded format is larger (returns -EAGAIN).

2.  lib/zmap.c:
    - z_erofs_map_blocks_ext() is updated to handle the
      Z_EROFS_ADVISE_INLINE_PCLUSTER advise bit. For the tail-packed
      extent, it calculates the absolute disk offset by adding the
      metadata size to the inode's metadata start position.
    - It correctly sets EROFS_MAP_META and EROFS_MAP_ENCODED flags for
      inlined extents.
    - When EROFS_GET_BLOCKS_FINDTAIL is passed, it correctly initializes
      vi->z_fragmentoff and vi->z_idata_size from the encoded extent
      data.
    - z_erofs_fill_inode_lazy() is updated to trigger a FINDTAIL map
      operation when an encoded-extent-based inode has the inline
      pcluster bit set. This ensures all internal metadata is correctly
      loaded upon the first access.

With this patch, mkfs.erofs can now generate images that combine
the benefits of encoded extents and tail-packing, as verified by
dump.erofs and content validation.

Signed-off-by: Saksham <aghi.saksham5 at gmail.com>
---
 lib/compress.c | 15 ++++++++++++---
 lib/zmap.c     | 31 +++++++++++++++++++++++++++++--
 2 files changed, 41 insertions(+), 5 deletions(-)

diff --git a/lib/compress.c b/lib/compress.c
index 4a0d890..7f723f0 100644
--- a/lib/compress.c
+++ b/lib/compress.c
@@ -1068,10 +1068,16 @@ static void *z_erofs_write_extents(struct z_erofs_compress_ictx *ctx)
 		pstart_hi |= (ei->e.pstart > UINT32_MAX);
 		if ((ei->e.pstart | ei->e.plen) & ((1U << sbi->blkszbits) - 1))
 			unaligned_data = true;
-		if (pend != ei->e.pstart)
+
+		/*
+		 * Inlined extents (ztailpacking) or non-contiguous pclusters
+		 * break physical continuity.
+		 */
+		if (ei->e.inlined || pend != ei->e.pstart)
 			pend = EROFS_NULL_ADDR;
 		else
 			pend += ei->e.plen;
+
 		if (ei->e.length != 1 << lclusterbits) {
 			if (ei->list.next != &ctx->extents ||
 			    ei->e.length > 1 << lclusterbits)
@@ -1149,8 +1155,11 @@ static void *z_erofs_write_indexes(struct z_erofs_compress_ictx *ctx)
 	struct z_erofs_extent_item *ei, *n;
 	void *metabuf;
 
-	/* TODO: support writing encoded extents for ztailpacking later. */
-	if (erofs_sb_has_48bit(sbi) && !inode->idata_size) {
+	/*
+	 * Encoded extents (a.k.a. non-compact indexes) are preferred for
+	 * 48-bit physical addresses or when ztailpacking is used.
+	 */
+	if (erofs_sb_has_48bit(sbi) || inode->idata_size) {
 		metabuf = z_erofs_write_extents(ctx);
 		if (metabuf != ERR_PTR(-EAGAIN)) {
 			if (IS_ERR(metabuf))
diff --git a/lib/zmap.c b/lib/zmap.c
index 0e7af4e..f98512f 100644
--- a/lib/zmap.c
+++ b/lib/zmap.c
@@ -618,11 +618,28 @@ static int z_erofs_map_blocks_ext(struct erofs_inode *vi,
 
 	if (lstart < lend) {
 		map->m_la = lstart;
-		if (last && (vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER)) {
-			map->m_flags = EROFS_MAP_FRAGMENT;
+		if (flags & EROFS_GET_BLOCKS_FINDTAIL)
+			vi->z_tailextent_headlcn = lstart >> vi->z_lclusterbits;
+
+		if (last && (vi->z_advise & (Z_EROFS_ADVISE_FRAGMENT_PCLUSTER |
+					    Z_EROFS_ADVISE_INLINE_PCLUSTER))) {
+			if (vi->z_advise & Z_EROFS_ADVISE_INLINE_PCLUSTER) {
+				map->m_flags |= EROFS_MAP_META;
+				map->m_pa = pos + vi->z_extents * recsz;
+				if (recsz <= 4)
+					map->m_pa += 8;
+			} else {
+				map->m_flags = EROFS_MAP_FRAGMENT;
+			}
 			vi->z_fragmentoff = map->m_plen;
 			if (recsz > offsetof(struct z_erofs_extent, pstart_lo))
 				vi->z_fragmentoff |= map->m_pa << 32;
+
+			if (vi->z_advise & Z_EROFS_ADVISE_INLINE_PCLUSTER) {
+				vi->z_fragmentoff = map->m_pa;
+				vi->z_idata_size = map->m_plen &
+						   Z_EROFS_EXTENT_PLEN_MASK;
+			}
 		} else if (map->m_plen & Z_EROFS_EXTENT_PLEN_MASK) {
 			map->m_flags |= EROFS_MAP_MAPPED |
 				EROFS_MAP_FULL_MAPPED | EROFS_MAP_ENCODED;
@@ -677,6 +694,16 @@ static int z_erofs_fill_inode_lazy(struct erofs_inode *vi)
 	    (vi->z_advise & Z_EROFS_ADVISE_EXTENTS)) {
 		vi->z_extents = le32_to_cpu(h->h_extents_lo) |
 			((u64)le16_to_cpu(h->h_extents_hi) << 32);
+		if (vi->z_advise & Z_EROFS_ADVISE_INLINE_PCLUSTER) {
+			struct erofs_map_blocks map = {
+				.buf = __EROFS_BUF_INITIALIZER
+			};
+
+			err = z_erofs_map_blocks_ext(vi, &map,
+						     EROFS_GET_BLOCKS_FINDTAIL);
+			if (err < 0)
+				goto out_put_metabuf;
+		}
 		goto done;
 	}
 	vi->z_algorithmtype[0] = h->h_algorithmtype & 15;
-- 
2.53.0



More information about the Linux-erofs mailing list