| // SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 |
| /* |
| * Copyright (C) 2018-2019 HUAWEI, Inc. |
| * http://www.huawei.com/ |
| * Created by Miao Xie <miaoxie@huawei.com> |
| * with heavy changes by Gao Xiang <gaoxiang25@huawei.com> |
| */ |
| #ifndef _LARGEFILE64_SOURCE |
| #define _LARGEFILE64_SOURCE |
| #endif |
| #include <string.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include "erofs/print.h" |
| #include "erofs/io.h" |
| #include "erofs/cache.h" |
| #include "erofs/compress.h" |
| #include "erofs/dedupe.h" |
| #include "compressor.h" |
| #include "erofs/block_list.h" |
| #include "erofs/compress_hints.h" |
| #include "erofs/fragments.h" |
| |
| /* compressing configuration specified by users */ |
| struct erofs_compress_cfg { |
| struct erofs_compress handle; |
| unsigned int algorithmtype; |
| bool enable; |
| } erofs_ccfg[EROFS_MAX_COMPR_CFGS]; |
| |
| struct z_erofs_vle_compress_ctx { |
| u8 queue[EROFS_CONFIG_COMPR_MAX_SZ * 2]; |
| struct z_erofs_inmem_extent e; /* (lookahead) extent */ |
| |
| struct erofs_inode *inode; |
| struct erofs_compress_cfg *ccfg; |
| |
| u8 *metacur; |
| unsigned int head, tail; |
| erofs_off_t remaining; |
| unsigned int pclustersize; |
| erofs_blk_t blkaddr; /* pointing to the next blkaddr */ |
| u16 clusterofs; |
| |
| u32 tof_chksum; |
| bool fix_dedupedfrag; |
| bool fragemitted; |
| }; |
| |
| #define Z_EROFS_LEGACY_MAP_HEADER_SIZE Z_EROFS_FULL_INDEX_ALIGN(0) |
| |
| static void z_erofs_write_indexes_final(struct z_erofs_vle_compress_ctx *ctx) |
| { |
| const unsigned int type = Z_EROFS_LCLUSTER_TYPE_PLAIN; |
| struct z_erofs_lcluster_index di; |
| |
| if (!ctx->clusterofs) |
| return; |
| |
| di.di_clusterofs = cpu_to_le16(ctx->clusterofs); |
| di.di_u.blkaddr = 0; |
| di.di_advise = cpu_to_le16(type << Z_EROFS_LI_LCLUSTER_TYPE_BIT); |
| |
| memcpy(ctx->metacur, &di, sizeof(di)); |
| ctx->metacur += sizeof(di); |
| } |
| |
| static void z_erofs_write_indexes(struct z_erofs_vle_compress_ctx *ctx) |
| { |
| struct erofs_inode *inode = ctx->inode; |
| struct erofs_sb_info *sbi = inode->sbi; |
| unsigned int clusterofs = ctx->clusterofs; |
| unsigned int count = ctx->e.length; |
| unsigned int d0 = 0, d1 = (clusterofs + count) / erofs_blksiz(sbi); |
| struct z_erofs_lcluster_index di; |
| unsigned int type, advise; |
| |
| if (!count) |
| return; |
| |
| ctx->e.length = 0; /* mark as written first */ |
| di.di_clusterofs = cpu_to_le16(ctx->clusterofs); |
| |
| /* whether the tail-end (un)compressed block or not */ |
| if (!d1) { |
| /* |
| * A lcluster cannot have three parts with the middle one which |
| * is well-compressed for !ztailpacking cases. |
| */ |
| DBG_BUGON(!ctx->e.raw && !cfg.c_ztailpacking && !cfg.c_fragments); |
| DBG_BUGON(ctx->e.partial); |
| type = ctx->e.raw ? Z_EROFS_LCLUSTER_TYPE_PLAIN : |
| Z_EROFS_LCLUSTER_TYPE_HEAD1; |
| advise = type << Z_EROFS_LI_LCLUSTER_TYPE_BIT; |
| di.di_advise = cpu_to_le16(advise); |
| |
| if (inode->datalayout == EROFS_INODE_COMPRESSED_FULL && |
| !ctx->e.compressedblks) |
| di.di_u.blkaddr = cpu_to_le32(inode->fragmentoff >> 32); |
| else |
| di.di_u.blkaddr = cpu_to_le32(ctx->e.blkaddr); |
| memcpy(ctx->metacur, &di, sizeof(di)); |
| ctx->metacur += sizeof(di); |
| |
| /* don't add the final index if the tail-end block exists */ |
| ctx->clusterofs = 0; |
| return; |
| } |
| |
| do { |
| advise = 0; |
| /* XXX: big pcluster feature should be per-inode */ |
| if (d0 == 1 && erofs_sb_has_big_pcluster(sbi)) { |
| type = Z_EROFS_LCLUSTER_TYPE_NONHEAD; |
| di.di_u.delta[0] = cpu_to_le16(ctx->e.compressedblks | |
| Z_EROFS_LI_D0_CBLKCNT); |
| di.di_u.delta[1] = cpu_to_le16(d1); |
| } else if (d0) { |
| type = Z_EROFS_LCLUSTER_TYPE_NONHEAD; |
| |
| /* |
| * 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_LI_D0_CBLKCNT) |
| di.di_u.delta[0] = cpu_to_le16( |
| Z_EROFS_LI_D0_CBLKCNT - 1); |
| else |
| di.di_u.delta[0] = cpu_to_le16(d0); |
| di.di_u.delta[1] = cpu_to_le16(d1); |
| } else { |
| type = ctx->e.raw ? Z_EROFS_LCLUSTER_TYPE_PLAIN : |
| Z_EROFS_LCLUSTER_TYPE_HEAD1; |
| |
| if (inode->datalayout == EROFS_INODE_COMPRESSED_FULL && |
| !ctx->e.compressedblks) |
| di.di_u.blkaddr = cpu_to_le32(inode->fragmentoff >> 32); |
| else |
| di.di_u.blkaddr = cpu_to_le32(ctx->e.blkaddr); |
| |
| if (ctx->e.partial) { |
| DBG_BUGON(ctx->e.raw); |
| advise |= Z_EROFS_LI_PARTIAL_REF; |
| } |
| } |
| advise |= type << Z_EROFS_LI_LCLUSTER_TYPE_BIT; |
| di.di_advise = cpu_to_le16(advise); |
| |
| memcpy(ctx->metacur, &di, sizeof(di)); |
| ctx->metacur += sizeof(di); |
| |
| count -= erofs_blksiz(sbi) - clusterofs; |
| clusterofs = 0; |
| |
| ++d0; |
| --d1; |
| } while (clusterofs + count >= erofs_blksiz(sbi)); |
| |
| ctx->clusterofs = clusterofs + count; |
| } |
| |
| static int z_erofs_compress_dedupe(struct z_erofs_vle_compress_ctx *ctx, |
| unsigned int *len) |
| { |
| struct erofs_inode *inode = ctx->inode; |
| const unsigned int lclustermask = (1 << inode->z_logical_clusterbits) - 1; |
| struct erofs_sb_info *sbi = inode->sbi; |
| int ret = 0; |
| |
| /* |
| * No need dedupe for packed inode since it is composed of |
| * fragments which have already been deduplicated. |
| */ |
| if (erofs_is_packed_inode(inode)) |
| goto out; |
| |
| do { |
| struct z_erofs_dedupe_ctx dctx = { |
| .start = ctx->queue + ctx->head - ({ int rc; |
| if (ctx->e.length <= erofs_blksiz(sbi)) |
| rc = 0; |
| else if (ctx->e.length - erofs_blksiz(sbi) >= ctx->head) |
| rc = ctx->head; |
| else |
| rc = ctx->e.length - erofs_blksiz(sbi); |
| rc; }), |
| .end = ctx->queue + ctx->head + *len, |
| .cur = ctx->queue + ctx->head, |
| }; |
| int delta; |
| |
| if (z_erofs_dedupe_match(&dctx)) |
| break; |
| |
| delta = ctx->queue + ctx->head - dctx.cur; |
| /* |
| * For big pcluster dedupe, leave two indices at least to store |
| * CBLKCNT as the first step. Even laterly, an one-block |
| * decompresssion could be done as another try in practice. |
| */ |
| if (dctx.e.compressedblks > 1 && |
| ((ctx->clusterofs + ctx->e.length - delta) & lclustermask) + |
| dctx.e.length < 2 * (lclustermask + 1)) |
| break; |
| |
| if (delta) { |
| DBG_BUGON(delta < 0); |
| DBG_BUGON(!ctx->e.length); |
| |
| /* |
| * For big pcluster dedupe, if we decide to shorten the |
| * previous big pcluster, make sure that the previous |
| * CBLKCNT is still kept. |
| */ |
| if (ctx->e.compressedblks > 1 && |
| (ctx->clusterofs & lclustermask) + ctx->e.length |
| - delta < 2 * (lclustermask + 1)) |
| break; |
| ctx->e.partial = true; |
| ctx->e.length -= delta; |
| } |
| |
| /* fall back to noncompact indexes for deduplication */ |
| inode->z_advise &= ~Z_EROFS_ADVISE_COMPACTED_2B; |
| inode->datalayout = EROFS_INODE_COMPRESSED_FULL; |
| erofs_sb_set_dedupe(sbi); |
| |
| sbi->saved_by_deduplication += |
| dctx.e.compressedblks * erofs_blksiz(sbi); |
| erofs_dbg("Dedupe %u %scompressed data (delta %d) to %u of %u blocks", |
| dctx.e.length, dctx.e.raw ? "un" : "", |
| delta, dctx.e.blkaddr, dctx.e.compressedblks); |
| z_erofs_write_indexes(ctx); |
| ctx->e = dctx.e; |
| ctx->head += dctx.e.length - delta; |
| DBG_BUGON(*len < dctx.e.length - delta); |
| *len -= dctx.e.length - delta; |
| |
| if (ctx->head >= EROFS_CONFIG_COMPR_MAX_SZ) { |
| const unsigned int qh_aligned = |
| round_down(ctx->head, erofs_blksiz(sbi)); |
| const unsigned int qh_after = ctx->head - qh_aligned; |
| |
| memmove(ctx->queue, ctx->queue + qh_aligned, |
| *len + qh_after); |
| ctx->head = qh_after; |
| ctx->tail = qh_after + *len; |
| ret = -EAGAIN; |
| break; |
| } |
| } while (*len); |
| |
| out: |
| z_erofs_write_indexes(ctx); |
| return ret; |
| } |
| |
| static int write_uncompressed_extent(struct z_erofs_vle_compress_ctx *ctx, |
| unsigned int *len, char *dst) |
| { |
| int ret; |
| struct erofs_sb_info *sbi = ctx->inode->sbi; |
| unsigned int count, interlaced_offset, rightpart; |
| |
| /* reset clusterofs to 0 if permitted */ |
| if (!erofs_sb_has_lz4_0padding(sbi) && ctx->clusterofs && |
| ctx->head >= ctx->clusterofs) { |
| ctx->head -= ctx->clusterofs; |
| *len += ctx->clusterofs; |
| ctx->clusterofs = 0; |
| } |
| |
| count = min(erofs_blksiz(sbi), *len); |
| |
| /* write interlaced uncompressed data if needed */ |
| if (ctx->inode->z_advise & Z_EROFS_ADVISE_INTERLACED_PCLUSTER) |
| interlaced_offset = ctx->clusterofs; |
| else |
| interlaced_offset = 0; |
| rightpart = min(erofs_blksiz(sbi) - interlaced_offset, count); |
| |
| memset(dst, 0, erofs_blksiz(sbi)); |
| |
| memcpy(dst + interlaced_offset, ctx->queue + ctx->head, rightpart); |
| memcpy(dst, ctx->queue + ctx->head + rightpart, count - rightpart); |
| |
| erofs_dbg("Writing %u uncompressed data to block %u", |
| count, ctx->blkaddr); |
| ret = blk_write(sbi, dst, ctx->blkaddr, 1); |
| if (ret) |
| return ret; |
| return count; |
| } |
| |
| static unsigned int z_erofs_get_max_pclustersize(struct erofs_inode *inode) |
| { |
| unsigned int pclusterblks; |
| |
| if (erofs_is_packed_inode(inode)) |
| pclusterblks = cfg.c_pclusterblks_packed; |
| #ifndef NDEBUG |
| else if (cfg.c_random_pclusterblks) |
| pclusterblks = 1 + rand() % cfg.c_pclusterblks_max; |
| #endif |
| else if (cfg.c_compress_hints_file) { |
| z_erofs_apply_compress_hints(inode); |
| DBG_BUGON(!inode->z_physical_clusterblks); |
| pclusterblks = inode->z_physical_clusterblks; |
| } else { |
| pclusterblks = cfg.c_pclusterblks_def; |
| } |
| return pclusterblks * erofs_blksiz(inode->sbi); |
| } |
| |
| 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(struct z_erofs_vle_compress_ctx *ctx, |
| struct erofs_compress *ec, |
| void *in, unsigned int *insize, |
| void *out, int *compressedsize) |
| { |
| struct erofs_sb_info *sbi = ctx->inode->sbi; |
| static char tmp[Z_EROFS_PCLUSTER_MAX_SIZE]; |
| unsigned int count; |
| int ret = *compressedsize; |
| |
| /* no need to recompress */ |
| if (!(ret & (erofs_blksiz(sbi) - 1))) |
| return; |
| |
| count = *insize; |
| ret = erofs_compress_destsize(ec, in, &count, (void *)tmp, |
| rounddown(ret, erofs_blksiz(sbi)), false); |
| if (ret <= 0 || ret + (*insize - count) >= |
| roundup(*compressedsize, erofs_blksiz(sbi))) |
| return; |
| |
| /* replace the original compressed data if any gain */ |
| memcpy(out, tmp, ret); |
| *insize = count; |
| *compressedsize = ret; |
| } |
| |
| static bool z_erofs_fixup_deduped_fragment(struct z_erofs_vle_compress_ctx *ctx, |
| unsigned int len) |
| { |
| struct erofs_inode *inode = ctx->inode; |
| struct erofs_sb_info *sbi = inode->sbi; |
| const unsigned int newsize = ctx->remaining + len; |
| |
| DBG_BUGON(!inode->fragment_size); |
| |
| /* try to fix again if it gets larger (should be rare) */ |
| if (inode->fragment_size < newsize) { |
| ctx->pclustersize = min(z_erofs_get_max_pclustersize(inode), |
| roundup(newsize - inode->fragment_size, |
| erofs_blksiz(sbi))); |
| return false; |
| } |
| |
| inode->fragmentoff += inode->fragment_size - newsize; |
| inode->fragment_size = newsize; |
| |
| erofs_dbg("Reducing fragment size to %u at %llu", |
| inode->fragment_size, inode->fragmentoff | 0ULL); |
| |
| /* it's the end */ |
| DBG_BUGON(ctx->tail - ctx->head + ctx->remaining != newsize); |
| ctx->head = ctx->tail; |
| ctx->remaining = 0; |
| return true; |
| } |
| |
| static int vle_compress_one(struct z_erofs_vle_compress_ctx *ctx) |
| { |
| static char dstbuf[EROFS_CONFIG_COMPR_MAX_SZ + EROFS_MAX_BLOCK_SIZE]; |
| struct erofs_inode *inode = ctx->inode; |
| struct erofs_sb_info *sbi = inode->sbi; |
| char *const dst = dstbuf + erofs_blksiz(sbi); |
| struct erofs_compress *const h = &ctx->ccfg->handle; |
| unsigned int len = ctx->tail - ctx->head; |
| bool is_packed_inode = erofs_is_packed_inode(inode); |
| bool final = !ctx->remaining; |
| int ret; |
| |
| while (len) { |
| bool may_packing = (cfg.c_fragments && final && |
| !is_packed_inode); |
| bool may_inline = (cfg.c_ztailpacking && final && |
| !may_packing); |
| bool fix_dedupedfrag = ctx->fix_dedupedfrag; |
| |
| if (z_erofs_compress_dedupe(ctx, &len) && !final) |
| break; |
| |
| if (len <= ctx->pclustersize) { |
| if (!final || !len) |
| break; |
| if (may_packing) { |
| if (inode->fragment_size && !fix_dedupedfrag) { |
| ctx->pclustersize = |
| roundup(len, erofs_blksiz(sbi)); |
| goto fix_dedupedfrag; |
| } |
| ctx->e.length = len; |
| goto frag_packing; |
| } |
| if (!may_inline && len <= erofs_blksiz(sbi)) |
| goto nocompression; |
| } |
| |
| ctx->e.length = min(len, |
| cfg.c_max_decompressed_extent_bytes); |
| ret = erofs_compress_destsize(h, ctx->queue + ctx->head, |
| &ctx->e.length, dst, ctx->pclustersize, |
| !(final && len == ctx->e.length)); |
| 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(sbi)) { |
| ret = z_erofs_fill_inline_data(inode, |
| ctx->queue + ctx->head, |
| len, true); |
| } else { |
| may_inline = false; |
| may_packing = false; |
| nocompression: |
| ret = write_uncompressed_extent(ctx, &len, dst); |
| } |
| |
| if (ret < 0) |
| return ret; |
| ctx->e.length = 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->e.compressedblks = 1; |
| ctx->e.raw = true; |
| } else if (may_packing && len == ctx->e.length && |
| ret < ctx->pclustersize && |
| (!inode->fragment_size || fix_dedupedfrag)) { |
| frag_packing: |
| ret = z_erofs_pack_fragments(inode, |
| ctx->queue + ctx->head, |
| len, ctx->tof_chksum); |
| if (ret < 0) |
| return ret; |
| ctx->e.compressedblks = 0; /* indicate a fragment */ |
| ctx->e.raw = false; |
| ctx->fragemitted = true; |
| fix_dedupedfrag = false; |
| /* tailpcluster should be less than 1 block */ |
| } else if (may_inline && len == ctx->e.length && |
| ret < erofs_blksiz(sbi)) { |
| if (ctx->clusterofs + len <= erofs_blksiz(sbi)) { |
| 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->e.compressedblks = 1; |
| ctx->e.raw = false; |
| } else { |
| unsigned int tailused, padding; |
| |
| /* |
| * If there's space left for the last round when |
| * deduping fragments, try to read the fragment and |
| * recompress a little more to check whether it can be |
| * filled up. Fix up the fragment if succeeds. |
| * Otherwise, just drop it and go to packing. |
| */ |
| if (may_packing && len == ctx->e.length && |
| (ret & (erofs_blksiz(sbi) - 1)) && |
| ctx->tail < sizeof(ctx->queue)) { |
| ctx->pclustersize = BLK_ROUND_UP(sbi, ret) * |
| erofs_blksiz(sbi); |
| goto fix_dedupedfrag; |
| } |
| |
| if (may_inline && len == ctx->e.length) |
| tryrecompress_trailing(ctx, h, |
| ctx->queue + ctx->head, |
| &ctx->e.length, dst, &ret); |
| |
| tailused = ret & (erofs_blksiz(sbi) - 1); |
| padding = 0; |
| ctx->e.compressedblks = BLK_ROUND_UP(sbi, ret); |
| DBG_BUGON(ctx->e.compressedblks * erofs_blksiz(sbi) >= |
| ctx->e.length); |
| |
| /* zero out garbage trailing data for non-0padding */ |
| if (!erofs_sb_has_lz4_0padding(sbi)) |
| memset(dst + ret, 0, |
| roundup(ret, erofs_blksiz(sbi)) - ret); |
| else if (tailused) |
| padding = erofs_blksiz(sbi) - tailused; |
| |
| /* write compressed data */ |
| erofs_dbg("Writing %u compressed data to %u of %u blocks", |
| ctx->e.length, ctx->blkaddr, |
| ctx->e.compressedblks); |
| |
| ret = blk_write(sbi, dst - padding, ctx->blkaddr, |
| ctx->e.compressedblks); |
| if (ret) |
| return ret; |
| ctx->e.raw = false; |
| may_inline = false; |
| may_packing = false; |
| } |
| ctx->e.partial = false; |
| ctx->e.blkaddr = ctx->blkaddr; |
| if (!may_inline && !may_packing && !is_packed_inode) |
| (void)z_erofs_dedupe_insert(&ctx->e, |
| ctx->queue + ctx->head); |
| ctx->blkaddr += ctx->e.compressedblks; |
| ctx->head += ctx->e.length; |
| len -= ctx->e.length; |
| |
| if (fix_dedupedfrag && |
| z_erofs_fixup_deduped_fragment(ctx, len)) |
| break; |
| |
| if (!final && ctx->head >= EROFS_CONFIG_COMPR_MAX_SZ) { |
| const unsigned int qh_aligned = |
| round_down(ctx->head, erofs_blksiz(sbi)); |
| const unsigned int qh_after = ctx->head - qh_aligned; |
| |
| memmove(ctx->queue, ctx->queue + qh_aligned, |
| len + qh_after); |
| ctx->head = qh_after; |
| ctx->tail = qh_after + len; |
| break; |
| } |
| } |
| return 0; |
| |
| fix_dedupedfrag: |
| DBG_BUGON(!inode->fragment_size); |
| ctx->remaining += inode->fragment_size; |
| ctx->e.length = 0; |
| ctx->fix_dedupedfrag = true; |
| return 0; |
| } |
| |
| struct z_erofs_compressindex_vec { |
| union { |
| erofs_blk_t blkaddr; |
| u16 delta[2]; |
| } u; |
| u16 clusterofs; |
| u8 clustertype; |
| }; |
| |
| static void *parse_legacy_indexes(struct z_erofs_compressindex_vec *cv, |
| unsigned int nr, void *metacur) |
| { |
| struct z_erofs_lcluster_index *const db = metacur; |
| unsigned int i; |
| |
| for (i = 0; i < nr; ++i, ++cv) { |
| struct z_erofs_lcluster_index *const di = db + i; |
| const unsigned int advise = le16_to_cpu(di->di_advise); |
| |
| cv->clustertype = (advise >> Z_EROFS_LI_LCLUSTER_TYPE_BIT) & |
| ((1 << Z_EROFS_LI_LCLUSTER_TYPE_BITS) - 1); |
| cv->clusterofs = le16_to_cpu(di->di_clusterofs); |
| |
| if (cv->clustertype == Z_EROFS_LCLUSTER_TYPE_NONHEAD) { |
| cv->u.delta[0] = le16_to_cpu(di->di_u.delta[0]); |
| cv->u.delta[1] = le16_to_cpu(di->di_u.delta[1]); |
| } else { |
| cv->u.blkaddr = le32_to_cpu(di->di_u.blkaddr); |
| } |
| } |
| return db + nr; |
| } |
| |
| static void *write_compacted_indexes(u8 *out, |
| struct z_erofs_compressindex_vec *cv, |
| erofs_blk_t *blkaddr_ret, |
| unsigned int destsize, |
| unsigned int logical_clusterbits, |
| bool final, bool *dummy_head, |
| bool update_blkaddr) |
| { |
| unsigned int vcnt, encodebits, pos, i, cblks; |
| erofs_blk_t blkaddr; |
| |
| if (destsize == 4) |
| vcnt = 2; |
| else if (destsize == 2 && logical_clusterbits == 12) |
| vcnt = 16; |
| else |
| return ERR_PTR(-EINVAL); |
| encodebits = (vcnt * destsize * 8 - 32) / vcnt; |
| blkaddr = *blkaddr_ret; |
| |
| pos = 0; |
| for (i = 0; i < vcnt; ++i) { |
| unsigned int offset, v; |
| u8 ch, rem; |
| |
| if (cv[i].clustertype == Z_EROFS_LCLUSTER_TYPE_NONHEAD) { |
| if (cv[i].u.delta[0] & Z_EROFS_LI_D0_CBLKCNT) { |
| cblks = cv[i].u.delta[0] & ~Z_EROFS_LI_D0_CBLKCNT; |
| offset = cv[i].u.delta[0]; |
| blkaddr += cblks; |
| *dummy_head = false; |
| } else if (i + 1 == vcnt) { |
| offset = min_t(u16, cv[i].u.delta[1], |
| (1 << logical_clusterbits) - 1); |
| } else { |
| offset = cv[i].u.delta[0]; |
| } |
| } else { |
| offset = cv[i].clusterofs; |
| if (*dummy_head) { |
| ++blkaddr; |
| if (update_blkaddr) |
| *blkaddr_ret = blkaddr; |
| } |
| *dummy_head = true; |
| update_blkaddr = false; |
| |
| if (cv[i].u.blkaddr != blkaddr) { |
| if (i + 1 != vcnt) |
| DBG_BUGON(!final); |
| DBG_BUGON(cv[i].u.blkaddr); |
| } |
| } |
| v = (cv[i].clustertype << logical_clusterbits) | offset; |
| rem = pos & 7; |
| ch = out[pos / 8] & ((1 << rem) - 1); |
| out[pos / 8] = (v << rem) | ch; |
| out[pos / 8 + 1] = v >> (8 - rem); |
| out[pos / 8 + 2] = v >> (16 - rem); |
| pos += encodebits; |
| } |
| DBG_BUGON(destsize * vcnt * 8 != pos + 32); |
| *(__le32 *)(out + destsize * vcnt - 4) = cpu_to_le32(*blkaddr_ret); |
| *blkaddr_ret = blkaddr; |
| return out + destsize * vcnt; |
| } |
| |
| int z_erofs_convert_to_compacted_format(struct erofs_inode *inode, |
| erofs_blk_t blkaddr, |
| unsigned int legacymetasize, |
| void *compressmeta) |
| { |
| const unsigned int mpos = roundup(inode->inode_isize + |
| inode->xattr_isize, 8) + |
| sizeof(struct z_erofs_map_header); |
| const unsigned int totalidx = (legacymetasize - |
| Z_EROFS_LEGACY_MAP_HEADER_SIZE) / |
| sizeof(struct z_erofs_lcluster_index); |
| const unsigned int logical_clusterbits = inode->z_logical_clusterbits; |
| u8 *out, *in; |
| struct z_erofs_compressindex_vec cv[16]; |
| struct erofs_sb_info *sbi = inode->sbi; |
| /* # of 8-byte units so that it can be aligned with 32 bytes */ |
| unsigned int compacted_4b_initial, compacted_4b_end; |
| unsigned int compacted_2b; |
| bool dummy_head; |
| bool big_pcluster = erofs_sb_has_big_pcluster(sbi); |
| |
| if (logical_clusterbits < sbi->blkszbits || sbi->blkszbits < 12) |
| return -EINVAL; |
| if (logical_clusterbits > 14) { |
| erofs_err("compact format is unsupported for lcluster size %u", |
| 1 << logical_clusterbits); |
| return -EOPNOTSUPP; |
| } |
| |
| if (inode->z_advise & Z_EROFS_ADVISE_COMPACTED_2B) { |
| if (logical_clusterbits != 12) { |
| erofs_err("compact 2B is unsupported for lcluster size %u", |
| 1 << logical_clusterbits); |
| return -EINVAL; |
| } |
| |
| compacted_4b_initial = (32 - mpos % 32) / 4; |
| if (compacted_4b_initial == 32 / 4) |
| compacted_4b_initial = 0; |
| |
| if (compacted_4b_initial > totalidx) { |
| compacted_4b_initial = compacted_2b = 0; |
| compacted_4b_end = totalidx; |
| } else { |
| compacted_2b = rounddown(totalidx - |
| compacted_4b_initial, 16); |
| compacted_4b_end = totalidx - compacted_4b_initial - |
| compacted_2b; |
| } |
| } else { |
| compacted_2b = compacted_4b_initial = 0; |
| compacted_4b_end = totalidx; |
| } |
| |
| out = in = compressmeta; |
| |
| out += sizeof(struct z_erofs_map_header); |
| in += Z_EROFS_LEGACY_MAP_HEADER_SIZE; |
| |
| dummy_head = false; |
| /* prior to bigpcluster, blkaddr was bumped up once coming into HEAD */ |
| if (!big_pcluster) { |
| --blkaddr; |
| dummy_head = true; |
| } |
| |
| /* generate compacted_4b_initial */ |
| while (compacted_4b_initial) { |
| in = parse_legacy_indexes(cv, 2, in); |
| out = write_compacted_indexes(out, cv, &blkaddr, |
| 4, logical_clusterbits, false, |
| &dummy_head, big_pcluster); |
| compacted_4b_initial -= 2; |
| } |
| DBG_BUGON(compacted_4b_initial); |
| |
| /* generate compacted_2b */ |
| while (compacted_2b) { |
| in = parse_legacy_indexes(cv, 16, in); |
| out = write_compacted_indexes(out, cv, &blkaddr, |
| 2, logical_clusterbits, false, |
| &dummy_head, big_pcluster); |
| compacted_2b -= 16; |
| } |
| DBG_BUGON(compacted_2b); |
| |
| /* generate compacted_4b_end */ |
| while (compacted_4b_end > 1) { |
| in = parse_legacy_indexes(cv, 2, in); |
| out = write_compacted_indexes(out, cv, &blkaddr, |
| 4, logical_clusterbits, false, |
| &dummy_head, big_pcluster); |
| compacted_4b_end -= 2; |
| } |
| |
| /* generate final compacted_4b_end if needed */ |
| if (compacted_4b_end) { |
| memset(cv, 0, sizeof(cv)); |
| in = parse_legacy_indexes(cv, 1, in); |
| out = write_compacted_indexes(out, cv, &blkaddr, |
| 4, logical_clusterbits, true, |
| &dummy_head, big_pcluster); |
| } |
| inode->extent_isize = out - (u8 *)compressmeta; |
| return 0; |
| } |
| |
| static void z_erofs_write_mapheader(struct erofs_inode *inode, |
| void *compressmeta) |
| { |
| struct erofs_sb_info *sbi = inode->sbi; |
| struct z_erofs_map_header h = { |
| .h_advise = cpu_to_le16(inode->z_advise), |
| .h_algorithmtype = inode->z_algorithmtype[1] << 4 | |
| inode->z_algorithmtype[0], |
| /* lclustersize */ |
| .h_clusterbits = inode->z_logical_clusterbits - sbi->blkszbits, |
| }; |
| |
| if (inode->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) |
| h.h_fragmentoff = cpu_to_le32(inode->fragmentoff); |
| else |
| h.h_idata_size = cpu_to_le16(inode->idata_size); |
| |
| memset(compressmeta, 0, Z_EROFS_LEGACY_MAP_HEADER_SIZE); |
| /* write out map header */ |
| memcpy(compressmeta, &h, sizeof(struct z_erofs_map_header)); |
| } |
| |
| 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; |
| |
| h->h_advise = cpu_to_le16(le16_to_cpu(h->h_advise) & |
| ~Z_EROFS_ADVISE_INLINE_PCLUSTER); |
| h->h_idata_size = 0; |
| if (!inode->eof_tailraw) |
| return; |
| DBG_BUGON(inode->compressed_idata != true); |
| |
| /* patch the EOF lcluster 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); |
| __le16 advise = |
| cpu_to_le16(type << Z_EROFS_LI_LCLUSTER_TYPE_BIT); |
| |
| di->di_advise = advise; |
| } else if (inode->datalayout == EROFS_INODE_COMPRESSED_COMPACT) { |
| /* handle the last compacted 4B pack */ |
| unsigned int eofs, base, pos, v, lo; |
| u8 *out; |
| |
| 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; |
| lo = erofs_blkoff(sbi, get_unaligned_le32(out + pos / 8)); |
| v = (type << sbi->blkszbits) | 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, int fd) |
| { |
| struct erofs_buffer_head *bh; |
| static struct z_erofs_vle_compress_ctx ctx; |
| erofs_blk_t blkaddr, compressed_blocks; |
| unsigned int legacymetasize; |
| int ret; |
| struct erofs_sb_info *sbi = inode->sbi; |
| u8 *compressmeta = malloc(BLK_ROUND_UP(sbi, inode->i_size) * |
| sizeof(struct z_erofs_lcluster_index) + |
| Z_EROFS_LEGACY_MAP_HEADER_SIZE); |
| |
| if (!compressmeta) |
| return -ENOMEM; |
| |
| /* allocate main data buffer */ |
| bh = erofs_balloc(DATA, 0, 0, 0); |
| if (IS_ERR(bh)) { |
| ret = PTR_ERR(bh); |
| goto err_free_meta; |
| } |
| |
| /* initialize per-file compression setting */ |
| inode->z_advise = 0; |
| inode->z_logical_clusterbits = sbi->blkszbits; |
| if (!cfg.c_legacy_compress && inode->z_logical_clusterbits <= 14) { |
| if (inode->z_logical_clusterbits <= 12) |
| inode->z_advise |= Z_EROFS_ADVISE_COMPACTED_2B; |
| inode->datalayout = EROFS_INODE_COMPRESSED_COMPACT; |
| } else { |
| inode->datalayout = EROFS_INODE_COMPRESSED_FULL; |
| } |
| |
| if (erofs_sb_has_big_pcluster(sbi)) { |
| inode->z_advise |= Z_EROFS_ADVISE_BIG_PCLUSTER_1; |
| if (inode->datalayout == EROFS_INODE_COMPRESSED_COMPACT) |
| inode->z_advise |= Z_EROFS_ADVISE_BIG_PCLUSTER_2; |
| } |
| if (cfg.c_fragments && !cfg.c_dedupe) |
| inode->z_advise |= Z_EROFS_ADVISE_INTERLACED_PCLUSTER; |
| |
| #ifndef NDEBUG |
| if (cfg.c_random_algorithms) { |
| while (1) { |
| inode->z_algorithmtype[0] = |
| rand() % EROFS_MAX_COMPR_CFGS; |
| if (erofs_ccfg[inode->z_algorithmtype[0]].enable) |
| break; |
| } |
| } |
| #endif |
| ctx.ccfg = &erofs_ccfg[inode->z_algorithmtype[0]]; |
| inode->z_algorithmtype[0] = ctx.ccfg[0].algorithmtype; |
| inode->z_algorithmtype[1] = 0; |
| |
| inode->idata_size = 0; |
| inode->fragment_size = 0; |
| |
| /* |
| * Handle tails in advance to avoid writing duplicated |
| * parts into the packed inode. |
| */ |
| if (cfg.c_fragments && !erofs_is_packed_inode(inode)) { |
| ret = z_erofs_fragments_dedupe(inode, fd, &ctx.tof_chksum); |
| if (ret < 0) |
| goto err_bdrop; |
| } |
| |
| blkaddr = erofs_mapbh(bh->block); /* start_blkaddr */ |
| ctx.inode = inode; |
| ctx.pclustersize = z_erofs_get_max_pclustersize(inode); |
| ctx.blkaddr = blkaddr; |
| ctx.metacur = compressmeta + Z_EROFS_LEGACY_MAP_HEADER_SIZE; |
| ctx.head = ctx.tail = 0; |
| ctx.clusterofs = 0; |
| ctx.e.length = 0; |
| ctx.remaining = inode->i_size - inode->fragment_size; |
| ctx.fix_dedupedfrag = false; |
| ctx.fragemitted = false; |
| if (cfg.c_all_fragments && !erofs_is_packed_inode(inode) && |
| !inode->fragment_size) { |
| ret = z_erofs_pack_file_from_fd(inode, fd, ctx.tof_chksum); |
| if (ret) |
| goto err_free_idata; |
| } else { |
| while (ctx.remaining) { |
| const u64 rx = min_t(u64, ctx.remaining, |
| sizeof(ctx.queue) - ctx.tail); |
| |
| ret = read(fd, ctx.queue + ctx.tail, rx); |
| if (ret != rx) { |
| ret = -errno; |
| goto err_bdrop; |
| } |
| ctx.remaining -= rx; |
| ctx.tail += rx; |
| |
| ret = vle_compress_one(&ctx); |
| if (ret) |
| goto err_free_idata; |
| } |
| } |
| DBG_BUGON(ctx.head != ctx.tail); |
| |
| /* fall back to no compression mode */ |
| compressed_blocks = ctx.blkaddr - blkaddr; |
| DBG_BUGON(compressed_blocks < !!inode->idata_size); |
| compressed_blocks -= !!inode->idata_size; |
| |
| /* generate an extent for the deduplicated fragment */ |
| if (inode->fragment_size && !ctx.fragemitted) { |
| z_erofs_write_indexes(&ctx); |
| ctx.e.length = inode->fragment_size; |
| ctx.e.compressedblks = 0; |
| ctx.e.raw = false; |
| ctx.e.partial = false; |
| ctx.e.blkaddr = ctx.blkaddr; |
| } |
| z_erofs_fragments_commit(inode); |
| |
| z_erofs_write_indexes(&ctx); |
| z_erofs_write_indexes_final(&ctx); |
| legacymetasize = ctx.metacur - compressmeta; |
| /* estimate if data compression saves space or not */ |
| if (!inode->fragment_size && |
| compressed_blocks * erofs_blksiz(sbi) + inode->idata_size + |
| legacymetasize >= inode->i_size) { |
| z_erofs_dedupe_commit(true); |
| ret = -ENOSPC; |
| goto err_free_idata; |
| } |
| z_erofs_dedupe_commit(false); |
| z_erofs_write_mapheader(inode, compressmeta); |
| |
| if (!ctx.fragemitted) |
| sbi->saved_by_deduplication += inode->fragment_size; |
| |
| /* if the entire file is a fragment, a simplified form is used. */ |
| if (inode->i_size == inode->fragment_size) { |
| DBG_BUGON(inode->fragmentoff >> 63); |
| *(__le64 *)compressmeta = |
| cpu_to_le64(inode->fragmentoff | 1ULL << 63); |
| inode->datalayout = EROFS_INODE_COMPRESSED_FULL; |
| legacymetasize = Z_EROFS_LEGACY_MAP_HEADER_SIZE; |
| } |
| |
| if (compressed_blocks) { |
| ret = erofs_bh_balloon(bh, erofs_pos(sbi, compressed_blocks)); |
| DBG_BUGON(ret != erofs_blksiz(sbi)); |
| } else { |
| if (!cfg.c_fragments && !cfg.c_dedupe) |
| 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); |
| |
| if (inode->idata_size) { |
| bh->op = &erofs_skip_write_bhops; |
| inode->bh_data = bh; |
| } else { |
| erofs_bdrop(bh, false); |
| } |
| |
| inode->u.i_blocks = compressed_blocks; |
| |
| if (inode->datalayout == EROFS_INODE_COMPRESSED_FULL) { |
| inode->extent_isize = legacymetasize; |
| } else { |
| ret = z_erofs_convert_to_compacted_format(inode, blkaddr, |
| legacymetasize, |
| compressmeta); |
| DBG_BUGON(ret); |
| } |
| inode->compressmeta = compressmeta; |
| if (!erofs_is_packed_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_free_meta: |
| free(compressmeta); |
| return ret; |
| } |
| |
| static int z_erofs_build_compr_cfgs(struct erofs_sb_info *sbi, |
| struct erofs_buffer_head *sb_bh) |
| { |
| struct erofs_buffer_head *bh = sb_bh; |
| int ret = 0; |
| |
| if (sbi->available_compr_algs & (1 << Z_EROFS_COMPRESSION_LZ4)) { |
| struct { |
| __le16 size; |
| struct z_erofs_lz4_cfgs lz4; |
| } __packed lz4alg = { |
| .size = cpu_to_le16(sizeof(struct z_erofs_lz4_cfgs)), |
| .lz4 = { |
| .max_distance = |
| cpu_to_le16(sbi->lz4_max_distance), |
| .max_pclusterblks = cfg.c_pclusterblks_max, |
| } |
| }; |
| |
| bh = erofs_battach(bh, META, sizeof(lz4alg)); |
| if (IS_ERR(bh)) { |
| DBG_BUGON(1); |
| return PTR_ERR(bh); |
| } |
| erofs_mapbh(bh->block); |
| ret = dev_write(sbi, &lz4alg, erofs_btell(bh, false), |
| sizeof(lz4alg)); |
| bh->op = &erofs_drop_directly_bhops; |
| } |
| #ifdef HAVE_LIBLZMA |
| if (sbi->available_compr_algs & (1 << Z_EROFS_COMPRESSION_LZMA)) { |
| struct { |
| __le16 size; |
| struct z_erofs_lzma_cfgs lzma; |
| } __packed lzmaalg = { |
| .size = cpu_to_le16(sizeof(struct z_erofs_lzma_cfgs)), |
| .lzma = { |
| .dict_size = cpu_to_le32(cfg.c_dict_size), |
| } |
| }; |
| |
| bh = erofs_battach(bh, META, sizeof(lzmaalg)); |
| if (IS_ERR(bh)) { |
| DBG_BUGON(1); |
| return PTR_ERR(bh); |
| } |
| erofs_mapbh(bh->block); |
| ret = dev_write(sbi, &lzmaalg, erofs_btell(bh, false), |
| sizeof(lzmaalg)); |
| bh->op = &erofs_drop_directly_bhops; |
| } |
| #endif |
| if (sbi->available_compr_algs & (1 << Z_EROFS_COMPRESSION_DEFLATE)) { |
| struct { |
| __le16 size; |
| struct z_erofs_deflate_cfgs z; |
| } __packed zalg = { |
| .size = cpu_to_le16(sizeof(struct z_erofs_deflate_cfgs)), |
| .z = { |
| .windowbits = |
| cpu_to_le32(ilog2(cfg.c_dict_size)), |
| } |
| }; |
| |
| bh = erofs_battach(bh, META, sizeof(zalg)); |
| if (IS_ERR(bh)) { |
| DBG_BUGON(1); |
| return PTR_ERR(bh); |
| } |
| erofs_mapbh(bh->block); |
| ret = dev_write(sbi, &zalg, erofs_btell(bh, false), |
| sizeof(zalg)); |
| bh->op = &erofs_drop_directly_bhops; |
| } |
| return ret; |
| } |
| |
| int z_erofs_compress_init(struct erofs_sb_info *sbi, struct erofs_buffer_head *sb_bh) |
| { |
| int i, ret; |
| |
| for (i = 0; cfg.c_compr_alg[i]; ++i) { |
| struct erofs_compress *c = &erofs_ccfg[i].handle; |
| |
| ret = erofs_compressor_init(sbi, c, cfg.c_compr_alg[i]); |
| if (ret) |
| return ret; |
| |
| ret = erofs_compressor_setlevel(c, cfg.c_compr_level[i]); |
| if (ret) |
| return ret; |
| |
| erofs_ccfg[i].algorithmtype = |
| z_erofs_get_compress_algorithm_id(c); |
| erofs_ccfg[i].enable = true; |
| sbi->available_compr_algs |= 1 << erofs_ccfg[i].algorithmtype; |
| if (erofs_ccfg[i].algorithmtype != Z_EROFS_COMPRESSION_LZ4) |
| erofs_sb_set_compr_cfgs(sbi); |
| } |
| |
| /* |
| * if primary algorithm is empty (e.g. compression off), |
| * clear 0PADDING feature for old kernel compatibility. |
| */ |
| if (!cfg.c_compr_alg[0] || |
| (cfg.c_legacy_compress && !strncmp(cfg.c_compr_alg[0], "lz4", 3))) |
| erofs_sb_clear_lz4_0padding(sbi); |
| |
| if (!cfg.c_compr_alg[0]) |
| return 0; |
| |
| /* |
| * if big pcluster is enabled, an extra CBLKCNT lcluster index needs |
| * to be loaded in order to get those compressed block counts. |
| */ |
| if (cfg.c_pclusterblks_max > 1) { |
| if (cfg.c_pclusterblks_max > |
| Z_EROFS_PCLUSTER_MAX_SIZE / erofs_blksiz(sbi)) { |
| erofs_err("unsupported clusterblks %u (too large)", |
| cfg.c_pclusterblks_max); |
| return -EINVAL; |
| } |
| erofs_sb_set_big_pcluster(sbi); |
| } |
| if (cfg.c_pclusterblks_packed > cfg.c_pclusterblks_max) { |
| erofs_err("invalid physical cluster size for the packed file"); |
| return -EINVAL; |
| } |
| |
| if (erofs_sb_has_compr_cfgs(sbi)) |
| return z_erofs_build_compr_cfgs(sbi, sb_bh); |
| return 0; |
| } |
| |
| int z_erofs_compress_exit(void) |
| { |
| int i, ret; |
| |
| for (i = 0; cfg.c_compr_alg[i]; ++i) { |
| ret = erofs_compressor_exit(&erofs_ccfg[i].handle); |
| if (ret) |
| return ret; |
| } |
| return 0; |
| } |