Skip to content

Commit

Permalink
Merge pull request #1352 from hjmallon/pr.negative_zstd
Browse files Browse the repository at this point in the history
zstd: Support negative compression-levels
  • Loading branch information
mmatuska committed Apr 10, 2020
2 parents d9ee19f + c13d55f commit afaaf3a
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 8 deletions.
71 changes: 65 additions & 6 deletions libarchive/archive_write_add_filter_zstd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 *);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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.
*/
Expand All @@ -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;
Expand Down Expand Up @@ -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");
Expand Down
3 changes: 2 additions & 1 deletion libarchive/archive_write_set_options.3
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions libarchive/test/test_write_filter_zstd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
3 changes: 2 additions & 1 deletion tar/bsdtar.1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit afaaf3a

Please sign in to comment.