From c13d55f2ee980b593f4b6410bfc12a0cdb63cef7 Mon Sep 17 00:00:00 2001 From: Harry Mallon Date: Wed, 11 Mar 2020 12:47:49 +0000 Subject: [PATCH] zstd: Support negative compression-levels --- libarchive/archive_write_add_filter_zstd.c | 71 ++++++++++++++++++++-- libarchive/archive_write_set_options.3 | 3 +- libarchive/test/test_write_filter_zstd.c | 3 + tar/bsdtar.1 | 3 +- 4 files changed, 72 insertions(+), 8 deletions(-) diff --git a/libarchive/archive_write_add_filter_zstd.c b/libarchive/archive_write_add_filter_zstd.c index e02d1c1cb..3ec52c5a0 100644 --- a/libarchive/archive_write_add_filter_zstd.c +++ b/libarchive/archive_write_add_filter_zstd.c @@ -59,7 +59,15 @@ struct private_data { #endif }; +/* If we don't have the library use default range values (zstdcli.c v1.4.0) */ +#define CLEVEL_MIN -99 +#define CLEVEL_STD_MIN 0 /* prior to 1.3.4 and more recent without using --fast */ +#define CLEVEL_DEFAULT 3 #define CLEVEL_STD_MAX 19 /* without using --ultra */ +#define CLEVEL_MAX 22 + +#define MINVER_NEGCLEVEL 10304 +#define MINVER_MINCLEVEL 10306 static int archive_compressor_zstd_options(struct archive_write_filter *, const char *, const char *); @@ -98,7 +106,7 @@ archive_write_add_filter_zstd(struct archive *_a) f->free = &archive_compressor_zstd_free; f->code = ARCHIVE_FILTER_ZSTD; f->name = "zstd"; - data->compression_level = 3; /* Default level used by the zstd CLI */ + data->compression_level = CLEVEL_DEFAULT; #if HAVE_ZSTD_H && HAVE_LIBZSTD data->cstream = ZSTD_createCStream(); if (data->cstream == NULL) { @@ -137,6 +145,31 @@ archive_compressor_zstd_free(struct archive_write_filter *f) return (ARCHIVE_OK); } +static int string_is_numeric (const char* value) +{ + size_t len = strlen(value); + size_t i; + + if (len == 0) { + return (ARCHIVE_WARN); + } + else if (len == 1 && !(value[0] >= '0' && value[0] <= '9')) { + return (ARCHIVE_WARN); + } + else if (!(value[0] >= '0' && value[0] <= '9') && + value[0] != '-' && value[0] != '+') { + return (ARCHIVE_WARN); + } + + for (i = 1; i < len; i++) { + if (!(value[i] >= '0' && value[i] <= '9')) { + return (ARCHIVE_WARN); + } + } + + return (ARCHIVE_OK); +} + /* * Set write options. */ @@ -148,12 +181,24 @@ archive_compressor_zstd_options(struct archive_write_filter *f, const char *key, if (strcmp(key, "compression-level") == 0) { int level = atoi(value); -#if HAVE_ZSTD_H && HAVE_LIBZSTD - if (level < 1 || level > ZSTD_maxCLevel()) { -#else /* If we don't have the library, hard-code the max level */ - if (level < 1 || level > 22) { + int minimum = CLEVEL_MIN; + int maximum = CLEVEL_MAX; + if (string_is_numeric(value) != ARCHIVE_OK) { + return (ARCHIVE_WARN); + } +#if HAVE_ZSTD_H && HAVE_LIBZSTD + maximum = ZSTD_maxCLevel(); +#endif +#if HAVE_ZSTD_H && HAVE_LIBZSTD && ZSTD_VERSION_NUMBER >= MINVER_MINCLEVEL + if (ZSTD_versionNumber() >= MINVER_MINCLEVEL) { + minimum = ZSTD_minCLevel(); + } + else if (ZSTD_versionNumber() < MINVER_NEGCLEVEL) { + minimum = CLEVEL_STD_MIN; + } #endif + if (level < minimum || level > maximum) { return (ARCHIVE_WARN); } data->compression_level = level; @@ -300,7 +345,21 @@ archive_compressor_zstd_open(struct archive_write_filter *f) archive_string_init(&as); /* --no-check matches library default */ - archive_string_sprintf(&as, "zstd -%d --no-check", data->compression_level); + archive_strcpy(&as, "zstd --no-check"); + + if (data->compression_level < CLEVEL_STD_MIN) { + struct archive_string as2; + archive_string_init(&as2); + archive_string_sprintf(&as2, " --fast=%d", -data->compression_level); + archive_string_concat(&as, &as2); + archive_string_free(&as2); + } else { + struct archive_string as2; + archive_string_init(&as2); + archive_string_sprintf(&as2, " -%d", data->compression_level); + archive_string_concat(&as, &as2); + archive_string_free(&as2); + } if (data->compression_level > CLEVEL_STD_MAX) { archive_strcat(&as, " --ultra"); diff --git a/libarchive/archive_write_set_options.3 b/libarchive/archive_write_set_options.3 index cffe571e9..d4a52e322 100644 --- a/libarchive/archive_write_set_options.3 +++ b/libarchive/archive_write_set_options.3 @@ -255,7 +255,8 @@ If supported, the default value is read from .Bl -tag -compact -width indent .It Cm compression-level The value is interpreted as a decimal integer specifying the -compression level. Supported values are from 1 to 22. +compression level. Supported values depend on the library version, +common values are from 1 to 22. .El .It Format 7zip .Bl -tag -compact -width indent diff --git a/libarchive/test/test_write_filter_zstd.c b/libarchive/test/test_write_filter_zstd.c index ba1b6bfe7..b5f061a00 100644 --- a/libarchive/test/test_write_filter_zstd.c +++ b/libarchive/test/test_write_filter_zstd.c @@ -124,6 +124,9 @@ DEFINE_TEST(test_write_filter_zstd) archive_write_set_filter_option(a, NULL, "compression-level", "25")); /* too big */ assertEqualIntA(a, ARCHIVE_OK, archive_write_set_filter_option(a, NULL, "compression-level", "9")); + /* Following is disabled as it will fail on library versions < 1.3.4 */ + /* assertEqualIntA(a, ARCHIVE_OK, + archive_write_set_filter_option(a, NULL, "compression-level", "-1")); */ assertEqualIntA(a, ARCHIVE_OK, archive_write_set_filter_option(a, NULL, "compression-level", "7")); assertEqualIntA(a, ARCHIVE_OK, archive_write_open_memory(a, buff, buffsize, &used2)); diff --git a/tar/bsdtar.1 b/tar/bsdtar.1 index f15742349..86a06bbd1 100644 --- a/tar/bsdtar.1 +++ b/tar/bsdtar.1 @@ -631,7 +631,8 @@ A decimal integer from 4 to 7 specifying the lz4 compression block size Use the previous block of the block being compressed for a compression dictionary to improve compression ratio. .It Cm zstd:compression-level -A decimal integer from 1 to 22 specifying the zstd compression level. +A decimal integer specifying the zstd compression level. Supported values depend +on the library version, common values are from 1 to 22. .It Cm lzop:compression-level A decimal integer from 1 to 9 specifying the lzop compression level. .It Cm xz:compression-level -- 2.47.2