With ztailpacking, the final compressed pcluster is first stored as
inline data.  If the inode metadata area cannot hold it, mkfs falls back
to a normal tail block and drops the inline pcluster marker.

The current fallback path assumes that the inline tail pcluster belongs
to the EOF lcluster.  That is not always true: the tail pcluster can
start in the previous lcluster and end at EOF, while its raw size still
fits in one block.  In that case, patching the EOF lcluster is
semantically wrong.

Let's keep raw tail data whenever it fits in one block, and convert the
corresponding lcluster index to PLAIN during fallback.

Reported-by: Alberto Salvia Novella <[email protected]>
Closes: https://github.com/erofs/erofs-utils/issues/51
Assisted-by: Codex:GPT-5.5
Signed-off-by: Yifan Zhao <[email protected]>
---
 include/erofs/internal.h |   5 +-
 lib/compress.c           | 109 ++++++++++++++++++++++++++++-----------
 2 files changed, 83 insertions(+), 31 deletions(-)

diff --git a/include/erofs/internal.h b/include/erofs/internal.h
index 2cc9cc8..bdde41f 100644
--- a/include/erofs/internal.h
+++ b/include/erofs/internal.h
@@ -212,8 +212,11 @@ struct erofs_diskbuf;
 
 enum erofs_idata_type {
        EROFS_IDATA_TYPE_RAW,
-       EROFS_IDATA_TYPE_COMPRESSED_DEFAULT,
+       EROFS_IDATA_TYPE_COMPRESSED,
+       /* compressed idata follows a final 2B compacted index pack */
        EROFS_IDATA_TYPE_COMPRESSED_END_OF_2B,
+       /* compressed idata follows a final single-entry 4B pack after a 2B 
pack */
+       EROFS_IDATA_TYPE_COMPRESSED_4B1_PREV2B,
 };
 
 #define EROFS_I_BLKADDR_DEV_ID_BIT             48
diff --git a/lib/compress.c b/lib/compress.c
index f7ad5a1..ec90f65 100644
--- a/lib/compress.c
+++ b/lib/compress.c
@@ -483,7 +483,7 @@ static int z_erofs_fill_inline_data(struct erofs_inode 
*inode, void *data,
 {
        inode->z_advise |= Z_EROFS_ADVISE_INLINE_PCLUSTER;
        inode->idata_size = len;
-       inode->idata_type = EROFS_IDATA_TYPE_COMPRESSED_DEFAULT;
+       inode->idata_type = EROFS_IDATA_TYPE_COMPRESSED;
 
        inode->idata = malloc(inode->idata_size);
        if (!inode->idata)
@@ -664,7 +664,7 @@ frag_packing:
                ictx->fragemitted = true;
        /* tailpcluster should be less than 1 block */
        } else if (may_inline && len == e->length && compressedsize < blksz) {
-               if (ctx->clusterofs + len <= blksz) {
+               if (len <= blksz) {
                        inode->eof_tailraw = malloc(len);
                        if (!inode->eof_tailraw)
                                return -ENOMEM;
@@ -962,6 +962,14 @@ int z_erofs_convert_to_compacted_format(struct erofs_inode 
*inode,
                dummy_head = true;
        }
 
+       if (inode->idata_size) {
+               if (compacted_2b && !compacted_4b_end)
+                       inode->idata_type = 
EROFS_IDATA_TYPE_COMPRESSED_END_OF_2B;
+               else if (compacted_2b && compacted_4b_end == 1)
+                       inode->idata_type =
+                               EROFS_IDATA_TYPE_COMPRESSED_4B1_PREV2B;
+       }
+
        /* generate compacted_4b_initial */
        while (compacted_4b_initial) {
                in = parse_legacy_indexes(cv, 2, in);
@@ -974,8 +982,6 @@ int z_erofs_convert_to_compacted_format(struct erofs_inode 
*inode,
 
        /* generate compacted_2b */
        if (compacted_2b) {
-               if (!compacted_4b_end && inode->idata_size)
-                       inode->idata_type = 
EROFS_IDATA_TYPE_COMPRESSED_END_OF_2B;
                do {
                        in = parse_legacy_indexes(cv, 16, in);
                        out = write_compacted_indexes(out, cv, &blkaddr,
@@ -1205,11 +1211,65 @@ out:
        return metabuf;
 }
 
+static void z_erofs_patch_tail_compacted_index(struct erofs_inode *inode,
+                                              bool previous,
+                                              unsigned int type)
+{
+       const unsigned int totalidx = BLK_ROUND_UP(inode->sbi, inode->i_size);
+       const unsigned int lobits = max_t(unsigned int, inode->z_lclusterbits,
+                               ilog2(Z_EROFS_LI_D0_CBLKCNT) + 1U);
+       u8 *base = inode->compressmeta;
+       u8 *pack = base + inode->extent_isize;
+       unsigned int bitpos, bitoff;
+       u8 *out;
+       u32 v;
+
+       DBG_BUGON(!totalidx);
+       DBG_BUGON(inode->z_lclusterbits > 14);
+
+       if (inode->idata_type == EROFS_IDATA_TYPE_COMPRESSED_END_OF_2B) {
+               pack -= 32;
+               bitpos = 14 * (previous ? 14 : 15);
+               goto out;
+       }
+
+       /* Last compacted index pack is 4B */
+       pack -= 8;
+       if (!(totalidx & 1)) {
+               bitpos = previous ? 0 : 16;
+               goto out;
+       }
+
+       if (!previous) {
+               bitpos = 0;
+               goto out;
+       }
+
+       /* Second to last compacted index pack is 2B */
+       if (inode->idata_type == EROFS_IDATA_TYPE_COMPRESSED_4B1_PREV2B) {
+               pack -= 32;
+               bitpos = 14 * 15;
+       } else {
+               pack -= 8;
+               bitpos = 16;
+       }
+out:
+       DBG_BUGON(pack < base);
+       bitoff = bitpos & 7;
+       out = pack + bitpos / 8;
+       v = get_unaligned_le32(out);
+       v &= ~(Z_EROFS_LI_LCLUSTER_TYPE_MASK << (lobits + bitoff));
+       v |= type << (lobits + bitoff);
+       put_unaligned_le32(v, out);
+}
+
 void z_erofs_drop_inline_pcluster(struct erofs_inode *inode)
 {
        struct erofs_sb_info *sbi = inode->sbi;
        const unsigned int type = Z_EROFS_LCLUSTER_TYPE_PLAIN;
        struct z_erofs_map_header *h = inode->compressmeta;
+       erofs_off_t rawstart;
+       erofs_blk_t head_lcn, eof_lcn;
 
        h->h_advise = cpu_to_le16(le16_to_cpu(h->h_advise) &
                                  ~Z_EROFS_ADVISE_INLINE_PCLUSTER);
@@ -1218,38 +1278,27 @@ void z_erofs_drop_inline_pcluster(struct erofs_inode 
*inode)
        if (!inode->eof_tailraw)
                return;
        DBG_BUGON(inode->idata_type == EROFS_IDATA_TYPE_RAW);
+       DBG_BUGON(!inode->i_size);
+       DBG_BUGON(inode->eof_tailrawsize > erofs_blksiz(sbi));
+       DBG_BUGON(inode->eof_tailrawsize > inode->i_size);
+
+       rawstart = inode->i_size - inode->eof_tailrawsize;
+       head_lcn = rawstart >> sbi->blkszbits;
+       eof_lcn = (inode->i_size - 1) >> sbi->blkszbits;
+       DBG_BUGON(head_lcn != eof_lcn && head_lcn + 1 != eof_lcn);
 
-       /* patch the EOF lcluster to uncompressed type first */
+       /* patch the tail pcluster head to uncompressed type first */
        if (inode->datalayout == EROFS_INODE_COMPRESSED_FULL) {
                struct z_erofs_lcluster_index *di =
-                       (inode->compressmeta + inode->extent_isize) -
-                       sizeof(struct z_erofs_lcluster_index);
+                       (void *)((u8 *)inode->compressmeta +
+                                Z_EROFS_LEGACY_MAP_HEADER_SIZE +
+                                head_lcn *
+                                sizeof(struct z_erofs_lcluster_index));
 
                di->di_advise = cpu_to_le16(type);
        } else if (inode->datalayout == EROFS_INODE_COMPRESSED_COMPACT) {
-               /* handle the last compacted 4B/2B pack */
-               unsigned int lclusterbits = inode->z_lclusterbits;
-               unsigned int lobits, eofs, base, pos, v;
-               u8 *out;
-
-               lobits = max(lclusterbits, ilog2(Z_EROFS_LI_D0_CBLKCNT) + 1U);
-
-               if (inode->idata_type == EROFS_IDATA_TYPE_COMPRESSED_DEFAULT) {
-                       eofs = inode->extent_isize -
-                               (4 << (BLK_ROUND_UP(sbi, inode->i_size) & 1));
-                       base = round_down(eofs, 8);
-                       pos = 16 /* encodebits */ * ((eofs - base) / 4);
-                       out = inode->compressmeta + base + pos / 8;
-               } else {
-                       out = inode->compressmeta + inode->extent_isize -
-                               sizeof(__le32) - sizeof(__le16);
-                       lobits = 16 - 14 /* encodebits */ + lobits;
-               }
-
-               v = (get_unaligned_le16(out) & (BIT(lobits) - 1)) |
-                       (type << lobits);
-               *out = v & 0xff;
-               *(out + 1) = v >> 8;
+               z_erofs_patch_tail_compacted_index(inode, head_lcn != eof_lcn,
+                                                  type);
        } else {
                DBG_BUGON(1);
                return;
-- 
2.47.3


Reply via email to