2 * Copyright (c) 2017 Sean Purcell
3 * Copyright (c) 2023-2024 Klara, Inc.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #include "archive_platform.h"
46 #include "archive_private.h"
47 #include "archive_string.h"
48 #include "archive_write_private.h"
50 /* Don't compile this if we don't have zstd.h */
53 int compression_level
;
56 #if HAVE_ZSTD_H && HAVE_ZSTD_compressStream
71 ZSTD_CStream
*cstream
;
74 struct archive_write_program_data
*pdata
;
78 /* If we don't have the library use default range values (zstdcli.c v1.4.0) */
79 #define CLEVEL_MIN -99
80 #define CLEVEL_STD_MIN 0 /* prior to 1.3.4 and more recent without using --fast */
81 #define CLEVEL_DEFAULT 3
82 #define CLEVEL_STD_MAX 19 /* without using --ultra */
87 #define MINVER_NEGCLEVEL 10304
88 #define MINVER_MINCLEVEL 10306
89 #define MINVER_LONG 10302
91 static int archive_compressor_zstd_options(struct archive_write_filter
*,
92 const char *, const char *);
93 static int archive_compressor_zstd_open(struct archive_write_filter
*);
94 static int archive_compressor_zstd_write(struct archive_write_filter
*,
95 const void *, size_t);
96 static int archive_compressor_zstd_flush(struct archive_write_filter
*);
97 static int archive_compressor_zstd_close(struct archive_write_filter
*);
98 static int archive_compressor_zstd_free(struct archive_write_filter
*);
99 #if HAVE_ZSTD_H && HAVE_ZSTD_compressStream
100 static int drive_compressor(struct archive_write_filter
*,
101 struct private_data
*, int, const void *, size_t);
106 * Add a zstd compression filter to this write handle.
109 archive_write_add_filter_zstd(struct archive
*_a
)
111 struct archive_write
*a
= (struct archive_write
*)_a
;
112 struct archive_write_filter
*f
= __archive_write_allocate_filter(_a
);
113 struct private_data
*data
;
114 archive_check_magic(&a
->archive
, ARCHIVE_WRITE_MAGIC
,
115 ARCHIVE_STATE_NEW
, "archive_write_add_filter_zstd");
117 data
= calloc(1, sizeof(*data
));
119 archive_set_error(&a
->archive
, ENOMEM
, "Out of memory");
120 return (ARCHIVE_FATAL
);
123 f
->open
= &archive_compressor_zstd_open
;
124 f
->options
= &archive_compressor_zstd_options
;
125 f
->flush
= &archive_compressor_zstd_flush
;
126 f
->close
= &archive_compressor_zstd_close
;
127 f
->free
= &archive_compressor_zstd_free
;
128 f
->code
= ARCHIVE_FILTER_ZSTD
;
130 data
->compression_level
= CLEVEL_DEFAULT
;
132 data
->long_distance
= 0;
133 #if HAVE_ZSTD_H && HAVE_ZSTD_compressStream
134 data
->frame_per_file
= 0;
135 data
->min_frame_in
= 0;
136 data
->max_frame_in
= SIZE_MAX
;
137 data
->min_frame_out
= 0;
138 data
->max_frame_out
= SIZE_MAX
;
139 data
->cur_frame_in
= 0;
140 data
->cur_frame_out
= 0;
141 data
->cstream
= ZSTD_createCStream();
142 if (data
->cstream
== NULL
) {
144 archive_set_error(&a
->archive
, ENOMEM
,
145 "Failed to allocate zstd compressor object");
146 return (ARCHIVE_FATAL
);
151 data
->pdata
= __archive_write_program_allocate("zstd");
152 if (data
->pdata
== NULL
) {
154 archive_set_error(&a
->archive
, ENOMEM
, "Out of memory");
155 return (ARCHIVE_FATAL
);
157 archive_set_error(&a
->archive
, ARCHIVE_ERRNO_MISC
,
158 "Using external zstd program");
159 return (ARCHIVE_WARN
);
164 archive_compressor_zstd_free(struct archive_write_filter
*f
)
166 struct private_data
*data
= (struct private_data
*)f
->data
;
167 #if HAVE_ZSTD_H && HAVE_ZSTD_compressStream
168 ZSTD_freeCStream(data
->cstream
);
171 __archive_write_program_free(data
->pdata
);
179 string_to_number(const char *string
, intmax_t *numberp
)
183 if (string
== NULL
|| *string
== '\0')
184 return (ARCHIVE_WARN
);
185 *numberp
= strtoimax(string
, &end
, 10);
186 if (end
== string
|| *end
!= '\0' || errno
== EOVERFLOW
) {
188 return (ARCHIVE_WARN
);
193 #if HAVE_ZSTD_H && HAVE_ZSTD_compressStream
195 string_to_size(const char *string
, size_t *numberp
)
199 unsigned int shift
= 0;
201 if (string
== NULL
|| *string
== '\0' || *string
== '-')
202 return (ARCHIVE_WARN
);
203 number
= strtoumax(string
, &end
, 10);
205 if (*end
== 'K' || *end
== 'k') {
208 } else if (*end
== 'M' || *end
== 'm') {
211 } else if (*end
== 'G' || *end
== 'g') {
215 if (*end
== 'B' || *end
== 'b') {
219 if (end
== string
|| *end
!= '\0' || errno
== EOVERFLOW
) {
220 return (ARCHIVE_WARN
);
222 if (number
> (uintmax_t)SIZE_MAX
>> shift
) {
223 return (ARCHIVE_WARN
);
225 *numberp
= (size_t)(number
<< shift
);
234 archive_compressor_zstd_options(struct archive_write_filter
*f
, const char *key
,
237 struct private_data
*data
= (struct private_data
*)f
->data
;
239 if (strcmp(key
, "compression-level") == 0) {
241 if (string_to_number(value
, &level
) != ARCHIVE_OK
) {
242 return (ARCHIVE_WARN
);
244 /* If we don't have the library, hard-code the max level */
245 int minimum
= CLEVEL_MIN
;
246 int maximum
= CLEVEL_MAX
;
247 #if HAVE_ZSTD_H && HAVE_ZSTD_compressStream
248 maximum
= ZSTD_maxCLevel();
249 #if ZSTD_VERSION_NUMBER >= MINVER_MINCLEVEL
250 if (ZSTD_versionNumber() >= MINVER_MINCLEVEL
) {
251 minimum
= ZSTD_minCLevel();
255 if (ZSTD_versionNumber() < MINVER_NEGCLEVEL
) {
256 minimum
= CLEVEL_STD_MIN
;
259 if (level
< minimum
|| level
> maximum
) {
260 return (ARCHIVE_WARN
);
262 data
->compression_level
= (int)level
;
264 } else if (strcmp(key
, "threads") == 0) {
266 if (string_to_number(value
, &threads
) != ARCHIVE_OK
) {
267 return (ARCHIVE_WARN
);
270 return (ARCHIVE_WARN
);
272 data
->threads
= (int)threads
;
274 #if HAVE_ZSTD_H && HAVE_ZSTD_compressStream
275 } else if (strcmp(key
, "frame-per-file") == 0) {
276 data
->frame_per_file
= 1;
278 } else if (strcmp(key
, "min-frame-in") == 0) {
279 if (string_to_size(value
, &data
->min_frame_in
) != ARCHIVE_OK
) {
280 return (ARCHIVE_WARN
);
283 } else if (strcmp(key
, "min-frame-out") == 0 ||
284 strcmp(key
, "min-frame-size") == 0) {
285 if (string_to_size(value
, &data
->min_frame_out
) != ARCHIVE_OK
) {
286 return (ARCHIVE_WARN
);
289 } else if (strcmp(key
, "max-frame-in") == 0 ||
290 strcmp(key
, "max-frame-size") == 0) {
291 if (string_to_size(value
, &data
->max_frame_in
) != ARCHIVE_OK
||
292 data
->max_frame_in
< 1024) {
293 return (ARCHIVE_WARN
);
296 } else if (strcmp(key
, "max-frame-out") == 0) {
297 if (string_to_size(value
, &data
->max_frame_out
) != ARCHIVE_OK
||
298 data
->max_frame_out
< 1024) {
299 return (ARCHIVE_WARN
);
304 else if (strcmp(key
, "long") == 0) {
305 intmax_t long_distance
;
306 if (string_to_number(value
, &long_distance
) != ARCHIVE_OK
) {
307 return (ARCHIVE_WARN
);
309 #if HAVE_ZSTD_H && HAVE_ZSTD_compressStream && ZSTD_VERSION_NUMBER >= MINVER_LONG
310 ZSTD_bounds bounds
= ZSTD_cParam_getBounds(ZSTD_c_windowLog
);
311 if (ZSTD_isError(bounds
.error
)) {
312 int max_distance
= ((int)(sizeof(size_t) == 4 ? 30 : 31));
313 if (((int)long_distance
) < 10 || (int)long_distance
> max_distance
)
314 return (ARCHIVE_WARN
);
316 if ((int)long_distance
< bounds
.lowerBound
|| (int)long_distance
> bounds
.upperBound
)
317 return (ARCHIVE_WARN
);
320 int max_distance
= ((int)(sizeof(size_t) == 4 ? 30 : 31));
321 if (((int)long_distance
) < 10 || (int)long_distance
> max_distance
)
322 return (ARCHIVE_WARN
);
324 data
->long_distance
= (int)long_distance
;
328 /* Note: The "warn" return is just to inform the options
329 * supervisor that we didn't handle it. It will generate
330 * a suitable error if no one used this option. */
331 return (ARCHIVE_WARN
);
334 #if HAVE_ZSTD_H && HAVE_ZSTD_compressStream
339 archive_compressor_zstd_open(struct archive_write_filter
*f
)
341 struct private_data
*data
= (struct private_data
*)f
->data
;
343 if (data
->out
.dst
== NULL
) {
344 size_t bs
= ZSTD_CStreamOutSize(), bpb
;
345 if (f
->archive
->magic
== ARCHIVE_WRITE_MAGIC
) {
346 /* Buffer size should be a multiple number of
347 * the of bytes per block for performance. */
348 bpb
= archive_write_get_bytes_per_block(f
->archive
);
357 = (unsigned char *)malloc(data
->out
.size
);
358 if (data
->out
.dst
== NULL
) {
359 archive_set_error(f
->archive
, ENOMEM
,
360 "Can't allocate data for compression buffer");
361 return (ARCHIVE_FATAL
);
365 f
->write
= archive_compressor_zstd_write
;
367 if (ZSTD_isError(ZSTD_initCStream(data
->cstream
,
368 data
->compression_level
))) {
369 archive_set_error(f
->archive
, ARCHIVE_ERRNO_MISC
,
370 "Internal error initializing zstd compressor object");
371 return (ARCHIVE_FATAL
);
374 ZSTD_CCtx_setParameter(data
->cstream
, ZSTD_c_nbWorkers
, data
->threads
);
376 #if ZSTD_VERSION_NUMBER >= MINVER_LONG
377 ZSTD_CCtx_setParameter(data
->cstream
, ZSTD_c_windowLog
, data
->long_distance
);
384 * Write data to the compressed stream.
387 archive_compressor_zstd_write(struct archive_write_filter
*f
, const void *buff
,
390 struct private_data
*data
= (struct private_data
*)f
->data
;
392 return (drive_compressor(f
, data
, 0, buff
, length
));
396 * Flush the compressed stream.
399 archive_compressor_zstd_flush(struct archive_write_filter
*f
)
401 struct private_data
*data
= (struct private_data
*)f
->data
;
403 if (data
->frame_per_file
&& data
->state
== running
) {
404 if (data
->cur_frame_in
> data
->min_frame_in
&&
405 data
->cur_frame_out
> data
->min_frame_out
) {
406 data
->state
= finishing
;
409 return (drive_compressor(f
, data
, 1, NULL
, 0));
413 * Finish the compression...
416 archive_compressor_zstd_close(struct archive_write_filter
*f
)
418 struct private_data
*data
= (struct private_data
*)f
->data
;
420 if (data
->state
== running
)
421 data
->state
= finishing
;
422 return (drive_compressor(f
, data
, 1, NULL
, 0));
426 * Utility function to push input data through compressor,
427 * writing full output blocks as necessary.
430 drive_compressor(struct archive_write_filter
*f
,
431 struct private_data
*data
, int flush
, const void *src
, size_t length
)
433 ZSTD_inBuffer in
= { .src
= src
, .size
= length
, .pos
= 0 };
434 size_t ipos
, opos
, zstdret
= 0;
439 opos
= data
->out
.pos
;
440 switch (data
->state
) {
442 if (in
.pos
== in
.size
)
444 zstdret
= ZSTD_compressStream(data
->cstream
,
446 if (ZSTD_isError(zstdret
))
450 zstdret
= ZSTD_endStream(data
->cstream
, &data
->out
);
451 if (ZSTD_isError(zstdret
))
454 data
->state
= resetting
;
457 ZSTD_CCtx_reset(data
->cstream
, ZSTD_reset_session_only
);
459 data
->cur_frame_in
= 0;
460 data
->cur_frame_out
= 0;
461 data
->state
= running
;
464 data
->total_in
+= in
.pos
- ipos
;
465 data
->cur_frame_in
+= in
.pos
- ipos
;
466 data
->cur_frame_out
+= data
->out
.pos
- opos
;
467 if (data
->state
== running
) {
468 if (data
->cur_frame_in
>= data
->max_frame_in
||
469 data
->cur_frame_out
>= data
->max_frame_out
) {
470 data
->state
= finishing
;
473 if (data
->out
.pos
== data
->out
.size
||
474 (flush
&& data
->out
.pos
> 0)) {
475 ret
= __archive_write_filter(f
->next_filter
,
476 data
->out
.dst
, data
->out
.pos
);
477 if (ret
!= ARCHIVE_OK
)
483 archive_set_error(f
->archive
, ARCHIVE_ERRNO_MISC
,
484 "Zstd compression failed: %s",
485 ZSTD_getErrorName(zstdret
));
487 return (ARCHIVE_FATAL
);
490 #else /* HAVE_ZSTD_H && HAVE_ZSTD_compressStream */
493 archive_compressor_zstd_open(struct archive_write_filter
*f
)
495 struct private_data
*data
= (struct private_data
*)f
->data
;
496 struct archive_string as
;
499 archive_string_init(&as
);
500 /* --no-check matches library default */
501 archive_strcpy(&as
, "zstd --no-check");
503 if (data
->compression_level
< CLEVEL_STD_MIN
) {
504 archive_string_sprintf(&as
, " --fast=%d", -data
->compression_level
);
506 archive_string_sprintf(&as
, " -%d", data
->compression_level
);
509 if (data
->compression_level
> CLEVEL_STD_MAX
) {
510 archive_strcat(&as
, " --ultra");
513 if (data
->threads
!= 0) {
514 archive_string_sprintf(&as
, " --threads=%d", data
->threads
);
517 if (data
->long_distance
!= 0) {
518 archive_string_sprintf(&as
, " --long=%d", data
->long_distance
);
521 f
->write
= archive_compressor_zstd_write
;
522 r
= __archive_write_program_open(f
, data
->pdata
, as
.s
);
523 archive_string_free(&as
);
528 archive_compressor_zstd_write(struct archive_write_filter
*f
, const void *buff
,
531 struct private_data
*data
= (struct private_data
*)f
->data
;
533 return __archive_write_program_write(f
, data
->pdata
, buff
, length
);
537 archive_compressor_zstd_flush(struct archive_write_filter
*f
)
539 (void)f
; /* UNUSED */
545 archive_compressor_zstd_close(struct archive_write_filter
*f
)
547 struct private_data
*data
= (struct private_data
*)f
->data
;
549 return __archive_write_program_close(f
, data
->pdata
);
552 #endif /* HAVE_ZSTD_H && HAVE_ZSTD_compressStream */