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