xref: /aosp_15_r20/external/erofs-utils/lib/compressor_libzstd.c (revision 33b1fccf6a0fada2c2875d400ed01119b7676ee5)
1 // SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0
2 #include "erofs/internal.h"
3 #include "erofs/print.h"
4 #include "erofs/config.h"
5 #include <zstd.h>
6 #include <zstd_errors.h>
7 #include <alloca.h>
8 #include "compressor.h"
9 #include "erofs/atomic.h"
10 
libzstd_compress_destsize(const struct erofs_compress * c,const void * src,unsigned int * srcsize,void * dst,unsigned int dstsize)11 static int libzstd_compress_destsize(const struct erofs_compress *c,
12 				     const void *src, unsigned int *srcsize,
13 				     void *dst, unsigned int dstsize)
14 {
15 	ZSTD_CCtx *cctx = c->private_data;
16 	size_t l = 0;		/* largest input that fits so far */
17 	size_t l_csize = 0;
18 	size_t r = *srcsize + 1; /* smallest input that doesn't fit so far */
19 	size_t m;
20 	u8 *fitblk_buffer = alloca(dstsize + 32);
21 
22 	m = dstsize * 4;
23 	for (;;) {
24 		size_t csize;
25 
26 		m = max(m, l + 1);
27 		m = min(m, r - 1);
28 
29 		csize = ZSTD_compress2(cctx, fitblk_buffer,
30 				       dstsize + 32, src, m);
31 		if (ZSTD_isError(csize)) {
32 			if (ZSTD_getErrorCode(csize) == ZSTD_error_dstSize_tooSmall)
33 				goto doesnt_fit;
34 			return -EFAULT;
35 		}
36 
37 		if (csize > 0 && csize <= dstsize) {
38 			/* Fits */
39 			memcpy(dst, fitblk_buffer, csize);
40 			l = m;
41 			l_csize = csize;
42 			if (r <= l + 1 || csize + 1 >= dstsize)
43 				break;
44 			/*
45 			 * Estimate needed input prefix size based on current
46 			 * compression ratio.
47 			 */
48 			m = (dstsize * m) / csize;
49 		} else {
50 doesnt_fit:
51 			/* Doesn't fit */
52 			r = m;
53 			if (r <= l + 1)
54 				break;
55 			m = (l + r) / 2;
56 		}
57 	}
58 	*srcsize = l;
59 	return l_csize;
60 }
61 
compressor_libzstd_exit(struct erofs_compress * c)62 static int compressor_libzstd_exit(struct erofs_compress *c)
63 {
64 	if (!c->private_data)
65 		return -EINVAL;
66 	ZSTD_freeCCtx(c->private_data);
67 	return 0;
68 }
69 
erofs_compressor_libzstd_setlevel(struct erofs_compress * c,int compression_level)70 static int erofs_compressor_libzstd_setlevel(struct erofs_compress *c,
71 					     int compression_level)
72 {
73 	if (compression_level > erofs_compressor_libzstd.best_level) {
74 		erofs_err("invalid compression level %d", compression_level);
75 		return -EINVAL;
76 	}
77 	c->compression_level = compression_level;
78 	return 0;
79 }
80 
erofs_compressor_libzstd_setdictsize(struct erofs_compress * c,u32 dict_size)81 static int erofs_compressor_libzstd_setdictsize(struct erofs_compress *c,
82 						u32 dict_size)
83 {
84 	if (!dict_size) {
85 		if (erofs_compressor_libzstd.default_dictsize) {
86 			dict_size = erofs_compressor_libzstd.default_dictsize;
87 		} else {
88 			dict_size = min_t(u32, Z_EROFS_ZSTD_MAX_DICT_SIZE,
89 					  cfg.c_mkfs_pclustersize_max << 3);
90 			dict_size = 1 << ilog2(dict_size);
91 		}
92 	}
93 	if (dict_size != 1 << ilog2(dict_size) ||
94 	    dict_size > Z_EROFS_ZSTD_MAX_DICT_SIZE) {
95 		erofs_err("invalid dictionary size %u", dict_size);
96 		return -EINVAL;
97 	}
98 	c->dict_size = dict_size;
99 	return 0;
100 }
101 
compressor_libzstd_init(struct erofs_compress * c)102 static int compressor_libzstd_init(struct erofs_compress *c)
103 {
104 	static erofs_atomic_bool_t __warnonce;
105 	ZSTD_CCtx *cctx = c->private_data;
106 	size_t err;
107 
108 	ZSTD_freeCCtx(cctx);
109 	cctx = ZSTD_createCCtx();
110 	if (!cctx)
111 		return -ENOMEM;
112 
113 	err = ZSTD_CCtx_setParameter(cctx, ZSTD_c_compressionLevel, c->compression_level);
114 	if (ZSTD_isError(err)) {
115 		erofs_err("failed to set compression level: %s",
116 			  ZSTD_getErrorName(err));
117 		return -EINVAL;
118 	}
119 	err = ZSTD_CCtx_setParameter(cctx, ZSTD_c_windowLog, ilog2(c->dict_size));
120 	if (ZSTD_isError(err)) {
121 		erofs_err("failed to set window log: %s", ZSTD_getErrorName(err));
122 		return -EINVAL;
123 	}
124 	c->private_data = cctx;
125 
126 	if (!erofs_atomic_test_and_set(&__warnonce)) {
127 		erofs_warn("EXPERIMENTAL libzstd compressor in use. Note that `fitblk` isn't supported by upstream zstd for now.");
128 		erofs_warn("Therefore it will takes more time in order to get the optimal result.");
129 		erofs_info("You could clarify further needs in zstd repository <https://github.com/facebook/zstd/issues> for reference too.");
130 	}
131 	return 0;
132 }
133 
134 const struct erofs_compressor erofs_compressor_libzstd = {
135 	.default_level = ZSTD_CLEVEL_DEFAULT,
136 	.best_level = 22,
137 	.max_dictsize = Z_EROFS_ZSTD_MAX_DICT_SIZE,
138 	.init = compressor_libzstd_init,
139 	.exit = compressor_libzstd_exit,
140 	.setlevel = erofs_compressor_libzstd_setlevel,
141 	.setdictsize = erofs_compressor_libzstd_setdictsize,
142 	.compress_destsize = libzstd_compress_destsize,
143 };
144