// SPDX-License-Identifier: GPL-2.0+ OR MIT
/*
 * Copyright (C) 2018-2019 HUAWEI, Inc.
 *             http://www.huawei.com/
 * Created by Miao Xie <miaoxie@huawei.com>
 * with heavy changes by Gao Xiang <xiang@kernel.org>
 */
#ifndef _LARGEFILE64_SOURCE
#define _LARGEFILE64_SOURCE
#endif
#ifdef EROFS_MT_ENABLED
#include <pthread.h>
#endif
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "erofs/print.h"
#include "erofs/dedupe.h"
#include "compressor.h"
#include "erofs/block_list.h"
#include "erofs/compress_hints.h"
#ifdef EROFS_MT_ENABLED
#include "erofs/workqueue.h"
#endif
#include "liberofs_cache.h"
#include "liberofs_compress.h"
#include "liberofs_fragments.h"
#include "liberofs_metabox.h"

#define Z_EROFS_DESTBUF_SZ	(Z_EROFS_PCLUSTER_MAX_SIZE + EROFS_MAX_BLOCK_SIZE * 2)

struct z_erofs_extent_item {
	struct list_head list;
	struct z_erofs_inmem_extent e;
};

struct z_erofs_compress_ictx {		/* inode context */
	struct erofs_vfile _vf, *vf;
	struct erofs_importer *im;
	struct erofs_inode *inode;
	struct erofs_compress_cfg *ccfg;
	int fd;
	u64 fpos;

	u32 tofh;
	bool fix_dedupedfrag;
	bool fragemitted;
	bool dedupe;
	bool data_unaligned;

	/* fields for write indexes */
	u8 *metacur;
	struct list_head extents;
	u16 clusterofs;
	int seg_num;
	u32 max_compressed_extent_size;

#if EROFS_MT_ENABLED
	pthread_mutex_t mutex;
	pthread_cond_t cond;

	struct erofs_compress_work *mtworks;
#endif
};

struct z_erofs_compress_sctx {		/* segment context */
	union {
		struct list_head extents;
		struct list_head sibling;
	};
	struct z_erofs_compress_ictx *ictx;

	u8 *queue;
	struct z_erofs_extent_item *pivot;

	struct erofs_compress *chandle;
	char *destbuf;

	erofs_off_t remaining;
	unsigned int head, tail;

	unsigned int pclustersize;
	erofs_off_t pstart, poff;
	u16 clusterofs;

	int seg_idx;

	void *membuf;
};

#ifdef EROFS_MT_ENABLED
struct erofs_compress_wq_tls {
	u8 *queue;
	char *destbuf;
	struct erofs_compress_cfg *ccfg;
};

struct erofs_compress_work {
	/* Note: struct erofs_work must be the first member */
	struct erofs_work work;
	struct z_erofs_compress_sctx ctx;
	pthread_cond_t cond;
	struct erofs_compress_work *next;
	struct z_erofs_paramset *zset;

	int errcode;
};

static struct {
	struct erofs_workqueue wq;
	struct erofs_compress_work *idle;
	pthread_mutex_t mutex;
} z_erofs_mt_ctrl;

struct z_erofs_compress_fslot {
	struct list_head pending;
	pthread_mutex_t lock;
	bool inprogress;
};

#endif

/* compressing configuration specified by users */
struct erofs_compress_cfg {
	struct erofs_compress handle;
	struct z_erofs_paramset zset;
	int algorithmtype;
	bool enable;
};

struct z_erofs_mgr {
	struct erofs_compress_cfg ccfg[EROFS_MAX_COMPR_CFGS];
#ifdef EROFS_MT_ENABLED
	struct z_erofs_compress_fslot fslot[1024];
#endif
};

static bool z_erofs_mt_enabled;

#define Z_EROFS_LEGACY_MAP_HEADER_SIZE	Z_EROFS_FULL_INDEX_START(0)

static void z_erofs_fini_full_indexes(struct z_erofs_compress_ictx *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);

	memcpy(ctx->metacur, &di, sizeof(di));
	ctx->metacur += sizeof(di);
}

static void z_erofs_write_full_indexes(struct z_erofs_compress_ictx *ctx,
				       struct z_erofs_inmem_extent *e)
{
	const struct erofs_importer_params *params = ctx->im->params;
	struct erofs_inode *inode = ctx->inode;
	struct erofs_sb_info *sbi = inode->sbi;
	unsigned int clusterofs = ctx->clusterofs;
	unsigned int count = e->length;
	unsigned int bbits = sbi->blkszbits;
	unsigned int d0 = 0, d1 = (clusterofs + count) >> bbits;
	struct z_erofs_lcluster_index di;
	unsigned int type, advise;

	DBG_BUGON(!count);
	DBG_BUGON(e->pstart & (BIT(bbits) - 1));

	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(!e->raw && !params->ztailpacking && !params->fragments);
		DBG_BUGON(e->partial);
		type = e->raw ? Z_EROFS_LCLUSTER_TYPE_PLAIN :
			Z_EROFS_LCLUSTER_TYPE_HEAD1;
		di.di_advise = cpu_to_le16(type);

		if (inode->datalayout == EROFS_INODE_COMPRESSED_FULL && !e->plen)
			di.di_u.blkaddr = cpu_to_le32(inode->fragmentoff >> 32);
		else
			di.di_u.blkaddr = cpu_to_le32(e->pstart >> bbits);
		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((e->plen >> bbits) |
						       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 = e->raw ? Z_EROFS_LCLUSTER_TYPE_PLAIN :
				Z_EROFS_LCLUSTER_TYPE_HEAD1;

			if (inode->datalayout == EROFS_INODE_COMPRESSED_FULL &&
			    !e->plen)
				di.di_u.blkaddr = cpu_to_le32(inode->fragmentoff >> 32);
			else
				di.di_u.blkaddr = cpu_to_le32(e->pstart >> bbits);

			if (e->partial) {
				DBG_BUGON(e->raw);
				advise |= Z_EROFS_LI_PARTIAL_REF;
			}
		}
		di.di_advise = cpu_to_le16(advise | type);

		memcpy(ctx->metacur, &di, sizeof(di));
		ctx->metacur += sizeof(di);

		count -= (1 << bbits) - clusterofs;
		clusterofs = 0;

		++d0;
		--d1;
	} while (clusterofs + count >= 1 << bbits);

	ctx->clusterofs = clusterofs + count;
}

static bool z_erofs_need_refill(struct z_erofs_compress_sctx *ctx)
{
	const bool final = !ctx->remaining;
	unsigned int qh_aligned, qh_after;
	struct erofs_inode *inode = ctx->ictx->inode;

	if (final || ctx->head < EROFS_CONFIG_COMPR_MAX_SZ)
		return false;

	qh_aligned = round_down(ctx->head, erofs_blksiz(inode->sbi));
	qh_after = ctx->head - qh_aligned;
	memmove(ctx->queue, ctx->queue + qh_aligned, ctx->tail - qh_aligned);
	ctx->tail -= qh_aligned;
	ctx->head = qh_after;
	return true;
}

static struct z_erofs_extent_item dummy_pivot = {
	.e.length = 0
};

static void z_erofs_commit_extent(struct z_erofs_compress_sctx *ctx,
				  struct z_erofs_extent_item *ei)
{
	if (ei == &dummy_pivot)
		return;

	list_add_tail(&ei->list, &ctx->extents);
	ctx->clusterofs = (ctx->clusterofs + ei->e.length) &
			  (erofs_blksiz(ctx->ictx->inode->sbi) - 1);
}

static int z_erofs_compress_dedupe(struct z_erofs_compress_sctx *ctx)
{
	struct erofs_inode *inode = ctx->ictx->inode;
	const unsigned int lclustermask = (1 << inode->z_lclusterbits) - 1;
	struct erofs_sb_info *sbi = inode->sbi;
	struct z_erofs_extent_item *ei = ctx->pivot;

	if (!ei)
		return 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 (ei->e.length <= erofs_blksiz(sbi))
					rc = 0;
				else if (ei->e.length - erofs_blksiz(sbi) >= ctx->head)
					rc = ctx->head;
				else
					rc = ei->e.length - erofs_blksiz(sbi);
				rc; }),
			.end = ctx->queue + ctx->tail,
			.cur = ctx->queue + ctx->head,
		};
		int delta;

		if (z_erofs_dedupe_match(&dctx))
			break;

		DBG_BUGON(dctx.e.inlined);
		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.plen > erofs_blksiz(sbi) &&
		    ((ctx->clusterofs + ei->e.length - delta) & lclustermask) +
			dctx.e.length < 2 * (lclustermask + 1))
			break;

		ctx->pivot = malloc(sizeof(struct z_erofs_extent_item));
		if (!ctx->pivot) {
			z_erofs_commit_extent(ctx, ei);
			return -ENOMEM;
		}

		if (delta) {
			DBG_BUGON(delta < 0);
			DBG_BUGON(!ei->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 (ei->e.plen > erofs_blksiz(sbi) &&
			    (ctx->clusterofs & lclustermask) + ei->e.length
				- delta < 2 * (lclustermask + 1))
				break;
			ei->e.partial = true;
			ei->e.length -= delta;
		}
		ctx->ictx->dedupe = true;
		erofs_sb_set_dedupe(sbi);

		sbi->saved_by_deduplication += dctx.e.plen;
		erofs_dbg("Dedupe %u %scompressed data (delta %d) to %llu of %u bytes",
			  dctx.e.length, dctx.e.raw ? "un" : "",
			  delta, dctx.e.pstart | 0ULL, dctx.e.plen);

		z_erofs_commit_extent(ctx, ei);
		ei = ctx->pivot;
		init_list_head(&ei->list);
		ei->e = dctx.e;

		ctx->head += dctx.e.length - delta;
		DBG_BUGON(ctx->head > ctx->tail);

		if (z_erofs_need_refill(ctx))
			return 1;
	} while (ctx->tail > ctx->head);
out:
	z_erofs_commit_extent(ctx, ei);
	ctx->pivot = NULL;
	return 0;
}

/* TODO: reset clusterofs to 0 if permitted */
static int write_uncompressed_block(struct z_erofs_compress_sctx *ctx,
				    unsigned int len, char *dst)
{
	struct erofs_inode *inode = ctx->ictx->inode;
	struct erofs_sb_info *sbi = inode->sbi;
	unsigned int count = min(erofs_blksiz(sbi), len);
	unsigned int interlaced_offset, rightpart;
	int ret;

	/* write interlaced uncompressed data if needed */
	if (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);

	if (ctx->membuf) {
		erofs_dbg("Recording %u uncompressed data of %s", count,
			  inode->i_srcpath);
		memcpy(ctx->membuf + ctx->poff, dst, erofs_blksiz(sbi));
	} else {
		erofs_dbg("Writing %u uncompressed data to %llu", count,
			  ctx->pstart | 0ULL);
		ret = erofs_dev_write(sbi, dst, ctx->pstart, erofs_blksiz(sbi));
		if (ret)
			return ret;
	}
	ctx->poff += erofs_blksiz(sbi);
	return count;
}

static int write_uncompressed_extents(struct z_erofs_compress_sctx *ctx,
				      unsigned int size, unsigned int processed,
				      char *dst)
{
	struct erofs_inode *inode = ctx->ictx->inode;
	unsigned int lclustersize = 1 << inode->z_lclusterbits;
	struct z_erofs_extent_item *ei;
	int count;

	while (1) {
		count = write_uncompressed_block(ctx, size, dst);
		if (count < 0)
			return count;

		size -= count;
		if (processed < lclustersize + count)
			break;
		processed -= count;

		ei = malloc(sizeof(*ei));
		if (!ei)
			return -ENOMEM;
		init_list_head(&ei->list);

		ei->e = (struct z_erofs_inmem_extent) {
			.length = count,
			.plen = round_up(count, erofs_blksiz(inode->sbi)),
			.raw = true,
			.pstart = ctx->pstart,
		};
		if (ctx->pstart != EROFS_NULL_ADDR)
			ctx->pstart += ei->e.plen;
		z_erofs_commit_extent(ctx, ei);
		ctx->head += count;
	}
	return count;
}

static unsigned int z_erofs_get_pclustersize(struct z_erofs_compress_ictx *ictx)
{
	struct erofs_importer_params *params = ictx->im->params;
	struct erofs_inode *inode = ictx->inode;
	unsigned int blkszbits = inode->sbi->blkszbits;

	if (erofs_is_packed_inode(inode))
		return params->pclusterblks_packed << blkszbits;
	if (erofs_is_metabox_inode(inode))
		return params->pclusterblks_metabox << blkszbits;
#ifndef NDEBUG
	if (cfg.c_random_pclusterblks)
		return (1 + rand() % params->pclusterblks_max) << blkszbits;
#endif
	if (cfg.c_compress_hints_file) {
		z_erofs_apply_compress_hints(ictx->im, inode);
		DBG_BUGON(!inode->z_physical_clusterblks);
		return inode->z_physical_clusterblks << blkszbits;
	}
	return params->pclusterblks_def << blkszbits;
}

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->idata_type = EROFS_IDATA_TYPE_COMPRESSED_DEFAULT;

	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 int tryrecompress_trailing(struct z_erofs_compress_sctx *ctx,
				  struct erofs_compress *ec,
				  void *in, unsigned int *insize,
				  void *out, unsigned int *compressedsize)
{
	struct erofs_sb_info *sbi = ctx->ictx->inode->sbi;
	char *tmp;
	unsigned int count;
	int ret = *compressedsize;

	/* no need to recompress */
	if (!(ret & (erofs_blksiz(sbi) - 1)))
		return 0;

	tmp = malloc(Z_EROFS_PCLUSTER_MAX_SIZE);
	if (!tmp)
		return -ENOMEM;

	count = *insize;
	ret = erofs_compress_destsize(ec, in, &count, (void *)tmp,
				      rounddown(ret, erofs_blksiz(sbi)));
	if (ret <= 0 || ret + (*insize - count) >=
			roundup(*compressedsize, erofs_blksiz(sbi)))
		goto out;

	/* replace the original compressed data if any gain */
	memcpy(out, tmp, ret);
	*insize = count;
	*compressedsize = ret;

out:
	free(tmp);
	return 0;
}

static bool z_erofs_fixup_deduped_fragment(struct z_erofs_compress_sctx *ctx)
{
	struct z_erofs_compress_ictx *ictx = ctx->ictx;
	struct erofs_inode *inode = ictx->inode;
	struct erofs_sb_info *sbi = inode->sbi;
	const unsigned int newsize = ctx->remaining + ctx->tail - ctx->head;

	DBG_BUGON(!inode->fragment_size);

	/* try to fix again if it gets larger (should be rare) */
	if (inode->fragment_size < newsize) {
		ctx->pclustersize = min_t(erofs_off_t,
				z_erofs_get_pclustersize(ictx),
				roundup(newsize - inode->fragment_size,
					erofs_blksiz(sbi)));
		return false;
	}

	inode->fragment_size = newsize;
	erofs_dbg("Reducing fragment size to %llu", inode->fragment_size | 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 __z_erofs_compress_one(struct z_erofs_compress_sctx *ctx,
				  struct z_erofs_inmem_extent *e)
{
	static char g_dstbuf[Z_EROFS_DESTBUF_SZ];
	char *dstbuf = ctx->destbuf ?: g_dstbuf;
	struct z_erofs_compress_ictx *ictx = ctx->ictx;
	const struct erofs_importer_params *params = ictx->im->params;
	struct erofs_inode *inode = ictx->inode;
	struct erofs_sb_info *sbi = inode->sbi;
	unsigned int blksz = erofs_blksiz(sbi);
	char *const dst = dstbuf + blksz;
	struct erofs_compress *const h = ctx->chandle;
	unsigned int len = ctx->tail - ctx->head;
	bool is_packed_inode = erofs_is_packed_inode(inode);
	bool tsg = (ctx->seg_idx + 1 >= ictx->seg_num), final = !ctx->remaining;
	bool may_packing = (params->fragments && tsg && final && !is_packed_inode &&
			    !erofs_is_metabox_inode(inode));
	bool data_unaligned = ictx->data_unaligned;
	bool may_inline = (params->ztailpacking && !data_unaligned && tsg &&
			   final && !may_packing);
	unsigned int compressedsize;
	int ret;

	DBG_BUGON(ctx->pivot);
	*e = (struct z_erofs_inmem_extent){};
	if (len <= ctx->pclustersize) {
		if (!final || !len)
			return 1;
		if (may_packing && inode->fragment_size && !ictx->fix_dedupedfrag) {
			ctx->pclustersize = roundup(len, blksz);
			goto fix_dedupedfrag;
		}
		e->length = len;
		if (may_packing)
			goto frag_packing;
		if (!may_inline && len <= blksz)
			goto nocompression;
	}

	e->length = min(len, ictx->max_compressed_extent_size);
	if (data_unaligned) {
		ret = erofs_compress(h, ctx->queue + ctx->head, e->length,
				     dst, ctx->pclustersize);
		if (ret == -EOPNOTSUPP) {
			data_unaligned = false;
			goto retry_aligned;
		}
	} else {
retry_aligned:
		ret = erofs_compress_destsize(h, ctx->queue + ctx->head,
					      &e->length, dst, ctx->pclustersize);
	}

	if (ret > 0) {
		compressedsize = ret;
		/* even compressed size is smaller, there is no real gain */
		if (!data_unaligned && !(may_inline && e->length == len && ret < blksz))
			ret = roundup(ret, blksz);
	} else if (ret != -ENOSPC) {
		erofs_err("failed to compress %s: %s", inode->i_srcpath,
			  erofs_strerror(ret));
		return ret;
	}

	/* check if there is enough gain to keep the compressed data */
	if (ret < 0 || ret * h->compress_threshold / 100 >= e->length) {
		if (may_inline && len < blksz) {
			ret = z_erofs_fill_inline_data(inode,
					ctx->queue + ctx->head, len, true);
			if (ret < 0)
				return ret;
			e->inlined = true;
		} else {
			may_inline = false;
			may_packing = false;
			e->length = min_t(u32, e->length, ctx->pclustersize);
nocompression:
			if (params->dedupe == EROFS_DEDUPE_FORCE_ON)
				ret = write_uncompressed_block(ctx, len, dst);
			else
				ret = write_uncompressed_extents(ctx, len,
							e->length, dst);
			if (ret < 0)
				return ret;
		}
		e->length = ret;

		/*
		 * XXX: For now, we have to leave `ctx->compressedblk = 1'
		 * since there is no way to generate compressed indexes after
		 * the time that ztailpacking is decided.
		 */
		e->plen = blksz;
		e->raw = true;
	} else if (may_packing && len == e->length &&
		   compressedsize < ctx->pclustersize &&
		   (!inode->fragment_size || ictx->fix_dedupedfrag)) {
frag_packing:
		ret = erofs_fragment_pack(inode, ctx->queue + ctx->head,
					  ~0ULL, len, ictx->tofh, false);
		if (ret < 0)
			return ret;
		e->plen = 0;	/* indicate a fragment */
		e->raw = false;
		ictx->fragemitted = true;
	/* tailpcluster should be less than 1 block */
	} else if (may_inline && len == e->length && compressedsize < blksz) {
		if (ctx->clusterofs + len <= blksz) {
			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,
				compressedsize, false);
		if (ret < 0)
			return ret;
		e->inlined = true;
		e->plen = blksz;
		e->raw = false;
	} else {
		unsigned int 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 the fragment
		 * if succeeds.  Otherwise, just drop it and go on packing.
		 */
		if (!data_unaligned && may_packing && len == e->length &&
		    (compressedsize & (blksz - 1)) &&
		    ctx->tail < Z_EROFS_COMPR_QUEUE_SZ) {
			ctx->pclustersize = roundup(compressedsize, blksz);
			goto fix_dedupedfrag;
		}

		if (may_inline && len == e->length) {
			ret = tryrecompress_trailing(ctx, h,
						     ctx->queue + ctx->head,
						     &e->length, dst,
						     &compressedsize);
			if (ret)
				return ret;
		}

		if (data_unaligned)
			e->plen = compressedsize;
		else
			e->plen = round_up(compressedsize, blksz);
		DBG_BUGON(e->plen >= e->length);
		padding = e->plen - compressedsize;

		/* zero out garbage trailing data for non-0padding */
		if (!erofs_sb_has_lz4_0padding(sbi)) {
			memset(dst + compressedsize, 0, padding);
			padding = 0;
		}

		/* write compressed data */
		if (ctx->membuf) {
			erofs_dbg("Recording %u compressed data of %u bytes of %s",
				  e->length, e->plen, inode->i_srcpath);

			memcpy(ctx->membuf + ctx->poff, dst - padding, e->plen);
		} else {
			erofs_dbg("Writing %u compressed data to %llu of %u bytes",
				  e->length, ctx->pstart, e->plen);

			ret = erofs_dev_write(sbi, dst - padding, ctx->pstart,
					      e->plen);
			if (ret)
				return ret;
		}
		ctx->poff += e->plen;
		e->raw = false;
		may_inline = false;
		may_packing = false;
	}
	e->partial = false;
	e->pstart = ctx->pstart;
	if (ctx->pstart != EROFS_NULL_ADDR)
		ctx->pstart += e->plen;
	if (!may_inline && !may_packing && !is_packed_inode)
		(void)z_erofs_dedupe_insert(e, ctx->queue + ctx->head);
	ctx->head += e->length;
	return 0;

fix_dedupedfrag:
	DBG_BUGON(!inode->fragment_size);
	ctx->remaining += inode->fragment_size;
	ictx->fix_dedupedfrag = true;
	return 1;
}

static int z_erofs_compress_one(struct z_erofs_compress_sctx *ctx)
{
	struct z_erofs_compress_ictx *ictx = ctx->ictx;
	bool tsg = ctx->seg_idx + 1 >= ictx->seg_num;
	struct z_erofs_extent_item *ei;

	while (ctx->tail > ctx->head) {
		int ret = z_erofs_compress_dedupe(ctx);

		if (ret < 0)
			return ret;
		if (ret > 0)
			break;

		ei = malloc(sizeof(*ei));
		if (!ei)
			return -ENOMEM;
		init_list_head(&ei->list);

		ret = __z_erofs_compress_one(ctx, &ei->e);
		if (ret) {
			free(ei);
			if (ret > 0)
				break;		/* need more data */
			return ret;
		}

		ctx->pivot = ei;
		if (tsg && ictx->fix_dedupedfrag && !ictx->fragemitted &&
		    z_erofs_fixup_deduped_fragment(ctx))
			break;

		if (z_erofs_need_refill(ctx))
			break;
	}
	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_MASK;
		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 lclusterbits,
				     bool final, bool *dummy_head,
				     bool update_blkaddr)
{
	unsigned int vcnt, lobits, encodebits, pos, i, cblks;
	erofs_blk_t blkaddr;

	if (destsize == 4)
		vcnt = 2;
	else if (destsize == 2 && lclusterbits <= 12)
		vcnt = 16;
	else
		return ERR_PTR(-EINVAL);
	lobits = max(lclusterbits, ilog2(Z_EROFS_LI_D0_CBLKCNT) + 1U);
	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],
					       Z_EROFS_LI_D0_CBLKCNT - 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 << lobits) | 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_off_t pstart,
					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_lclusterbits;
	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);
	erofs_blk_t blkaddr;

	if (logical_clusterbits < sbi->blkszbits)
		return -EINVAL;
	if (pstart & (erofs_blksiz(sbi) - 1))
		return -EINVAL;
	if (logical_clusterbits > 14) {
		erofs_err("compact format is unsupported for lcluster size %u",
			  1 << logical_clusterbits);
		return -EOPNOTSUPP;
	}

	blkaddr = pstart >> sbi->blkszbits;
	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 */
	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,
						      2, logical_clusterbits, false,
						      &dummy_head, big_pcluster);
			compacted_2b -= 16;
		} while(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;

	if (inode->datalayout == EROFS_INODE_COMPRESSED_FULL &&
	    (inode->z_advise & Z_EROFS_ADVISE_EXTENTS)) {
		int recsz = z_erofs_extent_recsize(inode->z_advise);

		if (recsz > offsetof(struct z_erofs_extent, pstart_hi)) {
			h = (struct z_erofs_map_header) {
				.h_advise = cpu_to_le16(inode->z_advise),
				.h_extents_lo = cpu_to_le32(inode->z_extents),
			};
		} else {
			DBG_BUGON(inode->z_lclusterbits < sbi->blkszbits);
			h = (struct z_erofs_map_header) {
				.h_advise = cpu_to_le16(inode->z_advise),
				.h_clusterbits = inode->z_lclusterbits - sbi->blkszbits,
			};
		}
	} else {
		h = (struct z_erofs_map_header) {
			.h_advise = cpu_to_le16(inode->z_advise),
			.h_algorithmtype = inode->z_algorithmtype[1] << 4 |
					   inode->z_algorithmtype[0],
			/* lclustersize */
			.h_clusterbits = inode->z_lclusterbits - 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));
}

#define EROFS_FULL_INDEXES_SZ(inode)	\
	(BLK_ROUND_UP(inode->sbi, inode->i_size) * \
	 sizeof(struct z_erofs_lcluster_index) + Z_EROFS_LEGACY_MAP_HEADER_SIZE)

static void *z_erofs_write_extents(struct z_erofs_compress_ictx *ctx)
{
	struct erofs_inode *inode = ctx->inode;
	struct erofs_sb_info *sbi = inode->sbi;
	struct z_erofs_extent_item *ei, *n;
	unsigned int lclusterbits, nexts;
	bool pstart_hi = false, unaligned_data = false;
	erofs_off_t pstart, pend, lstart;
	unsigned int recsz, metasz, moff;
	void *metabuf;

	ei = list_first_entry(&ctx->extents, struct z_erofs_extent_item,
			      list);
	lclusterbits = max_t(u8, ilog2(ei->e.length - 1) + 1, sbi->blkszbits);
	pend = pstart = ei->e.pstart;
	nexts = 0;
	list_for_each_entry(ei, &ctx->extents, list) {
		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)
			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)
				lclusterbits = 0;
		}
		++nexts;
	}

	recsz = inode->i_size > UINT32_MAX ? 32 : 16;
	if (lclusterbits) {
		if (pend != EROFS_NULL_ADDR)
			recsz = 4;
		else if (recsz <= 16 && !pstart_hi)
			recsz = 8;
	}

	moff = Z_EROFS_MAP_HEADER_END(inode->inode_isize + inode->xattr_isize);
	moff = round_up(moff, recsz) -
		Z_EROFS_MAP_HEADER_START(inode->inode_isize + inode->xattr_isize);
	metasz = moff + recsz * nexts + 8 * (recsz <= 4);
	if (!unaligned_data && metasz > EROFS_FULL_INDEXES_SZ(inode))
		return ERR_PTR(-EAGAIN);

	metabuf = malloc(metasz);
	if (!metabuf)
		return ERR_PTR(-ENOMEM);
	inode->z_lclusterbits = lclusterbits;
	inode->z_extents = nexts;
	ctx->metacur = metabuf + moff;
	if (recsz <= 4) {
		*(__le64 *)ctx->metacur	= cpu_to_le64(pstart);
		ctx->metacur += sizeof(__le64);
	}

	nexts = 0;
	lstart = 0;
	list_for_each_entry_safe(ei, n, &ctx->extents, list) {
		struct z_erofs_extent de;
		u32 fmt, plen;

		plen = ei->e.plen;
		if (!plen) {
			plen = inode->fragmentoff;
			ei->e.pstart = inode->fragmentoff >> 32;
		} else {
			fmt = ei->e.raw ? 0 : inode->z_algorithmtype[0] + 1;
			plen |= fmt << Z_EROFS_EXTENT_PLEN_FMT_BIT;
			if (ei->e.partial)
				plen |= Z_EROFS_EXTENT_PLEN_PARTIAL;
		}
		de = (struct z_erofs_extent) {
			.plen = cpu_to_le32(plen),
			.pstart_lo = cpu_to_le32(ei->e.pstart),
			.lstart_lo = cpu_to_le32(lstart),
			.pstart_hi = cpu_to_le32(ei->e.pstart >> 32),
			.lstart_hi = cpu_to_le32(lstart >> 32),
		};
		memcpy(ctx->metacur, &de, recsz);
		ctx->metacur += recsz;
		lstart += ei->e.length;
		list_del(&ei->list);
		free(ei);
	}
	inode->datalayout = EROFS_INODE_COMPRESSED_FULL;
	inode->z_advise |= Z_EROFS_ADVISE_EXTENTS |
		((ilog2(recsz) - 2) << Z_EROFS_ADVISE_EXTRECSZ_BIT);
	return metabuf;
}

static void *z_erofs_write_indexes(struct z_erofs_compress_ictx *ctx)
{
	const struct erofs_importer_params *params = ctx->im->params;
	struct erofs_inode *inode = ctx->inode;
	struct erofs_sb_info *sbi = inode->sbi;
	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) {
		metabuf = z_erofs_write_extents(ctx);
		if (metabuf != ERR_PTR(-EAGAIN)) {
			if (IS_ERR(metabuf))
				return metabuf;
			goto out;
		}
	}

	/*
	 * If the packed inode is larger than 4GiB, the full fragmentoff
	 * will be recorded by switching to the noncompact layout anyway.
	 */
	if (inode->fragment_size && inode->fragmentoff >> 32) {
		inode->datalayout = EROFS_INODE_COMPRESSED_FULL;
	} else if (!params->no_zcompact && !ctx->dedupe &&
		   inode->z_lclusterbits <= 14) {
		if (inode->z_lclusterbits <= 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;
	}

	metabuf = malloc(BLK_ROUND_UP(inode->sbi, inode->i_size) *
			 sizeof(struct z_erofs_lcluster_index) +
			 Z_EROFS_LEGACY_MAP_HEADER_SIZE);
	if (!metabuf)
		return ERR_PTR(-ENOMEM);

	ctx->metacur = metabuf + Z_EROFS_LEGACY_MAP_HEADER_SIZE;
	ctx->clusterofs = 0;
	list_for_each_entry_safe(ei, n, &ctx->extents, list) {
		DBG_BUGON(ei->list.next != &ctx->extents &&
			  ctx->clusterofs + ei->e.length < erofs_blksiz(sbi));
		z_erofs_write_full_indexes(ctx, &ei->e);

		list_del(&ei->list);
		free(ei);
	}
	z_erofs_fini_full_indexes(ctx);
out:
	z_erofs_write_mapheader(inode, metabuf);
	return metabuf;
}

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);
	DBG_BUGON(inode->idata_size != le16_to_cpu(h->h_idata_size));
	h->h_idata_size = 0;
	if (!inode->eof_tailraw)
		return;
	DBG_BUGON(inode->idata_type == EROFS_IDATA_TYPE_RAW);

	/* 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);

		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;
	} 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->idata_type = EROFS_IDATA_TYPE_RAW;
	inode->eof_tailraw = NULL;
}

int z_erofs_compress_segment(struct z_erofs_compress_sctx *ctx,
			     u64 offset, erofs_off_t pstart)
{
	struct z_erofs_compress_ictx *ictx = ctx->ictx;
	const struct erofs_importer_params *params = ictx->im->params;
	struct erofs_inode *inode = ictx->inode;
	bool frag = params->fragments && !erofs_is_packed_inode(inode) &&
		!erofs_is_metabox_inode(inode) &&
		ctx->seg_idx >= ictx->seg_num - 1;
	struct erofs_vfile *vf = ictx->vf;
	int ret;

	DBG_BUGON(offset != -1 && frag && inode->fragment_size);
	if (offset != -1 && frag && !inode->fragment_size &&
	    params->fragdedupe != EROFS_FRAGDEDUPE_OFF) {
		ret = erofs_fragment_findmatch(inode, vf, ictx->fpos,
					       ictx->tofh);
		if (ret < 0)
			return ret;
		if (inode->fragment_size > ctx->remaining)
			inode->fragment_size = ctx->remaining;
		ctx->remaining -= inode->fragment_size;
	}

	ctx->pstart = pstart;
	ctx->poff = 0;
	while (ctx->remaining) {
		const u64 rx = min_t(u64, ctx->remaining,
				     Z_EROFS_COMPR_QUEUE_SZ - ctx->tail);
		int ret;

		ret = (offset == -1 ?
			erofs_io_read(vf, ctx->queue + ctx->tail, rx) :
			erofs_io_pread(vf, ctx->queue + ctx->tail, rx,
				       ictx->fpos + offset));
		if (ret != rx)
			return -errno;

		ctx->remaining -= rx;
		ctx->tail += rx;
		if (offset != -1)
			offset += rx;

		ret = z_erofs_compress_one(ctx);
		if (ret)
			return ret;
	}
	DBG_BUGON(ctx->head != ctx->tail);

	if (ctx->pivot) {
		z_erofs_commit_extent(ctx, ctx->pivot);
		ctx->pivot = NULL;
	}

	/* generate an extra extent for the deduplicated fragment */
	if (frag && inode->fragment_size && !ictx->fragemitted) {
		struct z_erofs_extent_item *ei;

		ei = malloc(sizeof(*ei));
		if (!ei)
			return -ENOMEM;

		ei->e = (struct z_erofs_inmem_extent) {
			.length = inode->fragment_size,
			.plen = 0,
			.raw = false,
			.partial = false,
			.pstart = ctx->pstart,
		};
		init_list_head(&ei->list);
		z_erofs_commit_extent(ctx, ei);
	}
	return 0;
}

int erofs_commit_compressed_file(struct z_erofs_compress_ictx *ictx,
				 struct erofs_buffer_head *bh,
				 erofs_off_t pstart, erofs_off_t ptotal)
{
	struct erofs_inode *inode = ictx->inode;
	const struct erofs_importer_params *params = ictx->im->params;
	struct erofs_sb_info *sbi = inode->sbi;
	unsigned int legacymetasize, bbits = sbi->blkszbits;
	u8 *compressmeta;
	int ret;

	if (inode->fragment_size) {
		ret = erofs_fragment_commit(inode, ictx->tofh);
		if (ret) {
			erofs_err("failed to commit fragment for %s: %s",
				  inode->i_srcpath, erofs_strerror(ret));
			goto err_free_idata;
		}
		inode->z_advise |= Z_EROFS_ADVISE_FRAGMENT_PCLUSTER;
		erofs_sb_set_fragments(sbi);
	}

	/* fall back to no compression mode */
	DBG_BUGON(pstart < (!!inode->idata_size) << bbits);
	ptotal -= (u64)(!!inode->idata_size) << bbits;

	compressmeta = z_erofs_write_indexes(ictx);
	if (!compressmeta) {
		ret = -ENOMEM;
		goto err_free_idata;
	}

	legacymetasize = ictx->metacur - compressmeta;
	/* estimate if data compression saves space or not */
	if (!inode->fragment_size && ptotal + inode->idata_size +
	    legacymetasize >= inode->i_size) {
		z_erofs_dedupe_ext_commit(true);
		z_erofs_dedupe_commit(true);
		ret = EROFS_RETVAL_FALLBACK;
		goto err_free_meta;
	}
	z_erofs_dedupe_ext_commit(false);
	z_erofs_dedupe_commit(false);

	if (!ictx->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->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 (ptotal)
		(void)erofs_bh_balloon(bh, ptotal);
	else if (!params->fragments && params->dedupe != EROFS_DEDUPE_FORCE_ON)
		DBG_BUGON(!inode->idata_size);

	erofs_info("compressed %s (%llu bytes) into %llu bytes",
		   inode->i_srcpath, inode->i_size | 0ULL, ptotal | 0ULL);

	if (inode->idata_size) {
		bh->op = &erofs_skip_write_bhops;
		inode->bh_data = bh;
	} else {
		erofs_bdrop(bh, false);
	}

	inode->u.i_blocks = BLK_ROUND_UP(sbi, ptotal);

	if (inode->datalayout == EROFS_INODE_COMPRESSED_FULL) {
		inode->extent_isize = legacymetasize;
	} else {
		ret = z_erofs_convert_to_compacted_format(inode, pstart,
							  legacymetasize,
							  compressmeta);
		DBG_BUGON(ret);
	}
	inode->compressmeta = compressmeta;
	return 0;

err_free_meta:
	free(compressmeta);
	inode->compressmeta = NULL;
err_free_idata:
	erofs_bdrop(bh, true);	/* revoke buffer */
	if (inode->idata) {
		free(inode->idata);
		inode->idata = NULL;
	}
	return ret;
}

static struct z_erofs_compress_ictx g_ictx;

#ifdef EROFS_MT_ENABLED
void *z_erofs_mt_wq_tls_alloc(struct erofs_workqueue *wq, void *ptr)
{
	struct erofs_compress_wq_tls *tls;

	tls = calloc(1, sizeof(*tls));
	if (!tls)
		return NULL;

	tls->queue = malloc(Z_EROFS_COMPR_QUEUE_SZ);
	if (!tls->queue)
		goto err_free_priv;

	tls->destbuf = calloc(1, Z_EROFS_DESTBUF_SZ);
	if (!tls->destbuf)
		goto err_free_queue;

	tls->ccfg = calloc(EROFS_MAX_COMPR_CFGS, sizeof(*tls->ccfg));
	if (!tls->ccfg)
		goto err_free_destbuf;
	return tls;

err_free_destbuf:
	free(tls->destbuf);
err_free_queue:
	free(tls->queue);
err_free_priv:
	free(tls);
	return NULL;
}

void *z_erofs_mt_wq_tls_free(struct erofs_workqueue *wq, void *priv)
{
	struct erofs_compress_wq_tls *tls = priv;
	int i;

	for (i = 0; i < EROFS_MAX_COMPR_CFGS; i++) {
		if (!tls->ccfg[i].enable)
			continue;
		erofs_compressor_exit(&tls->ccfg[i].handle);
	}
	free(tls->ccfg);
	free(tls->destbuf);
	free(tls->queue);
	free(tls);
	return NULL;
}

void z_erofs_mt_workfn(struct erofs_work *work, void *tlsp)
{
	struct erofs_compress_work *cwork = (struct erofs_compress_work *)work;
	struct erofs_compress_wq_tls *tls = tlsp;
	struct z_erofs_compress_sctx *sctx = &cwork->ctx;
	struct z_erofs_compress_ictx *ictx = sctx->ictx;
	struct erofs_inode *inode = ictx->inode;
	struct erofs_sb_info *sbi = inode->sbi;
	int zsetno = container_of(cwork->zset, struct erofs_compress_cfg, zset)
			- sbi->zmgr->ccfg;
	struct erofs_compress_cfg *lc = &tls->ccfg[zsetno];
	int ret;

	if (__erofs_unlikely(!lc->enable)) {
		unsigned int pclustersize_max =
			ictx->im->params->pclusterblks_max << sbi->blkszbits;

		lc->zset = *cwork->zset;
		ret = erofs_compressor_init(sbi, &lc->handle, &lc->zset,
					    pclustersize_max);
		if (ret)
			goto out;
		lc->algorithmtype =
			z_erofs_get_compress_algorithm_id(&lc->handle);
		lc->enable = true;
	}

	sctx->pclustersize = z_erofs_get_pclustersize(ictx);
	DBG_BUGON(sctx->pclustersize > Z_EROFS_PCLUSTER_MAX_SIZE);
	sctx->queue = tls->queue;
	sctx->destbuf = tls->destbuf;
	sctx->chandle = &lc->handle;
	erofs_compressor_reset(sctx->chandle);
	sctx->membuf = malloc(round_up(sctx->remaining, erofs_blksiz(sbi)));
	if (!sctx->membuf) {
		ret = -ENOMEM;
		goto out;
	}
	ret = z_erofs_compress_segment(sctx, sctx->seg_idx * cfg.c_mkfs_segment_size,
				       EROFS_NULL_ADDR);

out:
	DBG_BUGON(ret > 0);
	pthread_mutex_lock(&ictx->mutex);
	cwork->errcode = ret;
	pthread_cond_signal(&cwork->cond);
	pthread_mutex_unlock(&ictx->mutex);
}

void z_erofs_mt_f_workfn(struct erofs_work *work, void *tlsp)
{
	struct erofs_compress_work *cwork = (struct erofs_compress_work *)work;
	struct erofs_sb_info *sbi = cwork->ctx.ictx->inode->sbi;
	u32 tofh = cwork->ctx.ictx->tofh;
	struct z_erofs_compress_fslot *fs = &sbi->zmgr->fslot[tofh & 1023];

	while (1) {
		z_erofs_mt_workfn(work, tlsp);
		pthread_mutex_lock(&fs->lock);

		if (list_empty(&fs->pending)) {
			fs->inprogress = false;
			pthread_mutex_unlock(&fs->lock);
			break;
		}
		cwork = list_first_entry(&fs->pending,
					 struct erofs_compress_work,
					 ctx.sibling);
		list_del(&cwork->ctx.sibling);
		pthread_mutex_unlock(&fs->lock);
		init_list_head(&cwork->ctx.extents);
		work = &cwork->work;
	}
}

int z_erofs_merge_segment(struct z_erofs_compress_ictx *ictx,
			  struct z_erofs_compress_sctx *sctx)
{
	struct z_erofs_extent_item *ei, *n;
	const struct erofs_importer_params *params = ictx->im->params;
	struct erofs_sb_info *sbi = ictx->inode->sbi;
	bool dedupe_ext = params->fragments;
	erofs_off_t off = 0;
	int ret = 0, ret2;
	erofs_off_t dpo;
	u64 hash;

	list_for_each_entry_safe(ei, n, &sctx->extents, list) {
		list_del(&ei->list);
		list_add_tail(&ei->list, &ictx->extents);

		if (ei->e.pstart != EROFS_NULL_ADDR)	/* deduped extents */
			continue;

		ei->e.pstart = sctx->pstart;
		sctx->pstart += ei->e.plen;

		/* skip write data but leave blkaddr for inline fallback */
		if (ei->e.inlined || !ei->e.plen)
			continue;

		if (dedupe_ext) {
			dpo = z_erofs_dedupe_ext_match(sbi, sctx->membuf + off,
						ei->e.plen, ei->e.raw, &hash);
			if (dpo) {
				ei->e.pstart = dpo;
				sctx->pstart -= ei->e.plen;
				off += ei->e.plen;
				ictx->dedupe = true;
				erofs_sb_set_dedupe(sbi);
				sbi->saved_by_deduplication += ei->e.plen;
				erofs_dbg("Dedupe %u %scompressed data to %llu of %u bytes",
					  ei->e.length, ei->e.raw ? "un" : "",
					  ei->e.pstart | 0ULL, ei->e.plen);
				continue;
			}
		}
		erofs_dbg("Writing %u %scompressed data of %s to %llu", ei->e.length,
			  ei->e.raw ? "un" : "", ictx->inode->i_srcpath, ei->e.pstart);
		ret2 = erofs_dev_write(sbi, sctx->membuf + off, ei->e.pstart,
				       ei->e.plen);
		off += ei->e.plen;
		if (ret2)
			ret = ret2;
		else if (dedupe_ext)
			z_erofs_dedupe_ext_insert(&ei->e, hash);
	}
	free(sctx->membuf);
	sctx->membuf = NULL;
	return ret;
}

int z_erofs_mt_compress(struct z_erofs_compress_ictx *ictx)
{
	struct erofs_compress_work *cur, *head = NULL, **last = &head;
	struct erofs_compress_cfg *ccfg = ictx->ccfg;
	struct erofs_inode *inode = ictx->inode;
	unsigned int segsz = cfg.c_mkfs_segment_size;
	int nsegs, i;

	nsegs = DIV_ROUND_UP(inode->i_size - inode->fragment_size, segsz);
	if (!nsegs)
		nsegs = 1;
	ictx->seg_num = nsegs;
	pthread_mutex_init(&ictx->mutex, NULL);
	pthread_cond_init(&ictx->cond, NULL);

	for (i = 0; i < nsegs; i++) {
		pthread_mutex_lock(&z_erofs_mt_ctrl.mutex);
		cur = z_erofs_mt_ctrl.idle;
		if (cur) {
			z_erofs_mt_ctrl.idle = cur->next;
			cur->next = NULL;
		}
		pthread_mutex_unlock(&z_erofs_mt_ctrl.mutex);
		if (!cur) {
			cur = calloc(1, sizeof(*cur));
			if (!cur)
				return -ENOMEM;
			pthread_cond_init(&cur->cond, NULL);
		}
		*last = cur;
		last = &cur->next;

		cur->ctx = (struct z_erofs_compress_sctx) {
			.ictx = ictx,
			.seg_idx = i,
			.pivot = &dummy_pivot,
		};
		init_list_head(&cur->ctx.extents);

		cur->zset = &ccfg->zset;
		cur->errcode = 1;	/* mark as "in progress" */

		if (i >= nsegs - 1) {
			struct z_erofs_mgr *zmgr = inode->sbi->zmgr;

			cur->ctx.remaining = inode->i_size -
					inode->fragment_size - (u64)i * segsz;

			if (zmgr->fslot[0].pending.next && ictx->tofh != ~0U) {
				struct z_erofs_compress_fslot *fs =
					&zmgr->fslot[ictx->tofh & 1023];

				pthread_mutex_lock(&fs->lock);
				if (fs->inprogress) {
					list_add_tail(&cur->ctx.sibling,
						      &fs->pending);
				} else {
					fs->inprogress = true;
					cur->work.fn = z_erofs_mt_f_workfn;
					erofs_queue_work(&z_erofs_mt_ctrl.wq,
						 &cur->work);
				}
				pthread_mutex_unlock(&fs->lock);
				continue;
			}
		} else {
			cur->ctx.remaining = segsz;
		}
		cur->work.fn = z_erofs_mt_workfn;
		erofs_queue_work(&z_erofs_mt_ctrl.wq, &cur->work);
	}
	ictx->mtworks = head;
	return 0;
}

int erofs_mt_write_compressed_file(struct z_erofs_compress_ictx *ictx)
{
	struct erofs_sb_info *sbi = ictx->inode->sbi;
	struct erofs_buffer_head *bh = NULL;
	struct erofs_compress_work *head = ictx->mtworks, *cur;
	erofs_off_t pstart, ptotal = 0;
	int ret;

	bh = erofs_balloc(sbi->bmgr, DATA, 0, 0);
	if (IS_ERR(bh)) {
		ret = PTR_ERR(bh);
		goto out;
	}

	DBG_BUGON(!head);
	pstart = erofs_pos(sbi, erofs_mapbh(NULL, bh->block));

	ret = 0;
	do {
		cur = head;
		head = cur->next;

		pthread_mutex_lock(&ictx->mutex);
		while ((ret = cur->errcode) > 0)
			pthread_cond_wait(&cur->cond, &ictx->mutex);
		pthread_mutex_unlock(&ictx->mutex);

		if (!ret) {
			int ret2;

			cur->ctx.pstart = pstart;
			ret2 = z_erofs_merge_segment(ictx, &cur->ctx);
			if (ret2)
				ret = ret2;

			ptotal += cur->ctx.pstart - pstart;
			pstart = cur->ctx.pstart;
		}

		pthread_mutex_lock(&z_erofs_mt_ctrl.mutex);
		cur->next = z_erofs_mt_ctrl.idle;
		z_erofs_mt_ctrl.idle = cur;
		pthread_mutex_unlock(&z_erofs_mt_ctrl.mutex);
	} while (head);

	if (ret)
		goto out;
	ret = erofs_commit_compressed_file(ictx, bh, pstart - ptotal, ptotal);

out:
	free(ictx);
	return ret;
}

static int z_erofs_mt_global_init(struct erofs_importer *im)
{
	static erofs_atomic_bool_t __initonce;
	struct erofs_importer_params *params = im->params;
	unsigned int workers = cfg.c_mt_workers;
	int ret;

	if (erofs_atomic_test_and_set(&__initonce))
		return 0;

	z_erofs_mt_enabled = false;
	if (workers < 1)
		return 0;
	/* XXX: `dedupe` is actually not a global option here. */
	if (workers >= 1 && params->dedupe == EROFS_DEDUPE_FORCE_ON) {
		erofs_warn("multi-threaded dedupe is NOT implemented for now");
		cfg.c_mt_workers = 0;
	} else {
		ret = erofs_alloc_workqueue(&z_erofs_mt_ctrl.wq, workers,
					    workers << 2,
					    z_erofs_mt_wq_tls_alloc,
					    z_erofs_mt_wq_tls_free);
		if (ret) {
			erofs_atomic_set(&__initonce, 0);
			return ret;
		}
		z_erofs_mt_enabled = true;
	}
	pthread_mutex_init(&g_ictx.mutex, NULL);
	pthread_cond_init(&g_ictx.cond, NULL);
	return 0;
}

int z_erofs_mt_global_exit(void)
{
	struct erofs_compress_work *n;
	int ret;

	if (z_erofs_mt_enabled) {
		ret = erofs_destroy_workqueue(&z_erofs_mt_ctrl.wq);
		if (ret)
			return ret;
		while (z_erofs_mt_ctrl.idle) {
			n = z_erofs_mt_ctrl.idle->next;
			free(z_erofs_mt_ctrl.idle);
			z_erofs_mt_ctrl.idle = n;
		}
		z_erofs_mt_enabled = false;
	}
	return 0;
}
#else
static int z_erofs_mt_global_init(struct erofs_importer *im)
{
	z_erofs_mt_enabled = false;
	return 0;
}

int z_erofs_mt_global_exit(void)
{
	return 0;
}
#endif

void *erofs_prepare_compressed_file(struct erofs_importer *im,
				    struct erofs_inode *inode)
{
	const struct erofs_importer_params *params = im->params;
	struct erofs_sb_info *sbi = inode->sbi;
	struct z_erofs_compress_ictx *ictx;
	bool frag = params->fragments && !erofs_is_packed_inode(inode) &&
		!erofs_is_metabox_inode(inode);
	bool all_fragments = params->all_fragments && frag;

	/* initialize per-file compression setting */
	inode->z_advise = 0;
	inode->z_lclusterbits = sbi->blkszbits;
#ifndef NDEBUG
	if (cfg.c_random_algorithms) {
		while (1) {
			inode->z_algorithmtype[0] =
				rand() % EROFS_MAX_COMPR_CFGS;
			if (sbi->zmgr->ccfg[inode->z_algorithmtype[0]].enable)
				break;
		}
	}
#endif
	inode->idata_size = 0;
	inode->fragment_size = 0;

	if (!z_erofs_mt_enabled || all_fragments) {
#ifdef EROFS_MT_ENABLED
		pthread_mutex_lock(&g_ictx.mutex);
		while (g_ictx.seg_num) {
			if (g_ictx.seg_num == INT_MAX) {
				pthread_mutex_unlock(&g_ictx.mutex);
				return ERR_PTR(-ECHILD);
			}
			pthread_cond_wait(&g_ictx.cond, &g_ictx.mutex);
		}
		g_ictx.seg_num = 1;
		pthread_mutex_unlock(&g_ictx.mutex);
#endif
		ictx = &g_ictx;
	} else {
		ictx = malloc(sizeof(*ictx));
		if (!ictx)
			return ERR_PTR(-ENOMEM);
	}
	ictx->im = im;
	ictx->inode = inode;
	if (erofs_is_metabox_inode(inode))
		ictx->ccfg = &sbi->zmgr->ccfg[cfg.c_mkfs_metabox_algid];
	else
		ictx->ccfg = &sbi->zmgr->ccfg[inode->z_algorithmtype[0]];
	inode->z_algorithmtype[0] = ictx->ccfg->algorithmtype;
	inode->z_algorithmtype[1] = 0;

	if (params->max_compressed_extent_size ==
	    EROFS_COMPRESSED_EXTENT_UNSPECIFIED) {
		if (erofs_sb_has_48bit(sbi) && ictx->ccfg->handle.alg->c->compress) {
			ictx->max_compressed_extent_size =
				z_erofs_get_pclustersize(ictx);
			ictx->data_unaligned = true;
		} else {
			ictx->max_compressed_extent_size = UINT32_MAX;
			ictx->data_unaligned = false;
		}
	} else if (params->max_compressed_extent_size <=
		   (s32)z_erofs_get_pclustersize(ictx)) {
		if (params->max_compressed_extent_size < 0) {
			ictx->max_compressed_extent_size =
				-params->max_compressed_extent_size;
			if (!erofs_sb_has_48bit(sbi)) {
				erofs_err("Unaligned compressed extents must be used with the 48bit encoded extent layout",
					  ictx->max_compressed_extent_size);
				goto err_out;
			}
			ictx->data_unaligned = true;
		} else {
			ictx->max_compressed_extent_size =
				params->max_compressed_extent_size;
			if (erofs_sb_has_48bit(sbi))
				ictx->data_unaligned = true;
		}
		if (ictx->max_compressed_extent_size < erofs_blksiz(sbi)) {
			erofs_err("Maximum compressed extent size (%u) must be at least the block size (%u)",
				  ictx->max_compressed_extent_size, erofs_blksiz(sbi));
			goto err_out;
		}
	} else {
		ictx->max_compressed_extent_size =
			params->max_compressed_extent_size;
		ictx->data_unaligned = false;
	}
	if (params->fragments && params->dedupe != EROFS_DEDUPE_FORCE_ON &&
	    !ictx->data_unaligned)
		inode->z_advise |= Z_EROFS_ADVISE_INTERLACED_PCLUSTER;

	init_list_head(&ictx->extents);
	ictx->fix_dedupedfrag = false;
	ictx->fragemitted = false;
	ictx->dedupe = false;
	return ictx;
err_out:
	free(ictx);
	return ERR_PTR(-EINVAL);
}

void erofs_bind_compressed_file_with_fd(struct z_erofs_compress_ictx *ictx,
					int fd, u64 fpos)
{
	ictx->_vf = (struct erofs_vfile){ .fd = fd };
	ictx->vf = &ictx->_vf;
	ictx->fpos = fpos;
}

int erofs_begin_compressed_file(struct z_erofs_compress_ictx *ictx)
{
	const struct erofs_importer_params *params = ictx->im->params;
	struct erofs_inode *inode = ictx->inode;
	bool frag = params->fragments && !erofs_is_packed_inode(inode) &&
		!erofs_is_metabox_inode(inode);
	bool all_fragments = params->all_fragments && frag;
	int ret;

	if (frag) {
		ictx->tofh = z_erofs_fragments_tofh(inode,
						    ictx->vf, ictx->fpos);
		if (ictx == &g_ictx &&
		    params->fragdedupe != EROFS_FRAGDEDUPE_OFF) {
			/*
			 * Handle tails in advance to avoid writing duplicated
			 * parts into the packed inode.
			 */
			ret = erofs_fragment_findmatch(inode,
					ictx->vf, ictx->fpos, ictx->tofh);
			if (ret < 0)
				goto err_out;

			if (params->fragdedupe == EROFS_FRAGDEDUPE_INODE &&
			    inode->fragment_size < inode->i_size) {
				erofs_dbg("Discard the sub-inode tail fragment of %s",
					  inode->i_srcpath);
				inode->fragment_size = 0;
			}
		}
	}

	if (all_fragments && !inode->fragment_size) {
		ret = erofs_pack_file_from_fd(inode, ictx->vf, ictx->fpos,
					      ictx->tofh);
		if (ret)
			goto err_out;
	}

#ifdef EROFS_MT_ENABLED
	if (ictx != &g_ictx) {
		ret = z_erofs_mt_compress(ictx);
		if (ret)
			goto err_out;
	}
#endif
	return 0;
err_out:
	if (inode->idata) {
		free(inode->idata);
		inode->idata = NULL;
	}
	if (ictx != &g_ictx)
		free(ictx);
	return ret;
}

int erofs_write_compressed_file(struct z_erofs_compress_ictx *ictx)
{
	static u8 g_queue[Z_EROFS_COMPR_QUEUE_SZ];
	struct erofs_buffer_head *bh;
	static struct z_erofs_compress_sctx sctx;
	struct erofs_compress_cfg *ccfg = ictx->ccfg;
	struct erofs_inode *inode = ictx->inode;
	struct erofs_sb_info *sbi = inode->sbi;
	erofs_off_t pstart;
	int ret;

#ifdef EROFS_MT_ENABLED
	if (ictx != &g_ictx)
		return erofs_mt_write_compressed_file(ictx);
#endif

	/* allocate main data buffer */
	bh = erofs_balloc(inode->sbi->bmgr, DATA, 0, 0);
	if (IS_ERR(bh)) {
		ret = PTR_ERR(bh);
		goto err_free_idata;
	}
	pstart = erofs_pos(sbi, erofs_mapbh(NULL, bh->block));

	ictx->seg_num = 1;
	sctx = (struct z_erofs_compress_sctx) {
		.ictx = ictx,
		.queue = g_queue,
		.chandle = &ccfg->handle,
		.remaining = inode->i_size - inode->fragment_size,
		.seg_idx = 0,
		.pivot = &dummy_pivot,
		.pclustersize = z_erofs_get_pclustersize(ictx),
	};
	init_list_head(&sctx.extents);

	ret = z_erofs_compress_segment(&sctx, -1, pstart);
	if (ret)
		goto err_free_idata;

	list_splice_tail(&sctx.extents, &ictx->extents);
	ret = erofs_commit_compressed_file(ictx, bh, pstart,
					   sctx.pstart - pstart);
	goto out;

err_free_idata:
	erofs_bdrop(bh, true);	/* revoke buffer */
	if (inode->idata) {
		free(inode->idata);
		inode->idata = NULL;
	}
out:
#ifdef EROFS_MT_ENABLED
	pthread_mutex_lock(&ictx->mutex);
	if (ret < 0 && ret != EROFS_RETVAL_FALLBACK)
		/* mark as failed to avoid further processing */
		ictx->seg_num = INT_MAX;
	else
		ictx->seg_num = 0;
	pthread_cond_signal(&ictx->cond);
	pthread_mutex_unlock(&ictx->mutex);
#endif
	return ret;
}

int erofs_begin_compress_dir(struct erofs_importer *im,
			     struct erofs_inode *inode)

{
	if (!im->params->compress_dir ||
	    inode->i_size < Z_EROFS_LEGACY_MAP_HEADER_SIZE)
		return EROFS_RETVAL_FALLBACK;

	inode->z_advise |= Z_EROFS_ADVISE_FRAGMENT_PCLUSTER;
	erofs_sb_set_fragments(inode->sbi);
	inode->datalayout = EROFS_INODE_COMPRESSED_FULL;
	inode->extent_isize = Z_EROFS_LEGACY_MAP_HEADER_SIZE;
	inode->compressmeta = NULL;
	return 0;
}

int erofs_write_compress_dir(struct erofs_inode *inode, struct erofs_vfile *vf)
{
	void *compressmeta;
	int err;

	if (inode->datalayout != EROFS_INODE_COMPRESSED_FULL ||
	    inode->extent_isize < Z_EROFS_LEGACY_MAP_HEADER_SIZE) {
		DBG_BUGON(1);
		return -EINVAL;
	}

	err = erofs_pack_file_from_fd(inode, vf, 0, ~0U);
	if (err || !inode->fragment_size)
		return err;
	err = erofs_fragment_commit(inode, ~0);
	if (err)
		return err;

	compressmeta = calloc(1, Z_EROFS_LEGACY_MAP_HEADER_SIZE);
	if (!compressmeta)
		return -ENOMEM;
	*(__le64 *)compressmeta =
		cpu_to_le64(inode->fragmentoff | 1ULL << 63);
	inode->compressmeta = compressmeta;
	return 0;
}

static int z_erofs_build_compr_cfgs(struct erofs_importer *im,
				    u32 *max_dict_size)
{
	struct erofs_sb_info *sbi = im->sbi;
	struct erofs_buffer_head *bh = sbi->bh_sb;
	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 = im->params->pclusterblks_max,
			}
		};

		bh = erofs_battach(bh, META, sizeof(lz4alg));
		if (IS_ERR(bh)) {
			DBG_BUGON(1);
			return PTR_ERR(bh);
		}
		erofs_mapbh(NULL, bh->block);
		ret = erofs_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(
					max_dict_size
						[Z_EROFS_COMPRESSION_LZMA]),
			}
		};

		bh = erofs_battach(bh, META, sizeof(lzmaalg));
		if (IS_ERR(bh)) {
			DBG_BUGON(1);
			return PTR_ERR(bh);
		}
		erofs_mapbh(NULL, bh->block);
		ret = erofs_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(
					max_dict_size
						[Z_EROFS_COMPRESSION_DEFLATE])),
			}
		};

		bh = erofs_battach(bh, META, sizeof(zalg));
		if (IS_ERR(bh)) {
			DBG_BUGON(1);
			return PTR_ERR(bh);
		}
		erofs_mapbh(NULL, bh->block);
		ret = erofs_dev_write(sbi, &zalg, erofs_btell(bh, false),
				      sizeof(zalg));
		bh->op = &erofs_drop_directly_bhops;
	}
#ifdef HAVE_LIBZSTD
	if (sbi->available_compr_algs & (1 << Z_EROFS_COMPRESSION_ZSTD)) {
		struct {
			__le16 size;
			struct z_erofs_zstd_cfgs z;
		} __packed zalg = {
			.size = cpu_to_le16(sizeof(struct z_erofs_zstd_cfgs)),
			.z = {
				.windowlog =
					ilog2(max_dict_size[Z_EROFS_COMPRESSION_ZSTD]) - 10,
			}
		};

		bh = erofs_battach(bh, META, sizeof(zalg));
		if (IS_ERR(bh)) {
			DBG_BUGON(1);
			return PTR_ERR(bh);
		}
		erofs_mapbh(NULL, bh->block);
		ret = erofs_dev_write(sbi, &zalg, erofs_btell(bh, false),
				      sizeof(zalg));
		bh->op = &erofs_drop_directly_bhops;
	}
#endif
	return ret;
}

int z_erofs_compress_init(struct erofs_importer *im)
{
	const struct erofs_importer_params *params = im->params;
	struct erofs_sb_info *sbi = im->sbi;
	unsigned int pclustersize_max = params->pclusterblks_max << sbi->blkszbits;
	u32 max_dict_size[Z_EROFS_COMPRESSION_MAX] = {};
	u32 available_compr_algs = 0;
	bool newzmgr __maybe_unused = false;
	int i, ret, id;

	if (!sbi->zmgr) {
		sbi->zmgr = calloc(1, sizeof(*sbi->zmgr));
		if (!sbi->zmgr)
			return -ENOMEM;
		newzmgr = true;
	}

	for (i = 0; params->z_paramsets[i].alg; ++i) {
		struct erofs_compress_cfg *ccfg = &sbi->zmgr->ccfg[i];
		struct erofs_compress *c = &ccfg->handle;
		const struct z_erofs_paramset *zset = &params->z_paramsets[i];

		if (ccfg->enable)
			return -EINVAL;

		ccfg->zset = *zset;
		ccfg->zset.alg = strdup(zset->alg);
		if (!ccfg->zset.alg)
			return -ENOMEM;
		if (zset->extraopts) {
			ccfg->zset.extraopts = strdup(zset->extraopts);
			if (!ccfg->zset.extraopts)
				return -ENOMEM;
		}

		ret = erofs_compressor_init(sbi, c, &ccfg->zset,
					    pclustersize_max);
		if (ret)
			return ret;

		id = z_erofs_get_compress_algorithm_id(c);
		ccfg->algorithmtype = id;
		ccfg->enable = true;
		available_compr_algs |= 1 << id;
		if (id != Z_EROFS_COMPRESSION_LZ4)
			erofs_sb_set_compr_cfgs(sbi);
		if (c->dict_size > max_dict_size[id])
			max_dict_size[id] = c->dict_size;
	}
	if (!available_compr_algs)
		return 0;

	if (sbi->available_compr_algs) {
		u32 dalg = available_compr_algs & (~sbi->available_compr_algs);

		if (dalg) {
			erofs_err("unavailable algorithms 0x%x on incremental builds",
				  dalg);
			return -EOPNOTSUPP;
		}
		if ((available_compr_algs & BIT(Z_EROFS_COMPRESSION_LZ4)) &&
		    sbi->lz4.max_pclusterblks < params->pclusterblks_max) {
			erofs_err("pcluster size (%u blocks) cannot increase on incremental builds",
				  params->pclusterblks_max);
			return -EOPNOTSUPP;
		}
	} else {
		sbi->available_compr_algs = available_compr_algs;

		erofs_sb_set_lz4_0padding(sbi);
		if (available_compr_algs & ~(1 << Z_EROFS_COMPRESSION_LZ4))
			erofs_sb_set_compr_cfgs(sbi);
	}

	/*
	 * if big pcluster is enabled, an extra CBLKCNT lcluster index needs
	 * to be loaded in order to get those compressed block counts.
	 */
	if (params->pclusterblks_max > 1) {
		if (pclustersize_max > Z_EROFS_PCLUSTER_MAX_SIZE) {
			erofs_err("pcluster size (%u blocks) is too large",
				  params->pclusterblks_max);
			return -EINVAL;
		}
		erofs_sb_set_big_pcluster(sbi);
	}

	if (params->pclusterblks_packed > params->pclusterblks_max) {
		erofs_err("pcluster size (%u blocks) for packed inode exceeds maximum",
			  params->pclusterblks_packed);
		return -EINVAL;
	}

	if (params->pclusterblks_metabox > (s32)params->pclusterblks_max) {
		erofs_err("pcluster size (%u blocks) for metabox inode exceeds maximum",
			  params->pclusterblks_metabox, params->pclusterblks_max);
		return -EINVAL;
	}

	if (sbi->bh_sb && erofs_sb_has_compr_cfgs(sbi)) {
		ret = z_erofs_build_compr_cfgs(im, max_dict_size);
		if (ret)
			return ret;
	}

	ret = z_erofs_mt_global_init(im);
	if (ret)
		return ret;

#ifdef EROFS_MT_ENABLED
	if (params->fragments && cfg.c_mt_workers > 1 && newzmgr) {
		for (i = 0; i < ARRAY_SIZE(sbi->zmgr->fslot); ++i) {
			init_list_head(&sbi->zmgr->fslot[i].pending);
			pthread_mutex_init(&sbi->zmgr->fslot[i].lock, NULL);
		}
	}
#endif
	return 0;
}

int z_erofs_compress_exit(struct erofs_sb_info *sbi)
{
	int i, ret;

	/* If `zmgr` is uninitialized, return directly. */
	if (!sbi->zmgr)
		return 0;

	for (i = 0; i < ARRAY_SIZE(sbi->zmgr->ccfg); ++i) {
		if (!sbi->zmgr->ccfg[i].enable)
			continue;
		ret = erofs_compressor_exit(&sbi->zmgr->ccfg[i].handle);
		if (ret)
			return ret;
		free(sbi->zmgr->ccfg[i].zset.alg);
		free(sbi->zmgr->ccfg[i].zset.extraopts);
	}
	free(sbi->zmgr);
	return 0;
}
