diff options
Diffstat (limited to 'lib/compress.c')
-rw-r--r-- | lib/compress.c | 242 |
1 files changed, 195 insertions, 47 deletions
diff --git a/lib/compress.c b/lib/compress.c index 98be7a2..ee3b856 100644 --- a/lib/compress.c +++ b/lib/compress.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 /* * Copyright (C) 2018-2019 HUAWEI, Inc. * http://www.huawei.com/ @@ -70,11 +70,15 @@ static void vle_write_indexes(struct z_erofs_vle_compress_ctx *ctx, di.di_clusterofs = cpu_to_le16(ctx->clusterofs); - /* whether the tail-end uncompressed block or not */ + /* whether the tail-end (un)compressed block or not */ if (!d1) { - /* TODO: tail-packing inline compressed data */ - DBG_BUGON(!raw); - type = Z_EROFS_VLE_CLUSTER_TYPE_PLAIN; + /* + * A lcluster cannot have three parts with the middle one which + * is well-compressed for !ztailpacking cases. + */ + DBG_BUGON(!raw && !cfg.c_ztailpacking); + type = raw ? Z_EROFS_VLE_CLUSTER_TYPE_PLAIN : + Z_EROFS_VLE_CLUSTER_TYPE_HEAD; advise = cpu_to_le16(type << Z_EROFS_VLE_DI_CLUSTER_TYPE_BIT); di.di_advise = advise; @@ -97,7 +101,22 @@ static void vle_write_indexes(struct z_erofs_vle_compress_ctx *ctx, } else if (d0) { type = Z_EROFS_VLE_CLUSTER_TYPE_NONHEAD; - di.di_u.delta[0] = cpu_to_le16(d0); + /* + * If the |Z_EROFS_VLE_DI_D0_CBLKCNT| bit is set, parser + * will interpret |delta[0]| as size of pcluster, rather + * than distance to last head cluster. Normally this + * isn't a problem, because uncompressed extent size are + * below Z_EROFS_VLE_DI_D0_CBLKCNT * BLOCK_SIZE = 8MB. + * But with large pcluster it's possible to go over this + * number, resulting in corrupted compressed indices. + * To solve this, we replace d0 with + * Z_EROFS_VLE_DI_D0_CBLKCNT-1. + */ + if (d0 >= Z_EROFS_VLE_DI_D0_CBLKCNT) + di.di_u.delta[0] = cpu_to_le16( + Z_EROFS_VLE_DI_D0_CBLKCNT - 1); + else + di.di_u.delta[0] = cpu_to_le16(d0); di.di_u.delta[1] = cpu_to_le16(d1); } else { type = raw ? Z_EROFS_VLE_CLUSTER_TYPE_PLAIN : @@ -162,6 +181,47 @@ static unsigned int z_erofs_get_max_pclusterblks(struct erofs_inode *inode) return cfg.c_pclusterblks_def; } +static int z_erofs_fill_inline_data(struct erofs_inode *inode, void *data, + unsigned int len, bool raw) +{ + inode->z_advise |= Z_EROFS_ADVISE_INLINE_PCLUSTER; + inode->idata_size = len; + inode->compressed_idata = !raw; + + inode->idata = malloc(inode->idata_size); + if (!inode->idata) + return -ENOMEM; + erofs_dbg("Recording %u %scompressed inline data", + inode->idata_size, raw ? "un" : ""); + memcpy(inode->idata, data, inode->idata_size); + return len; +} + +static void tryrecompress_trailing(void *in, unsigned int *insize, + void *out, int *compressedsize) +{ + static char tmp[Z_EROFS_PCLUSTER_MAX_SIZE]; + unsigned int count; + int ret = *compressedsize; + + /* no need to recompress */ + if (!(ret & (EROFS_BLKSIZ - 1))) + return; + + count = *insize; + ret = erofs_compress_destsize(&compresshandle, + in, &count, (void *)tmp, + rounddown(ret, EROFS_BLKSIZ), false); + if (ret <= 0 || ret + (*insize - count) >= + roundup(*compressedsize, EROFS_BLKSIZ)) + return; + + /* replace the original compressed data if any gain */ + memcpy(out, tmp, ret); + *insize = count; + *compressedsize = ret; +} + static int vle_compress_one(struct erofs_inode *inode, struct z_erofs_vle_compress_ctx *ctx, bool final) @@ -174,41 +234,75 @@ static int vle_compress_one(struct erofs_inode *inode, char *const dst = dstbuf + EROFS_BLKSIZ; while (len) { - const unsigned int pclustersize = + unsigned int pclustersize = z_erofs_get_max_pclusterblks(inode) * EROFS_BLKSIZ; + bool may_inline = (cfg.c_ztailpacking && final); bool raw; if (len <= pclustersize) { - if (final) { - if (len <= EROFS_BLKSIZ) - goto nocompression; - } else { + if (!final) break; - } + if (!may_inline && len <= EROFS_BLKSIZ) + goto nocompression; } count = min(len, cfg.c_max_decompressed_extent_bytes); ret = erofs_compress_destsize(h, ctx->queue + ctx->head, - &count, dst, pclustersize); + &count, dst, pclustersize, + !(final && len == count)); if (ret <= 0) { if (ret != -EAGAIN) { erofs_err("failed to compress %s: %s", inode->i_srcpath, erofs_strerror(ret)); } + + if (may_inline && len < EROFS_BLKSIZ) + ret = z_erofs_fill_inline_data(inode, + ctx->queue + ctx->head, + len, true); + else nocompression: - ret = write_uncompressed_extent(ctx, &len, dst); + ret = write_uncompressed_extent(ctx, &len, dst); + if (ret < 0) return ret; count = ret; + + /* + * XXX: For now, we have to leave `ctx->compressedblks + * = 1' since there is no way to generate compressed + * indexes after the time that ztailpacking is decided. + */ ctx->compressedblks = 1; raw = true; + /* tailpcluster should be less than 1 block */ + } else if (may_inline && len == count && + ret < EROFS_BLKSIZ) { + if (ctx->clusterofs + len <= EROFS_BLKSIZ) { + inode->eof_tailraw = malloc(len); + if (!inode->eof_tailraw) + return -ENOMEM; + + memcpy(inode->eof_tailraw, + ctx->queue + ctx->head, len); + inode->eof_tailrawsize = len; + } + + ret = z_erofs_fill_inline_data(inode, dst, ret, false); + if (ret < 0) + return ret; + ctx->compressedblks = 1; + raw = false; } else { - const unsigned int tailused = ret & (EROFS_BLKSIZ - 1); - const unsigned int padding = - erofs_sb_has_lz4_0padding() && tailused ? - EROFS_BLKSIZ - tailused : 0; + unsigned int tailused, padding; + if (may_inline && len == count) + tryrecompress_trailing(ctx->queue + ctx->head, + &count, dst, &ret); + + tailused = ret & (EROFS_BLKSIZ - 1); + padding = 0; ctx->compressedblks = DIV_ROUND_UP(ret, EROFS_BLKSIZ); DBG_BUGON(ctx->compressedblks * EROFS_BLKSIZ >= count); @@ -216,6 +310,8 @@ nocompression: if (!erofs_sb_has_lz4_0padding()) memset(dst + ret, 0, roundup(ret, EROFS_BLKSIZ) - ret); + else if (tailused) + padding = EROFS_BLKSIZ - tailused; /* write compressed data */ erofs_dbg("Writing %u compressed data to %u of %u blocks", @@ -359,7 +455,8 @@ int z_erofs_convert_to_compacted_format(struct erofs_inode *inode, inode->xattr_isize) + sizeof(struct z_erofs_map_header); const unsigned int totalidx = (legacymetasize - - Z_EROFS_LEGACY_MAP_HEADER_SIZE) / 8; + Z_EROFS_LEGACY_MAP_HEADER_SIZE) / + sizeof(struct z_erofs_vle_decompressed_index); const unsigned int logical_clusterbits = inode->z_logical_clusterbits; u8 *out, *in; struct z_erofs_compressindex_vec cv[16]; @@ -449,6 +546,7 @@ static void z_erofs_write_mapheader(struct erofs_inode *inode, { struct z_erofs_map_header h = { .h_advise = cpu_to_le16(inode->z_advise), + .h_idata_size = cpu_to_le16(inode->idata_size), .h_algorithmtype = inode->z_algorithmtype[1] << 4 | inode->z_algorithmtype[0], /* lclustersize */ @@ -460,10 +558,56 @@ static void z_erofs_write_mapheader(struct erofs_inode *inode, memcpy(compressmeta, &h, sizeof(struct z_erofs_map_header)); } +void z_erofs_drop_inline_pcluster(struct erofs_inode *inode) +{ + const unsigned int type = Z_EROFS_VLE_CLUSTER_TYPE_PLAIN; + struct z_erofs_map_header *h = inode->compressmeta; + + h->h_advise = cpu_to_le16(le16_to_cpu(h->h_advise) & + ~Z_EROFS_ADVISE_INLINE_PCLUSTER); + if (!inode->eof_tailraw) + return; + DBG_BUGON(inode->compressed_idata != true); + + /* patch the EOF lcluster to uncompressed type first */ + if (inode->datalayout == EROFS_INODE_FLAT_COMPRESSION_LEGACY) { + struct z_erofs_vle_decompressed_index *di = + (inode->compressmeta + inode->extent_isize) - + sizeof(struct z_erofs_vle_decompressed_index); + __le16 advise = + cpu_to_le16(type << Z_EROFS_VLE_DI_CLUSTER_TYPE_BIT); + + di->di_advise = advise; + } else if (inode->datalayout == EROFS_INODE_FLAT_COMPRESSION) { + /* handle the last compacted 4B pack */ + unsigned int eofs, base, pos, v, lo; + u8 *out; + + eofs = inode->extent_isize - + (4 << (DIV_ROUND_UP(inode->i_size, EROFS_BLKSIZ) & 1)); + base = round_down(eofs, 8); + pos = 16 /* encodebits */ * ((eofs - base) / 4); + out = inode->compressmeta + base; + lo = get_unaligned_le32(out + pos / 8) & (EROFS_BLKSIZ - 1); + v = (type << LOG_BLOCK_SIZE) | lo; + out[pos / 8] = v & 0xff; + out[pos / 8 + 1] = v >> 8; + } else { + DBG_BUGON(1); + return; + } + free(inode->idata); + /* replace idata with prepared uncompressed data */ + inode->idata = inode->eof_tailraw; + inode->idata_size = inode->eof_tailrawsize; + inode->compressed_idata = false; + inode->eof_tailraw = NULL; +} + int erofs_write_compressed_file(struct erofs_inode *inode) { struct erofs_buffer_head *bh; - struct z_erofs_vle_compress_ctx ctx; + static struct z_erofs_vle_compress_ctx ctx; erofs_off_t remaining; erofs_blk_t blkaddr, compressed_blocks; unsigned int legacymetasize; @@ -476,7 +620,7 @@ int erofs_write_compressed_file(struct erofs_inode *inode) fd = open(inode->i_srcpath, O_RDONLY | O_BINARY); if (fd < 0) { ret = -errno; - goto err_free; + goto err_free_meta; } /* allocate main data buffer */ @@ -504,8 +648,6 @@ int erofs_write_compressed_file(struct erofs_inode *inode) inode->z_algorithmtype[1] = algorithmtype[1]; inode->z_logical_clusterbits = LOG_BLOCK_SIZE; - z_erofs_write_mapheader(inode, compressmeta); - blkaddr = erofs_mapbh(bh->block); /* start_blkaddr */ ctx.blkaddr = blkaddr; ctx.metacur = compressmeta + Z_EROFS_LEGACY_MAP_HEADER_SIZE; @@ -525,44 +667,46 @@ int erofs_write_compressed_file(struct erofs_inode *inode) remaining -= readcount; ctx.tail += readcount; - /* do one compress round */ - ret = vle_compress_one(inode, &ctx, false); + ret = vle_compress_one(inode, &ctx, !remaining); if (ret) - goto err_bdrop; + goto err_free_idata; } - - /* do the final round */ - ret = vle_compress_one(inode, &ctx, true); - if (ret) - goto err_bdrop; + DBG_BUGON(ctx.head != ctx.tail); /* fall back to no compression mode */ compressed_blocks = ctx.blkaddr - blkaddr; - if (compressed_blocks >= BLK_ROUND_UP(inode->i_size)) { - ret = -ENOSPC; - goto err_bdrop; - } + DBG_BUGON(compressed_blocks < !!inode->idata_size); + compressed_blocks -= !!inode->idata_size; vle_write_indexes_final(&ctx); + legacymetasize = ctx.metacur - compressmeta; + /* estimate if data compression saves space or not */ + if (compressed_blocks * EROFS_BLKSIZ + inode->idata_size + + legacymetasize >= inode->i_size) { + ret = -ENOSPC; + goto err_free_idata; + } + z_erofs_write_mapheader(inode, compressmeta); close(fd); - DBG_BUGON(!compressed_blocks); - ret = erofs_bh_balloon(bh, blknr_to_addr(compressed_blocks)); - DBG_BUGON(ret != EROFS_BLKSIZ); + if (compressed_blocks) { + ret = erofs_bh_balloon(bh, blknr_to_addr(compressed_blocks)); + DBG_BUGON(ret != EROFS_BLKSIZ); + } else { + DBG_BUGON(!inode->idata_size); + } erofs_info("compressed %s (%llu bytes) into %u blocks", inode->i_srcpath, (unsigned long long)inode->i_size, compressed_blocks); - /* - * TODO: need to move erofs_bdrop to erofs_write_tail_end - * when both mkfs & kernel support compression inline. - */ - erofs_bdrop(bh, false); - inode->idata_size = 0; + if (inode->idata_size) + inode->bh_data = bh; + else + erofs_bdrop(bh, false); + inode->u.i_blocks = compressed_blocks; - legacymetasize = ctx.metacur - compressmeta; if (inode->datalayout == EROFS_INODE_FLAT_COMPRESSION_LEGACY) { inode->extent_isize = legacymetasize; } else { @@ -575,11 +719,16 @@ int erofs_write_compressed_file(struct erofs_inode *inode) erofs_droid_blocklist_write(inode, blkaddr, compressed_blocks); return 0; +err_free_idata: + if (inode->idata) { + free(inode->idata); + inode->idata = NULL; + } err_bdrop: erofs_bdrop(bh, true); /* revoke buffer */ err_close: close(fd); -err_free: +err_free_meta: free(compressmeta); return ret; } @@ -691,7 +840,6 @@ int z_erofs_compress_init(struct erofs_buffer_head *sb_bh) return -EINVAL; } erofs_sb_set_big_pcluster(); - erofs_warn("EXPERIMENTAL big pcluster feature in use. Use at your own risk!"); } if (ret != Z_EROFS_COMPRESSION_LZ4) |