2 * Copyright (c) 2016-present, Yann Collet, Facebook, Inc.
5 * This source code is licensed under both the BSD-style license (found in the
6 * LICENSE file in the root directory of this source tree) and the GPLv2 (found
7 * in the COPYING file in the root directory of this source tree).
8 * You may select, at your option, one of the above-listed licenses.
16 #define ZSTD_STATIC_LINKING_ONLY
19 #define MIN(x, y) ((x) < (y) ? (x) : (y))
21 static char const* g_zstdcli = NULL;
23 void method_set_zstdcli(char const* zstdcli) {
28 * Macro to get a pointer of type, given ptr, which is a member variable with
29 * the given name, member.
31 * method_state_t* base = ...;
32 * buffer_state_t* state = container_of(base, buffer_state_t, base);
34 #define container_of(ptr, type, member) \
35 ((type*)(ptr == NULL ? NULL : (char*)(ptr)-offsetof(type, member)))
37 /** State to reuse the same buffers between compression calls. */
40 data_buffers_t inputs; /**< The input buffer for each file. */
41 data_buffer_t dictionary; /**< The dictionary. */
42 data_buffer_t compressed; /**< The compressed data buffer. */
43 data_buffer_t decompressed; /**< The decompressed data buffer. */
46 static size_t buffers_max_size(data_buffers_t buffers) {
48 for (size_t i = 0; i < buffers.size; ++i) {
49 if (buffers.buffers[i].size > max)
50 max = buffers.buffers[i].size;
55 static method_state_t* buffer_state_create(data_t const* data) {
56 buffer_state_t* state = (buffer_state_t*)calloc(1, sizeof(buffer_state_t));
59 state->base.data = data;
60 state->inputs = data_buffers_get(data);
61 state->dictionary = data_buffer_get_dict(data);
62 size_t const max_size = buffers_max_size(state->inputs);
63 state->compressed = data_buffer_create(ZSTD_compressBound(max_size));
64 state->decompressed = data_buffer_create(max_size);
68 static void buffer_state_destroy(method_state_t* base) {
71 buffer_state_t* state = container_of(base, buffer_state_t, base);
75 static int buffer_state_bad(
76 buffer_state_t const* state,
77 config_t const* config) {
79 fprintf(stderr, "buffer_state_t is NULL\n");
82 if (state->inputs.size == 0 || state->compressed.data == NULL ||
83 state->decompressed.data == NULL) {
84 fprintf(stderr, "buffer state allocation failure\n");
87 if (config->use_dictionary && state->dictionary.data == NULL) {
88 fprintf(stderr, "dictionary loading failed\n");
94 static result_t simple_compress(method_state_t* base, config_t const* config) {
95 buffer_state_t* state = container_of(base, buffer_state_t, base);
97 if (buffer_state_bad(state, config))
98 return result_error(result_error_system_error);
100 /* Keep the tests short by skipping directories, since behavior shouldn't
103 if (base->data->type != data_type_file)
104 return result_error(result_error_skip);
106 if (config->use_dictionary || config->no_pledged_src_size)
107 return result_error(result_error_skip);
109 /* If the config doesn't specify a level, skip. */
110 int const level = config_get_level(config);
111 if (level == CONFIG_NO_LEVEL)
112 return result_error(result_error_skip);
114 data_buffer_t const input = state->inputs.buffers[0];
116 /* Compress, decompress, and check the result. */
117 state->compressed.size = ZSTD_compress(
118 state->compressed.data,
119 state->compressed.capacity,
123 if (ZSTD_isError(state->compressed.size))
124 return result_error(result_error_compression_error);
126 state->decompressed.size = ZSTD_decompress(
127 state->decompressed.data,
128 state->decompressed.capacity,
129 state->compressed.data,
130 state->compressed.size);
131 if (ZSTD_isError(state->decompressed.size))
132 return result_error(result_error_decompression_error);
133 if (data_buffer_compare(input, state->decompressed))
134 return result_error(result_error_round_trip_error);
137 data.total_size = state->compressed.size;
138 return result_data(data);
141 static result_t compress_cctx_compress(
142 method_state_t* base,
143 config_t const* config) {
144 buffer_state_t* state = container_of(base, buffer_state_t, base);
146 if (buffer_state_bad(state, config))
147 return result_error(result_error_system_error);
149 if (config->no_pledged_src_size)
150 return result_error(result_error_skip);
152 if (base->data->type != data_type_dir)
153 return result_error(result_error_skip);
155 int const level = config_get_level(config);
157 ZSTD_CCtx* cctx = ZSTD_createCCtx();
158 ZSTD_DCtx* dctx = ZSTD_createDCtx();
159 if (cctx == NULL || dctx == NULL) {
160 fprintf(stderr, "context creation failed\n");
161 return result_error(result_error_system_error);
165 result_data_t data = {.total_size = 0};
166 for (size_t i = 0; i < state->inputs.size; ++i) {
167 data_buffer_t const input = state->inputs.buffers[i];
168 ZSTD_parameters const params =
169 config_get_zstd_params(config, input.size, state->dictionary.size);
171 if (level == CONFIG_NO_LEVEL)
172 state->compressed.size = ZSTD_compress_advanced(
174 state->compressed.data,
175 state->compressed.capacity,
178 config->use_dictionary ? state->dictionary.data : NULL,
179 config->use_dictionary ? state->dictionary.size : 0,
181 else if (config->use_dictionary)
182 state->compressed.size = ZSTD_compress_usingDict(
184 state->compressed.data,
185 state->compressed.capacity,
188 state->dictionary.data,
189 state->dictionary.size,
192 state->compressed.size = ZSTD_compressCCtx(
194 state->compressed.data,
195 state->compressed.capacity,
200 if (ZSTD_isError(state->compressed.size)) {
201 result = result_error(result_error_compression_error);
205 if (config->use_dictionary)
206 state->decompressed.size = ZSTD_decompress_usingDict(
208 state->decompressed.data,
209 state->decompressed.capacity,
210 state->compressed.data,
211 state->compressed.size,
212 state->dictionary.data,
213 state->dictionary.size);
215 state->decompressed.size = ZSTD_decompressDCtx(
217 state->decompressed.data,
218 state->decompressed.capacity,
219 state->compressed.data,
220 state->compressed.size);
221 if (ZSTD_isError(state->decompressed.size)) {
222 result = result_error(result_error_decompression_error);
225 if (data_buffer_compare(input, state->decompressed)) {
226 result = result_error(result_error_round_trip_error);
230 data.total_size += state->compressed.size;
233 result = result_data(data);
240 /** Generic state creation function. */
241 static method_state_t* method_state_create(data_t const* data) {
242 method_state_t* state = (method_state_t*)malloc(sizeof(method_state_t));
249 static void method_state_destroy(method_state_t* state) {
253 static result_t cli_compress(method_state_t* state, config_t const* config) {
254 if (config->cli_args == NULL)
255 return result_error(result_error_skip);
257 /* We don't support no pledged source size with directories. Too slow. */
258 if (state->data->type == data_type_dir && config->no_pledged_src_size)
259 return result_error(result_error_skip);
261 if (g_zstdcli == NULL)
262 return result_error(result_error_system_error);
264 /* '<zstd>' -cqr <args> [-D '<dict>'] '<file/dir>' */
266 size_t const cmd_size = snprintf(
269 "'%s' -cqr %s %s%s%s %s '%s'",
272 config->use_dictionary ? "-D '" : "",
273 config->use_dictionary ? state->data->dict.path : "",
274 config->use_dictionary ? "'" : "",
275 config->no_pledged_src_size ? "<" : "",
276 state->data->data.path);
277 if (cmd_size >= sizeof(cmd)) {
278 fprintf(stderr, "command too large: %s\n", cmd);
279 return result_error(result_error_system_error);
281 FILE* zstd = popen(cmd, "r");
283 fprintf(stderr, "failed to popen command: %s\n", cmd);
284 return result_error(result_error_system_error);
288 size_t total_size = 0;
290 size_t const size = fread(out, 1, sizeof(out), zstd);
292 if (size != sizeof(out))
295 if (ferror(zstd) || pclose(zstd) != 0) {
296 fprintf(stderr, "zstd failed with command: %s\n", cmd);
297 return result_error(result_error_compression_error);
300 result_data_t const data = {.total_size = total_size};
301 return result_data(data);
304 static int advanced_config(
306 buffer_state_t* state,
307 config_t const* config) {
308 ZSTD_CCtx_reset(cctx, ZSTD_reset_session_and_parameters);
309 for (size_t p = 0; p < config->param_values.size; ++p) {
310 param_value_t const pv = config->param_values.data[p];
311 if (ZSTD_isError(ZSTD_CCtx_setParameter(cctx, pv.param, pv.value))) {
315 if (config->use_dictionary) {
316 if (ZSTD_isError(ZSTD_CCtx_loadDictionary(
317 cctx, state->dictionary.data, state->dictionary.size))) {
324 static result_t advanced_one_pass_compress_output_adjustment(
325 method_state_t* base,
326 config_t const* config,
327 size_t const subtract) {
328 buffer_state_t* state = container_of(base, buffer_state_t, base);
330 if (buffer_state_bad(state, config))
331 return result_error(result_error_system_error);
333 ZSTD_CCtx* cctx = ZSTD_createCCtx();
336 if (!cctx || advanced_config(cctx, state, config)) {
337 result = result_error(result_error_compression_error);
341 result_data_t data = {.total_size = 0};
342 for (size_t i = 0; i < state->inputs.size; ++i) {
343 data_buffer_t const input = state->inputs.buffers[i];
345 if (!config->no_pledged_src_size) {
346 if (ZSTD_isError(ZSTD_CCtx_setPledgedSrcSize(cctx, input.size))) {
347 result = result_error(result_error_compression_error);
351 size_t const size = ZSTD_compress2(
353 state->compressed.data,
354 ZSTD_compressBound(input.size) - subtract,
357 if (ZSTD_isError(size)) {
358 result = result_error(result_error_compression_error);
361 data.total_size += size;
364 result = result_data(data);
370 static result_t advanced_one_pass_compress(
371 method_state_t* base,
372 config_t const* config) {
373 return advanced_one_pass_compress_output_adjustment(base, config, 0);
376 static result_t advanced_one_pass_compress_small_output(
377 method_state_t* base,
378 config_t const* config) {
379 return advanced_one_pass_compress_output_adjustment(base, config, 1);
382 static result_t advanced_streaming_compress(
383 method_state_t* base,
384 config_t const* config) {
385 buffer_state_t* state = container_of(base, buffer_state_t, base);
387 if (buffer_state_bad(state, config))
388 return result_error(result_error_system_error);
390 ZSTD_CCtx* cctx = ZSTD_createCCtx();
393 if (!cctx || advanced_config(cctx, state, config)) {
394 result = result_error(result_error_compression_error);
398 result_data_t data = {.total_size = 0};
399 for (size_t i = 0; i < state->inputs.size; ++i) {
400 data_buffer_t input = state->inputs.buffers[i];
402 if (!config->no_pledged_src_size) {
403 if (ZSTD_isError(ZSTD_CCtx_setPledgedSrcSize(cctx, input.size))) {
404 result = result_error(result_error_compression_error);
409 while (input.size > 0) {
410 ZSTD_inBuffer in = {input.data, MIN(input.size, 4096)};
411 input.data += in.size;
412 input.size -= in.size;
413 ZSTD_EndDirective const op =
414 input.size > 0 ? ZSTD_e_continue : ZSTD_e_end;
416 while (in.pos < in.size || (op == ZSTD_e_end && ret != 0)) {
417 ZSTD_outBuffer out = {state->compressed.data,
418 MIN(state->compressed.capacity, 1024)};
419 ret = ZSTD_compressStream2(cctx, &out, &in, op);
420 if (ZSTD_isError(ret)) {
421 result = result_error(result_error_compression_error);
424 data.total_size += out.pos;
429 result = result_data(data);
435 static int init_cstream(
436 buffer_state_t* state,
438 config_t const* config,
444 ZSTD_parameters const params = config_get_zstd_params(config, 0, 0);
445 ZSTD_CDict* dict = NULL;
447 *cdict = ZSTD_createCDict_advanced(
448 state->dictionary.data,
449 state->dictionary.size,
457 zret = ZSTD_initCStream_usingCDict_advanced(
458 zcs, *cdict, params.fParams, ZSTD_CONTENTSIZE_UNKNOWN);
460 zret = ZSTD_initCStream_advanced(
462 state->dictionary.data,
463 state->dictionary.size,
465 ZSTD_CONTENTSIZE_UNKNOWN);
468 int const level = config_get_level(config);
470 *cdict = ZSTD_createCDict(
471 state->dictionary.data,
472 state->dictionary.size,
477 zret = ZSTD_initCStream_usingCDict(zcs, *cdict);
478 } else if (config->use_dictionary) {
479 zret = ZSTD_initCStream_usingDict(
480 zcs, state->dictionary.data, state->dictionary.size, level);
482 zret = ZSTD_initCStream(zcs, level);
485 if (ZSTD_isError(zret)) {
491 static result_t old_streaming_compress_internal(
492 method_state_t* base,
493 config_t const* config,
496 buffer_state_t* state = container_of(base, buffer_state_t, base);
498 if (buffer_state_bad(state, config))
499 return result_error(result_error_system_error);
502 ZSTD_CStream* zcs = ZSTD_createCStream();
503 ZSTD_CDict* cd = NULL;
506 result = result_error(result_error_compression_error);
509 if (init_cstream(state, zcs, config, advanced, cdict ? &cd : NULL)) {
510 result = result_error(result_error_compression_error);
514 result_data_t data = {.total_size = 0};
515 for (size_t i = 0; i < state->inputs.size; ++i) {
516 data_buffer_t input = state->inputs.buffers[i];
517 size_t zret = ZSTD_resetCStream(
519 config->no_pledged_src_size ? ZSTD_CONTENTSIZE_UNKNOWN : input.size);
520 if (ZSTD_isError(zret)) {
521 result = result_error(result_error_compression_error);
525 while (input.size > 0) {
526 ZSTD_inBuffer in = {input.data, MIN(input.size, 4096)};
527 input.data += in.size;
528 input.size -= in.size;
529 ZSTD_EndDirective const op =
530 input.size > 0 ? ZSTD_e_continue : ZSTD_e_end;
532 while (in.pos < in.size || (op == ZSTD_e_end && zret != 0)) {
533 ZSTD_outBuffer out = {state->compressed.data,
534 MIN(state->compressed.capacity, 1024)};
535 if (op == ZSTD_e_continue || in.pos < in.size)
536 zret = ZSTD_compressStream(zcs, &out, &in);
538 zret = ZSTD_endStream(zcs, &out);
539 if (ZSTD_isError(zret)) {
540 result = result_error(result_error_compression_error);
543 data.total_size += out.pos;
548 result = result_data(data);
550 ZSTD_freeCStream(zcs);
555 static result_t old_streaming_compress(
556 method_state_t* base,
557 config_t const* config)
559 return old_streaming_compress_internal(
560 base, config, /* advanced */ 0, /* cdict */ 0);
563 static result_t old_streaming_compress_advanced(
564 method_state_t* base,
565 config_t const* config)
567 return old_streaming_compress_internal(
568 base, config, /* advanced */ 1, /* cdict */ 0);
571 static result_t old_streaming_compress_cdict(
572 method_state_t* base,
573 config_t const* config)
575 return old_streaming_compress_internal(
576 base, config, /* advanced */ 0, /* cdict */ 1);
579 static result_t old_streaming_compress_cdict_advanced(
580 method_state_t* base,
581 config_t const* config)
583 return old_streaming_compress_internal(
584 base, config, /* advanced */ 1, /* cdict */ 1);
587 method_t const simple = {
588 .name = "compress simple",
589 .create = buffer_state_create,
590 .compress = simple_compress,
591 .destroy = buffer_state_destroy,
594 method_t const compress_cctx = {
595 .name = "compress cctx",
596 .create = buffer_state_create,
597 .compress = compress_cctx_compress,
598 .destroy = buffer_state_destroy,
601 method_t const advanced_one_pass = {
602 .name = "advanced one pass",
603 .create = buffer_state_create,
604 .compress = advanced_one_pass_compress,
605 .destroy = buffer_state_destroy,
608 method_t const advanced_one_pass_small_out = {
609 .name = "advanced one pass small out",
610 .create = buffer_state_create,
611 .compress = advanced_one_pass_compress,
612 .destroy = buffer_state_destroy,
615 method_t const advanced_streaming = {
616 .name = "advanced streaming",
617 .create = buffer_state_create,
618 .compress = advanced_streaming_compress,
619 .destroy = buffer_state_destroy,
622 method_t const old_streaming = {
623 .name = "old streaming",
624 .create = buffer_state_create,
625 .compress = old_streaming_compress,
626 .destroy = buffer_state_destroy,
629 method_t const old_streaming_advanced = {
630 .name = "old streaming advanced",
631 .create = buffer_state_create,
632 .compress = old_streaming_compress,
633 .destroy = buffer_state_destroy,
636 method_t const old_streaming_cdict = {
637 .name = "old streaming cdcit",
638 .create = buffer_state_create,
639 .compress = old_streaming_compress,
640 .destroy = buffer_state_destroy,
643 method_t const old_streaming_advanced_cdict = {
644 .name = "old streaming advanced cdict",
645 .create = buffer_state_create,
646 .compress = old_streaming_compress,
647 .destroy = buffer_state_destroy,
650 method_t const cli = {
652 .create = method_state_create,
653 .compress = cli_compress,
654 .destroy = method_state_destroy,
657 static method_t const* g_methods[] = {
662 &advanced_one_pass_small_out,
665 &old_streaming_advanced,
666 &old_streaming_cdict,
667 &old_streaming_advanced_cdict,
671 method_t const* const* methods = g_methods;