]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Add Zstandard write support
authorSean Purcell <me@seanp.xyz>
Wed, 19 Apr 2017 23:51:50 +0000 (16:51 -0700)
committerSean Purcell <iburinoc@gmail.com>
Tue, 16 May 2017 03:06:48 +0000 (23:06 -0400)
16 files changed:
Makefile.am
contrib/android/Android.mk
cpio/bsdcpio.1
cpio/cmdline.c
cpio/cpio.c
cpio/cpio.h
libarchive/archive_read_support_filter_zstd.c
libarchive/archive_write_add_filter.c
libarchive/archive_write_add_filter_by_name.c
libarchive/archive_write_add_filter_zstd.c [new file with mode: 0644]
libarchive/archive_write_filter.3
tar/bsdtar.1
tar/bsdtar.c
tar/bsdtar.h
tar/cmdline.c
tar/creation_set.c

index ff613d93406c6e369d750980319362eecb9f7196..a3762e640da9f8473296a47b420b47a77da8393c 100644 (file)
@@ -209,6 +209,7 @@ libarchive_la_SOURCES= \
        libarchive/archive_write_add_filter_gzip.c \
        libarchive/archive_write_add_filter_lrzip.c \
        libarchive/archive_write_add_filter_lz4.c \
+       libarchive/archive_write_add_filter_zstd.c \
        libarchive/archive_write_add_filter_lzop.c \
        libarchive/archive_write_add_filter_none.c \
        libarchive/archive_write_add_filter_program.c \
index 87c06b0fc2dcb00cfe47f72cb6154c0c695fbee3..0d67f7ac4728aaa2c3f9be208e36cc538994a2ea 100644 (file)
@@ -112,6 +112,7 @@ libarchive_src_files := libarchive/archive_acl.c \
                                                libarchive/archive_write_add_filter_gzip.c \
                                                libarchive/archive_write_add_filter_lrzip.c \
                                                libarchive/archive_write_add_filter_lz4.c \
+                                               libarchive/archive_write_add_filter_zstd.c \
                                                libarchive/archive_write_add_filter_lzop.c \
                                                libarchive/archive_write_add_filter_none.c \
                                                libarchive/archive_write_add_filter_program.c \
index e52546e6f761a14ee1bb003235b97799422bf427..786a717097ed33b70b986e7dd069a8b29e00bd8d 100644 (file)
@@ -187,6 +187,11 @@ In input mode, this option is ignored.
 Compress the archive with lz4-compatible compression before writing it.
 In input mode, this option is ignored; lz4 compression is recognized
 automatically on input.
+.It Fl Fl zstd
+(o mode only)
+Compress the archive with zstd-compatible compression before writing it.
+In input mode, this option is ignored; zstd compression is recognized
+automatically on input.
 .It Fl Fl lzma
 (o mode only)
 Compress the file with lzma-compatible compression before writing it.
index 0c10b2cdcf8cbffed11e75144101cfe345f75681..4f371cee10a8d7f268bddd3f7d195d735d0f0c1d 100644 (file)
@@ -75,6 +75,7 @@ static const struct option {
        { "list",                       0, 't' },
        { "lrzip",                      0, OPTION_LRZIP },
        { "lz4",                        0, OPTION_LZ4 },
+       { "zstd",                       0, OPTION_ZSTD },
        { "lzma",                       0, OPTION_LZMA },
        { "lzop",                       0, OPTION_LZOP },
        { "make-directories",           0, 'd' },
index 5beedd0d16bcf5b4bae0720f0bd63399f5acca57..17d12ff6a24eca0614712896c353dbc58837d2ac 100644 (file)
@@ -267,6 +267,7 @@ main(int argc, char *argv[])
                        break;
                case OPTION_LRZIP:
                case OPTION_LZ4:
+               case OPTION_ZSTD:
                case OPTION_LZMA: /* GNU tar, others */
                case OPTION_LZOP: /* GNU tar, others */
                        cpio->compress = opt;
@@ -540,6 +541,9 @@ mode_out(struct cpio *cpio)
        case OPTION_LZ4:
                r = archive_write_add_filter_lz4(cpio->archive);
                break;
+       case OPTION_ZSTD:
+               r = archive_write_add_filter_zstd(cpio->archive);
+               break;
        case OPTION_LZMA:
                r = archive_write_add_filter_lzma(cpio->archive);
                break;
index 1036dece93b03e140c675228333dc6964ef94b2a..14a77c11ff9021890aee5ceac50b84cb4fda191d 100644 (file)
@@ -104,6 +104,7 @@ enum {
        OPTION_INSECURE,
        OPTION_LRZIP,
        OPTION_LZ4,
+       OPTION_ZSTD,
        OPTION_LZMA,
        OPTION_LZOP,
        OPTION_PASSPHRASE,
index f9bf2e545fa1596b3ab05d2eb631a13e5704ec20..3a8c54054206d5714a310b30291a6a63b4201ca6 100644 (file)
@@ -221,12 +221,14 @@ zstd_filter_read(struct archive_read_filter *self, const void **p)
        /* Try to fill the output buffer. */
        while (out.pos < out.size && !state->eof) {
                if (!state->in_stream) {
-                       if (ZSTD_isError(ZSTD_initDStream(state->dstream))) {
+                       ret = ZSTD_initDStream(state->dstream);
+                       if (ZSTD_isError(ret)) {
                                free(state->out_block);
                                free(state);
                                archive_set_error(&self->archive->archive,
                                    ARCHIVE_ERRNO_MISC,
-                                   "Error initializing zstd decompressor");
+                                   "Error initializing zstd decompressor: %s",
+                                   ZSTD_getErrorName(ret));
                                return (ARCHIVE_FATAL);
                        }
                        state->in_stream = 1;
@@ -236,7 +238,7 @@ zstd_filter_read(struct archive_read_filter *self, const void **p)
                if (in.src == NULL && avail_in <= 0) {
                        archive_set_error(&self->archive->archive,
                            ARCHIVE_ERRNO_MISC,
-                           "truncated zstd input");
+                           "Truncated zstd input");
                        return (ARCHIVE_FATAL);
                }
                in.size = avail_in;
@@ -247,7 +249,8 @@ zstd_filter_read(struct archive_read_filter *self, const void **p)
                if (ZSTD_isError(ret)) {
                        archive_set_error(&self->archive->archive,
                            ARCHIVE_ERRNO_MISC,
-                           "zstd decompression failed");
+                           "Zstd decompression failed: %s",
+                           ZSTD_getErrorName(ret));
                        return (ARCHIVE_FATAL);
                }
 
index 08f518adec4bfae08d3fb76e36d9f9b4966367a2..acefc69e697c7b3bcbce3e2c53bf8c63a304067a 100644 (file)
@@ -48,6 +48,7 @@ struct { int code; int (*setter)(struct archive *); } codes[] =
        { ARCHIVE_FILTER_GRZIP,         archive_write_add_filter_grzip },
        { ARCHIVE_FILTER_LRZIP,         archive_write_add_filter_lrzip },
        { ARCHIVE_FILTER_LZ4,           archive_write_add_filter_lz4 },
+       { ARCHIVE_FILTER_ZSTD,          archive_write_add_filter_zstd },
        { ARCHIVE_FILTER_LZIP,          archive_write_add_filter_lzip },
        { ARCHIVE_FILTER_LZMA,          archive_write_add_filter_lzma },
        { ARCHIVE_FILTER_LZOP,          archive_write_add_filter_lzip },
index 85a8d47534150a6bfb1d38d21e8c62361f796fbf..f3971bf518bc90966999e56c8cc56f793fb72e5a 100644 (file)
@@ -52,6 +52,7 @@ struct { const char *name; int (*setter)(struct archive *); } names[] =
        { "gzip",               archive_write_add_filter_gzip },
        { "lrzip",              archive_write_add_filter_lrzip },
        { "lz4",                archive_write_add_filter_lz4 },
+       { "zstd",               archive_write_add_filter_zstd },
        { "lzip",               archive_write_add_filter_lzip },
        { "lzma",               archive_write_add_filter_lzma },
        { "lzop",               archive_write_add_filter_lzop },
diff --git a/libarchive/archive_write_add_filter_zstd.c b/libarchive/archive_write_add_filter_zstd.c
new file mode 100644 (file)
index 0000000..c47b2de
--- /dev/null
@@ -0,0 +1,334 @@
+/*-
+ * Copyright (c) 2017 Sean Purcell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "archive_platform.h"
+
+__FBSDID("$FreeBSD$");
+
+
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#ifdef HAVE_ZSTD_H
+#include <zstd.h>
+#endif
+
+#include "archive.h"
+#include "archive_private.h"
+#include "archive_string.h"
+#include "archive_write_private.h"
+
+/* Don't compile this if we don't have zstd.h */
+
+struct private_data {
+       int              compression_level;
+#if HAVE_ZSTD_H && HAVE_LIBZSTD
+       ZSTD_CStream    *cstream;
+       int64_t          total_in;
+       ZSTD_outBuffer   out;
+#else
+       struct archive_write_program_data *pdata;
+#endif
+};
+
+static int archive_compressor_zstd_options(struct archive_write_filter *,
+                   const char *, const char *);
+static int archive_compressor_zstd_open(struct archive_write_filter *);
+static int archive_compressor_zstd_write(struct archive_write_filter *,
+                   const void *, size_t);
+static int archive_compressor_zstd_close(struct archive_write_filter *);
+static int archive_compressor_zstd_free(struct archive_write_filter *);
+#if HAVE_ZSTD_H && HAVE_LIBZSTD
+static int drive_compressor(struct archive_write_filter *,
+                   struct private_data *, int, const void *, size_t);
+#endif
+
+
+/*
+ * Add a zstd compression filter to this write handle.
+ */
+int
+archive_write_add_filter_zstd(struct archive *_a)
+{
+       struct archive_write *a = (struct archive_write *)_a;
+       struct archive_write_filter *f = __archive_write_allocate_filter(_a);
+       struct private_data *data;
+       archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_write_add_filter_zstd");
+
+       data = calloc(1, sizeof(*data));
+       if (data == NULL) {
+               archive_set_error(&a->archive, ENOMEM, "Out of memory");
+               return (ARCHIVE_FATAL);
+       }
+       f->data = data;
+       f->open = &archive_compressor_zstd_open;
+       f->options = &archive_compressor_zstd_options;
+       f->close = &archive_compressor_zstd_close;
+       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 */
+#if HAVE_ZSTD_H && HAVE_LIBZSTD
+       data->cstream = ZSTD_createCStream();
+       if (data->cstream == NULL) {
+               free(data);
+               archive_set_error(&a->archive, ENOMEM,
+                   "Failed to allocate zstd compressor object");
+               return (ARCHIVE_FATAL);
+       }
+
+       return (ARCHIVE_OK);
+#else
+       data->pdata = __archive_write_program_allocate("zstd");
+       if (data->pdata == NULL) {
+               free(data);
+               archive_set_error(&a->archive, ENOMEM, "Out of memory");
+               return (ARCHIVE_FATAL);
+       }
+       data->compression_level = 0;
+       archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+           "Using external zstd program");
+       return (ARCHIVE_WARN);
+#endif
+}
+
+static int
+archive_compressor_zstd_free(struct archive_write_filter *f)
+{
+       struct private_data *data = (struct private_data *)f->data;
+#if HAVE_ZSTD_H && HAVE_LIBZSTD
+       ZSTD_freeCStream(data->cstream);
+       free(data->out.dst);
+#else
+       __archive_write_program_free(data->pdata);
+#endif
+       free(data);
+       f->data = NULL;
+       return (ARCHIVE_OK);
+}
+
+/*
+ * Set write options.
+ */
+static int
+archive_compressor_zstd_options(struct archive_write_filter *f, const char *key,
+    const char *value)
+{
+       struct private_data *data = (struct private_data *)f->data;
+
+       if (strcmp(key, "compression-level") == 0) {
+               int level = atoi(value);
+               if (level < 1 || level > ZSTD_maxCLevel()) {
+                       return (ARCHIVE_WARN);
+               }
+               data->compression_level = level;
+               return (ARCHIVE_OK);
+       }
+
+       /* Note: The "warn" return is just to inform the options
+        * supervisor that we didn't handle it.  It will generate
+        * a suitable error if no one used this option. */
+       return (ARCHIVE_WARN);
+}
+
+#if HAVE_ZSTD_H && HAVE_LIBZSTD
+/*
+ * Setup callback.
+ */
+static int
+archive_compressor_zstd_open(struct archive_write_filter *f)
+{
+       struct private_data *data = (struct private_data *)f->data;
+       int ret;
+
+       ret = __archive_write_open_filter(f->next_filter);
+       if (ret != ARCHIVE_OK)
+               return (ret);
+
+       if (data->out.dst == NULL) {
+               size_t bs = ZSTD_CStreamOutSize(), bpb;
+               if (f->archive->magic == ARCHIVE_WRITE_MAGIC) {
+                       /* Buffer size should be a multiple number of
+                        * the of bytes per block for performance. */
+                       bpb = archive_write_get_bytes_per_block(f->archive);
+                       if (bpb > bs)
+                               bs = bpb;
+                       else if (bpb != 0)
+                               bs -= bs % bpb;
+               }
+               data->out.size = bs;
+               data->out.pos = 0;
+               data->out.dst
+                   = (unsigned char *)malloc(data->out.size);
+               if (data->out.dst == NULL) {
+                       archive_set_error(f->archive, ENOMEM,
+                           "Can't allocate data for compression buffer");
+                       return (ARCHIVE_FATAL);
+               }
+       }
+
+       f->write = archive_compressor_zstd_write;
+
+       if (ZSTD_isError(ZSTD_initCStream(data->cstream,
+           data->compression_level))) {
+               archive_set_error(f->archive, ARCHIVE_ERRNO_MISC,
+                   "Internal error initializing zstd compressor object");
+               return (ARCHIVE_FATAL);
+       }
+
+       return (ARCHIVE_OK);
+}
+
+/*
+ * Write data to the compressed stream.
+ */
+static int
+archive_compressor_zstd_write(struct archive_write_filter *f, const void *buff,
+    size_t length)
+{
+       struct private_data *data = (struct private_data *)f->data;
+       int ret;
+
+       /* Update statistics */
+       data->total_in += length;
+
+       if ((ret = drive_compressor(f, data, 0, buff, length)) != ARCHIVE_OK)
+               return (ret);
+
+       return (ARCHIVE_OK);
+}
+
+/*
+ * Finish the compression...
+ */
+static int
+archive_compressor_zstd_close(struct archive_write_filter *f)
+{
+       struct private_data *data = (struct private_data *)f->data;
+       int r1, r2;
+
+       /* Finish zstd frame */
+       r1 = drive_compressor(f, data, 1, NULL, 0);
+
+       r2 = __archive_write_close_filter(f->next_filter);
+
+       return r1 < r2 ? r1 : r2;
+}
+
+/*
+ * Utility function to push input data through compressor,
+ * writing full output blocks as necessary.
+ *
+ * Note that this handles both the regular write case (finishing ==
+ * false) and the end-of-archive case (finishing == true).
+ */
+static int
+drive_compressor(struct archive_write_filter *f,
+    struct private_data *data, int finishing, const void *src, size_t length)
+{
+       int ret;
+       size_t zstdret;
+       ZSTD_inBuffer in = (ZSTD_inBuffer) { src, length, 0 };
+
+       for (;;) {
+               if (data->out.pos == data->out.size) {
+                       ret = __archive_write_filter(f->next_filter,
+                           data->out.dst, data->out.size);
+                       if (ret != ARCHIVE_OK)
+                               return (ARCHIVE_FATAL);
+                       data->out.pos = 0;
+               }
+
+               /* If there's nothing to do, we're done. */
+               if (!finishing && in.pos == in.size)
+                       return (ARCHIVE_OK);
+
+               if (!finishing) {
+                       zstdret = ZSTD_compressStream(data->cstream,
+                           &data->out, &in);
+               } else {
+                       zstdret = ZSTD_endStream(data->cstream,
+                           &data->out);
+               }
+
+               if (ZSTD_isError(zstdret)) {
+                       archive_set_error(f->archive, ARCHIVE_ERRNO_MISC,
+                           "Zstd compression failed: %s",
+                           ZSTD_getErrorName(zstdret));
+                       return (ARCHIVE_FATAL);
+               }
+
+               /* If we're finishing, 0 means nothing left to flush */
+               if (finishing && zstdret == 0) {
+                       ret = __archive_write_filter(f->next_filter,
+                           data->out.dst, data->out.pos);
+                       return (ret);
+               }
+       }
+}
+
+#else /* HAVE_ZSTD_H && HAVE_LIBZSTD */
+
+static int
+archive_compressor_zstd_open(struct archive_write_filter *f)
+{
+       struct private_data *data = (struct private_data *)f->data;
+       struct archive_string as;
+       int r;
+
+       archive_string_init(&as);
+       archive_string_sprintf(&as, "zstd -%d", data->compression_level);
+
+       f->write = archive_compressor_zstd_write;
+       r = __archive_write_program_open(f, data->pdata, as.s);
+       archive_string_free(&as);
+       return (r);
+}
+
+static int
+archive_compressor_zstd_write(struct archive_write_filter *f, const void *buff,
+    size_t length)
+{
+       struct private_data *data = (struct private_data *)f->data;
+
+       return __archive_write_program_write(f, data->pdata, buff, length);
+}
+
+static int
+archive_compressor_zstd_close(struct archive_write_filter *f)
+{
+       struct private_data *data = (struct private_data *)f->data;
+
+       return __archive_write_program_close(f, data->pdata);
+}
+
+#endif /* HAVE_ZSTD_H && HAVE_LIBZSTD */
index e1d1891506449765b48a6bbd48c8e9d43329e4ca..896cd2426f1787fdb0c297ddbab5d4a3448143d0 100644 (file)
@@ -36,6 +36,7 @@
 .Nm archive_write_add_filter_gzip ,
 .Nm archive_write_add_filter_lrzip ,
 .Nm archive_write_add_filter_lz4 ,
+.Nm archive_write_add_filter_zstd ,
 .Nm archive_write_add_filter_lzip ,
 .Nm archive_write_add_filter_lzma ,
 .Nm archive_write_add_filter_lzop ,
@@ -63,6 +64,8 @@ Streaming Archive Library (libarchive, -larchive)
 .Ft int
 .Fn archive_write_add_filter_lz4 "struct archive *"
 .Ft int
+.Fn archive_write_add_filter_zstd "struct archive *"
+.Ft int
 .Fn archive_write_add_filter_lzip "struct archive *"
 .Ft int
 .Fn archive_write_add_filter_lzma "struct archive *"
@@ -85,6 +88,7 @@ Streaming Archive Library (libarchive, -larchive)
 .Fn archive_write_add_filter_gzip ,
 .Fn archive_write_add_filter_lrzip ,
 .Fn archive_write_add_filter_lz4 ,
+.Fn archive_write_add_filter_zstd ,
 .Fn archive_write_add_filter_lzip ,
 .Fn archive_write_add_filter_lzma ,
 .Fn archive_write_add_filter_lzop ,
index cdc317b6cf4dc16d41010e39a6c8747b6e034b0e..89b5077912d52baef702f8bfe5fe7e0a2def9de6 100644 (file)
@@ -342,6 +342,10 @@ In extract or list modes, this option is ignored.
 Compress the archive with lz4-compatible compression before writing it.
 In input mode, this option is ignored; lz4 compression is recognized
 automatically on input.
+.It Fl Fl zstd
+Compress the archive with zstd-compatible compression before writing it.
+In input mode, this option is ignored; lz4 compression is recognized
+automatically on input.
 .It Fl Fl lzma
 (c mode only) Compress the resulting archive with the original LZMA algorithm.
 Use of this option is discouraged and new archives should be created with
@@ -577,6 +581,8 @@ A decimal integer from 4 to 7 specifying the lz4 compression block size
 .It Cm lz4:block-dependence
 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.
 .It Cm lzop:compression-level
 A decimal integer from 1 to 9 specifying the lzop compression level.
 .It Cm xz:compression-level
index 9fc68332e5dcef1683590f62ceffc6732e9b97d6..1b9851f99bc6620647b2e6d479beef99366636d6 100644 (file)
@@ -416,6 +416,7 @@ main(int argc, char **argv)
                        break;
                case OPTION_LRZIP:
                case OPTION_LZ4:
+               case OPTION_ZSTD:
                case OPTION_LZIP: /* GNU tar beginning with 1.23 */
                case OPTION_LZMA: /* GNU tar beginning with 1.20 */
                case OPTION_LZOP: /* GNU tar beginning with 1.21 */
@@ -427,9 +428,10 @@ main(int argc, char **argv)
                        switch (opt) {
                        case OPTION_LRZIP: compression_name = "lrzip"; break;
                        case OPTION_LZ4:  compression_name = "lz4"; break;
-                       case OPTION_LZIP: compression_name = "lzip"; break; 
-                       case OPTION_LZMA: compression_name = "lzma"; break; 
-                       case OPTION_LZOP: compression_name = "lzop"; break; 
+                       case OPTION_ZSTD:  compression_name = "zstd"; break;
+                       case OPTION_LZIP: compression_name = "lzip"; break;
+                       case OPTION_LZMA: compression_name = "lzma"; break;
+                       case OPTION_LZOP: compression_name = "lzop"; break;
                        }
                        break;
                case 'm': /* SUSv2 */
index 10a2cf2f9bd0cc2b2a87a0900d48ff7783646b38..304fb568b25c926a4dab82bcc448760a444a69d2 100644 (file)
@@ -147,6 +147,7 @@ enum {
        OPTION_KEEP_NEWER_FILES,
        OPTION_LRZIP,
        OPTION_LZ4,
+       OPTION_ZSTD,
        OPTION_LZIP,
        OPTION_LZMA,
        OPTION_LZOP,
index e36c545b3336ebe2657f5f30337552d68c7c781e..23388db0651d791ef824c29ac2bed7acfc261513 100644 (file)
@@ -107,6 +107,7 @@ static const struct bsdtar_option {
        { "list",                 0, 't' },
        { "lrzip",                0, OPTION_LRZIP },
        { "lz4",                  0, OPTION_LZ4 },
+       { "zstd",                 0, OPTION_ZSTD },
        { "lzip",                 0, OPTION_LZIP },
        { "lzma",                 0, OPTION_LZMA },
        { "lzop",                 0, OPTION_LZOP },
index 24cf3fcdd4ef8712221ec832fea99990a427e133..e246c0a579d1e748d4047b93994848153117bb12 100644 (file)
@@ -76,13 +76,14 @@ get_filter_code(const char *suffix)
                { ".lrz",       "lrzip" },
                { ".lz",        "lzip" },
                { ".lz4",       "lz4" },
+               { ".zstd",      "zstd"},
                { ".lzo",       "lzop" },
                { ".lzma",      "lzma" },
                { ".uu",        "uuencode" },
                { ".xz",        "xz" },
                { NULL,         NULL }
        };
-       
+
        return get_suffix_code(filters, suffix);
 }