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
;