[PREVIEW] [RFC PATCH v2 4/5] erofs-mkfs: add compression support

Li Guifu bluce.liguifu at huawei.com
Wed Nov 21 01:32:38 AEDT 2018


This patch introduces a compression framework, so it can support
different compression algorithm in principle, currently lz4hc is
added right now due to the kernel implementation.

Signed-off-by: Li Guifu <bluce.liguifu at huawei.com>
Signed-off-by: Miao Xie <miaoxie at huawei.com>
Signed-off-by: Fang Wei <fangwei1 at huawei.com>
---
 erofs_compressor.c | 144 ++++++++++
 erofs_compressor.h |  49 ++++
 erofs_lz4hc.c      |  52 ++++
 erofs_lz4hc.h      |  22 ++
 mkfs_file.c        | 698 +++++++++++++++++++++++++++++++++++++++++++++
 mkfs_inode.c       |  61 +++-
 mkfs_main.c        |   4 +
 7 files changed, 1028 insertions(+), 2 deletions(-)
 create mode 100644 erofs_compressor.c
 create mode 100644 erofs_compressor.h
 create mode 100644 erofs_lz4hc.c
 create mode 100644 erofs_lz4hc.h

diff --git a/erofs_compressor.c b/erofs_compressor.c
new file mode 100644
index 0000000..139ae53
--- /dev/null
+++ b/erofs_compressor.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * erofs_compressor.c
+ *
+ * Copyright (C) 2018 HUAWEI, Inc.
+ *             http://www.huawei.com/
+ * Created by Miao Xie <miaoxie at huawei.com>
+ */
+#include <string.h>
+#include <assert.h>
+
+#include "erofs_error.h"
+#include "erofs_compressor.h"
+#include "erofs_lz4hc.h"
+#include "erofs_debug.h"
+#include "mkfs_erofs.h"
+
+static struct erofs_compr_alg erofs_compr_desc[EROFS_COMPR_ALG_MAX] = {
+	[EROFS_COMPR_NONE] = {
+		.ca_name    = "none",
+		.ca_idx     = EROFS_COMPR_NONE,
+		.ca_max_lvl = 0,
+		.ca_min_lvl = 0,
+		.ca_def_lvl = 0,
+		.ca_compress    = NULL,
+		.ca_init    = NULL,
+		.ca_deinit  = NULL,
+	},
+	[EROFS_COMPR_LZ4HC] = {
+		.ca_name    = "lz4hc",
+		.ca_idx     = EROFS_COMPR_LZ4HC,
+		.ca_max_lvl = LZ4HC_CLEVEL_MAX,
+		.ca_min_lvl = LZ4HC_CLEVEL_MIN,
+		.ca_def_lvl = EROFS_COMPR_LZ4HC_DEF_LVL,
+		.ca_compress    = erofs_lz4hc_compress,
+		.ca_init    = erofs_lz4hc_init,
+		.ca_deinit  = erofs_lz4hc_deinit,
+	},
+};
+
+void erofs_compress_alg_init(const char *name)
+{
+	int level;
+	struct erofs_compr_alg *alg;
+
+	if (!name) {
+		erofs_err("compress alg name is NULL !!!");
+		exit(EXIT_FAILURE);
+	}
+
+	/* name:  lz4hc or none */
+	alg = erofs_get_compress_alg(name);
+	if (!alg) {
+		erofs_err("can found alg[%s]", name);
+		exit(EXIT_FAILURE);
+	}
+	erofs_cfg.c_compr_alg       = alg;
+	erofs_cfg.c_compr_maxsz = BLK_ALIGN(EROFS_CONFIG_COMPR_MAX_SZ);
+
+	level = erofs_adjust_compress_level(alg, EROFS_COMPR_LZ4HC_DEF_LVL);
+	erofs_cfg.c_compr_lvl       = level;
+	erofs_cfg.c_compr_boundary  = EROFS_CONFIG_COMPR_DEF_BOUNDARY;
+	erofs_cfg.c_compr_ratio_limit   = EROFS_CONFIG_COMPR_RATIO_MAX_LIMIT;
+
+}
+struct erofs_compr_alg *erofs_get_compress_alg(const char *name)
+{
+	int i;
+
+	for (i = EROFS_COMPR_NONE; i < EROFS_COMPR_ALG_MAX; i++) {
+		if (strcmp(name, erofs_compr_desc[i].ca_name) == 0)
+			return &erofs_compr_desc[i];
+	}
+
+	return NULL;
+}
+
+int erofs_adjust_compress_level(struct erofs_compr_alg *alg, int lvl)
+{
+	if (!alg || alg->ca_idx == EROFS_COMPR_NONE)
+		return 0;
+
+	if (lvl > alg->ca_max_lvl) {
+		erofs_err("Compress level(%d) is greater than max level(%d), adjust it to default level(%d).\n",
+			   lvl, alg->ca_max_lvl, EROFS_COMPR_LZ4HC_DEF_LVL);
+		return alg->ca_def_lvl;
+	}
+
+	if (lvl < alg->ca_min_lvl) {
+		erofs_err("Compress level(%d) is less than min level(%d), adjust it to default level(%d).\n",
+			   lvl, alg->ca_min_lvl, EROFS_COMPR_LZ4HC_DEF_LVL);
+		return alg->ca_def_lvl;
+	}
+
+	return lvl;
+}
+
+void *erofs_compress_init(struct erofs_compr_alg *alg)
+{
+	void *ctx;
+
+	if (!alg->ca_init)
+		return NULL;
+
+	ctx = alg->ca_init();
+
+	return ctx;
+}
+
+void erofs_compress_deinit(struct erofs_compr_alg *alg, void *cctx)
+{
+	if (!alg->ca_deinit)
+		return;
+
+	alg->ca_deinit(cctx);
+}
+
+int64_t erofs_compress_onctx(struct erofs_compr_alg *alg, void *ctx,
+			     char *in, size_t insz,
+			     char *out, size_t outsz, size_t *srcsz, int lvl)
+{
+	assert(alg->ca_compress);
+
+	return alg->ca_compress(in, insz, out, outsz, srcsz, lvl, ctx);
+}
+
+int64_t erofs_compress(struct erofs_compr_alg *alg, char *in, size_t insz,
+		       char *out, size_t outsz, size_t *srcsz, int lvl)
+{
+	void *ctx = NULL;
+	int64_t ret;
+
+	if (alg->ca_init) {
+		ctx = alg->ca_init();
+		if (IS_ERR(ctx))
+			return EROFS_COMPRESS_ERROR;
+	}
+
+	ret = alg->ca_compress(in, insz, out, outsz, srcsz, lvl, ctx);
+	if (alg->ca_deinit)
+		alg->ca_deinit(ctx);
+
+	return ret;
+}
diff --git a/erofs_compressor.h b/erofs_compressor.h
new file mode 100644
index 0000000..f2d8b09
--- /dev/null
+++ b/erofs_compressor.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * erofs_compressor.h
+ *
+ * Copyright (C) 2018 HUAWEI, Inc.
+ *             http://www.huawei.com/
+ * Created by Li Guifu <bluce.liguifu at huawei.com>
+ */
+#ifndef __EROFS_COMPRESSOR_H__
+#define __EROFS_COMPRESSOR_H__
+
+#include <stdint.h>
+
+#define EROFS_COMPRESS_ERROR    (-1LL)
+
+enum erofs_compr_algs {
+	EROFS_COMPR_NONE,
+	EROFS_COMPR_LZ4HC,
+	EROFS_COMPR_ALG_MAX,
+};
+
+typedef int64_t (*compress_func)(char *in, size_t insize, char *out,
+				 size_t outsize, size_t *insizeptr, int level,
+				 void*);
+typedef void* (*init_func)();
+typedef void (*deinit_func)(void *cctx);
+
+struct erofs_compr_alg {
+	char        *ca_name;
+	int         ca_idx;
+	int         ca_max_lvl;
+	int         ca_min_lvl;
+	int         ca_def_lvl;
+	compress_func   ca_compress;
+	init_func       ca_init;
+	deinit_func     ca_deinit;
+};
+
+void erofs_compress_alg_init(const char *name);
+struct erofs_compr_alg *erofs_get_compress_alg(const char *name);
+int erofs_adjust_compress_level(struct erofs_compr_alg *alg, int lvl);
+void *erofs_compress_init(struct erofs_compr_alg *alg);
+void erofs_compress_deinit(struct erofs_compr_alg *alg, void *cctx);
+int64_t erofs_compress_onctx(struct erofs_compr_alg *alg, void *ctx,
+			     char *in, size_t insz,
+			     char *out, size_t outsz, size_t *srcsz, int lvl);
+int64_t erofs_compress(struct erofs_compr_alg *alg, char *in, size_t insz,
+		       char *out, size_t outsz, size_t *srcsz, int lvl);
+#endif
diff --git a/erofs_lz4hc.c b/erofs_lz4hc.c
new file mode 100644
index 0000000..1d155b7
--- /dev/null
+++ b/erofs_lz4hc.c
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * erofs_lz4hc.c
+ *
+ * Copyright (C) 2018 HUAWEI, Inc.
+ *             http://www.huawei.com/
+ * Created by Li Guifu <bluce.liguifu at huawei.com>
+ */
+#include <errno.h>
+#define LZ4_HC_STATIC_LINKING_ONLY  (1)
+#include <lz4hc.h>
+
+#include "erofs_error.h"
+#include "erofs_lz4hc.h"
+#include "erofs_compressor.h"
+#include "erofs_debug.h"
+
+void *erofs_lz4hc_init(void)
+{
+	LZ4_streamHC_t *ctx;
+
+	ctx = LZ4_createStreamHC();
+	if (!ctx) {
+		erofs_err("Cannot allocate LZ4HC context");
+		return ERR_PTR(-ENOMEM);
+	}
+
+	return (void *)ctx;
+}
+
+void erofs_lz4hc_deinit(void *ctx)
+{
+	if (!ctx)
+		return;
+
+	LZ4_freeStreamHC((LZ4_streamHC_t *)ctx);
+}
+
+int64_t erofs_lz4hc_compress(char *in, size_t insz, char *out, size_t outsz,
+			     size_t *inszptr, int level, void *ctx)
+{
+	int count;
+
+	*inszptr = insz;
+	count = LZ4_compress_HC_destSize((LZ4_streamHC_t *)ctx, in, out,
+					 (int *)inszptr, outsz, level);
+	if (count <= 0) {
+		erofs_err("Failed to compress data by LZ4HC");
+		return EROFS_COMPRESS_ERROR;
+	}
+	return (int64_t)count;
+}
diff --git a/erofs_lz4hc.h b/erofs_lz4hc.h
new file mode 100644
index 0000000..029c4f4
--- /dev/null
+++ b/erofs_lz4hc.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * erofs_lz4hc.h
+ *
+ * Copyright (C) 2018 HUAWEI, Inc.
+ *             http://www.huawei.com/
+ * Created by Li Guifu <bluce.liguifu at huawei.com>
+ */
+#ifndef __EROFS_LZ4HC_H__
+#define __EROFS_LZ4HC_H__
+
+#include <stdint.h>
+#include <lz4hc.h>
+
+#define EROFS_COMPR_LZ4HC_DEF_LVL       (9)
+
+void *erofs_lz4hc_init(void);
+void erofs_lz4hc_deinit(void *ctx);
+int64_t erofs_lz4hc_compress(char *in, size_t insz, char *out,
+			     size_t outsz, size_t *inszptr,
+			     int level, void*);
+#endif
diff --git a/mkfs_file.c b/mkfs_file.c
index beeb89d..a979c14 100644
--- a/mkfs_file.c
+++ b/mkfs_file.c
@@ -36,6 +36,7 @@
 #include "erofs_types.h"
 #include "list_head.h"
 #include "erofs_cache.h"
+#include "erofs_compressor.h"
 
 #define pr_fmt(fmt) "MKFS-FILE: "FUNC_LINE_FMT fmt"\n"
 #include "erofs_debug.h"
@@ -304,3 +305,700 @@ struct erofs_node_info *alloc_erofs_node(void)
 	init_list_head(&f->i_xattr_head);
 
 	return f;
+}
+
+static void
+erofs_compr_idx_host_to_disk(struct erofs_compr_idx *hidx,
+			     struct z_erofs_vle_decompressed_index *didx)
+{
+	switch (hidx->di_advise) {
+	case Z_EROFS_VLE_CLUSTER_TYPE_PLAIN:
+	case Z_EROFS_VLE_CLUSTER_TYPE_HEAD:
+		didx->di_advise = cpu_to_le16(hidx->di_advise);
+		didx->di_clusterofs = cpu_to_le16(hidx->di_clusterofs);
+		didx->di_u.blkaddr = cpu_to_le32(hidx->blkaddr);
+		break;
+
+	case Z_EROFS_VLE_CLUSTER_TYPE_NONHEAD:
+		didx->di_advise = cpu_to_le16(hidx->di_advise);
+		didx->di_clusterofs = cpu_to_le16(hidx->di_clusterofs);
+		didx->di_u.delta[0] = cpu_to_le16(hidx->delta[0]);
+		didx->di_u.delta[1] = cpu_to_le16(hidx->delta[1]);
+		break;
+
+	default:
+		assert(0);
+		break;
+	}
+}
+
+static int erofs_compress_inline_file_data(struct erofs_compr_info *cinfo,
+					   struct erofs_compr_ctx *ctx)
+{
+	int64_t         compr_count;
+	size_t          comprsz = 0;
+
+	assert(ctx->cc_srclen <= EROFS_BLKSIZE);
+	assert(ctx->cc_buflen >= 2 * EROFS_BLKSIZE);
+
+	compr_count = erofs_compress(cinfo->ci_alg,
+				     ctx->cc_srcbuf, ctx->cc_srclen,
+				     ctx->cc_dstbuf, EROFS_BLKSIZE,
+				     &comprsz, cinfo->ci_lvl);
+
+	if (compr_count == 0 || compr_count == EROFS_COMPRESS_ERROR) {
+		erofs_err("Failed to compress data by %s",
+			   cinfo->ci_alg->ca_name);
+		return -EIO;
+	}
+
+	assert(comprsz == (size_t)ctx->cc_srclen);
+
+	ctx->cc_dstlen = (int)compr_count;
+	ctx->cc_nidxs = EROFS_COMPR_CTX_INLINED_DATA;
+	return 0;
+}
+
+/* Note: it is not for inline data compress */
+static int
+erofs_compress_noinline_file_data(struct erofs_compr_info *cinfo,
+				  struct erofs_compr_ctx *ctx)
+{
+	char        *in;
+	char        *out;
+	size_t      insz;
+	size_t      outsz;
+	u32         blkaddr;
+	size_t      comprsz;
+	int64_t     compr_count;
+	long long   pos;
+	int         start;
+	int         end;
+	int         i;
+	int         advise;
+	int         clusterofs;
+	int         delta;
+	int         cross;
+	int         nidxs;
+	struct erofs_compr_idx *idx;
+
+	in = ctx->cc_srcbuf;
+	insz = ctx->cc_srclen;
+	out = ctx->cc_dstbuf;
+	outsz = EROFS_BLKSIZE;
+	blkaddr = 0;
+	pos = ctx->cc_pos;
+	nidxs = 0;
+
+	assert(pos % EROFS_BLKSIZE == 0);
+	assert(insz % EROFS_BLKSIZE == 0);
+
+	while (insz > 0) {
+		advise = Z_EROFS_VLE_CLUSTER_TYPE_MAX;
+		/* Data is less than a block, don't compress */
+		if (insz <= EROFS_BLKSIZE) {
+			advise = Z_EROFS_VLE_CLUSTER_TYPE_PLAIN;
+			comprsz = insz;
+			compr_count = insz;
+			memcpy(out, in, insz);
+			goto update_index;
+		}
+
+		comprsz = 0;
+		compr_count = erofs_compress(cinfo->ci_alg, in, insz,
+					     out, outsz,
+					     &comprsz, cinfo->ci_lvl);
+
+		if (compr_count == 0 ||
+		    compr_count == EROFS_COMPRESS_ERROR) {
+			erofs_err("Failed to compress data by %s",
+				   cinfo->ci_alg->ca_name);
+			return -EIO;
+		}
+
+		/* compress ratio is very bad, don't compress */
+		if ((int)comprsz - (int)compr_count <
+		    erofs_cfg.c_compr_boundary) {
+			advise = Z_EROFS_VLE_CLUSTER_TYPE_PLAIN;
+
+			if (pos % EROFS_BLKSIZE == 0)
+				comprsz = EROFS_BLKSIZE;
+			else
+				comprsz = (int)(round_up(pos,
+							 EROFS_BLKSIZE) - pos);
+
+			compr_count = comprsz;
+			memcpy(out, in, comprsz);
+			goto update_index;
+		}
+
+		if ((pos + comprsz) % EROFS_BLKSIZE <=
+			(unsigned int)erofs_cfg.c_compr_boundary)
+			comprsz -= (int)((pos + comprsz) % EROFS_BLKSIZE);
+
+		assert(comprsz);
+
+update_index:
+		start = (int)((pos - ctx->cc_pos) / EROFS_BLKSIZE);
+		end = (int)((pos + comprsz - ctx->cc_pos) / EROFS_BLKSIZE);
+
+		assert(end > start);
+
+		if ((pos + comprsz) % EROFS_BLKSIZE != 0)
+			cross = end - start + 1;
+		else
+			cross = end - start;
+
+		clusterofs = pos % PAGE_SIZE;
+		delta = 0;
+
+		/*
+		 * Here we against the rule that the length of code should
+		 * less than 80 bytes, it is because we want to make
+		 * the readability of mathematical expression be better.
+		 */
+		erofs_dbg("Compress range(Original[%lld - %lld], Index[%d - %d], Aligned[%lld - %lld], Index[%lld - %lld]) Start index %s, end index %s, end pos %s\n",
+			  pos, pos + comprsz - 1,
+			  start, end,
+			  round_down(pos, EROFS_BLKSIZE),
+			  round_up(pos + comprsz - 1, EROFS_BLKSIZE) - 1,
+			  (round_down(pos, EROFS_BLKSIZE) - ctx->cc_pos) / EROFS_BLKSIZE,
+			  (round_up(pos + comprsz - 1, EROFS_BLKSIZE) - ctx->cc_pos) / EROFS_BLKSIZE - 1,
+			  start == (int)(round_down(pos, EROFS_BLKSIZE) - ctx->cc_pos) / EROFS_BLKSIZE ?  "SAME" : "DIFF",
+			  end == (int)(round_up(pos + comprsz - 1, EROFS_BLKSIZE) - ctx->cc_pos) / EROFS_BLKSIZE - 1 ?  "SAME" : "DIFF",
+			  pos + comprsz - 1 == round_up(pos + comprsz - 1, EROFS_BLKSIZE) - 1 ?  "SAME" : "DIFF");
+
+		for (i = start; i < end; i++) {
+			idx = &ctx->cc_idxs[i];
+			if (advise == Z_EROFS_VLE_CLUSTER_TYPE_MAX) {
+				if (delta == 0) {
+					idx->di_advise =
+						Z_EROFS_VLE_CLUSTER_TYPE_HEAD;
+				} else {
+					idx->di_advise =
+						Z_EROFS_VLE_CLUSTER_TYPE_NONHEAD;
+				}
+			} else {
+				idx->di_advise = advise;
+			}
+			idx->di_clusterofs = clusterofs;
+			idx->delta[0] = delta;
+			idx->delta[1] = cross - delta - 1;
+			/* Allocate the blocks later */
+			idx->blkaddr = blkaddr;
+
+			erofs_dbg("Compress Index: advise - %u, clusterofs - %u, delta0 - %u, delta1 - %u, blkaddr - %u",
+				  idx->di_advise, clusterofs,
+				  delta, cross - delta,
+				  blkaddr);
+			delta++;
+			nidxs++;
+		}
+
+		insz -= comprsz;
+		in += comprsz;
+		out += EROFS_BLKSIZE;
+		pos += comprsz;
+		blkaddr++;
+	}
+
+	ctx->cc_dstlen = (int)(out - ctx->cc_dstbuf);
+	ctx->cc_nidxs  = nidxs;
+	return 0;
+}
+
+int erofs_write_compress_data(struct erofs_compr_ctx *cctx)
+{
+	u32 nblocks;
+	u32 blkaddr;
+	int ret;
+	int i;
+
+	nblocks = cctx->cc_dstlen / EROFS_BLKSIZE;
+	blkaddr = erofs_alloc_blocks(nblocks);
+
+	if (!blkaddr)
+		return -ENOSPC;
+
+	ret = dev_write(cctx->cc_dstbuf, BLKNO_TO_ADDR(blkaddr),
+			cctx->cc_dstlen);
+
+	if (ret)
+		return -EIO;
+
+	for (i  = 0; i < cctx->cc_nidxs; i++)
+		cctx->cc_idxs[i].blkaddr += blkaddr;
+
+	return 0;
+}
+
+int erofs_update_indexes(struct erofs_node_info *inode,
+			 struct erofs_compr_ctx *cctx)
+{
+	u64 index = cctx->cc_pos / EROFS_BLKSIZE;
+	struct erofs_index_info *iinfo;
+	struct z_erofs_vle_decompressed_index *didx;
+	int i;
+	int j = 0;
+	int end;
+
+	iinfo = inode->i_compr_cur_index_info;
+
+	/* Find index struct which we want */
+	if (iinfo && index >= iinfo->i_1st_idx)
+		goto search_next_index_info;
+
+	if (index >= inode->i_compr_inlined_nidxs)
+		goto search_from_1st_index_info;
+
+	didx = (void *)(inode->i_inline_data +
+			sizeof(struct erofs_extent_header));
+	end = inode->i_compr_inlined_nidxs;
+
+	for (i = (int)index; i < end && j < cctx->cc_nidxs; i++, j++)
+		erofs_compr_idx_host_to_disk(&cctx->cc_idxs[j], &didx[i]);
+
+	if (j == cctx->cc_nidxs)
+		return 0;
+
+	index = i;
+search_from_1st_index_info:
+	iinfo = list_first_entry(&inode->i_compr_idxs_list,
+				 struct erofs_index_info, i_node);
+search_next_index_info:
+	list_for_each_entry_from(iinfo, &inode->i_compr_idxs_list, i_node) {
+		if (index < iinfo->i_1st_idx + iinfo->i_nidxs)
+			break;
+	}
+
+	assert(index >= iinfo->i_1st_idx);
+
+	do {
+		didx = iinfo->i_idxs;
+		i = index - iinfo->i_1st_idx;
+		end = iinfo->i_nidxs;
+
+		for (; i < end && j < cctx->cc_nidxs; i++, j++)
+			erofs_compr_idx_host_to_disk(&cctx->cc_idxs[j],
+						     &didx[i]);
+
+		if (j == cctx->cc_nidxs)
+			break;
+
+		index = i + iinfo->i_1st_idx;
+		iinfo = list_next_entry(iinfo, i_node);
+	} while (1);
+
+	inode->i_compr_cur_index_info = iinfo;
+	return 0;
+}
+
+int erofs_compress_file(struct erofs_node_info *inode)
+{
+	int         fd = -1;
+	size_t      read_count;
+	off64_t     pos = 0;
+	u64         isize = inode->i_size;
+	u64         itotal = 0;
+	u64         ototal = 0;
+	u64         nidxs = 0;
+	int         ret = 0;
+	struct erofs_compr_ctx *cctx = &inode->i_compr_ctx;
+	struct erofs_compr_info *compressor = &inode->i_compressor;
+	struct erofs_extent_header *header;
+
+	assert(!inode->i_inline_data);
+	assert(inode->i_size > 0);
+
+	inode->i_inline_data = malloc(EROFS_BLKSIZE);
+
+	if (!inode->i_inline_data) {
+		erofs_err("Fail to alloc inline data buffer(%s)",
+			  inode->i_name);
+		return -ENOMEM;
+	}
+
+	memset(inode->i_inline_data, 0, EROFS_BLKSIZE);
+
+	/* Init header */
+	header = (struct erofs_extent_header *)inode->i_inline_data;
+	header->eh_checksum = 0;
+
+	/*
+	 * We have compressed some data at the head of the file when we check
+	 * the compressible, so we should go to the branch, put a assert here
+	 * to check LOGICAL BUG in the code.
+	 */
+	if (cctx->cc_pos != 0 || cctx->cc_nidxs == 0) {
+		assert(0);
+		return -EIO;
+	}
+
+	/*
+	 * Check cctx, write out the compress data and update the metadatae if
+	 * we have compressed some data before.
+	 */
+	if (cctx->cc_nidxs == EROFS_COMPR_CTX_INLINED_DATA) {
+		/*
+		 * TODO: Now we don't support inlined compress data,
+		 * we will implement it in the future, add a assert
+		 * here to avoid someone making a mistake.
+		 *
+		 * ? where can we keep the compress data len? i_blocks?
+		 */
+		assert(0);
+		erofs_dbg("Size: %d(%"PRIu64") ==> %d, Inline Compress, Compress Ratio %.2lf%%.\n",
+			  cctx->cc_srclen, isize, cctx->cc_dstlen,
+			  (double)cctx->cc_dstlen * 100 / (double)cctx->cc_srclen);
+		return 0;
+	} else if (cctx->cc_nidxs < 0) {
+		/* There is something wrong with nidxs */
+		assert(0);
+		return -EIO;
+	}
+
+	ret = erofs_write_compress_data(cctx);
+
+	if (ret)
+		return ret;
+
+	ret = erofs_update_indexes(inode, cctx);
+
+	if (ret)
+		return ret;
+
+	itotal = cctx->cc_srclen;
+	ototal = cctx->cc_dstlen;
+	nidxs = cctx->cc_nidxs;
+
+	pos = cctx->cc_pos + cctx->cc_srclen;
+
+	if ((u64)pos >= inode->i_size)
+		goto compress_complete;
+
+	fd = open(inode->i_fullpath, O_RDONLY | O_BINARY);
+
+	if (fd < 0) {
+		erofs_err("Fail to open a file(%s)", inode->i_name);
+		return -ENOENT;
+	}
+
+	pos = lseek64(fd, pos, SEEK_SET);
+
+	if (pos == (off64_t) -1ULL) {
+		ret = -EINVAL;
+		goto close_file;
+	}
+
+	assert(pos != 0);
+
+	while (1) {
+		erofs_reset_compress_context(cctx);
+
+		read_count = read(fd, cctx->cc_srcbuf, cctx->cc_buflen);
+
+		if (read_count == 0) {
+			if (itotal == isize) {
+				/* EOF, go out and complete compression */
+				ret = 0;
+			} else {
+				/*
+				 * Read error happened and the operation was
+				 * interrupted.
+				 */
+				erofs_err("Read file(%s) interrupted at offset - %lld",
+					  inode->i_name, (long long)pos);
+				ret = -EIO;
+			}
+
+			break;
+		}
+
+		itotal += read_count;
+
+		if (itotal > isize) {
+			erofs_err("Read overflow(File: %s, Real Size:%llu, Read Size: %llu)",
+				  inode->i_name, (long long unsigned)isize,
+				  (long long unsigned)itotal);
+			ret = -EIO;
+			break;
+		} else if (itotal == isize) {
+			read_count = round_up(read_count, EROFS_BLKSIZE);
+		} else {
+			if (read_count % EROFS_BLKSIZE != 0) {
+				erofs_err("Read size is not aligned(File: %s, Pos: %"PRIu64", Size: %zd)",
+					  inode->i_name, (u64)pos, read_count);
+				ret = -EIO;
+				break;
+			}
+		}
+
+		cctx->cc_pos = pos;
+		cctx->cc_srclen = read_count;
+
+		ret = erofs_compress_noinline_file_data(compressor, cctx);
+
+		if (ret) {
+			erofs_err("Compress file Fail(File: %s, Pos: %"PRIu64", Size: %zd)",
+				  inode->i_name, (u64)pos, read_count);
+			ret = -EIO;
+			break;
+		}
+
+		ret = erofs_write_compress_data(cctx);
+
+		if (ret)
+			break;
+
+		ret = erofs_update_indexes(inode, cctx);
+
+		if (ret)
+			break;
+
+		ototal += cctx->cc_dstlen;
+		nidxs += cctx->cc_nidxs;
+		pos += read_count;
+	}
+
+compress_complete:
+
+	if (!ret) {
+		inode->i_blocks = (u32)(ototal / EROFS_BLKSIZE);
+		erofs_dbg("Size: %"PRIu64"(%"PRIu64") ==> %"PRIu64", Indexs %"PRIu64", Compress Ratio %.2lf%%.\n",
+			  itotal, isize, ototal, nidxs,
+			  (double)ototal * 100 / (double)itotal);
+	}
+
+close_file:
+
+	if (fd >= 0)
+		close(fd);
+
+	return ret;
+}
+
+int erofs_try_compress_file_once(struct erofs_node_info *inode,
+				 struct erofs_compr_info *cinfo,
+				 struct erofs_compr_ctx *cctx)
+{
+	int         fd;
+	size_t      read_count;
+	loff_t      pos = 0;
+	u64         isize = inode->i_size;
+	int         inlined = 0;
+	int         ret = 0;
+
+	assert(cinfo->ci_alg);
+	assert(cinfo->ci_alg->ca_idx != EROFS_COMPR_NONE);
+	assert(cctx->cc_buflen > EROFS_BLKSIZE &&
+	       cctx->cc_buflen % EROFS_BLKSIZE == 0);
+	assert(cctx->cc_pos == 0);
+	assert(inode->i_size > 0);
+	assert(inode->i_compressor.ci_alg == NULL);
+	assert(inode->i_compr_ctx.cc_srcbuf == NULL);
+
+	fd = open(inode->i_fullpath, O_RDONLY | O_BINARY);
+
+	if (fd < 0) {
+		erofs_err("Fail to open a file(%s)", inode->i_fullpath);
+		return -ENOENT;
+	}
+
+	read_count = read(fd, cctx->cc_srcbuf, cctx->cc_buflen);
+
+	if (read_count == 0) {
+		erofs_err("Read file(%s) interrupted at offset - %"PRIu64"",
+			  inode->i_name, (u64)pos);
+		ret = -EIO;
+		goto close_file;
+	}
+
+	if (read_count > isize) {
+		erofs_err("Read overflow(File: %s, Real Size:%"PRIu64", Read Size: %zd)",
+			  inode->i_name, (u64)isize, read_count);
+		ret = -EIO;
+		goto close_file;
+	} else if (read_count == isize) {
+		if (isize > EROFS_BLKSIZE)
+			read_count = round_up(read_count, EROFS_BLKSIZE);
+		else
+			inlined = 1;
+	} else {
+		if (read_count % EROFS_BLKSIZE != 0) {
+			erofs_err("Read size is not aligned(File: %s, Pos: %"PRIu64", Size: %zd)",
+				  inode->i_name, (u64)pos, read_count);
+			ret = -EIO;
+			goto close_file;
+		}
+	}
+
+	cctx->cc_pos = 0;
+	cctx->cc_srclen = read_count;
+
+	if (inlined)
+		ret = erofs_compress_inline_file_data(cinfo, cctx);
+	else
+		ret = erofs_compress_noinline_file_data(cinfo, cctx);
+
+	if (ret) {
+		erofs_err("Compress file Fail(File: %s, Pos: %"PRIu64", Size: %zd)",
+			  inode->i_name, (u64)pos, read_count);
+		ret = -EIO;
+	}
+
+close_file:
+	close(fd);
+	return ret;
+}
+
+static int erofs_get_node_compress_info(struct erofs_node_info *inode,
+					struct erofs_compr_info *cinfo)
+{
+	/* Get specified compress algorithm which is set in the config file */
+	/*
+	 * Now we have not implement it, just use the algorithm
+	 * set in command line.
+	 */
+	(void)inode;
+	cinfo->ci_alg = erofs_cfg.c_compr_alg;
+	cinfo->ci_lvl = erofs_cfg.c_compr_lvl;
+
+	return 0;
+}
+
+void erofs_deinit_compress_context(struct erofs_compr_ctx *ctx)
+{
+	if (ctx->cc_srcbuf)
+		free(ctx->cc_srcbuf);
+
+	if (ctx->cc_dstbuf)
+		free(ctx->cc_dstbuf);
+
+	if (ctx->cc_idxs)
+		free(ctx->cc_idxs);
+
+	memset(ctx, 0, sizeof(struct erofs_compr_ctx));
+}
+
+int erofs_init_compress_context(struct erofs_compr_ctx *ctx)
+{
+	memset(ctx, 0, sizeof(struct erofs_compr_ctx));
+
+	ctx->cc_srcbuf = malloc(erofs_cfg.c_compr_maxsz);
+	ctx->cc_dstbuf = malloc(erofs_cfg.c_compr_maxsz * 2);
+	ctx->cc_idxs = calloc(erofs_cfg.c_compr_maxsz / EROFS_BLKSIZE,
+			      sizeof(struct erofs_compr_idx));
+
+	if (!ctx->cc_srcbuf || !ctx->cc_dstbuf || !ctx->cc_idxs) {
+		erofs_deinit_compress_context(ctx);
+		return -ENOMEM;
+	}
+
+	ctx->cc_buflen = erofs_cfg.c_compr_maxsz;
+
+	memset(ctx->cc_srcbuf, 0, ctx->cc_buflen);
+	memset(ctx->cc_dstbuf, 0, ctx->cc_buflen);
+	memset(ctx->cc_idxs, 0,
+	       ctx->cc_buflen / EROFS_BLKSIZE * sizeof(struct erofs_compr_idx));
+
+	return 0;
+}
+
+void erofs_reset_compress_context(struct erofs_compr_ctx *ctx)
+{
+	ctx->cc_pos = 0;
+	ctx->cc_srclen = 0;
+	ctx->cc_dstlen = 0;
+	ctx->cc_nidxs = 0;
+	memset(ctx->cc_srcbuf, 0, ctx->cc_buflen);
+	memset(ctx->cc_dstbuf, 0, ctx->cc_buflen);
+	memset(ctx->cc_idxs, 0,
+	       ctx->cc_buflen / EROFS_BLKSIZE * sizeof(struct erofs_compr_idx));
+}
+
+int erofs_check_compressible(struct erofs_node_info *inode)
+{
+	struct erofs_compr_info cinfo;
+	struct erofs_compr_ctx ctx;
+	int ratio;
+	int ret;
+
+	if (erofs_cfg.c_compr_alg->ca_idx == EROFS_COMPR_NONE) {
+		/* Compress is disable by the user */
+		return 0;
+	}
+
+	if (inode->i_type != EROFS_FT_REG_FILE)
+		return 0;
+
+	/* check if we can inline data directly */
+	if (inode->i_size <= erofs_calc_inline_data_size(inode))
+		return 0;
+
+	/* check if the user don't want to compress this file */
+	cinfo.ci_alg = NULL;
+	cinfo.ci_lvl = 0;
+
+	ret = erofs_get_node_compress_info(inode, &cinfo);
+
+	if (ret) {
+		erofs_err("Failed to get compress algorithm for %s",
+			  inode->i_name);
+		assert(ret < 0);
+		return ret;
+	}
+
+	if (!cinfo.ci_alg || cinfo.ci_alg->ca_idx == EROFS_COMPR_NONE)
+		return 0;
+
+	assert(erofs_cfg.c_compr_maxsz % EROFS_BLKSIZE == 0);
+	ret = erofs_init_compress_context(&ctx);
+
+	if (ret)
+		return ret;
+
+	ret = erofs_try_compress_file_once(inode, &cinfo, &ctx);
+
+	if (ret) {
+		erofs_deinit_compress_context(&ctx);
+		return ret;
+	}
+
+	/* FIXME: Now we don't implement inline compress, so... */
+	if (inode->i_size <= EROFS_BLKSIZE) {
+		/*
+		 * TODO: Now we haven't support inline compress data, so
+		 * disable compress if
+		 *  inline_data_size < i_size <= block_size
+		 */
+#ifdef CONFIG_EROFS_INLINE_COMPRESS_DATA
+		if (ctx.dstlen > erofs_calc_inline_data_size(inode)) {
+		    erofs_deinit_compress_context(&ctx);
+			return 0;
+		}
+
+#else
+		erofs_deinit_compress_context(&ctx);
+		return 0;
+#endif
+	} else {
+		ratio = ctx.cc_dstlen * 100 / ctx.cc_srclen;
+
+		if (ratio > erofs_cfg.c_compr_ratio_limit ||
+		    ctx.cc_srclen - ctx.cc_dstlen < EROFS_BLKSIZE) {
+			erofs_deinit_compress_context(&ctx);
+			return 0;
+		}
+	}
+
+	/*
+	 * Check the file compress ratio by trying to compress the 1st segment,
+	 * If the ratio is greater than the limit or we can not save a block,
+	 * don't compress.
+	 */
+	inode->i_compressor.ci_alg = cinfo.ci_alg;
+	inode->i_compressor.ci_lvl = cinfo.ci_lvl;
+	memcpy(&inode->i_compr_ctx, &ctx, sizeof(struct erofs_compr_ctx));
+	return 1;
+}
+
diff --git a/mkfs_inode.c b/mkfs_inode.c
index e190853..4089e18 100644
--- a/mkfs_inode.c
+++ b/mkfs_inode.c
@@ -61,6 +61,23 @@ static inline u64 erofs_calc_compr_index_count(struct erofs_node_info *inode)
 	return round_up(inode->i_size, EROFS_BLKSIZE) / EROFS_BLKSIZE;
 }
 
+static int erofs_calc_inline_compr_index_count(struct erofs_node_info *inode)
+{
+	int size;
+
+	size = erofs_calc_inode_base_size(inode);
+	size = round_up(size, EROFS_INLINE_INDEX_ALIGN_SIZE);
+	size += sizeof(struct erofs_extent_header);
+
+	assert(size < EROFS_BLKSIZE);
+
+	size = EROFS_BLKSIZE - size;
+
+	assert(size % EROFS_DECOMPR_IDX_SZ == 0);
+
+	return size / EROFS_DECOMPR_IDX_SZ;
+}
+
 u8 erofs_check_disk_inode_version(struct erofs_node_info *inode)
 {
 #if 1
@@ -79,6 +96,33 @@ u8 erofs_check_disk_inode_version(struct erofs_node_info *inode)
 #endif
 }
 
+static void erofs_init_compress_inode(struct erofs_node_info *inode)
+{
+	int inlined_nidxs;
+
+	inode->i_dmode = EROFS_INODE_LAYOUT_COMPRESSION;
+
+	if (inode->i_compr_ctx.cc_nidxs == EROFS_COMPR_CTX_INLINED_DATA) {
+		inode->i_inline_datalen = inode->i_compr_ctx.cc_dstlen;
+		return;
+	}
+
+	inode->i_compr_nidxs = erofs_calc_compr_index_count(inode);
+
+	inlined_nidxs = erofs_calc_inline_compr_index_count(inode);
+
+	if (inode->i_compr_nidxs > (u64)inlined_nidxs)
+		inode->i_compr_inlined_nidxs = inlined_nidxs;
+	else
+		inode->i_compr_inlined_nidxs = inode->i_compr_nidxs;
+
+	inlined_nidxs = inode->i_compr_inlined_nidxs * EROFS_DECOMPR_IDX_SZ;
+	inode->i_inline_datalen = sizeof(struct erofs_extent_header);
+	inode->i_inline_datalen += inlined_nidxs;
+
+	inode->i_inline_align_size = EROFS_INLINE_INDEX_ALIGN_SIZE;
+}
+
 void mkfs_rank_inode(struct erofs_node_info *inode)
 {
 	block_buffer_t *blk;
@@ -201,6 +245,7 @@ struct erofs_node_info *mkfs_prepare_root_inode(char *root)
 
 void mkfs_relocate_sub_inodes(struct erofs_node_info *inode)
 {
+	int compressible;
 	u32 blkaddr;
 	u32 nblocks;
 	u32 unaligned;
@@ -208,6 +253,14 @@ void mkfs_relocate_sub_inodes(struct erofs_node_info *inode)
 
 	switch (d->i_type) {
 	case EROFS_FT_REG_FILE:
+		compressible = erofs_check_compressible(d);
+		if (compressible < 0) {
+			assert(0);
+		} else if (compressible > 0) {
+			erofs_init_compress_inode(d);
+			mkfs_rank_inode(d);
+			break;
+		}
 	case EROFS_FT_DIR:
 	case EROFS_FT_SYMLINK:
 		unaligned = d->i_size % EROFS_BLKSIZE;
@@ -488,8 +541,12 @@ static void mkfs_write_inode_regfile(struct erofs_node_info *inode)
 		break;
 
 	case EROFS_INODE_LAYOUT_COMPRESSION:
-		/* it isn't supported right now */
-		assert(0);
+		ret = erofs_compress_file(inode);
+		if (ret) {
+			erofs_err("Compress file failed");
+			exit(EXIT_FAILURE);
+		}
+		break;
 
 	case EROFS_INODE_LAYOUT_INLINE:
 		if (inode->i_size == 0)
diff --git a/mkfs_main.c b/mkfs_main.c
index 6e1423f..a334bb9 100644
--- a/mkfs_main.c
+++ b/mkfs_main.c
@@ -18,6 +18,7 @@
 #include "mkfs_erofs.h"
 #include "erofs_io.h"
 #include "mkfs_inode.h"
+#include "erofs_compressor.h"
 #define pr_fmt(fmt) "MKFS: "FUNC_LINE_FMT fmt"\n"
 #include "erofs_debug.h"
 
@@ -107,6 +108,9 @@ static void mkfs_parse_options_cfg(int argc, char *argv[])
 		usage(argv[0]);
 	}
 
+	assert(erofs_cfg.c_alg_name);
+	erofs_compress_alg_init(erofs_cfg.c_alg_name);
+
 	mkfs_dump_config();
 
 	if (dev_open(erofs_cfg.c_img_path) < 0) {
-- 
2.17.1



More information about the Linux-erofs mailing list