[prev in list] [next in list] [prev in thread] [next in thread] 

List:       busybox
Subject:    [PATCH 4/4] Add zstandard support and decompression applets
From:       Norbert Lange <nolange79 () gmail ! com>
Date:       2021-09-09 21:21:46
Message-ID: 20210909212146.23606-4-nolange79 () gmail ! com
[Download RAW message or body]

Add Config to allow adjusting the compromise between speed and
codesize.
At setting 0, the speed is comparable to the reference tool.

Add the zstd, unzstd and zstd applets, mimicking the reference
tools.

Add seamless zstd decompression for tar, modutils, etc.

Signed-off-by: Norbert Lange <nolange79@gmail.com>

---
Is there some concrete reference to calculating the codesize
applet are adding? I did compare a defconfig without any zstd
to adding a single applet on x86_64.

Similar, if some bloat-meter values are required, please
provide reference.

unzstd could be improved by supporting the -o flag which I
personally often use.

more research could be invested into figuring out good
combinations for flags for the speed/codesize tradeoffs.

I looked at providing an option for compression, but this adds
around 500KB. Doesnt seem usefull.
---
 archival/Config.src                     |  23 +++
 archival/bbunzip.c                      | 105 +++++++++++-
 archival/libarchive/Kbuild.src          |   2 +
 archival/libarchive/decompress_unzstd.c | 202 ++++++++++++++++++++++++
 archival/libarchive/open_transformer.c  |  12 ++
 archival/tar.c                          |  26 ++-
 include/bb_archive.h                    |   7 +
 include/libbb.h                         |   1 +
 8 files changed, 373 insertions(+), 5 deletions(-)
 create mode 100644 archival/libarchive/decompress_unzstd.c

diff --git a/archival/Config.src b/archival/Config.src
index 6f4f30c43..b96dd49fe 100644
--- a/archival/Config.src
+++ b/archival/Config.src
@@ -21,6 +21,10 @@ config FEATURE_SEAMLESS_GZ
 	bool "Make tar, rpm, modprobe etc understand .gz data"
 	default y
 
+config FEATURE_SEAMLESS_ZSTD
+	bool "Make tar, rpm, modprobe etc understand .zst data"
+	default y
+
 config FEATURE_SEAMLESS_Z
 	bool "Make tar, rpm, modprobe etc understand .Z data"
 	default n  # it is ancient
@@ -35,4 +39,23 @@ config FEATURE_LZMA_FAST
 	This option reduces decompression time by about 25% at the cost of
 	a 1K bigger binary.
 
+config FEATURE_ZSTD_SMALL
+	int "zstd speed vs side tradeoff (0:fast, 9:small)"
+	default 8  # all "fast or small" options default to small
+	range 0 9
+	depends on UNZSTD || ZSTDCAT || ZSTD || FEATURE_SEAMLESS_ZSTD
+
+	help
+	Trade code size versus speed.
+
+	Time to decompress a debian distro archive
+	using unzstd to a file of ~1900MB size. 
+	value         time (sec)  code size (x64)
+	9 (smallest)       4.56            20713
+	8                  4.08            22213
+	7                  3.69            24825
+	2                  3.46            34446
+	1                  3.30            56831
+	0 (fastest)        2.84            48831
+
 endmenu
diff --git a/archival/bbunzip.c b/archival/bbunzip.c
index 0ac059c19..81d9d940f 100644
--- a/archival/bbunzip.c
+++ b/archival/bbunzip.c
@@ -195,7 +195,7 @@ int FAST_FUNC bbunpack(char **argv,
 #if ENABLE_UNCOMPRESS \
  || ENABLE_FEATURE_BZIP2_DECOMPRESS \
  || ENABLE_UNLZMA || ENABLE_LZCAT || ENABLE_LZMA \
- || ENABLE_UNXZ || ENABLE_XZCAT || ENABLE_XZ
+ || ENABLE_UNXZ || ENABLE_XZCAT || ENABLE_XZ || ENABLE_ZSTD
 static
 char* FAST_FUNC make_new_name_generic(char *filename, const char *expected_ext)
 {
@@ -601,3 +601,106 @@ int unxz_main(int argc UNUSED_PARAM, char **argv)
 	return bbunpack(argv, unpack_xz_stream, make_new_name_generic, "xz");
 }
 #endif
+
+/*
+ * Copied from the unxz implementation,
+ * modifications to make it behave more like zstd tools
+ * done by Norbert Lange
+ */
+
+//usage:#define unzstd_trivial_usage
+//usage:       "[-cfk] [FILE]..."
+//usage:#define unzstd_full_usage "\n\n"
+//usage:       "Decompress FILEs (or stdin)\n"
+//usage:     "\n	-c	Write to stdout"
+//usage:     "\n	-f	Force"
+//usage:     IF_LONG_OPTS(
+//usage:     "\n	--rm	Remove input files"
+//usage:     )
+//usage:     "\n	-k	Keep input files (default)"
+//usage:     "\n	-t	Test integrity"
+//usage:
+//usage:#define zstd_trivial_usage
+//usage:       "-d [-cfk] [FILE]..."
+//usage:#define zstd_full_usage "\n\n"
+//usage:       "Decompress FILEs (or stdin)\n"
+//usage:     "\n	-d	Decompress"
+//usage:     "\n	-c	Write to stdout"
+//usage:     "\n	-f	Force"
+//usage:     IF_LONG_OPTS(
+//usage:     "\n	--rm	Remove input files"
+//usage:     )
+//usage:     "\n	-k	Keep input files (default)"
+//usage:     "\n	-t	Test integrity"
+//usage:
+//usage:#define zstdcat_trivial_usage
+//usage:       "[FILE]..."
+//usage:#define zstdcat_full_usage "\n\n"
+//usage:       "Decompress to stdout"
+
+//config:config UNZSTD
+//config:	bool "unzstd (22 kb)"
+//config:	default y
+//config:	help
+//config:	Zstandard is a fast compression algorithm.
+//config:
+//config:config ZSTDCAT
+//config:	bool "zstdcat (22 kb)"
+//config:	default y
+//config:	help
+//config:	Alias to "unzstd -c".
+//config:
+//config:config ZSTD
+//config:	bool "zstd -d"
+//config:	default y
+//config:	help
+//config:	Enable this option if you want commands like "zstd -d" to work.
+//config:	IOW: you'll get zstd applet, but it will always require -d option.
+
+//applet:IF_UNZSTD(APPLET(unzstd, BB_DIR_USR_BIN, BB_SUID_DROP))
+//                APPLET_ODDNAME:name   main  location        suid_type     help
+//applet:IF_ZSTDCAT(APPLET_ODDNAME(zstdcat, unzstd, BB_DIR_USR_BIN, BB_SUID_DROP, \
zstdcat)) +//applet:IF_ZSTD(   APPLET_ODDNAME(zstd,    unzstd, BB_DIR_USR_BIN, \
BB_SUID_DROP, zstd)) +//kbuild:lib-$(CONFIG_UNZSTD) += bbunzip.o
+//kbuild:lib-$(CONFIG_ZSTDCAT) += bbunzip.o
+//kbuild:lib-$(CONFIG_ZSTD) += bbunzip.o
+#if ENABLE_UNZSTD || ENABLE_ZSTDCAT || ENABLE_ZSTD
+int unzstd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int unzstd_main(int argc UNUSED_PARAM, char **argv)
+{
+# if ENABLE_LONG_OPTS
+	static const char unzstd_longopts[] ALIGN1 =
+		"stdout\0"      No_argument       "c"
+		"force\0"       No_argument       "f"
+		"keep\0"        No_argument       "k"
+		"verbose\0"     No_argument       "v"
+		"quiet\0"       No_argument       "q"
+		"decompress\0"  No_argument       "d"
+		"uncompress\0"  No_argument       "d"
+		"test\0"        No_argument       "t"
+		"rm\0"          No_argument       "\xff"
+	;
+# endif
+	enum {
+		OPT_ZSTD_RM = 1U << (BBUNPK_OPTSTRLEN - 1 + 3),
+	};
+
+	uint32_t opts = getopt32long(argv, IF_LONG_OPTS("^") BBUNPK_OPTSTR "dt" \
IF_LONG_OPTS("\xff""\0""k-\xff:\xff-k"), unzstd_longopts); +
+	/* zstd without -d or -t? */
+	if (ENABLE_ZSTD && applet_name[4] == '\0' && !(opts & \
(BBUNPK_OPT_DECOMPRESS|BBUNPK_OPT_TEST))) +		bb_show_usage();
+
+	/* set BBUNPK_OPT_STDOUT for zstdcat.
+	   (un)zstd keeps all files by default, --rm deletes. */
+	opts |= (ENABLE_LONG_OPTS && (opts & OPT_ZSTD_RM) ? 0 : BBUNPK_OPT_KEEP) |
+			(ENABLE_ZSTDCAT && applet_name[4] == 'c' ? BBUNPK_OPT_STDOUT : 0);
+	option_mask32 = opts;
+
+	argv += optind;
+	return bbunpack(argv, unpack_zstd_stream, make_new_name_generic, "zst");
+
+	/* dont alias with BBUNPK enums */
+	{ enum { unzstd_static_assert = 1/((unsigned)OPT_ZSTD_RM > \
(unsigned)BBUNPK_OPT_TEST) }; } +}
+#endif
diff --git a/archival/libarchive/Kbuild.src b/archival/libarchive/Kbuild.src
index d2f284b08..e5c2d6199 100644
--- a/archival/libarchive/Kbuild.src
+++ b/archival/libarchive/Kbuild.src
@@ -64,6 +64,7 @@ lib-$(CONFIG_UNXZ)                      += open_transformer.o \
decompress_unxz.o  lib-$(CONFIG_XZCAT)                     += open_transformer.o \
decompress_unxz.o  lib-$(CONFIG_XZ)                        += open_transformer.o \
decompress_unxz.o  lib-$(CONFIG_FEATURE_UNZIP_XZ)          += open_transformer.o \
decompress_unxz.o +lib-$(CONFIG_UNZSTD)                    += open_transformer.o \
decompress_unzstd.o  # 'gzip -d', gunzip or zcat selects FEATURE_GZIP_DECOMPRESS
 lib-$(CONFIG_FEATURE_GZIP_DECOMPRESS)   += open_transformer.o decompress_gunzip.o
 lib-$(CONFIG_UNCOMPRESS)                += open_transformer.o \
decompress_uncompress.o @@ -89,6 +90,7 @@ lib-$(CONFIG_FEATURE_SEAMLESS_GZ)       += \
open_transformer.o decompress_gunzip.  lib-$(CONFIG_FEATURE_SEAMLESS_BZ2)      += \
open_transformer.o decompress_bunzip2.o  lib-$(CONFIG_FEATURE_SEAMLESS_LZMA)     += \
open_transformer.o decompress_unlzma.o  lib-$(CONFIG_FEATURE_SEAMLESS_XZ)       += \
open_transformer.o decompress_unxz.o +lib-$(CONFIG_FEATURE_SEAMLESS_ZSTD)     += \
open_transformer.o decompress_unzstd.o  lib-$(CONFIG_FEATURE_COMPRESS_USAGE)    += \
open_transformer.o decompress_bunzip2.o  lib-$(CONFIG_FEATURE_COMPRESS_BBCONFIG) += \
open_transformer.o decompress_bunzip2.o  lib-$(CONFIG_FEATURE_SH_EMBEDDED_SCRIPTS) += \
                open_transformer.o decompress_bunzip2.o
diff --git a/archival/libarchive/decompress_unzstd.c \
b/archival/libarchive/decompress_unzstd.c new file mode 100644
index 000000000..4a38564c5
--- /dev/null
+++ b/archival/libarchive/decompress_unzstd.c
@@ -0,0 +1,202 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Glue for zstd decompression
+ * Copyright (C) 2021 Norbert Lange <nolange79@gmail.com>
+ *
+ * Based on compress.c from the systemd project,
+ * provided by Norbert Lange <nolange79@gmail.com>.
+ * Which originally was copied from the streaming_decompression.c
+ * example from the zstd project, written by Yann Collet 
+ * 
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+
+#include "libbb.h"
+#include "bb_archive.h"
+
+/* start with macros configuring zstd */
+
+#define DEBUGLEVEL 0
+#define ZSTD_LEGACY_SUPPORT 0
+#define ZSTD_LIB_DEPRECATED 0
+#define ZSTD_NO_UNUSED_FUNCTIONS 1
+#define ZSTD_STRIP_ERROR_STRINGS 1
+#define ZSTD_TRACE 0
+
+/* complete API as static functions */
+#define ZSTDLIB_VISIBILITY MEM_STATIC
+#define ZSTDERRORLIB_VISIBILITY MEM_STATIC
+
+#undef CONFIG_FEATURE_ZSTD_SMALL
+#define CONFIG_FEATURE_ZSTD_SMALL 7
+
+#if CONFIG_FEATURE_ZSTD_SMALL >= 9
+#define ZSTD_NO_INLINE 1
+#define NO_PREFETCH 1
+#endif
+
+#if CONFIG_FEATURE_ZSTD_SMALL >= 7
+// -4.2
+#define HUF_FORCE_DECOMPRESS_X1 1
+// #define HUF_FORCE_DECOMPRESS_X2 1
+// -8.4
+#define ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT 1
+// #define ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG 1
+#endif
+
+#if CONFIG_FEATURE_ZSTD_SMALL <= 7
+/* doesnt blow up code too much, -O3 is horrible */
+#ifdef __GNUC__
+#pragma GCC optimize ("O2")
+#endif
+#endif
+
+#if CONFIG_FEATURE_ZSTD_SMALL > 0
+/* no dynamic detection of bmi2 instruction,
+ * prefer using CFLAGS setting to -march=haswell or similar */
+#define DYNAMIC_BMI2 0
+#endif
+
+
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-function"
+#endif
+
+/* Include zstd_deps.h first with all the options we need enabled. */
+#define ZSTD_DEPS_NEED_MALLOC
+#define ZSTD_DEPS_NEED_MATH64
+#define ZSTD_DEPS_NEED_STDINT
+#include "zstd/common/zstd_deps.h"
+
+#include "zstd/common/entropy_common.c"
+#include "zstd/common/error_private.c"
+#include "zstd/common/fse_decompress.c"
+#include "zstd/common/zstd_common.c"
+
+#include "zstd/decompress/huf_decompress.c"
+#include "zstd/decompress/zstd_ddict.c"
+#include "zstd/decompress/zstd_decompress.c"
+#include "zstd/decompress/zstd_decompress_block.c"
+
+#include "zstd/common/xxhash.c"
+
+ALWAYS_INLINE static size_t roundupsize(size_t size, size_t align)
+{
+	return (size + align - 1U) & ~(align - 1);
+}
+
+ALWAYS_INLINE static IF_DESKTOP(long long) int
+unpack_zstd_stream_inner(transformer_state_t *xstate,
+	ZSTD_DStream *dctx, void *out_buff)
+{
+	const U32 zstd_magic = ZSTD_MAGIC;
+	const size_t in_allocsize = roundupsize(ZSTD_DStreamInSize(), 1024),
+		out_allocsize = roundupsize(ZSTD_DStreamOutSize(), 1024);
+
+	IF_DESKTOP(long long int total = 0;)
+	size_t last_result = ZSTD_error_maxCode + 1;
+	unsigned input_fixup;
+	void *in_buff = (char *)out_buff + out_allocsize;
+
+	memcpy(in_buff, &zstd_magic, 4);
+	input_fixup = xstate->signature_skipped ? 4 : 0;
+
+	/* This loop assumes that the input file is one or more concatenated
+	 * zstd streams. This example won't work if there is trailing non-zstd
+	 * data at the end, but streaming decompression in general handles this
+	 * case. ZSTD_decompressStream() returns 0 exactly when the frame is
+	 * completed, and doesn't consume input after the frame.
+	 */
+	for (;;) {
+		bool has_error = false;
+		ZSTD_inBuffer input;
+		ssize_t red;
+
+		red = safe_read(xstate->src_fd, (char *)in_buff + input_fixup, in_allocsize - \
input_fixup); +		if (red < 0) {
+			bb_simple_perror_msg(bb_msg_read_error);
+			return -1;
+		}
+		if (red == 0) {
+			break;
+		}
+
+		input.src = in_buff;
+		input.size = (size_t)red + input_fixup;
+		input.pos = 0;
+		input_fixup = 0;
+
+		/* Given a valid frame, zstd won't consume the last byte of the
+		 * frame until it has flushed all of the decompressed data of
+		 * the frame. So input.pos < input.size means frame is not done
+		 * or there is still output available.
+		 */
+		while (input.pos < input.size) {
+			ZSTD_outBuffer output = { out_buff, out_allocsize, 0 };
+			/* The return code is zero if the frame is complete, but
+			 * there may be multiple frames concatenated together.
+			 * Zstd will automatically reset the context when a
+			 * frame is complete. Still, calling ZSTD_DCtx_reset()
+			 * can be useful to reset the context to a clean state,
+			 * for instance if the last decompression call returned
+			 * an error.
+			 */
+			last_result = ZSTD_decompressStream(dctx, &output, &input);
+			if (ZSTD_isError(last_result)) {
+				has_error = true;
+				break;
+			}
+
+			xtransformer_write(xstate, output.dst, output.pos);
+			IF_DESKTOP(total += output.pos;)
+		}
+		if (has_error)
+			break;
+	}
+
+	if (last_result != 0) {
+		/* The last return value from ZSTD_decompressStream did not end
+		 * on a frame, but we reached the end of the file! We assume
+		 * this is an error, and the input was truncated.
+		 */
+		if (last_result == ZSTD_error_maxCode + 1) {
+			bb_simple_error_msg("could not read zstd data");
+		} else {
+#if defined(ZSTD_STRIP_ERROR_STRINGS) && ZSTD_STRIP_ERROR_STRINGS == 1
+			bb_error_msg("zstd decoder error: %u", (unsigned)last_result);
+#else
+			bb_error_msg("zstd decoder error: %s", ZSTD_getErrorName(last_result));
+#endif
+		}
+		return -1;
+	}
+
+	return IF_DESKTOP(total) + 0;
+}
+
+IF_DESKTOP(long long) int FAST_FUNC
+unpack_zstd_stream(transformer_state_t *xstate)
+{
+	const size_t in_allocsize = roundupsize(ZSTD_DStreamInSize(), 1024),
+		   out_allocsize = roundupsize(ZSTD_DStreamOutSize(), 1024);
+
+	IF_DESKTOP(long long) int result;
+	void *out_buff;
+	ZSTD_DStream *dctx;
+
+	dctx = ZSTD_createDStream_advanced(ZSTD_defaultCMem);
+	if (!dctx) {
+		/* should be the only possibly reason of failure */
+		bb_die_memory_exhausted();
+	}
+
+	out_buff = xmalloc(in_allocsize + out_allocsize);
+
+	result = unpack_zstd_stream_inner(xstate, dctx, out_buff);
+	free(out_buff);
+	ZSTD_freeDStream(dctx);
+	return result;
+}
+
diff --git a/archival/libarchive/open_transformer.c \
b/archival/libarchive/open_transformer.c index 44715ef25..3dcc5cbe5 100644
--- a/archival/libarchive/open_transformer.c
+++ b/archival/libarchive/open_transformer.c
@@ -202,12 +202,24 @@ static transformer_state_t *setup_transformer_on_fd(int fd, int \
fail_if_not_comp  goto found_magic;
 		}
 	}
+	if (ENABLE_FEATURE_SEAMLESS_ZSTD
+	 && xstate->magic.b16[0] == ZSTD_MAGIC1
+	) {
+		xstate->signature_skipped = 4;
+		xread(fd, &xstate->magic.b16[1], 2);
+		if (xstate->magic.b16[1] == ZSTD_MAGIC2) {
+			xstate->xformer = unpack_zstd_stream;
+			USE_FOR_NOMMU(xstate->xformer_prog = "unzstd";)
+			goto found_magic;
+		}
+	}
 
 	/* No known magic seen */
 	if (fail_if_not_compressed)
 		bb_simple_error_msg_and_die("no gzip"
 			IF_FEATURE_SEAMLESS_BZ2("/bzip2")
 			IF_FEATURE_SEAMLESS_XZ("/xz")
+			IF_FEATURE_SEAMLESS_XZ("/zstd")
 			" magic");
 
 	/* Some callers expect this function to "consume" fd
diff --git a/archival/tar.c b/archival/tar.c
index 9de37592e..77bae6560 100644
--- a/archival/tar.c
+++ b/archival/tar.c
@@ -43,7 +43,7 @@
 //config:config FEATURE_TAR_AUTODETECT
 //config:	bool "Autodetect compressed tarballs"
 //config:	default y
-//config:	depends on TAR && (FEATURE_SEAMLESS_Z || FEATURE_SEAMLESS_GZ || \
FEATURE_SEAMLESS_BZ2 || FEATURE_SEAMLESS_LZMA || FEATURE_SEAMLESS_XZ) \
+//config:	depends on TAR && (FEATURE_SEAMLESS_Z || FEATURE_SEAMLESS_GZ || \
FEATURE_SEAMLESS_BZ2 || FEATURE_SEAMLESS_LZMA || FEATURE_SEAMLESS_XZ || \
FEATURE_SEAMLESS_ZSTD)  //config:	help
 //config:	With this option tar can automatically detect compressed
 //config:	tarballs. Currently it works only on files (not pipes etc).
@@ -787,6 +787,11 @@ static llist_t *append_file_list_to_list(llist_t *list)
 //usage:     "\n	--lzma	(De)compress using lzma"
 //usage:	)
 //usage:	)
+//usage:	IF_FEATURE_SEAMLESS_ZSTD(
+//usage:	IF_FEATURE_TAR_LONG_OPTIONS(
+//usage:     "\n	--zstd	(De)compress using zstd"
+//usage:	)
+//usage:	)
 //usage:     "\n	-a	(De)compress based on extension"
 //usage:	IF_FEATURE_TAR_CREATE(
 //usage:     "\n	-h	Follow symlinks"
@@ -827,6 +832,7 @@ enum {
 	IF_FEATURE_TAR_NOPRESERVE_TIME(OPTBIT_NOPRESERVE_TIME,)
 #if ENABLE_FEATURE_TAR_LONG_OPTIONS
 	OPTBIT_STRIP_COMPONENTS,
+	IF_FEATURE_SEAMLESS_ZSTD(OPTBIT_ZSTD        ,)
 	IF_FEATURE_SEAMLESS_LZMA(OPTBIT_LZMA        ,)
 	OPTBIT_NORECURSION,
 	IF_FEATURE_TAR_TO_COMMAND(OPTBIT_2COMMAND   ,)
@@ -853,6 +859,7 @@ enum {
 	OPT_COMPRESS     = IF_FEATURE_SEAMLESS_Z(   (1 << OPTBIT_COMPRESS    )) + 0, // Z
 	OPT_AUTOCOMPRESS_BY_EXT = 1 << OPTBIT_AUTOCOMPRESS_BY_EXT,                   // a
 	OPT_NOPRESERVE_TIME  = IF_FEATURE_TAR_NOPRESERVE_TIME((1 << \
OPTBIT_NOPRESERVE_TIME)) + 0, // m +	OPT_ZSTD             = \
IF_FEATURE_TAR_LONG_OPTIONS(IF_FEATURE_SEAMLESS_ZSTD((1 << OPTBIT_ZSTD))) + 0, // \
zstd  OPT_STRIP_COMPONENTS = IF_FEATURE_TAR_LONG_OPTIONS((1 << \
OPTBIT_STRIP_COMPONENTS)) + 0, // strip-components  OPT_LZMA             = \
IF_FEATURE_TAR_LONG_OPTIONS(IF_FEATURE_SEAMLESS_LZMA((1 << OPTBIT_LZMA))) + 0, // \
lzma  OPT_NORECURSION      = IF_FEATURE_TAR_LONG_OPTIONS((1 << OPTBIT_NORECURSION    \
)) + 0, // no-recursion @@ -861,7 +868,7 @@ enum {
 	OPT_NOPRESERVE_PERM  = IF_FEATURE_TAR_LONG_OPTIONS((1 << OPTBIT_NOPRESERVE_PERM)) + \
0, // no-same-permissions  OPT_OVERWRITE        = IF_FEATURE_TAR_LONG_OPTIONS((1 << \
OPTBIT_OVERWRITE      )) + 0, // overwrite  
-	OPT_ANY_COMPRESS = (OPT_BZIP2 | OPT_LZMA | OPT_GZIP | OPT_XZ | OPT_COMPRESS),
+	OPT_ANY_COMPRESS = (OPT_BZIP2 | OPT_LZMA | OPT_GZIP | OPT_XZ | OPT_COMPRESS | \
OPT_ZSTD),  };
 #if ENABLE_FEATURE_TAR_LONG_OPTIONS
 static const char tar_longopts[] ALIGN1 =
@@ -900,6 +907,9 @@ static const char tar_longopts[] ALIGN1 =
 	"auto-compress\0"       No_argument       "a"
 # if ENABLE_FEATURE_TAR_NOPRESERVE_TIME
 	"touch\0"               No_argument       "m"
+# endif
+# if ENABLE_FEATURE_SEAMLESS_ZSTD
+	"zstd\0"                No_argument       "\xf7"
 # endif
 	"strip-components\0"	Required_argument "\xf8"
 # if ENABLE_FEATURE_SEAMLESS_LZMA
@@ -1049,6 +1059,7 @@ int tar_main(int argc UNUSED_PARAM, char **argv)
 	showopt(OPT_AUTOCOMPRESS_BY_EXT);
 	showopt(OPT_NOPRESERVE_TIME );
 	showopt(OPT_STRIP_COMPONENTS);
+	showopt(OPT_ZSTD            );
 	showopt(OPT_LZMA            );
 	showopt(OPT_NORECURSION     );
 	showopt(OPT_2COMMAND        );
@@ -1170,7 +1181,7 @@ int tar_main(int argc UNUSED_PARAM, char **argv)
 		} else {
 			tar_handle->src_fd = xopen(tar_filename, flags);
 #if ENABLE_FEATURE_TAR_CREATE
-			if ((OPT_GZIP | OPT_BZIP2 | OPT_XZ | OPT_LZMA) != 0 /* at least one is \
config-enabled */ +			if ((OPT_GZIP | OPT_BZIP2 | OPT_XZ | OPT_LZMA | OPT_ZSTD) != 0 \
/* at least one is config-enabled */  && (opt & OPT_AUTOCOMPRESS_BY_EXT)
 			 && flags != O_RDONLY
 			) {
@@ -1182,6 +1193,8 @@ int tar_main(int argc UNUSED_PARAM, char **argv)
 					opt |= OPT_XZ;
 				if (OPT_LZMA != 0 && is_suffixed_with(tar_filename, "lzma"))
 					opt |= OPT_LZMA;
+				if (OPT_ZSTD != 0 && is_suffixed_with(tar_filename, "zst"))
+					opt |= OPT_ZSTD;
 			}
 #endif
 		}
@@ -1206,6 +1219,8 @@ int tar_main(int argc UNUSED_PARAM, char **argv)
 			zipMode = "lzma";
 		if (opt & OPT_XZ)
 			zipMode = "xz";
+		if (opt & OPT_ZSTD)
+			zipMode = "zstd";
 # endif
 		tbInfo = xzalloc(sizeof(*tbInfo));
 		tbInfo->tarFd = tar_handle->src_fd;
@@ -1246,7 +1261,10 @@ int tar_main(int argc UNUSED_PARAM, char **argv)
 			USE_FOR_MMU(IF_FEATURE_SEAMLESS_XZ(xformer = unpack_xz_stream;))
 			USE_FOR_NOMMU(xformer_prog = "unxz";)
 		}
-
+		if (opt & OPT_ZSTD) {
+			USE_FOR_MMU(IF_FEATURE_SEAMLESS_ZSTD(xformer = unpack_zstd_stream;))
+			USE_FOR_NOMMU(xformer_prog = "unzstd";)
+		}
 		fork_transformer_with_sig(tar_handle->src_fd, xformer, xformer_prog);
 		/* Can't lseek over pipes */
 		tar_handle->seek = seek_by_read;
diff --git a/include/bb_archive.h b/include/bb_archive.h
index dc5e55f0a..57d89f810 100644
--- a/include/bb_archive.h
+++ b/include/bb_archive.h
@@ -17,6 +17,9 @@ enum {
 	/* (unsigned) cast suppresses "integer overflow in expression" warning */
 	XZ_MAGIC1a  = 256 * (unsigned)(256 * (256 * 0xfd + '7') + 'z') + 'X',
 	XZ_MAGIC2a  = 256 * 'Z' + 0,
+    ZSTD_MAGIC1 = 0x28B5,
+    ZSTD_MAGIC2 = 0x2FFD,
+    ZSTD_MAGIC  = 0x28B52FFD,
 #else
 	COMPRESS_MAGIC = 0x9d1f,
 	GZIP_MAGIC  = 0x8b1f,
@@ -25,6 +28,9 @@ enum {
 	XZ_MAGIC2   = 'z' + ('X' + ('Z' + 0 * 256) * 256) * 256,
 	XZ_MAGIC1a  = 0xfd + ('7' + ('z' + 'X' * 256) * 256) * 256,
 	XZ_MAGIC2a  = 'Z' + 0 * 256,
+    ZSTD_MAGIC1 = 0xB528,
+    ZSTD_MAGIC2 = 0xFD2F,
+    ZSTD_MAGIC  = 0xFD2FB528,
 #endif
 };
 
@@ -255,6 +261,7 @@ IF_DESKTOP(long long) int unpack_gz_stream(transformer_state_t \
*xstate) FAST_FUN  IF_DESKTOP(long long) int unpack_bz2_stream(transformer_state_t \
*xstate) FAST_FUNC;  IF_DESKTOP(long long) int unpack_lzma_stream(transformer_state_t \
*xstate) FAST_FUNC;  IF_DESKTOP(long long) int unpack_xz_stream(transformer_state_t \
*xstate) FAST_FUNC; +IF_DESKTOP(long long) int unpack_zstd_stream(transformer_state_t \
*xstate) FAST_FUNC;  
 char* append_ext(char *filename, const char *expected_ext) FAST_FUNC;
 int bbunpack(char **argv,
diff --git a/include/libbb.h b/include/libbb.h
index 7d6ab4a93..0a247b462 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -982,6 +982,7 @@ unsigned bb_clk_tck(void) FAST_FUNC;
  || ENABLE_FEATURE_SEAMLESS_LZMA \
  || ENABLE_FEATURE_SEAMLESS_BZ2 \
  || ENABLE_FEATURE_SEAMLESS_GZ \
+ || ENABLE_FEATURE_SEAMLESS_ZSTD \
  || ENABLE_FEATURE_SEAMLESS_Z)
 
 #if SEAMLESS_COMPRESSION
-- 
2.33.0

_______________________________________________
busybox mailing list
busybox@busybox.net
http://lists.busybox.net/mailman/listinfo/busybox


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic