linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 1/2] lib: add support for ZSTD-compressed kernel
@ 2020-03-16 13:50 Petr Malat
  2020-03-16 13:50 ` [PATCH 2/2] x86: Enable " Petr Malat
                   ` (2 more replies)
  0 siblings, 3 replies; 10+ messages in thread
From: Petr Malat @ 2020-03-16 13:50 UTC (permalink / raw)
  To: linux-kernel, linux-kbuild, x86
  Cc: terrelln, clm, gregkh, keescook, Petr Malat

Add support for extracting ZSTD-compressed kernel images, as well as
ZSTD-compressed initramfs.

Signed-off-by: Petr Malat <oss@malat.biz>
---
 include/linux/decompress/unzstd.h |  12 +++
 init/Kconfig                      |  15 ++-
 lib/Kconfig                       |   4 +
 lib/Makefile                      |   1 +
 lib/decompress.c                  |   5 +
 lib/decompress_unzstd.c           | 159 ++++++++++++++++++++++++++++++
 lib/zstd/decompress.c             |   2 +
 lib/zstd/fse_decompress.c         |   4 +-
 scripts/Makefile.lib              |   3 +
 usr/Kconfig                       |  24 +++++
 10 files changed, 227 insertions(+), 2 deletions(-)
 create mode 100644 include/linux/decompress/unzstd.h
 create mode 100644 lib/decompress_unzstd.c

diff --git a/include/linux/decompress/unzstd.h b/include/linux/decompress/unzstd.h
new file mode 100644
index 000000000000..dd2c49d47456
--- /dev/null
+++ b/include/linux/decompress/unzstd.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef DECOMPRESS_UNZSTD_H
+#define DECOMPRESS_UNZSTD_H
+
+int unzstd(unsigned char *inbuf, long len,
+	long (*fill)(void*, unsigned long),
+	long (*flush)(void*, unsigned long),
+	unsigned char *output,
+	long *pos,
+	void (*error)(char *x));
+#endif
+
diff --git a/init/Kconfig b/init/Kconfig
index a34064a031a5..628eb3c290a2 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -172,13 +172,16 @@ config HAVE_KERNEL_LZO
 config HAVE_KERNEL_LZ4
 	bool
 
+config HAVE_KERNEL_ZSTD
+	bool
+
 config HAVE_KERNEL_UNCOMPRESSED
 	bool
 
 choice
 	prompt "Kernel compression mode"
 	default KERNEL_GZIP
-	depends on HAVE_KERNEL_GZIP || HAVE_KERNEL_BZIP2 || HAVE_KERNEL_LZMA || HAVE_KERNEL_XZ || HAVE_KERNEL_LZO || HAVE_KERNEL_LZ4 || HAVE_KERNEL_UNCOMPRESSED
+	depends on HAVE_KERNEL_GZIP || HAVE_KERNEL_BZIP2 || HAVE_KERNEL_LZMA || HAVE_KERNEL_XZ || HAVE_KERNEL_LZO || HAVE_KERNEL_LZ4 || HAVE_KERNEL_ZSTD || HAVE_KERNEL_UNCOMPRESSED
 	help
 	  The linux kernel is a kind of self-extracting executable.
 	  Several compression algorithms are available, which differ
@@ -257,6 +260,16 @@ config KERNEL_LZ4
 	  is about 8% bigger than LZO. But the decompression speed is
 	  faster than LZO.
 
+config KERNEL_ZSTD
+	bool "ZSTD"
+	depends on HAVE_KERNEL_ZSTD
+	help
+	  Its compression ratio is roughly 10% worst than xz, but the
+	  decompression is 10x faster. Currently, this is one of the optimal
+	  algorithms available in the kernel, as there isn't an algorithm,
+	  which would provide a better compression ratio and a shorter
+	  decompression time.
+
 config KERNEL_UNCOMPRESSED
 	bool "None"
 	depends on HAVE_KERNEL_UNCOMPRESSED
diff --git a/lib/Kconfig b/lib/Kconfig
index 6e790dc55c5b..df301bd888d7 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -329,6 +329,10 @@ config DECOMPRESS_LZ4
 	select LZ4_DECOMPRESS
 	tristate
 
+config DECOMPRESS_ZSTD
+	select ZSTD_DECOMPRESS
+	tristate
+
 #
 # Generic allocator support is selected if needed
 #
diff --git a/lib/Makefile b/lib/Makefile
index 93217d44237f..3ab9f4c31f8b 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -158,6 +158,7 @@ lib-$(CONFIG_DECOMPRESS_LZMA) += decompress_unlzma.o
 lib-$(CONFIG_DECOMPRESS_XZ) += decompress_unxz.o
 lib-$(CONFIG_DECOMPRESS_LZO) += decompress_unlzo.o
 lib-$(CONFIG_DECOMPRESS_LZ4) += decompress_unlz4.o
+lib-$(CONFIG_DECOMPRESS_ZSTD) += decompress_unzstd.o
 
 obj-$(CONFIG_TEXTSEARCH) += textsearch.o
 obj-$(CONFIG_TEXTSEARCH_KMP) += ts_kmp.o
diff --git a/lib/decompress.c b/lib/decompress.c
index 857ab1af1ef3..ab3fc90ffc64 100644
--- a/lib/decompress.c
+++ b/lib/decompress.c
@@ -13,6 +13,7 @@
 #include <linux/decompress/inflate.h>
 #include <linux/decompress/unlzo.h>
 #include <linux/decompress/unlz4.h>
+#include <linux/decompress/unzstd.h>
 
 #include <linux/types.h>
 #include <linux/string.h>
@@ -37,6 +38,9 @@
 #ifndef CONFIG_DECOMPRESS_LZ4
 # define unlz4 NULL
 #endif
+#ifndef CONFIG_DECOMPRESS_ZSTD
+# define unzstd NULL
+#endif
 
 struct compress_format {
 	unsigned char magic[2];
@@ -52,6 +56,7 @@ static const struct compress_format compressed_formats[] __initconst = {
 	{ {0xfd, 0x37}, "xz", unxz },
 	{ {0x89, 0x4c}, "lzo", unlzo },
 	{ {0x02, 0x21}, "lz4", unlz4 },
+	{ {0x28, 0xb5}, "zstd", unzstd },
 	{ {0, 0}, NULL, NULL }
 };
 
diff --git a/lib/decompress_unzstd.c b/lib/decompress_unzstd.c
new file mode 100644
index 000000000000..5af647a49885
--- /dev/null
+++ b/lib/decompress_unzstd.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Wrapper for decompressing ZSTD-compressed kernel, initramfs, and initrd
+ * Based on decompress_unlz4.c
+ *
+ * Copyright (C) 2020, Petr Malat <oss@malat.biz>
+ */
+
+#ifdef STATIC
+#define PREBOOT
+#include "zstd/zstd_internal.h"
+#include "zstd/huf_decompress.c"
+#include "zstd/entropy_common.c"
+#include "zstd/fse_decompress.c"
+#include "zstd/zstd_common.c"
+#include "zstd/decompress.c"
+#include "xxhash.c"
+#else
+#include <linux/decompress/unzstd.h>
+#include <linux/zstd.h>
+#endif
+#include <linux/types.h>
+#include <linux/decompress/mm.h>
+#include <linux/compiler.h>
+
+STATIC inline int INIT unzstd(u8 *input, long in_len,
+				long (*fill)(void *, unsigned long),
+				long (*flush)(void *, unsigned long),
+				u8 *output, long *posp,
+				void (*error)(char *x))
+{
+	int ret = -1, ws = 1 << ZSTD_WINDOWLOG_MAX;
+	u8 *inp, *outp;
+	ZSTD_DStream *zstd;
+	void *workspace;
+	size_t workspace_size;
+	ZSTD_outBuffer out;
+	ZSTD_inBuffer in;
+	unsigned long out_len;
+	unsigned long pos;
+
+	if (output) {
+		out_len = ULONG_MAX; // Caller knows data will fit
+		outp = output;
+	} else if (!flush) {
+		error("NULL output pointer and no flush function provided");
+		goto exit_0;
+	} else {
+		out_len = ZSTD_DStreamOutSize();
+		outp = large_malloc(out_len);
+		if (!outp) {
+			error("Could not allocate output buffer");
+			goto exit_0;
+		}
+	}
+
+	if (input && fill) {
+		error("Both input pointer and fill function provided,");
+		goto exit_1;
+	} else if (input) {
+		ZSTD_frameParams p;
+
+		inp = input;
+		if (!ZSTD_getFrameParams(&p, input, in_len))
+			ws = p.windowSize;
+	} else if (!fill) {
+		error("NULL input pointer and missing fill function");
+		goto exit_1;
+	} else {
+		in_len = ZSTD_DStreamInSize();
+		inp = large_malloc(in_len);
+		if (!inp) {
+			error("Could not allocate input buffer");
+			goto exit_1;
+		}
+	}
+
+	workspace_size = ZSTD_DStreamWorkspaceBound(ws);
+	workspace = large_malloc(workspace_size);
+	if (!workspace) {
+		error("Could not allocate workspace");
+		goto exit_2;
+	}
+
+	zstd = ZSTD_initDStream(ws, workspace, workspace_size);
+	if (!zstd) {
+		error("Could not initialize ZSTD");
+		goto exit_3;
+	}
+
+	in.src = inp;
+	in.size = in_len;
+	in.pos = 0;
+	if (posp)
+		*posp = 0;
+
+	for (;;) {
+		if (fill) {
+			in.size = fill(inp, in_len);
+			if (in.size == 0)
+				break;
+		} else if (in.size == in.pos) {
+			break;
+		}
+init:		out.dst = outp;
+		out.size = out_len;
+		out.pos = 0;
+		pos = in.pos;
+
+		ret = ZSTD_decompressStream(zstd, &out, &in);
+		if (posp)
+			*posp += in.pos - pos;
+		if (ZSTD_isError(ret)) {
+			error("Decompression failed");
+			ret = -EIO;
+			goto exit_3;
+		}
+
+		if (flush && out.pos) {
+			if (flush(out.dst, out.pos) != out.pos) {
+				ret = -EIO;
+				goto exit_3;
+			}
+			goto init;
+		}
+
+		if (ret == 0) {
+			ret = ZSTD_resetDStream(zstd);
+			if (ZSTD_isError(ret)) {
+				ret = -EIO;
+				goto exit_3;
+			}
+		}
+		if (in.pos < in.size)
+			goto init_out;
+	}
+
+	ret = 0;
+
+exit_3:	large_free(workspace);
+exit_2:	if (!input)
+		large_free(inp);
+exit_1:	if (!output)
+		large_free(outp);
+exit_0:	return ret;
+}
+
+#ifdef PREBOOT
+STATIC int INIT __decompress(unsigned char *buf, long in_len,
+			      long (*fill)(void*, unsigned long),
+			      long (*flush)(void*, unsigned long),
+			      unsigned char *output, long out_len,
+			      long *posp,
+			      void (*error)(char *x)
+	)
+{
+	return unzstd(buf, in_len, fill, flush, output, posp, error);
+}
+#endif
diff --git a/lib/zstd/decompress.c b/lib/zstd/decompress.c
index 269ee9a796c1..6a5e1ce22719 100644
--- a/lib/zstd/decompress.c
+++ b/lib/zstd/decompress.c
@@ -42,9 +42,11 @@
 /*-*************************************
 *  Macros
 ***************************************/
+#ifndef PREBOOT
 #define ZSTD_isError ERR_isError /* for inlining */
 #define FSE_isError ERR_isError
 #define HUF_isError ERR_isError
+#endif
 
 /*_*******************************************************
 *  Memory operations
diff --git a/lib/zstd/fse_decompress.c b/lib/zstd/fse_decompress.c
index a84300e5a013..bd4e9c891d96 100644
--- a/lib/zstd/fse_decompress.c
+++ b/lib/zstd/fse_decompress.c
@@ -54,12 +54,13 @@
 /* **************************************************************
 *  Error Management
 ****************************************************************/
-#define FSE_isError ERR_isError
 #define FSE_STATIC_ASSERT(c)                                   \
 	{                                                      \
 		enum { FSE_static_assert = 1 / (int)(!!(c)) }; \
 	} /* use only *after* variable declarations */
 
+#ifndef PREBOOT
+#define FSE_isError ERR_isError
 /* check and forward error code */
 #define CHECK_F(f)                  \
 	{                           \
@@ -67,6 +68,7 @@
 		if (FSE_isError(e)) \
 			return e;   \
 	}
+#endif
 
 /* **************************************************************
 *  Templates
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index 3fa32f83b2d7..1c2f2dc528dc 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -337,6 +337,9 @@ quiet_cmd_lz4 = LZ4     $@
       cmd_lz4 = { cat $(real-prereqs) | lz4c -l -c1 stdin stdout; \
                   $(size_append); } > $@
 
+quiet_cmd_zstd = ZSTD    $@
+      cmd_zstd = { cat $(real-prereqs) | zstd -19 --zstd=wlog=21; $(size_append); } > $@
+
 # U-Boot mkimage
 # ---------------------------------------------------------------------------
 
diff --git a/usr/Kconfig b/usr/Kconfig
index a6b68503d177..892eb15957db 100644
--- a/usr/Kconfig
+++ b/usr/Kconfig
@@ -106,6 +106,15 @@ config RD_LZ4
 	  Support loading of a LZ4 encoded initial ramdisk or cpio buffer
 	  If unsure, say N.
 
+config RD_ZSTD
+	bool "Support initial ramdisk/ramfs compressed using ZSTD"
+	default y
+	depends on BLK_DEV_INITRD
+	select DECOMPRESS_ZSTD
+	help
+	  Support loading of a ZSTD encoded initial ramdisk or cpio buffer
+	  If unsure, say N.
+
 choice
 	prompt "Built-in initramfs compression mode"
 	depends on INITRAMFS_SOURCE!=""
@@ -214,6 +223,19 @@ config INITRAMFS_COMPRESSION_LZ4
 	  If you choose this, keep in mind that most distros don't provide lz4
 	  by default which could cause a build failure.
 
+config INITRAMFS_COMPRESSION_ZSTD
+	bool "ZSTD"
+	depends on RD_ZSTD
+	help
+	  Its compression ratio is roughly 10% worst than xz, but the
+	  decompression is 10x faster. Currently, this is one of the optimal
+	  algorithms available in the kernel, as there isn't an algorithm,
+	  which would provide a better compression ratio and a shorter
+	  decompression time.
+
+	  If you choose this, keep in mind that you may need to install the zstd
+	  tool to be able to compress the initram.
+
 endchoice
 
 config INITRAMFS_COMPRESSION
@@ -226,10 +248,12 @@ config INITRAMFS_COMPRESSION
 	default ".xz"   if INITRAMFS_COMPRESSION_XZ
 	default ".lzo"  if INITRAMFS_COMPRESSION_LZO
 	default ".lz4"  if INITRAMFS_COMPRESSION_LZ4
+	default ".zst"  if INITRAMFS_COMPRESSION_ZSTD
 	default ".gz"   if RD_GZIP
 	default ".lz4"  if RD_LZ4
 	default ".lzo"  if RD_LZO
 	default ".xz"   if RD_XZ
 	default ".lzma" if RD_LZMA
 	default ".bz2"  if RD_BZIP2
+	default ".zst"  if RD_ZSTD
 	default ""
-- 
2.20.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 2/2] x86: Enable support for ZSTD-compressed kernel
  2020-03-16 13:50 [PATCH 1/2] lib: add support for ZSTD-compressed kernel Petr Malat
@ 2020-03-16 13:50 ` Petr Malat
  2020-03-16 14:07   ` Greg KH
  2020-03-16 14:07 ` [PATCH " Greg KH
  2020-03-17 21:00 ` Nick Terrell
  2 siblings, 1 reply; 10+ messages in thread
From: Petr Malat @ 2020-03-16 13:50 UTC (permalink / raw)
  To: linux-kernel, linux-kbuild, x86
  Cc: terrelln, clm, gregkh, keescook, Petr Malat

Signed-off-by: Petr Malat <oss@malat.biz>
---
 arch/x86/Kconfig                  | 1 +
 arch/x86/boot/compressed/Makefile | 5 ++++-
 arch/x86/boot/compressed/misc.c   | 4 ++++
 arch/x86/include/asm/boot.h       | 4 ++--
 4 files changed, 11 insertions(+), 3 deletions(-)

diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 5e8949953660..b22312aae674 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -180,6 +180,7 @@ config X86
 	select HAVE_KERNEL_LZMA
 	select HAVE_KERNEL_LZO
 	select HAVE_KERNEL_XZ
+	select HAVE_KERNEL_ZSTD
 	select HAVE_KPROBES
 	select HAVE_KPROBES_ON_FTRACE
 	select HAVE_FUNCTION_ERROR_INJECTION
diff --git a/arch/x86/boot/compressed/Makefile b/arch/x86/boot/compressed/Makefile
index 1dac210f7d44..a87dc1e41772 100644
--- a/arch/x86/boot/compressed/Makefile
+++ b/arch/x86/boot/compressed/Makefile
@@ -24,7 +24,7 @@ OBJECT_FILES_NON_STANDARD	:= y
 KCOV_INSTRUMENT		:= n
 
 targets := vmlinux vmlinux.bin vmlinux.bin.gz vmlinux.bin.bz2 vmlinux.bin.lzma \
-	vmlinux.bin.xz vmlinux.bin.lzo vmlinux.bin.lz4
+	vmlinux.bin.xz vmlinux.bin.lzo vmlinux.bin.lz4 vmlinux.bin.zst
 
 KBUILD_CFLAGS := -m$(BITS) -O2
 KBUILD_CFLAGS += -fno-strict-aliasing $(call cc-option, -fPIE, -fPIC)
@@ -145,6 +145,8 @@ $(obj)/vmlinux.bin.lzo: $(vmlinux.bin.all-y) FORCE
 	$(call if_changed,lzo)
 $(obj)/vmlinux.bin.lz4: $(vmlinux.bin.all-y) FORCE
 	$(call if_changed,lz4)
+$(obj)/vmlinux.bin.zst: $(vmlinux.bin.all-y) FORCE
+	$(call if_changed,zstd)
 
 suffix-$(CONFIG_KERNEL_GZIP)	:= gz
 suffix-$(CONFIG_KERNEL_BZIP2)	:= bz2
@@ -152,6 +154,7 @@ suffix-$(CONFIG_KERNEL_LZMA)	:= lzma
 suffix-$(CONFIG_KERNEL_XZ)	:= xz
 suffix-$(CONFIG_KERNEL_LZO) 	:= lzo
 suffix-$(CONFIG_KERNEL_LZ4) 	:= lz4
+suffix-$(CONFIG_KERNEL_ZSTD) 	:= zst
 
 quiet_cmd_mkpiggy = MKPIGGY $@
       cmd_mkpiggy = $(obj)/mkpiggy $< > $@
diff --git a/arch/x86/boot/compressed/misc.c b/arch/x86/boot/compressed/misc.c
index 9652d5c2afda..39e592d0e0b4 100644
--- a/arch/x86/boot/compressed/misc.c
+++ b/arch/x86/boot/compressed/misc.c
@@ -77,6 +77,10 @@ static int lines, cols;
 #ifdef CONFIG_KERNEL_LZ4
 #include "../../../../lib/decompress_unlz4.c"
 #endif
+
+#ifdef CONFIG_KERNEL_ZSTD
+#include "../../../../lib/decompress_unzstd.c"
+#endif
 /*
  * NOTE: When adding a new decompressor, please update the analysis in
  * ../header.S.
diff --git a/arch/x86/include/asm/boot.h b/arch/x86/include/asm/boot.h
index 680c320363db..9838c183e9a8 100644
--- a/arch/x86/include/asm/boot.h
+++ b/arch/x86/include/asm/boot.h
@@ -24,9 +24,9 @@
 # error "Invalid value for CONFIG_PHYSICAL_ALIGN"
 #endif
 
-#ifdef CONFIG_KERNEL_BZIP2
+#if CONFIG_KERNEL_BZIP2 || CONFIG_KERNEL_ZSTD
 # define BOOT_HEAP_SIZE		0x400000
-#else /* !CONFIG_KERNEL_BZIP2 */
+#else /* !(CONFIG_KERNEL_BZIP2 || CONFIG_KERNEL_ZSTD)  */
 # define BOOT_HEAP_SIZE		 0x10000
 #endif
 
-- 
2.20.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH 1/2] lib: add support for ZSTD-compressed kernel
  2020-03-16 13:50 [PATCH 1/2] lib: add support for ZSTD-compressed kernel Petr Malat
  2020-03-16 13:50 ` [PATCH 2/2] x86: Enable " Petr Malat
@ 2020-03-16 14:07 ` Greg KH
  2020-03-16 14:35   ` Petr Malat
  2020-03-17 21:00 ` Nick Terrell
  2 siblings, 1 reply; 10+ messages in thread
From: Greg KH @ 2020-03-16 14:07 UTC (permalink / raw)
  To: Petr Malat; +Cc: linux-kernel, linux-kbuild, x86, terrelln, clm, keescook

On Mon, Mar 16, 2020 at 02:50:24PM +0100, Petr Malat wrote:
> Add support for extracting ZSTD-compressed kernel images, as well as
> ZSTD-compressed initramfs.
> 
> Signed-off-by: Petr Malat <oss@malat.biz>

That says _what_ you did here, but not _why_ you did this, or why anyone
would even want this.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 2/2] x86: Enable support for ZSTD-compressed kernel
  2020-03-16 13:50 ` [PATCH 2/2] x86: Enable " Petr Malat
@ 2020-03-16 14:07   ` Greg KH
  2020-03-16 14:30     ` [PATCH v2 1/2] lib: add " Petr Malat
  0 siblings, 1 reply; 10+ messages in thread
From: Greg KH @ 2020-03-16 14:07 UTC (permalink / raw)
  To: Petr Malat; +Cc: linux-kernel, linux-kbuild, x86, terrelln, clm, keescook

On Mon, Mar 16, 2020 at 02:50:25PM +0100, Petr Malat wrote:
> Signed-off-by: Petr Malat <oss@malat.biz>
> ---
>  arch/x86/Kconfig                  | 1 +
>  arch/x86/boot/compressed/Makefile | 5 ++++-
>  arch/x86/boot/compressed/misc.c   | 4 ++++
>  arch/x86/include/asm/boot.h       | 4 ++--
>  4 files changed, 11 insertions(+), 3 deletions(-)

I know I don't take patches without any changelog text, maybe other
maintainers are more lax...

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 1/2] lib: add support for ZSTD-compressed kernel
  2020-03-16 14:07   ` Greg KH
@ 2020-03-16 14:30     ` Petr Malat
  2020-03-16 14:30       ` [PATCH v2 2/2] x86: Enable " Petr Malat
  2020-03-17 21:02       ` [PATCH v2 1/2] lib: add " Kees Cook
  0 siblings, 2 replies; 10+ messages in thread
From: Petr Malat @ 2020-03-16 14:30 UTC (permalink / raw)
  To: linux-kbuild, linux-kernel, x86
  Cc: terrelln, clm, gregkh, keescook, Petr Malat

Add support for extracting ZSTD-compressed kernel images, as well as
ZSTD-compressed initramfs.

ZSTD compression ratio is roughly 10% worst than xz, but the
decompression is 10x faster. Currently, this is one of the optimal
algorithms available in the kernel, as there isn't an algorithm,
which would provide a better compression ratio and a shorter
decompression time.

Signed-off-by: Petr Malat <oss@malat.biz>
---
 include/linux/decompress/unzstd.h |  12 +++
 init/Kconfig                      |  15 ++-
 lib/Kconfig                       |   4 +
 lib/Makefile                      |   1 +
 lib/decompress.c                  |   5 +
 lib/decompress_unzstd.c           | 159 ++++++++++++++++++++++++++++++
 lib/zstd/decompress.c             |   2 +
 lib/zstd/fse_decompress.c         |   4 +-
 scripts/Makefile.lib              |   3 +
 usr/Kconfig                       |  24 +++++
 10 files changed, 227 insertions(+), 2 deletions(-)
 create mode 100644 include/linux/decompress/unzstd.h
 create mode 100644 lib/decompress_unzstd.c

diff --git a/include/linux/decompress/unzstd.h b/include/linux/decompress/unzstd.h
new file mode 100644
index 000000000000..dd2c49d47456
--- /dev/null
+++ b/include/linux/decompress/unzstd.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef DECOMPRESS_UNZSTD_H
+#define DECOMPRESS_UNZSTD_H
+
+int unzstd(unsigned char *inbuf, long len,
+	long (*fill)(void*, unsigned long),
+	long (*flush)(void*, unsigned long),
+	unsigned char *output,
+	long *pos,
+	void (*error)(char *x));
+#endif
+
diff --git a/init/Kconfig b/init/Kconfig
index a34064a031a5..628eb3c290a2 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -172,13 +172,16 @@ config HAVE_KERNEL_LZO
 config HAVE_KERNEL_LZ4
 	bool
 
+config HAVE_KERNEL_ZSTD
+	bool
+
 config HAVE_KERNEL_UNCOMPRESSED
 	bool
 
 choice
 	prompt "Kernel compression mode"
 	default KERNEL_GZIP
-	depends on HAVE_KERNEL_GZIP || HAVE_KERNEL_BZIP2 || HAVE_KERNEL_LZMA || HAVE_KERNEL_XZ || HAVE_KERNEL_LZO || HAVE_KERNEL_LZ4 || HAVE_KERNEL_UNCOMPRESSED
+	depends on HAVE_KERNEL_GZIP || HAVE_KERNEL_BZIP2 || HAVE_KERNEL_LZMA || HAVE_KERNEL_XZ || HAVE_KERNEL_LZO || HAVE_KERNEL_LZ4 || HAVE_KERNEL_ZSTD || HAVE_KERNEL_UNCOMPRESSED
 	help
 	  The linux kernel is a kind of self-extracting executable.
 	  Several compression algorithms are available, which differ
@@ -257,6 +260,16 @@ config KERNEL_LZ4
 	  is about 8% bigger than LZO. But the decompression speed is
 	  faster than LZO.
 
+config KERNEL_ZSTD
+	bool "ZSTD"
+	depends on HAVE_KERNEL_ZSTD
+	help
+	  Its compression ratio is roughly 10% worst than xz, but the
+	  decompression is 10x faster. Currently, this is one of the optimal
+	  algorithms available in the kernel, as there isn't an algorithm,
+	  which would provide a better compression ratio and a shorter
+	  decompression time.
+
 config KERNEL_UNCOMPRESSED
 	bool "None"
 	depends on HAVE_KERNEL_UNCOMPRESSED
diff --git a/lib/Kconfig b/lib/Kconfig
index 6e790dc55c5b..df301bd888d7 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -329,6 +329,10 @@ config DECOMPRESS_LZ4
 	select LZ4_DECOMPRESS
 	tristate
 
+config DECOMPRESS_ZSTD
+	select ZSTD_DECOMPRESS
+	tristate
+
 #
 # Generic allocator support is selected if needed
 #
diff --git a/lib/Makefile b/lib/Makefile
index 93217d44237f..3ab9f4c31f8b 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -158,6 +158,7 @@ lib-$(CONFIG_DECOMPRESS_LZMA) += decompress_unlzma.o
 lib-$(CONFIG_DECOMPRESS_XZ) += decompress_unxz.o
 lib-$(CONFIG_DECOMPRESS_LZO) += decompress_unlzo.o
 lib-$(CONFIG_DECOMPRESS_LZ4) += decompress_unlz4.o
+lib-$(CONFIG_DECOMPRESS_ZSTD) += decompress_unzstd.o
 
 obj-$(CONFIG_TEXTSEARCH) += textsearch.o
 obj-$(CONFIG_TEXTSEARCH_KMP) += ts_kmp.o
diff --git a/lib/decompress.c b/lib/decompress.c
index 857ab1af1ef3..ab3fc90ffc64 100644
--- a/lib/decompress.c
+++ b/lib/decompress.c
@@ -13,6 +13,7 @@
 #include <linux/decompress/inflate.h>
 #include <linux/decompress/unlzo.h>
 #include <linux/decompress/unlz4.h>
+#include <linux/decompress/unzstd.h>
 
 #include <linux/types.h>
 #include <linux/string.h>
@@ -37,6 +38,9 @@
 #ifndef CONFIG_DECOMPRESS_LZ4
 # define unlz4 NULL
 #endif
+#ifndef CONFIG_DECOMPRESS_ZSTD
+# define unzstd NULL
+#endif
 
 struct compress_format {
 	unsigned char magic[2];
@@ -52,6 +56,7 @@ static const struct compress_format compressed_formats[] __initconst = {
 	{ {0xfd, 0x37}, "xz", unxz },
 	{ {0x89, 0x4c}, "lzo", unlzo },
 	{ {0x02, 0x21}, "lz4", unlz4 },
+	{ {0x28, 0xb5}, "zstd", unzstd },
 	{ {0, 0}, NULL, NULL }
 };
 
diff --git a/lib/decompress_unzstd.c b/lib/decompress_unzstd.c
new file mode 100644
index 000000000000..b8be89250033
--- /dev/null
+++ b/lib/decompress_unzstd.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Wrapper for decompressing ZSTD-compressed kernel, initramfs, and initrd
+ * Based on decompress_unlz4.c
+ *
+ * Copyright (C) 2020, Petr Malat <oss@malat.biz>
+ */
+
+#ifdef STATIC
+#define PREBOOT
+#include "zstd/zstd_internal.h"
+#include "zstd/huf_decompress.c"
+#include "zstd/entropy_common.c"
+#include "zstd/fse_decompress.c"
+#include "zstd/zstd_common.c"
+#include "zstd/decompress.c"
+#include "xxhash.c"
+#else
+#include <linux/decompress/unzstd.h>
+#include <linux/zstd.h>
+#endif
+#include <linux/types.h>
+#include <linux/decompress/mm.h>
+#include <linux/compiler.h>
+
+STATIC inline int INIT unzstd(u8 *input, long in_len,
+				long (*fill)(void *, unsigned long),
+				long (*flush)(void *, unsigned long),
+				u8 *output, long *posp,
+				void (*error)(char *x))
+{
+	int ret = -1, ws = 1 << ZSTD_WINDOWLOG_MAX;
+	u8 *inp, *outp;
+	ZSTD_DStream *zstd;
+	void *workspace;
+	size_t workspace_size;
+	ZSTD_outBuffer out;
+	ZSTD_inBuffer in;
+	unsigned long out_len;
+	unsigned long pos;
+
+	if (output) {
+		out_len = ULONG_MAX; // Caller knows data will fit
+		outp = output;
+	} else if (!flush) {
+		error("NULL output pointer and no flush function provided");
+		goto exit_0;
+	} else {
+		out_len = ZSTD_DStreamOutSize();
+		outp = large_malloc(out_len);
+		if (!outp) {
+			error("Could not allocate output buffer");
+			goto exit_0;
+		}
+	}
+
+	if (input && fill) {
+		error("Both input pointer and fill function provided,");
+		goto exit_1;
+	} else if (input) {
+		ZSTD_frameParams p;
+
+		inp = input;
+		if (!ZSTD_getFrameParams(&p, input, in_len))
+			ws = p.windowSize;
+	} else if (!fill) {
+		error("NULL input pointer and missing fill function");
+		goto exit_1;
+	} else {
+		in_len = ZSTD_DStreamInSize();
+		inp = large_malloc(in_len);
+		if (!inp) {
+			error("Could not allocate input buffer");
+			goto exit_1;
+		}
+	}
+
+	workspace_size = ZSTD_DStreamWorkspaceBound(ws);
+	workspace = large_malloc(workspace_size);
+	if (!workspace) {
+		error("Could not allocate workspace");
+		goto exit_2;
+	}
+
+	zstd = ZSTD_initDStream(ws, workspace, workspace_size);
+	if (!zstd) {
+		error("Could not initialize ZSTD");
+		goto exit_3;
+	}
+
+	in.src = inp;
+	in.size = in_len;
+	in.pos = 0;
+	if (posp)
+		*posp = 0;
+
+	for (;;) {
+		if (fill) {
+			in.size = fill(inp, in_len);
+			if (in.size == 0)
+				break;
+		} else if (in.size == in.pos) {
+			break;
+		}
+init:		out.dst = outp;
+		out.size = out_len;
+		out.pos = 0;
+		pos = in.pos;
+
+		ret = ZSTD_decompressStream(zstd, &out, &in);
+		if (posp)
+			*posp += in.pos - pos;
+		if (ZSTD_isError(ret)) {
+			error("Decompression failed");
+			ret = -EIO;
+			goto exit_3;
+		}
+
+		if (flush && out.pos) {
+			if (flush(out.dst, out.pos) != out.pos) {
+				ret = -EIO;
+				goto exit_3;
+			}
+			goto init;
+		}
+
+		if (ret == 0) {
+			ret = ZSTD_resetDStream(zstd);
+			if (ZSTD_isError(ret)) {
+				ret = -EIO;
+				goto exit_3;
+			}
+		}
+		if (in.pos < in.size)
+			goto init;
+	}
+
+	ret = 0;
+
+exit_3:	large_free(workspace);
+exit_2:	if (!input)
+		large_free(inp);
+exit_1:	if (!output)
+		large_free(outp);
+exit_0:	return ret;
+}
+
+#ifdef PREBOOT
+STATIC int INIT __decompress(unsigned char *buf, long in_len,
+			      long (*fill)(void*, unsigned long),
+			      long (*flush)(void*, unsigned long),
+			      unsigned char *output, long out_len,
+			      long *posp,
+			      void (*error)(char *x)
+	)
+{
+	return unzstd(buf, in_len, fill, flush, output, posp, error);
+}
+#endif
diff --git a/lib/zstd/decompress.c b/lib/zstd/decompress.c
index 269ee9a796c1..6a5e1ce22719 100644
--- a/lib/zstd/decompress.c
+++ b/lib/zstd/decompress.c
@@ -42,9 +42,11 @@
 /*-*************************************
 *  Macros
 ***************************************/
+#ifndef PREBOOT
 #define ZSTD_isError ERR_isError /* for inlining */
 #define FSE_isError ERR_isError
 #define HUF_isError ERR_isError
+#endif
 
 /*_*******************************************************
 *  Memory operations
diff --git a/lib/zstd/fse_decompress.c b/lib/zstd/fse_decompress.c
index a84300e5a013..bd4e9c891d96 100644
--- a/lib/zstd/fse_decompress.c
+++ b/lib/zstd/fse_decompress.c
@@ -54,12 +54,13 @@
 /* **************************************************************
 *  Error Management
 ****************************************************************/
-#define FSE_isError ERR_isError
 #define FSE_STATIC_ASSERT(c)                                   \
 	{                                                      \
 		enum { FSE_static_assert = 1 / (int)(!!(c)) }; \
 	} /* use only *after* variable declarations */
 
+#ifndef PREBOOT
+#define FSE_isError ERR_isError
 /* check and forward error code */
 #define CHECK_F(f)                  \
 	{                           \
@@ -67,6 +68,7 @@
 		if (FSE_isError(e)) \
 			return e;   \
 	}
+#endif
 
 /* **************************************************************
 *  Templates
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index 3fa32f83b2d7..1c2f2dc528dc 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -337,6 +337,9 @@ quiet_cmd_lz4 = LZ4     $@
       cmd_lz4 = { cat $(real-prereqs) | lz4c -l -c1 stdin stdout; \
                   $(size_append); } > $@
 
+quiet_cmd_zstd = ZSTD    $@
+      cmd_zstd = { cat $(real-prereqs) | zstd -19 --zstd=wlog=21; $(size_append); } > $@
+
 # U-Boot mkimage
 # ---------------------------------------------------------------------------
 
diff --git a/usr/Kconfig b/usr/Kconfig
index a6b68503d177..892eb15957db 100644
--- a/usr/Kconfig
+++ b/usr/Kconfig
@@ -106,6 +106,15 @@ config RD_LZ4
 	  Support loading of a LZ4 encoded initial ramdisk or cpio buffer
 	  If unsure, say N.
 
+config RD_ZSTD
+	bool "Support initial ramdisk/ramfs compressed using ZSTD"
+	default y
+	depends on BLK_DEV_INITRD
+	select DECOMPRESS_ZSTD
+	help
+	  Support loading of a ZSTD encoded initial ramdisk or cpio buffer
+	  If unsure, say N.
+
 choice
 	prompt "Built-in initramfs compression mode"
 	depends on INITRAMFS_SOURCE!=""
@@ -214,6 +223,19 @@ config INITRAMFS_COMPRESSION_LZ4
 	  If you choose this, keep in mind that most distros don't provide lz4
 	  by default which could cause a build failure.
 
+config INITRAMFS_COMPRESSION_ZSTD
+	bool "ZSTD"
+	depends on RD_ZSTD
+	help
+	  Its compression ratio is roughly 10% worst than xz, but the
+	  decompression is 10x faster. Currently, this is one of the optimal
+	  algorithms available in the kernel, as there isn't an algorithm,
+	  which would provide a better compression ratio and a shorter
+	  decompression time.
+
+	  If you choose this, keep in mind that you may need to install the zstd
+	  tool to be able to compress the initram.
+
 endchoice
 
 config INITRAMFS_COMPRESSION
@@ -226,10 +248,12 @@ config INITRAMFS_COMPRESSION
 	default ".xz"   if INITRAMFS_COMPRESSION_XZ
 	default ".lzo"  if INITRAMFS_COMPRESSION_LZO
 	default ".lz4"  if INITRAMFS_COMPRESSION_LZ4
+	default ".zst"  if INITRAMFS_COMPRESSION_ZSTD
 	default ".gz"   if RD_GZIP
 	default ".lz4"  if RD_LZ4
 	default ".lzo"  if RD_LZO
 	default ".xz"   if RD_XZ
 	default ".lzma" if RD_LZMA
 	default ".bz2"  if RD_BZIP2
+	default ".zst"  if RD_ZSTD
 	default ""
-- 
2.20.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v2 2/2] x86: Enable support for ZSTD-compressed kernel
  2020-03-16 14:30     ` [PATCH v2 1/2] lib: add " Petr Malat
@ 2020-03-16 14:30       ` Petr Malat
  2020-03-17 21:02       ` [PATCH v2 1/2] lib: add " Kees Cook
  1 sibling, 0 replies; 10+ messages in thread
From: Petr Malat @ 2020-03-16 14:30 UTC (permalink / raw)
  To: linux-kbuild, linux-kernel, x86
  Cc: terrelln, clm, gregkh, keescook, Petr Malat

ZSTD compression ratio is roughly 10% worst than xz, but the
decompression is 10x faster. Currently, this is one of the optimal
algorithms available in the kernel, as there isn't an algorithm,
which would provide a better compression ratio and a shorter
decompression time.

Signed-off-by: Petr Malat <oss@malat.biz>
---
 arch/x86/Kconfig                  | 1 +
 arch/x86/boot/compressed/Makefile | 5 ++++-
 arch/x86/boot/compressed/misc.c   | 4 ++++
 arch/x86/include/asm/boot.h       | 4 ++--
 4 files changed, 11 insertions(+), 3 deletions(-)

diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 5e8949953660..b22312aae674 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -180,6 +180,7 @@ config X86
 	select HAVE_KERNEL_LZMA
 	select HAVE_KERNEL_LZO
 	select HAVE_KERNEL_XZ
+	select HAVE_KERNEL_ZSTD
 	select HAVE_KPROBES
 	select HAVE_KPROBES_ON_FTRACE
 	select HAVE_FUNCTION_ERROR_INJECTION
diff --git a/arch/x86/boot/compressed/Makefile b/arch/x86/boot/compressed/Makefile
index 1dac210f7d44..a87dc1e41772 100644
--- a/arch/x86/boot/compressed/Makefile
+++ b/arch/x86/boot/compressed/Makefile
@@ -24,7 +24,7 @@ OBJECT_FILES_NON_STANDARD	:= y
 KCOV_INSTRUMENT		:= n
 
 targets := vmlinux vmlinux.bin vmlinux.bin.gz vmlinux.bin.bz2 vmlinux.bin.lzma \
-	vmlinux.bin.xz vmlinux.bin.lzo vmlinux.bin.lz4
+	vmlinux.bin.xz vmlinux.bin.lzo vmlinux.bin.lz4 vmlinux.bin.zst
 
 KBUILD_CFLAGS := -m$(BITS) -O2
 KBUILD_CFLAGS += -fno-strict-aliasing $(call cc-option, -fPIE, -fPIC)
@@ -145,6 +145,8 @@ $(obj)/vmlinux.bin.lzo: $(vmlinux.bin.all-y) FORCE
 	$(call if_changed,lzo)
 $(obj)/vmlinux.bin.lz4: $(vmlinux.bin.all-y) FORCE
 	$(call if_changed,lz4)
+$(obj)/vmlinux.bin.zst: $(vmlinux.bin.all-y) FORCE
+	$(call if_changed,zstd)
 
 suffix-$(CONFIG_KERNEL_GZIP)	:= gz
 suffix-$(CONFIG_KERNEL_BZIP2)	:= bz2
@@ -152,6 +154,7 @@ suffix-$(CONFIG_KERNEL_LZMA)	:= lzma
 suffix-$(CONFIG_KERNEL_XZ)	:= xz
 suffix-$(CONFIG_KERNEL_LZO) 	:= lzo
 suffix-$(CONFIG_KERNEL_LZ4) 	:= lz4
+suffix-$(CONFIG_KERNEL_ZSTD) 	:= zst
 
 quiet_cmd_mkpiggy = MKPIGGY $@
       cmd_mkpiggy = $(obj)/mkpiggy $< > $@
diff --git a/arch/x86/boot/compressed/misc.c b/arch/x86/boot/compressed/misc.c
index 9652d5c2afda..39e592d0e0b4 100644
--- a/arch/x86/boot/compressed/misc.c
+++ b/arch/x86/boot/compressed/misc.c
@@ -77,6 +77,10 @@ static int lines, cols;
 #ifdef CONFIG_KERNEL_LZ4
 #include "../../../../lib/decompress_unlz4.c"
 #endif
+
+#ifdef CONFIG_KERNEL_ZSTD
+#include "../../../../lib/decompress_unzstd.c"
+#endif
 /*
  * NOTE: When adding a new decompressor, please update the analysis in
  * ../header.S.
diff --git a/arch/x86/include/asm/boot.h b/arch/x86/include/asm/boot.h
index 680c320363db..9838c183e9a8 100644
--- a/arch/x86/include/asm/boot.h
+++ b/arch/x86/include/asm/boot.h
@@ -24,9 +24,9 @@
 # error "Invalid value for CONFIG_PHYSICAL_ALIGN"
 #endif
 
-#ifdef CONFIG_KERNEL_BZIP2
+#if CONFIG_KERNEL_BZIP2 || CONFIG_KERNEL_ZSTD
 # define BOOT_HEAP_SIZE		0x400000
-#else /* !CONFIG_KERNEL_BZIP2 */
+#else /* !(CONFIG_KERNEL_BZIP2 || CONFIG_KERNEL_ZSTD)  */
 # define BOOT_HEAP_SIZE		 0x10000
 #endif
 
-- 
2.20.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH 1/2] lib: add support for ZSTD-compressed kernel
  2020-03-16 14:07 ` [PATCH " Greg KH
@ 2020-03-16 14:35   ` Petr Malat
  0 siblings, 0 replies; 10+ messages in thread
From: Petr Malat @ 2020-03-16 14:35 UTC (permalink / raw)
  To: Greg KH; +Cc: linux-kernel, linux-kbuild, x86, terrelln, clm, keescook

I have extended the description, thx.
  Petr

On Mon, Mar 16, 2020 at 03:07:26PM +0100, Greg KH wrote:
> On Mon, Mar 16, 2020 at 02:50:24PM +0100, Petr Malat wrote:
> > Add support for extracting ZSTD-compressed kernel images, as well as
> > ZSTD-compressed initramfs.
> > 
> > Signed-off-by: Petr Malat <oss@malat.biz>
> 
> That says _what_ you did here, but not _why_ you did this, or why anyone
> would even want this.
> 
> thanks,
> 
> greg k-h

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 1/2] lib: add support for ZSTD-compressed kernel
  2020-03-16 13:50 [PATCH 1/2] lib: add support for ZSTD-compressed kernel Petr Malat
  2020-03-16 13:50 ` [PATCH 2/2] x86: Enable " Petr Malat
  2020-03-16 14:07 ` [PATCH " Greg KH
@ 2020-03-17 21:00 ` Nick Terrell
  2 siblings, 0 replies; 10+ messages in thread
From: Nick Terrell @ 2020-03-17 21:00 UTC (permalink / raw)
  To: Petr Malat; +Cc: linux-kernel, linux-kbuild, x86, Chris Mason, gregkh, keescook

> On Mar 16, 2020, at 6:50 AM, Petr Malat <oss@malat.biz> wrote:
> 
> Add support for extracting ZSTD-compressed kernel images, as well as
> ZSTD-compressed initramfs.

Hi Petr,

Thanks for putting up this patch!

In the last month I rebased, retested, and refactored my patches [0][1], because
we had a new use case for a zstd compressed initramfs. We have been using
the patches I put up for quite some time internally, so we know they work well.

I will re-submit my patches today, once I write all the commit summaries and a
new cover letter. Thanks for the kick in the butt to resubmit it.

Your patches are missing several things that I have included in my patches [0][1]:
* Invocations of EXPORT_SYMBOL() and MODULE_LICENSE() in the pre-boot
  either emitted warnings or didn’t work in the pre-boot environment, unless that
  has changed.
* The memcpy() inside of ZSTD_copy8() which is the core of zstd’s hot loop gets
  outlined in the x86 preboot environment unless you use __builtin_memcpy().
  This destroys decompression speed.
* unzstd() can use less memory when fill & flush are both NULL by calling
  ZSTD_decompressDCtx().
* ZO_z_extra_bytes needs to be bumped because zstd can overlap more than
  Other compressors. If it isn’t you could corrupt the kernel.

I think it would be better to go with my patches. They have already been tested
in production for both correctness and performance, and I have boot tested on
x86, arm, and aarch64. I will make my case fully when I resubmit my patches.

Thanks again for submitting this, because I have been dawdling!
Nick

[0] https://lore.kernel.org/patchwork/patch/839674/
[1] https://lore.kernel.org/patchwork/patch/839675/

> Signed-off-by: Petr Malat <oss@malat.biz>
> ---
> include/linux/decompress/unzstd.h |  12 +++
> init/Kconfig                      |  15 ++-
> lib/Kconfig                       |   4 +
> lib/Makefile                      |   1 +
> lib/decompress.c                  |   5 +
> lib/decompress_unzstd.c           | 159 ++++++++++++++++++++++++++++++
> lib/zstd/decompress.c             |   2 +
> lib/zstd/fse_decompress.c         |   4 +-
> scripts/Makefile.lib              |   3 +
> usr/Kconfig                       |  24 +++++
> 10 files changed, 227 insertions(+), 2 deletions(-)
> create mode 100644 include/linux/decompress/unzstd.h
> create mode 100644 lib/decompress_unzstd.c
> 
> diff --git a/include/linux/decompress/unzstd.h b/include/linux/decompress/unzstd.h
> new file mode 100644
> index 000000000000..dd2c49d47456
> --- /dev/null
> +++ b/include/linux/decompress/unzstd.h
> @@ -0,0 +1,12 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef DECOMPRESS_UNZSTD_H
> +#define DECOMPRESS_UNZSTD_H
> +
> +int unzstd(unsigned char *inbuf, long len,
> +	long (*fill)(void*, unsigned long),
> +	long (*flush)(void*, unsigned long),
> +	unsigned char *output,
> +	long *pos,
> +	void (*error)(char *x));
> +#endif
> +
> diff --git a/init/Kconfig b/init/Kconfig
> index a34064a031a5..628eb3c290a2 100644
> --- a/init/Kconfig
> +++ b/init/Kconfig
> @@ -172,13 +172,16 @@ config HAVE_KERNEL_LZO
> config HAVE_KERNEL_LZ4
> 	bool
> 
> +config HAVE_KERNEL_ZSTD
> +	bool
> +
> config HAVE_KERNEL_UNCOMPRESSED
> 	bool
> 
> choice
> 	prompt "Kernel compression mode"
> 	default KERNEL_GZIP
> -	depends on HAVE_KERNEL_GZIP || HAVE_KERNEL_BZIP2 || HAVE_KERNEL_LZMA || HAVE_KERNEL_XZ || HAVE_KERNEL_LZO || HAVE_KERNEL_LZ4 || HAVE_KERNEL_UNCOMPRESSED
> +	depends on HAVE_KERNEL_GZIP || HAVE_KERNEL_BZIP2 || HAVE_KERNEL_LZMA || HAVE_KERNEL_XZ || HAVE_KERNEL_LZO || HAVE_KERNEL_LZ4 || HAVE_KERNEL_ZSTD || HAVE_KERNEL_UNCOMPRESSED
> 	help
> 	  The linux kernel is a kind of self-extracting executable.
> 	  Several compression algorithms are available, which differ
> @@ -257,6 +260,16 @@ config KERNEL_LZ4
> 	  is about 8% bigger than LZO. But the decompression speed is
> 	  faster than LZO.
> 
> +config KERNEL_ZSTD
> +	bool "ZSTD"
> +	depends on HAVE_KERNEL_ZSTD
> +	help
> +	  Its compression ratio is roughly 10% worst than xz, but the
> +	  decompression is 10x faster. Currently, this is one of the optimal
> +	  algorithms available in the kernel, as there isn't an algorithm,
> +	  which would provide a better compression ratio and a shorter
> +	  decompression time.
> +
> config KERNEL_UNCOMPRESSED
> 	bool "None"
> 	depends on HAVE_KERNEL_UNCOMPRESSED
> diff --git a/lib/Kconfig b/lib/Kconfig
> index 6e790dc55c5b..df301bd888d7 100644
> --- a/lib/Kconfig
> +++ b/lib/Kconfig
> @@ -329,6 +329,10 @@ config DECOMPRESS_LZ4
> 	select LZ4_DECOMPRESS
> 	tristate
> 
> +config DECOMPRESS_ZSTD
> +	select ZSTD_DECOMPRESS
> +	tristate
> +
> #
> # Generic allocator support is selected if needed
> #
> diff --git a/lib/Makefile b/lib/Makefile
> index 93217d44237f..3ab9f4c31f8b 100644
> --- a/lib/Makefile
> +++ b/lib/Makefile
> @@ -158,6 +158,7 @@ lib-$(CONFIG_DECOMPRESS_LZMA) += decompress_unlzma.o
> lib-$(CONFIG_DECOMPRESS_XZ) += decompress_unxz.o
> lib-$(CONFIG_DECOMPRESS_LZO) += decompress_unlzo.o
> lib-$(CONFIG_DECOMPRESS_LZ4) += decompress_unlz4.o
> +lib-$(CONFIG_DECOMPRESS_ZSTD) += decompress_unzstd.o
> 
> obj-$(CONFIG_TEXTSEARCH) += textsearch.o
> obj-$(CONFIG_TEXTSEARCH_KMP) += ts_kmp.o
> diff --git a/lib/decompress.c b/lib/decompress.c
> index 857ab1af1ef3..ab3fc90ffc64 100644
> --- a/lib/decompress.c
> +++ b/lib/decompress.c
> @@ -13,6 +13,7 @@
> #include <linux/decompress/inflate.h>
> #include <linux/decompress/unlzo.h>
> #include <linux/decompress/unlz4.h>
> +#include <linux/decompress/unzstd.h>
> 
> #include <linux/types.h>
> #include <linux/string.h>
> @@ -37,6 +38,9 @@
> #ifndef CONFIG_DECOMPRESS_LZ4
> # define unlz4 NULL
> #endif
> +#ifndef CONFIG_DECOMPRESS_ZSTD
> +# define unzstd NULL
> +#endif
> 
> struct compress_format {
> 	unsigned char magic[2];
> @@ -52,6 +56,7 @@ static const struct compress_format compressed_formats[] __initconst = {
> 	{ {0xfd, 0x37}, "xz", unxz },
> 	{ {0x89, 0x4c}, "lzo", unlzo },
> 	{ {0x02, 0x21}, "lz4", unlz4 },
> +	{ {0x28, 0xb5}, "zstd", unzstd },
> 	{ {0, 0}, NULL, NULL }
> };
> 
> diff --git a/lib/decompress_unzstd.c b/lib/decompress_unzstd.c
> new file mode 100644
> index 000000000000..5af647a49885
> --- /dev/null
> +++ b/lib/decompress_unzstd.c
> @@ -0,0 +1,159 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Wrapper for decompressing ZSTD-compressed kernel, initramfs, and initrd
> + * Based on decompress_unlz4.c
> + *
> + * Copyright (C) 2020, Petr Malat <oss@malat.biz>
> + */
> +
> +#ifdef STATIC
> +#define PREBOOT
> +#include "zstd/zstd_internal.h"
> +#include "zstd/huf_decompress.c"
> +#include "zstd/entropy_common.c"
> +#include "zstd/fse_decompress.c"
> +#include "zstd/zstd_common.c"
> +#include "zstd/decompress.c"
> +#include "xxhash.c"
> +#else
> +#include <linux/decompress/unzstd.h>
> +#include <linux/zstd.h>
> +#endif
> +#include <linux/types.h>
> +#include <linux/decompress/mm.h>
> +#include <linux/compiler.h>
> +
> +STATIC inline int INIT unzstd(u8 *input, long in_len,
> +				long (*fill)(void *, unsigned long),
> +				long (*flush)(void *, unsigned long),
> +				u8 *output, long *posp,
> +				void (*error)(char *x))
> +{
> +	int ret = -1, ws = 1 << ZSTD_WINDOWLOG_MAX;
> +	u8 *inp, *outp;
> +	ZSTD_DStream *zstd;
> +	void *workspace;
> +	size_t workspace_size;
> +	ZSTD_outBuffer out;
> +	ZSTD_inBuffer in;
> +	unsigned long out_len;
> +	unsigned long pos;
> +
> +	if (output) {
> +		out_len = ULONG_MAX; // Caller knows data will fit
> +		outp = output;
> +	} else if (!flush) {
> +		error("NULL output pointer and no flush function provided");
> +		goto exit_0;
> +	} else {
> +		out_len = ZSTD_DStreamOutSize();
> +		outp = large_malloc(out_len);
> +		if (!outp) {
> +			error("Could not allocate output buffer");
> +			goto exit_0;
> +		}
> +	}
> +
> +	if (input && fill) {
> +		error("Both input pointer and fill function provided,");
> +		goto exit_1;
> +	} else if (input) {
> +		ZSTD_frameParams p;
> +
> +		inp = input;
> +		if (!ZSTD_getFrameParams(&p, input, in_len))
> +			ws = p.windowSize;
> +	} else if (!fill) {
> +		error("NULL input pointer and missing fill function");
> +		goto exit_1;
> +	} else {
> +		in_len = ZSTD_DStreamInSize();
> +		inp = large_malloc(in_len);
> +		if (!inp) {
> +			error("Could not allocate input buffer");
> +			goto exit_1;
> +		}
> +	}
> +
> +	workspace_size = ZSTD_DStreamWorkspaceBound(ws);
> +	workspace = large_malloc(workspace_size);
> +	if (!workspace) {
> +		error("Could not allocate workspace");
> +		goto exit_2;
> +	}
> +
> +	zstd = ZSTD_initDStream(ws, workspace, workspace_size);
> +	if (!zstd) {
> +		error("Could not initialize ZSTD");
> +		goto exit_3;
> +	}
> +
> +	in.src = inp;
> +	in.size = in_len;
> +	in.pos = 0;
> +	if (posp)
> +		*posp = 0;
> +
> +	for (;;) {
> +		if (fill) {
> +			in.size = fill(inp, in_len);
> +			if (in.size == 0)
> +				break;
> +		} else if (in.size == in.pos) {
> +			break;
> +		}
> +init:		out.dst = outp;
> +		out.size = out_len;
> +		out.pos = 0;
> +		pos = in.pos;
> +
> +		ret = ZSTD_decompressStream(zstd, &out, &in);
> +		if (posp)
> +			*posp += in.pos - pos;
> +		if (ZSTD_isError(ret)) {
> +			error("Decompression failed");
> +			ret = -EIO;
> +			goto exit_3;
> +		}
> +
> +		if (flush && out.pos) {
> +			if (flush(out.dst, out.pos) != out.pos) {
> +				ret = -EIO;
> +				goto exit_3;
> +			}
> +			goto init;
> +		}
> +
> +		if (ret == 0) {
> +			ret = ZSTD_resetDStream(zstd);
> +			if (ZSTD_isError(ret)) {
> +				ret = -EIO;
> +				goto exit_3;
> +			}
> +		}
> +		if (in.pos < in.size)
> +			goto init_out;
> +	}
> +
> +	ret = 0;
> +
> +exit_3:	large_free(workspace);
> +exit_2:	if (!input)
> +		large_free(inp);
> +exit_1:	if (!output)
> +		large_free(outp);
> +exit_0:	return ret;
> +}
> +
> +#ifdef PREBOOT
> +STATIC int INIT __decompress(unsigned char *buf, long in_len,
> +			      long (*fill)(void*, unsigned long),
> +			      long (*flush)(void*, unsigned long),
> +			      unsigned char *output, long out_len,
> +			      long *posp,
> +			      void (*error)(char *x)
> +	)
> +{
> +	return unzstd(buf, in_len, fill, flush, output, posp, error);
> +}
> +#endif
> diff --git a/lib/zstd/decompress.c b/lib/zstd/decompress.c
> index 269ee9a796c1..6a5e1ce22719 100644
> --- a/lib/zstd/decompress.c
> +++ b/lib/zstd/decompress.c
> @@ -42,9 +42,11 @@
> /*-*************************************
> *  Macros
> ***************************************/
> +#ifndef PREBOOT
> #define ZSTD_isError ERR_isError /* for inlining */
> #define FSE_isError ERR_isError
> #define HUF_isError ERR_isError
> +#endif
> 
> /*_*******************************************************
> *  Memory operations
> diff --git a/lib/zstd/fse_decompress.c b/lib/zstd/fse_decompress.c
> index a84300e5a013..bd4e9c891d96 100644
> --- a/lib/zstd/fse_decompress.c
> +++ b/lib/zstd/fse_decompress.c
> @@ -54,12 +54,13 @@
> /* **************************************************************
> *  Error Management
> ****************************************************************/
> -#define FSE_isError ERR_isError
> #define FSE_STATIC_ASSERT(c)                                   \
> 	{                                                      \
> 		enum { FSE_static_assert = 1 / (int)(!!(c)) }; \
> 	} /* use only *after* variable declarations */
> 
> +#ifndef PREBOOT
> +#define FSE_isError ERR_isError
> /* check and forward error code */
> #define CHECK_F(f)                  \
> 	{                           \
> @@ -67,6 +68,7 @@
> 		if (FSE_isError(e)) \
> 			return e;   \
> 	}
> +#endif
> 
> /* **************************************************************
> *  Templates
> diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
> index 3fa32f83b2d7..1c2f2dc528dc 100644
> --- a/scripts/Makefile.lib
> +++ b/scripts/Makefile.lib
> @@ -337,6 +337,9 @@ quiet_cmd_lz4 = LZ4     $@
>       cmd_lz4 = { cat $(real-prereqs) | lz4c -l -c1 stdin stdout; \
>                   $(size_append); } > $@
> 
> +quiet_cmd_zstd = ZSTD    $@
> +      cmd_zstd = { cat $(real-prereqs) | zstd -19 --zstd=wlog=21; $(size_append); } > $@
> +
> # U-Boot mkimage
> # ---------------------------------------------------------------------------
> 
> diff --git a/usr/Kconfig b/usr/Kconfig
> index a6b68503d177..892eb15957db 100644
> --- a/usr/Kconfig
> +++ b/usr/Kconfig
> @@ -106,6 +106,15 @@ config RD_LZ4
> 	  Support loading of a LZ4 encoded initial ramdisk or cpio buffer
> 	  If unsure, say N.
> 
> +config RD_ZSTD
> +	bool "Support initial ramdisk/ramfs compressed using ZSTD"
> +	default y
> +	depends on BLK_DEV_INITRD
> +	select DECOMPRESS_ZSTD
> +	help
> +	  Support loading of a ZSTD encoded initial ramdisk or cpio buffer
> +	  If unsure, say N.
> +
> choice
> 	prompt "Built-in initramfs compression mode"
> 	depends on INITRAMFS_SOURCE!=""
> @@ -214,6 +223,19 @@ config INITRAMFS_COMPRESSION_LZ4
> 	  If you choose this, keep in mind that most distros don't provide lz4
> 	  by default which could cause a build failure.
> 
> +config INITRAMFS_COMPRESSION_ZSTD
> +	bool "ZSTD"
> +	depends on RD_ZSTD
> +	help
> +	  Its compression ratio is roughly 10% worst than xz, but the
> +	  decompression is 10x faster. Currently, this is one of the optimal
> +	  algorithms available in the kernel, as there isn't an algorithm,
> +	  which would provide a better compression ratio and a shorter
> +	  decompression time.
> +
> +	  If you choose this, keep in mind that you may need to install the zstd
> +	  tool to be able to compress the initram.
> +
> endchoice
> 
> config INITRAMFS_COMPRESSION
> @@ -226,10 +248,12 @@ config INITRAMFS_COMPRESSION
> 	default ".xz"   if INITRAMFS_COMPRESSION_XZ
> 	default ".lzo"  if INITRAMFS_COMPRESSION_LZO
> 	default ".lz4"  if INITRAMFS_COMPRESSION_LZ4
> +	default ".zst"  if INITRAMFS_COMPRESSION_ZSTD
> 	default ".gz"   if RD_GZIP
> 	default ".lz4"  if RD_LZ4
> 	default ".lzo"  if RD_LZO
> 	default ".xz"   if RD_XZ
> 	default ".lzma" if RD_LZMA
> 	default ".bz2"  if RD_BZIP2
> +	default ".zst"  if RD_ZSTD
> 	default ""
> -- 
> 2.20.1
> 


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2 1/2] lib: add support for ZSTD-compressed kernel
  2020-03-16 14:30     ` [PATCH v2 1/2] lib: add " Petr Malat
  2020-03-16 14:30       ` [PATCH v2 2/2] x86: Enable " Petr Malat
@ 2020-03-17 21:02       ` Kees Cook
  1 sibling, 0 replies; 10+ messages in thread
From: Kees Cook @ 2020-03-17 21:02 UTC (permalink / raw)
  To: Petr Malat; +Cc: linux-kbuild, linux-kernel, x86, terrelln, clm, gregkh

On Mon, Mar 16, 2020 at 03:30:16PM +0100, Petr Malat wrote:
> Add support for extracting ZSTD-compressed kernel images, as well as
> ZSTD-compressed initramfs.
> 
> ZSTD compression ratio is roughly 10% worst than xz, but the
> decompression is 10x faster. Currently, this is one of the optimal
> algorithms available in the kernel, as there isn't an algorithm,
> which would provide a better compression ratio and a shorter
> decompression time.

It might be worth splitting this patch into 2 pieces: one to extract the
logic (which is most of the patch and the description), and then a
separate one to add the initrd support (since that touches logically
separate things like a new Makefile cmd, etc etc). That patch
description could talk about requiring the "zstd" command line tool,
etc. (Which should likely be mentioned in the Kconfig description too.

More notes below...

> 
> Signed-off-by: Petr Malat <oss@malat.biz>
> ---
>  include/linux/decompress/unzstd.h |  12 +++
>  init/Kconfig                      |  15 ++-
>  lib/Kconfig                       |   4 +
>  lib/Makefile                      |   1 +
>  lib/decompress.c                  |   5 +
>  lib/decompress_unzstd.c           | 159 ++++++++++++++++++++++++++++++
>  lib/zstd/decompress.c             |   2 +
>  lib/zstd/fse_decompress.c         |   4 +-
>  scripts/Makefile.lib              |   3 +
>  usr/Kconfig                       |  24 +++++
>  10 files changed, 227 insertions(+), 2 deletions(-)
>  create mode 100644 include/linux/decompress/unzstd.h
>  create mode 100644 lib/decompress_unzstd.c
> 
> diff --git a/include/linux/decompress/unzstd.h b/include/linux/decompress/unzstd.h
> new file mode 100644
> index 000000000000..dd2c49d47456
> --- /dev/null
> +++ b/include/linux/decompress/unzstd.h
> @@ -0,0 +1,12 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef DECOMPRESS_UNZSTD_H
> +#define DECOMPRESS_UNZSTD_H
> +
> +int unzstd(unsigned char *inbuf, long len,
> +	long (*fill)(void*, unsigned long),
> +	long (*flush)(void*, unsigned long),
> +	unsigned char *output,
> +	long *pos,
> +	void (*error)(char *x));
> +#endif
> +
> diff --git a/init/Kconfig b/init/Kconfig
> index a34064a031a5..628eb3c290a2 100644
> --- a/init/Kconfig
> +++ b/init/Kconfig
> @@ -172,13 +172,16 @@ config HAVE_KERNEL_LZO
>  config HAVE_KERNEL_LZ4
>  	bool
>  
> +config HAVE_KERNEL_ZSTD
> +	bool

The HAVE_KERNEL_ZSTD changes should be put in the kernel enablement patch.

> +
>  config HAVE_KERNEL_UNCOMPRESSED
>  	bool
>  
>  choice
>  	prompt "Kernel compression mode"
>  	default KERNEL_GZIP
> -	depends on HAVE_KERNEL_GZIP || HAVE_KERNEL_BZIP2 || HAVE_KERNEL_LZMA || HAVE_KERNEL_XZ || HAVE_KERNEL_LZO || HAVE_KERNEL_LZ4 || HAVE_KERNEL_UNCOMPRESSED
> +	depends on HAVE_KERNEL_GZIP || HAVE_KERNEL_BZIP2 || HAVE_KERNEL_LZMA || HAVE_KERNEL_XZ || HAVE_KERNEL_LZO || HAVE_KERNEL_LZ4 || HAVE_KERNEL_ZSTD || HAVE_KERNEL_UNCOMPRESSED
>  	help
>  	  The linux kernel is a kind of self-extracting executable.
>  	  Several compression algorithms are available, which differ
> @@ -257,6 +260,16 @@ config KERNEL_LZ4
>  	  is about 8% bigger than LZO. But the decompression speed is
>  	  faster than LZO.
>  
> +config KERNEL_ZSTD
> +	bool "ZSTD"
> +	depends on HAVE_KERNEL_ZSTD
> +	help
> +	  Its compression ratio is roughly 10% worst than xz, but the
> +	  decompression is 10x faster. Currently, this is one of the optimal
> +	  algorithms available in the kernel, as there isn't an algorithm,
> +	  which would provide a better compression ratio and a shorter
> +	  decompression time.
> +
>  config KERNEL_UNCOMPRESSED
>  	bool "None"
>  	depends on HAVE_KERNEL_UNCOMPRESSED
> diff --git a/lib/Kconfig b/lib/Kconfig
> index 6e790dc55c5b..df301bd888d7 100644
> --- a/lib/Kconfig
> +++ b/lib/Kconfig
> @@ -329,6 +329,10 @@ config DECOMPRESS_LZ4
>  	select LZ4_DECOMPRESS
>  	tristate
>  
> +config DECOMPRESS_ZSTD
> +	select ZSTD_DECOMPRESS
> +	tristate
> +
>  #
>  # Generic allocator support is selected if needed
>  #
> diff --git a/lib/Makefile b/lib/Makefile
> index 93217d44237f..3ab9f4c31f8b 100644
> --- a/lib/Makefile
> +++ b/lib/Makefile
> @@ -158,6 +158,7 @@ lib-$(CONFIG_DECOMPRESS_LZMA) += decompress_unlzma.o
>  lib-$(CONFIG_DECOMPRESS_XZ) += decompress_unxz.o
>  lib-$(CONFIG_DECOMPRESS_LZO) += decompress_unlzo.o
>  lib-$(CONFIG_DECOMPRESS_LZ4) += decompress_unlz4.o
> +lib-$(CONFIG_DECOMPRESS_ZSTD) += decompress_unzstd.o
>  
>  obj-$(CONFIG_TEXTSEARCH) += textsearch.o
>  obj-$(CONFIG_TEXTSEARCH_KMP) += ts_kmp.o
> diff --git a/lib/decompress.c b/lib/decompress.c
> index 857ab1af1ef3..ab3fc90ffc64 100644
> --- a/lib/decompress.c
> +++ b/lib/decompress.c
> @@ -13,6 +13,7 @@
>  #include <linux/decompress/inflate.h>
>  #include <linux/decompress/unlzo.h>
>  #include <linux/decompress/unlz4.h>
> +#include <linux/decompress/unzstd.h>
>  
>  #include <linux/types.h>
>  #include <linux/string.h>
> @@ -37,6 +38,9 @@
>  #ifndef CONFIG_DECOMPRESS_LZ4
>  # define unlz4 NULL
>  #endif
> +#ifndef CONFIG_DECOMPRESS_ZSTD
> +# define unzstd NULL
> +#endif
>  
>  struct compress_format {
>  	unsigned char magic[2];
> @@ -52,6 +56,7 @@ static const struct compress_format compressed_formats[] __initconst = {
>  	{ {0xfd, 0x37}, "xz", unxz },
>  	{ {0x89, 0x4c}, "lzo", unlzo },
>  	{ {0x02, 0x21}, "lz4", unlz4 },
> +	{ {0x28, 0xb5}, "zstd", unzstd },
>  	{ {0, 0}, NULL, NULL }
>  };
>  
> diff --git a/lib/decompress_unzstd.c b/lib/decompress_unzstd.c
> new file mode 100644
> index 000000000000..b8be89250033
> --- /dev/null
> +++ b/lib/decompress_unzstd.c
> @@ -0,0 +1,159 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Wrapper for decompressing ZSTD-compressed kernel, initramfs, and initrd
> + * Based on decompress_unlz4.c
> + *
> + * Copyright (C) 2020, Petr Malat <oss@malat.biz>
> + */
> +
> +#ifdef STATIC
> +#define PREBOOT

Can you please namespace PREBOOT (as done for other decompressors),
as maybe ZSTD_PREBOOT?

> +#include "zstd/zstd_internal.h"
> +#include "zstd/huf_decompress.c"
> +#include "zstd/entropy_common.c"
> +#include "zstd/fse_decompress.c"
> +#include "zstd/zstd_common.c"
> +#include "zstd/decompress.c"
> +#include "xxhash.c"
> +#else
> +#include <linux/decompress/unzstd.h>
> +#include <linux/zstd.h>
> +#endif
> +#include <linux/types.h>
> +#include <linux/decompress/mm.h>
> +#include <linux/compiler.h>
> +
> +STATIC inline int INIT unzstd(u8 *input, long in_len,
> +				long (*fill)(void *, unsigned long),
> +				long (*flush)(void *, unsigned long),
> +				u8 *output, long *posp,
> +				void (*error)(char *x))
> +{
> +	int ret = -1, ws = 1 << ZSTD_WINDOWLOG_MAX;
> +	u8 *inp, *outp;
> +	ZSTD_DStream *zstd;
> +	void *workspace;
> +	size_t workspace_size;
> +	ZSTD_outBuffer out;
> +	ZSTD_inBuffer in;
> +	unsigned long out_len;
> +	unsigned long pos;
> +
> +	if (output) {
> +		out_len = ULONG_MAX; // Caller knows data will fit
> +		outp = output;
> +	} else if (!flush) {
> +		error("NULL output pointer and no flush function provided");
> +		goto exit_0;
> +	} else {
> +		out_len = ZSTD_DStreamOutSize();
> +		outp = large_malloc(out_len);
> +		if (!outp) {
> +			error("Could not allocate output buffer");
> +			goto exit_0;
> +		}
> +	}
> +
> +	if (input && fill) {
> +		error("Both input pointer and fill function provided,");
> +		goto exit_1;
> +	} else if (input) {
> +		ZSTD_frameParams p;
> +
> +		inp = input;
> +		if (!ZSTD_getFrameParams(&p, input, in_len))
> +			ws = p.windowSize;
> +	} else if (!fill) {
> +		error("NULL input pointer and missing fill function");
> +		goto exit_1;
> +	} else {
> +		in_len = ZSTD_DStreamInSize();
> +		inp = large_malloc(in_len);
> +		if (!inp) {
> +			error("Could not allocate input buffer");
> +			goto exit_1;
> +		}
> +	}
> +
> +	workspace_size = ZSTD_DStreamWorkspaceBound(ws);
> +	workspace = large_malloc(workspace_size);
> +	if (!workspace) {
> +		error("Could not allocate workspace");
> +		goto exit_2;
> +	}
> +
> +	zstd = ZSTD_initDStream(ws, workspace, workspace_size);
> +	if (!zstd) {
> +		error("Could not initialize ZSTD");
> +		goto exit_3;
> +	}
> +
> +	in.src = inp;
> +	in.size = in_len;
> +	in.pos = 0;
> +	if (posp)
> +		*posp = 0;
> +
> +	for (;;) {
> +		if (fill) {
> +			in.size = fill(inp, in_len);
> +			if (in.size == 0)
> +				break;
> +		} else if (in.size == in.pos) {
> +			break;
> +		}
> +init:		out.dst = outp;
> +		out.size = out_len;
> +		out.pos = 0;
> +		pos = in.pos;
> +
> +		ret = ZSTD_decompressStream(zstd, &out, &in);
> +		if (posp)
> +			*posp += in.pos - pos;
> +		if (ZSTD_isError(ret)) {
> +			error("Decompression failed");
> +			ret = -EIO;
> +			goto exit_3;
> +		}
> +
> +		if (flush && out.pos) {
> +			if (flush(out.dst, out.pos) != out.pos) {
> +				ret = -EIO;
> +				goto exit_3;
> +			}
> +			goto init;
> +		}
> +
> +		if (ret == 0) {
> +			ret = ZSTD_resetDStream(zstd);
> +			if (ZSTD_isError(ret)) {
> +				ret = -EIO;
> +				goto exit_3;
> +			}
> +		}
> +		if (in.pos < in.size)
> +			goto init;
> +	}
> +
> +	ret = 0;
> +
> +exit_3:	large_free(workspace);
> +exit_2:	if (!input)
> +		large_free(inp);
> +exit_1:	if (!output)
> +		large_free(outp);
> +exit_0:	return ret;
> +}
> +
> +#ifdef PREBOOT
> +STATIC int INIT __decompress(unsigned char *buf, long in_len,
> +			      long (*fill)(void*, unsigned long),
> +			      long (*flush)(void*, unsigned long),
> +			      unsigned char *output, long out_len,
> +			      long *posp,
> +			      void (*error)(char *x)
> +	)
> +{
> +	return unzstd(buf, in_len, fill, flush, output, posp, error);
> +}
> +#endif
> diff --git a/lib/zstd/decompress.c b/lib/zstd/decompress.c
> index 269ee9a796c1..6a5e1ce22719 100644
> --- a/lib/zstd/decompress.c
> +++ b/lib/zstd/decompress.c
> @@ -42,9 +42,11 @@
>  /*-*************************************
>  *  Macros
>  ***************************************/
> +#ifndef PREBOOT
>  #define ZSTD_isError ERR_isError /* for inlining */
>  #define FSE_isError ERR_isError
>  #define HUF_isError ERR_isError
> +#endif
>  
>  /*_*******************************************************
>  *  Memory operations
> diff --git a/lib/zstd/fse_decompress.c b/lib/zstd/fse_decompress.c
> index a84300e5a013..bd4e9c891d96 100644
> --- a/lib/zstd/fse_decompress.c
> +++ b/lib/zstd/fse_decompress.c
> @@ -54,12 +54,13 @@
>  /* **************************************************************
>  *  Error Management
>  ****************************************************************/
> -#define FSE_isError ERR_isError
>  #define FSE_STATIC_ASSERT(c)                                   \
>  	{                                                      \
>  		enum { FSE_static_assert = 1 / (int)(!!(c)) }; \
>  	} /* use only *after* variable declarations */
>  
> +#ifndef PREBOOT
> +#define FSE_isError ERR_isError
>  /* check and forward error code */
>  #define CHECK_F(f)                  \
>  	{                           \
> @@ -67,6 +68,7 @@
>  		if (FSE_isError(e)) \
>  			return e;   \
>  	}
> +#endif
>  
>  /* **************************************************************
>  *  Templates
> diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
> index 3fa32f83b2d7..1c2f2dc528dc 100644
> --- a/scripts/Makefile.lib
> +++ b/scripts/Makefile.lib
> @@ -337,6 +337,9 @@ quiet_cmd_lz4 = LZ4     $@
>        cmd_lz4 = { cat $(real-prereqs) | lz4c -l -c1 stdin stdout; \
>                    $(size_append); } > $@
>  
> +quiet_cmd_zstd = ZSTD    $@
> +      cmd_zstd = { cat $(real-prereqs) | zstd -19 --zstd=wlog=21; $(size_append); } > $@
> +
>  # U-Boot mkimage
>  # ---------------------------------------------------------------------------
>  
> diff --git a/usr/Kconfig b/usr/Kconfig
> index a6b68503d177..892eb15957db 100644
> --- a/usr/Kconfig
> +++ b/usr/Kconfig
> @@ -106,6 +106,15 @@ config RD_LZ4
>  	  Support loading of a LZ4 encoded initial ramdisk or cpio buffer
>  	  If unsure, say N.
>  
> +config RD_ZSTD
> +	bool "Support initial ramdisk/ramfs compressed using ZSTD"
> +	default y
> +	depends on BLK_DEV_INITRD
> +	select DECOMPRESS_ZSTD
> +	help
> +	  Support loading of a ZSTD encoded initial ramdisk or cpio buffer
> +	  If unsure, say N.
> +
>  choice
>  	prompt "Built-in initramfs compression mode"
>  	depends on INITRAMFS_SOURCE!=""
> @@ -214,6 +223,19 @@ config INITRAMFS_COMPRESSION_LZ4
>  	  If you choose this, keep in mind that most distros don't provide lz4
>  	  by default which could cause a build failure.
>  
> +config INITRAMFS_COMPRESSION_ZSTD
> +	bool "ZSTD"
> +	depends on RD_ZSTD
> +	help
> +	  Its compression ratio is roughly 10% worst than xz, but the
> +	  decompression is 10x faster. Currently, this is one of the optimal
> +	  algorithms available in the kernel, as there isn't an algorithm,
> +	  which would provide a better compression ratio and a shorter
> +	  decompression time.
> +
> +	  If you choose this, keep in mind that you may need to install the zstd
> +	  tool to be able to compress the initram.
> +
>  endchoice
>  
>  config INITRAMFS_COMPRESSION
> @@ -226,10 +248,12 @@ config INITRAMFS_COMPRESSION
>  	default ".xz"   if INITRAMFS_COMPRESSION_XZ
>  	default ".lzo"  if INITRAMFS_COMPRESSION_LZO
>  	default ".lz4"  if INITRAMFS_COMPRESSION_LZ4
> +	default ".zst"  if INITRAMFS_COMPRESSION_ZSTD
>  	default ".gz"   if RD_GZIP
>  	default ".lz4"  if RD_LZ4
>  	default ".lzo"  if RD_LZO
>  	default ".xz"   if RD_XZ
>  	default ".lzma" if RD_LZMA
>  	default ".bz2"  if RD_BZIP2
> +	default ".zst"  if RD_ZSTD
>  	default ""
> -- 
> 2.20.1
> 

The rest looks good (though as mentioned, I think splitting this from
initrd enablement makes sense).

-- 
Kees Cook

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 1/2] lib: Add support for ZSTD-compressed kernel
  2017-10-12 22:11 [PATCH v2 0/2] Add " Nick Terrell
@ 2017-10-12 22:11 ` Nick Terrell
  0 siblings, 0 replies; 10+ messages in thread
From: Nick Terrell @ 2017-10-12 22:11 UTC (permalink / raw)
  To: Nick Terrell
  Cc: Thomas Gleixner, Ingo Molnar, H. Peter Anvin, linux-kernel, x86,
	kernel-team, Chris Mason, Yann Collet, Rene Rebe, Adam Borowski

Add support for extracting ZSTD-compressed kernel images, as well as
ZSTD-compressed ramdisk images in the kernel boot process.

When neither `fill' nor `flush' are used, the decompression function
requires a constant amount of memory (192 KB is sufficient). When either
is used the decompression function requires memory proportional to the
window size used during compression, which is limited to 8 MB. The maximum
memory usage is just over 8 MB.

Fix up lib/zstd and lib/xxhash.c for the preboot environment. They avoid
declaring themselves as modules. A memcpy() call needs to be a
__builtin_memcpy() for performance. The gcc-7.1 bug in ZSTD_wildcopy() was
fixed in gcc-7.2, so it can be gated, since it hurts performance.

Signed-off-by: Nick Terrell <terrelln@fb.com>
---
v1 -> v2:
- Fix performance bug in zstd decompression
- Gate gcc bug workaround to buggy versions

 include/linux/decompress/unzstd.h |  26 +++
 init/Kconfig                      |  14 +-
 lib/Kconfig                       |   4 +
 lib/Makefile                      |   1 +
 lib/decompress.c                  |   5 +
 lib/decompress_unzstd.c           | 341 ++++++++++++++++++++++++++++++++++++++
 lib/xxhash.c                      |  21 ++-
 lib/zstd/decompress.c             |   2 +
 lib/zstd/fse_decompress.c         |   9 +-
 lib/zstd/zstd_internal.h          |  10 +-
 scripts/Makefile.lib              |  15 ++
 usr/Kconfig                       |  22 +++
 12 files changed, 451 insertions(+), 19 deletions(-)
 create mode 100644 include/linux/decompress/unzstd.h
 create mode 100644 lib/decompress_unzstd.c

diff --git a/include/linux/decompress/unzstd.h b/include/linux/decompress/unzstd.h
new file mode 100644
index 0000000..6f3022c
--- /dev/null
+++ b/include/linux/decompress/unzstd.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 Facebook
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef LINUX_DECOMPRESS_UNZSTD_H
+#define LINUX_DECOMPRESS_UNZSTD_H
+
+int unzstd(unsigned char *inbuf, long len,
+	   long (*fill)(void*, unsigned long),
+	   long (*flush)(void*, unsigned long),
+	   unsigned char *output,
+	   long *pos,
+	   void (*error_fn)(char *x));
+#endif
diff --git a/init/Kconfig b/init/Kconfig
index 78cb246..3caa314 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -127,10 +127,13 @@ config HAVE_KERNEL_LZO
 config HAVE_KERNEL_LZ4
 	bool
 
+config HAVE_KERNEL_ZSTD
+	bool
+
 choice
 	prompt "Kernel compression mode"
 	default KERNEL_GZIP
-	depends on HAVE_KERNEL_GZIP || HAVE_KERNEL_BZIP2 || HAVE_KERNEL_LZMA || HAVE_KERNEL_XZ || HAVE_KERNEL_LZO || HAVE_KERNEL_LZ4
+	depends on HAVE_KERNEL_GZIP || HAVE_KERNEL_BZIP2 || HAVE_KERNEL_LZMA || HAVE_KERNEL_XZ || HAVE_KERNEL_LZO || HAVE_KERNEL_LZ4 || HAVE_KERNEL_ZSTD
 	help
 	  The linux kernel is a kind of self-extracting executable.
 	  Several compression algorithms are available, which differ
@@ -209,6 +212,15 @@ config KERNEL_LZ4
 	  is about 8% bigger than LZO. But the decompression speed is
 	  faster than LZO.
 
+config KERNEL_ZSTD
+	bool "ZSTD"
+	depends on HAVE_KERNEL_ZSTD
+	help
+	  ZSTD is a compression algorithm targeting intermediate compression
+	  with fast decompression speed. It will compress better than GZIP and
+	  decompress around the same speed as LZO, but slower than LZ4. You
+	  will need at least 192 KB RAM or more for booting.
+
 endchoice
 
 config DEFAULT_HOSTNAME
diff --git a/lib/Kconfig b/lib/Kconfig
index b1445b2..4d32bec 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -285,6 +285,10 @@ config DECOMPRESS_LZ4
 	select LZ4_DECOMPRESS
 	tristate
 
+config DECOMPRESS_ZSTD
+	select ZSTD_DECOMPRESS
+	tristate
+
 #
 # Generic allocator support is selected if needed
 #
diff --git a/lib/Makefile b/lib/Makefile
index dafa796..8afcdc3 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -128,6 +128,7 @@ lib-$(CONFIG_DECOMPRESS_LZMA) += decompress_unlzma.o
 lib-$(CONFIG_DECOMPRESS_XZ) += decompress_unxz.o
 lib-$(CONFIG_DECOMPRESS_LZO) += decompress_unlzo.o
 lib-$(CONFIG_DECOMPRESS_LZ4) += decompress_unlz4.o
+lib-$(CONFIG_DECOMPRESS_ZSTD) += decompress_unzstd.o
 
 obj-$(CONFIG_TEXTSEARCH) += textsearch.o
 obj-$(CONFIG_TEXTSEARCH_KMP) += ts_kmp.o
diff --git a/lib/decompress.c b/lib/decompress.c
index 62696df..b17bb07 100644
--- a/lib/decompress.c
+++ b/lib/decompress.c
@@ -12,6 +12,7 @@
 #include <linux/decompress/inflate.h>
 #include <linux/decompress/unlzo.h>
 #include <linux/decompress/unlz4.h>
+#include <linux/decompress/unzstd.h>
 
 #include <linux/types.h>
 #include <linux/string.h>
@@ -36,6 +37,9 @@
 #ifndef CONFIG_DECOMPRESS_LZ4
 # define unlz4 NULL
 #endif
+#ifndef CONFIG_DECOMPRESS_ZSTD
+# define unzstd NULL
+#endif
 
 struct compress_format {
 	unsigned char magic[2];
@@ -51,6 +55,7 @@ static const struct compress_format compressed_formats[] __initconst = {
 	{ {0xfd, 0x37}, "xz", unxz },
 	{ {0x89, 0x4c}, "lzo", unlzo },
 	{ {0x02, 0x21}, "lz4", unlz4 },
+	{ {0x28, 0xb5}, "zstd", unzstd },
 	{ {0, 0}, NULL, NULL }
 };
 
diff --git a/lib/decompress_unzstd.c b/lib/decompress_unzstd.c
new file mode 100644
index 0000000..84315dc
--- /dev/null
+++ b/lib/decompress_unzstd.c
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2017 Facebook
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Important notes about in-place decompression
+ *
+ * At least on x86, the kernel is decompressed in place: the compressed data
+ * is placed to the end of the output buffer, and the decompressor overwrites
+ * most of the compressed data. There must be enough safety margin to
+ * guarantee that the write position is always behind the read position.
+ *
+ * The safety margin for ZSTD with a 128 KB block size is calculated below.
+ * Note that the margin with ZSTD is bigger than with GZIP or XZ!
+ *
+ * The worst case for in-place decompression is that the beginning of
+ * the file is compressed extremely well, and the rest of the file is
+ * uncompressible. Thus, we must look for worst-case expansion when the
+ * compressor is encoding uncompressible data.
+ *
+ * The structure of the .zst file in case of a compresed kernel is as follows.
+ * Maximum sizes (as bytes) of the fields are in parenthesis.
+ *
+ *    Frame Header: (18)
+ *    Blocks: (N)
+ *    Checksum: (4)
+ *
+ * The frame header and checksum overhead is at most 22 bytes.
+ *
+ * ZSTD stores the data in blocks. Each block has a header whose size is
+ * a 3 bytes. After the block header, there is up to 128 KB of payload.
+ * The maximum uncompressed size of the payload is 128 KB. The minimum
+ * uncompressed size of the payload is never less than the payload size
+ * (excluding the block header).
+ *
+ * The assumption, that the uncompressed size of the payload is never
+ * smaller than the payload itself, is valid only when talking about
+ * the payload as a whole. It is possible that the payload has parts where
+ * the decompressor consumes more input than it produces output. Calculating
+ * the worst case for this would be tricky. Instead of trying to do that,
+ * let's simply make sure that the decompressor never overwrites any bytes
+ * of the payload which it is currently reading.
+ *
+ * Now we have enough information to calculate the safety margin. We need
+ *   - 22 bytes for the .zst file format headers;
+ *   - 3 bytes per every 128 KiB of uncompressed size (one block header per
+ *     block); and
+ *   - 128 KiB (biggest possible zstd block size) to make sure that the
+ *     decompressor never overwrites anything from the block it is currently
+ *     reading.
+ *
+ * We get the following formula:
+ *
+ *    safety_margin = 22 + uncompressed_size * 3 / 131072 + 131072
+ *                 <= 22 + (uncompressed_size >> 15) + 131072
+ */
+
+#ifdef STATIC
+	/* Preboot environments #include "path/to/decompress_unzstd.c".
+	 * All of the source files we depend on must be #included.
+	 * zstd's only source dependeny is xxhash, which has no source
+	 * dependencies.
+	 *
+	 * zstd and xxhash both avoid declaring themselves as modules
+	 * when PREBOOT is defined.
+	 */
+#	define PREBOOT
+#	include "xxhash.c"
+#	include "zstd/entropy_common.c"
+#	include "zstd/fse_decompress.c"
+#	include "zstd/huf_decompress.c"
+#	include "zstd/zstd_common.c"
+#	include "zstd/decompress.c"
+#endif
+
+#include <linux/decompress/mm.h>
+#include <linux/kernel.h>
+#include <linux/zstd.h>
+
+/* 8 MB maximum window size */
+#define ZSTD_WINDOWSIZE_MAX	(1 << 23)
+/* Size of the input and output buffers in multi-call mdoe */
+#define ZSTD_IOBUF_SIZE		4096
+
+static int INIT handle_zstd_error(size_t ret, void (*error)(char *x))
+{
+	const int err = ZSTD_getErrorCode(ret);
+
+	if (!ZSTD_isError(ret))
+		return 0;
+
+	switch (err) {
+	case ZSTD_error_memory_allocation:
+		error("ZSTD decompressor ran out of memory");
+		break;
+	case ZSTD_error_prefix_unknown:
+		error("Input is not in the ZSTD format (wrong magic bytes)");
+		break;
+	case ZSTD_error_dstSize_tooSmall:
+	case ZSTD_error_corruption_detected:
+	case ZSTD_error_checksum_wrong:
+		error("ZSTD-compressed data is corrupt");
+		break;
+	default:
+		error("ZSTD-compressed data is probably corrupt");
+		break;
+	}
+	return -1;
+}
+
+/* Handle the case where we have the entire input and output in one segment.
+ * We can allocate less memory (no circular buffer for the sliding window),
+ * and avoid some memcpy() calls.
+ */
+static int INIT decompress_single(const u8 *in_buf, long in_len, u8 *out_buf,
+				  long out_len, long *in_pos,
+				  void (*error)(char *x))
+{
+	const size_t wksp_size = ZSTD_DCtxWorkspaceBound();
+	void *wksp = large_malloc(wksp_size);
+	ZSTD_DCtx *dctx = ZSTD_initDCtx(wksp, wksp_size);
+	int err;
+	size_t ret;
+
+	if (dctx == NULL) {
+		error("Out of memory while allocating ZSTD_DCtx");
+		err = -1;
+		goto out;
+	}
+	/* Find out how large the frame actually is, there may be junk at
+	 * the end of the frame that ZSTD_decompressDCtx() can't handle.
+	 */
+	ret = ZSTD_findFrameCompressedSize(in_buf, in_len);
+	err = handle_zstd_error(ret, error);
+	if (err)
+		goto out;
+	in_len = (long)ret;
+
+	ret = ZSTD_decompressDCtx(dctx, out_buf, out_len, in_buf, in_len);
+	err = handle_zstd_error(ret, error);
+	if (err)
+		goto out;
+
+	if (in_pos != NULL)
+		*in_pos = in_len;
+
+	err = 0;
+out:
+	if (wksp != NULL)
+		large_free(wksp);
+	return err;
+}
+
+static int INIT __unzstd(unsigned char *in_buf, long in_len,
+			 long (*fill)(void*, unsigned long),
+			 long (*flush)(void*, unsigned long),
+			 unsigned char *out_buf, long out_len,
+			 long *in_pos,
+			 void (*error)(char *x))
+{
+	ZSTD_inBuffer in;
+	ZSTD_outBuffer out;
+	ZSTD_frameParams params;
+	void *in_allocated = NULL;
+	void *out_allocated = NULL;
+	void *wksp = NULL;
+	size_t wksp_size;
+	ZSTD_DStream *dstream;
+	int err;
+	size_t ret;
+
+	if (out_len == 0)
+		out_len = LONG_MAX; /* no limit */
+
+	if (fill == NULL && flush == NULL)
+		/* We can decompress faster and with less memory when we have a
+		 * single chunk.
+		 */
+		return decompress_single(in_buf, in_len, out_buf, out_len,
+					 in_pos, error);
+
+	/* If in_buf is not provided, we must be using fill(), so allocate
+	 * a large enough buffer. If it is provided, it must be at least
+	 * ZSTD_IOBUF_SIZE large.
+	 */
+	if (in_buf == NULL) {
+		in_allocated = malloc(ZSTD_IOBUF_SIZE);
+		if (in_allocated == NULL) {
+			error("Out of memory while allocating input buffer");
+			err = -1;
+			goto out;
+		}
+		in_buf = in_allocated;
+		in_len = 0;
+	}
+	/* Read the first chunk, since we need to decode the frame header */
+	if (fill != NULL)
+		in_len = fill(in_buf, ZSTD_IOBUF_SIZE);
+	if (in_len < 0) {
+		error("ZSTD-compressed data is truncated");
+		err = -1;
+		goto out;
+	}
+	/* Set the first non-empty input buffer */
+	in.src = in_buf;
+	in.pos = 0;
+	in.size = in_len;
+	/* Allocate the output buffer if we are using flush(). */
+	if (flush != NULL) {
+		out_allocated = malloc(ZSTD_IOBUF_SIZE);
+		if (out_allocated == NULL) {
+			error("Out of memory while allocating output buffer");
+			err = -1;
+			goto out;
+		}
+		out_buf = out_allocated;
+		out_len = ZSTD_IOBUF_SIZE;
+	}
+	/* Set the output buffer */
+	out.dst = out_buf;
+	out.pos = 0;
+	out.size = out_len;
+
+	/* We need to know the window size to allocate the ZSTD_DStream.
+	 * Since we are streaming, we need to allocate a buffer for the sliding
+	 * window. The window size varies from 1 KB to ZSTD_WINDOWSIZE_MAX
+	 * (8 MB), so it is important to use the actual value so as not to
+	 * waste memory when it is smaller.
+	 */
+	ret = ZSTD_getFrameParams(&params, in.src, in.size);
+	err = handle_zstd_error(ret, error);
+	if (err)
+		goto out;
+	if (ret != 0) {
+		error("ZSTD-compressed data has an incomplete frame header");
+		err = -1;
+		goto out;
+	}
+	if (params.windowSize > ZSTD_WINDOWSIZE_MAX) {
+		error("ZSTD-compressed data has too large a window size");
+		err = -1;
+		goto out;
+	}
+
+	/* Allocate the ZSTD_DStream now that we know how much memory is
+	 * required.
+	 */
+	wksp_size = ZSTD_DStreamWorkspaceBound(params.windowSize);
+	wksp = large_malloc(wksp_size);
+	dstream = ZSTD_initDStream(params.windowSize, wksp, wksp_size);
+	if (dstream == NULL) {
+		error("Out of memory while allocating ZSTD_DStream");
+		err = -1;
+		goto out;
+	}
+	/* Decompression loop:
+	 * Read more data if necessary (error if no more data can be read).
+	 * Call the decompression function, which returns 0 when finished.
+	 * Flush any data produced if using flush().
+	 */
+	if (in_pos != NULL)
+		*in_pos = 0;
+	do {
+		/* If we need to reload data, either we have fill() and can
+		 * try to get more data, or we don't and the input is truncated.
+		 */
+		if (in.pos == in.size) {
+			if (in_pos != NULL)
+				*in_pos += in.pos;
+			in_len = fill ? fill(in_buf, ZSTD_IOBUF_SIZE) : -1;
+			if (in_len < 0) {
+				error("ZSTD-compressed data is truncated");
+				err = -1;
+				goto out;
+			}
+			in.pos = 0;
+			in.size = in_len;
+		}
+		/* Returns zero when the frame is complete */
+		ret = ZSTD_decompressStream(dstream, &out, &in);
+		err = handle_zstd_error(ret, error);
+		if (err)
+			goto out;
+		/* Flush all of the data produced if using flush() */
+		if (flush != NULL && out.pos > 0) {
+			if (out.pos != flush(out.dst, out.pos)) {
+				error("Failed to flush()");
+				err = -1;
+				goto out;
+			}
+			out.pos = 0;
+		}
+	} while (ret != 0);
+
+	if (in_pos != NULL)
+		*in_pos += in.pos;
+
+	err = 0;
+out:
+	if (in_allocated != NULL)
+		free(in_allocated);
+	if (out_allocated != NULL)
+		free(out_allocated);
+	if (wksp != NULL)
+		large_free(wksp);
+	return err;
+}
+
+#ifndef PREBOOT
+STATIC int INIT unzstd(unsigned char *buf, long len,
+		       long (*fill)(void*, unsigned long),
+		       long (*flush)(void*, unsigned long),
+		       unsigned char *out_buf,
+		       long *pos,
+		       void (*error)(char *x))
+{
+	return __unzstd(buf, len, fill, flush, out_buf, 0, pos, error);
+}
+#else
+STATIC int INIT __decompress(unsigned char *buf, long len,
+			     long (*fill)(void*, unsigned long),
+			     long (*flush)(void*, unsigned long),
+			     unsigned char *out_buf, long out_len,
+			     long *pos,
+			     void (*error)(char *x))
+{
+	return __unzstd(buf, len, fill, flush, out_buf, out_len, pos, error);
+}
+#endif
diff --git a/lib/xxhash.c b/lib/xxhash.c
index aa61e2a..7f1d3cb 100644
--- a/lib/xxhash.c
+++ b/lib/xxhash.c
@@ -80,13 +80,11 @@ void xxh32_copy_state(struct xxh32_state *dst, const struct xxh32_state *src)
 {
 	memcpy(dst, src, sizeof(*dst));
 }
-EXPORT_SYMBOL(xxh32_copy_state);
 
 void xxh64_copy_state(struct xxh64_state *dst, const struct xxh64_state *src)
 {
 	memcpy(dst, src, sizeof(*dst));
 }
-EXPORT_SYMBOL(xxh64_copy_state);
 
 /*-***************************
  * Simple Hash Functions
@@ -151,7 +149,6 @@ uint32_t xxh32(const void *input, const size_t len, const uint32_t seed)
 
 	return h32;
 }
-EXPORT_SYMBOL(xxh32);
 
 static uint64_t xxh64_round(uint64_t acc, const uint64_t input)
 {
@@ -234,7 +231,6 @@ uint64_t xxh64(const void *input, const size_t len, const uint64_t seed)
 
 	return h64;
 }
-EXPORT_SYMBOL(xxh64);
 
 /*-**************************************************
  * Advanced Hash Functions
@@ -251,7 +247,6 @@ void xxh32_reset(struct xxh32_state *statePtr, const uint32_t seed)
 	state.v4 = seed - PRIME32_1;
 	memcpy(statePtr, &state, sizeof(state));
 }
-EXPORT_SYMBOL(xxh32_reset);
 
 void xxh64_reset(struct xxh64_state *statePtr, const uint64_t seed)
 {
@@ -265,7 +260,6 @@ void xxh64_reset(struct xxh64_state *statePtr, const uint64_t seed)
 	state.v4 = seed - PRIME64_1;
 	memcpy(statePtr, &state, sizeof(state));
 }
-EXPORT_SYMBOL(xxh64_reset);
 
 int xxh32_update(struct xxh32_state *state, const void *input, const size_t len)
 {
@@ -334,7 +328,6 @@ int xxh32_update(struct xxh32_state *state, const void *input, const size_t len)
 
 	return 0;
 }
-EXPORT_SYMBOL(xxh32_update);
 
 uint32_t xxh32_digest(const struct xxh32_state *state)
 {
@@ -372,7 +365,6 @@ uint32_t xxh32_digest(const struct xxh32_state *state)
 
 	return h32;
 }
-EXPORT_SYMBOL(xxh32_digest);
 
 int xxh64_update(struct xxh64_state *state, const void *input, const size_t len)
 {
@@ -439,7 +431,6 @@ int xxh64_update(struct xxh64_state *state, const void *input, const size_t len)
 
 	return 0;
 }
-EXPORT_SYMBOL(xxh64_update);
 
 uint64_t xxh64_digest(const struct xxh64_state *state)
 {
@@ -494,7 +485,19 @@ uint64_t xxh64_digest(const struct xxh64_state *state)
 
 	return h64;
 }
+
+#ifndef PREBOOT
+EXPORT_SYMBOL(xxh32_copy_state);
+EXPORT_SYMBOL(xxh64_copy_state);
+EXPORT_SYMBOL(xxh32);
+EXPORT_SYMBOL(xxh64);
+EXPORT_SYMBOL(xxh32_reset);
+EXPORT_SYMBOL(xxh64_reset);
+EXPORT_SYMBOL(xxh32_update);
+EXPORT_SYMBOL(xxh32_digest);
+EXPORT_SYMBOL(xxh64_update);
 EXPORT_SYMBOL(xxh64_digest);
 
 MODULE_LICENSE("Dual BSD/GPL");
 MODULE_DESCRIPTION("xxHash");
+#endif
diff --git a/lib/zstd/decompress.c b/lib/zstd/decompress.c
index b178467..b7b6599 100644
--- a/lib/zstd/decompress.c
+++ b/lib/zstd/decompress.c
@@ -2487,6 +2487,7 @@ size_t ZSTD_decompressStream(ZSTD_DStream *zds, ZSTD_outBuffer *output, ZSTD_inB
 	}
 }
 
+#ifndef PREBOOT
 EXPORT_SYMBOL(ZSTD_DCtxWorkspaceBound);
 EXPORT_SYMBOL(ZSTD_initDCtx);
 EXPORT_SYMBOL(ZSTD_decompressDCtx);
@@ -2526,3 +2527,4 @@ EXPORT_SYMBOL(ZSTD_insertBlock);
 
 MODULE_LICENSE("Dual BSD/GPL");
 MODULE_DESCRIPTION("Zstd Decompressor");
+#endif
diff --git a/lib/zstd/fse_decompress.c b/lib/zstd/fse_decompress.c
index a84300e..0b35353 100644
--- a/lib/zstd/fse_decompress.c
+++ b/lib/zstd/fse_decompress.c
@@ -47,6 +47,7 @@
 ****************************************************************/
 #include "bitstream.h"
 #include "fse.h"
+#include "zstd_internal.h"
 #include <linux/compiler.h>
 #include <linux/kernel.h>
 #include <linux/string.h> /* memcpy, memset */
@@ -60,14 +61,6 @@
 		enum { FSE_static_assert = 1 / (int)(!!(c)) }; \
 	} /* use only *after* variable declarations */
 
-/* check and forward error code */
-#define CHECK_F(f)                  \
-	{                           \
-		size_t const e = f; \
-		if (FSE_isError(e)) \
-			return e;   \
-	}
-
 /* **************************************************************
 *  Templates
 ****************************************************************/
diff --git a/lib/zstd/zstd_internal.h b/lib/zstd/zstd_internal.h
index 1a79fab..40026c0 100644
--- a/lib/zstd/zstd_internal.h
+++ b/lib/zstd/zstd_internal.h
@@ -127,7 +127,13 @@ static const U32 OF_defaultNormLog = OF_DEFAULTNORMLOG;
 *  Shared functions to include for inlining
 *********************************************/
 ZSTD_STATIC void ZSTD_copy8(void *dst, const void *src) {
-	memcpy(dst, src, 8);
+	/* zstd relies heavily on gcc being able to analyze and inline this
+	 * memcpy() call, since it is called in a tight loop. Preboot mode
+	 * is compiled in freestanding mode, which stops gcc from analyzing
+	 * memcpy(). Use __builtin_memcpy() to tell gcc to analyze this as a
+	 * regular memcpy().
+	 */
+	__builtin_memcpy(dst, src, 8);
 }
 /*! ZSTD_wildcopy() :
 *   custom version of memcpy(), can copy up to 7 bytes too many (8 bytes if length==0) */
@@ -137,6 +143,7 @@ ZSTD_STATIC void ZSTD_wildcopy(void *dst, const void *src, ptrdiff_t length)
 	const BYTE* ip = (const BYTE*)src;
 	BYTE* op = (BYTE*)dst;
 	BYTE* const oend = op + length;
+#if GCC_VERSION >= 70000 && GCC_VERSION < 70200
 	/* Work around https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81388.
 	 * Avoid the bad case where the loop only runs once by handling the
 	 * special case separately. This doesn't trigger the bug because it
@@ -144,6 +151,7 @@ ZSTD_STATIC void ZSTD_wildcopy(void *dst, const void *src, ptrdiff_t length)
 	 */
 	if (length <= 8)
 		return ZSTD_copy8(dst, src);
+#endif
 	do {
 		ZSTD_copy8(op, ip);
 		op += 8;
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index 5e975fe..807c1fa 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -412,6 +412,21 @@ cmd_xzmisc = (cat $(filter-out FORCE,$^) | \
 	xz --check=crc32 --lzma2=dict=1MiB) > $@ || \
 	(rm -f $@ ; false)
 
+# ZSTD
+# ---------------------------------------------------------------------------
+# Appends the uncompressed size of the data using size_append. The .zst
+# format has the size information available at the beginning of the file too,
+# but it's in a more complex format and it's good to avoid changing the part
+# of the boot code that reads the uncompressed size.
+# Note that the bytes added by size_append will make the zstd tool think that
+# the file is corrupt. This is expected.
+
+quiet_cmd_zstd = ZSTD    $@
+cmd_zstd = (cat $(filter-out FORCE,$^) | \
+	zstd -19 && \
+        $(call size_append, $(filter-out FORCE,$^))) > $@ || \
+	(rm -f $@ ; false)
+
 # ASM offsets
 # ---------------------------------------------------------------------------
 
diff --git a/usr/Kconfig b/usr/Kconfig
index d53112fd..b63d8ee 100644
--- a/usr/Kconfig
+++ b/usr/Kconfig
@@ -105,6 +105,15 @@ config RD_LZ4
 	  Support loading of a LZ4 encoded initial ramdisk or cpio buffer
 	  If unsure, say N.
 
+config RD_ZSTD
+	bool "Support initial ramdisk/ramfs compressed using ZSTD"
+	default y
+	depends on BLK_DEV_INITRD
+	select DECOMPRESS_ZSTD
+	help
+	  Support loading of a ZSTD encoded initial ramdisk or cpio buffer.
+	  If unsure, say N.
+
 choice
 	prompt "Built-in initramfs compression mode"
 	depends on INITRAMFS_SOURCE!=""
@@ -213,6 +222,17 @@ config INITRAMFS_COMPRESSION_LZ4
 	  If you choose this, keep in mind that most distros don't provide lz4
 	  by default which could cause a build failure.
 
+config INITRAMFS_COMPRESSION_ZSTD
+	bool "ZSTD"
+	depends on RD_ZSTD
+	help
+	  ZSTD is a compression algorithm targeting intermediate compression
+	  with fast decompression speed. It will compress better than GZIP and
+	  decompress around the same speed as LZO, but slower than LZ4.
+
+	  If you choose this, keep in mind that you may need to install the zstd
+	  tool to be able to compress the initram.
+
 endchoice
 
 config INITRAMFS_COMPRESSION
@@ -225,10 +245,12 @@ config INITRAMFS_COMPRESSION
 	default ".xz"   if INITRAMFS_COMPRESSION_XZ
 	default ".lzo"  if INITRAMFS_COMPRESSION_LZO
 	default ".lz4"  if INITRAMFS_COMPRESSION_LZ4
+	default ".zst"  if INITRAMFS_COMPRESSION_ZSTD
 	default ".gz"   if RD_GZIP
 	default ".lz4"  if RD_LZ4
 	default ".lzo"  if RD_LZO
 	default ".xz"   if RD_XZ
 	default ".lzma" if RD_LZMA
 	default ".bz2"  if RD_BZIP2
+	default ".zst"  if RD_ZSTD
 	default ""
-- 
2.9.5

^ permalink raw reply related	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2020-03-17 21:02 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-03-16 13:50 [PATCH 1/2] lib: add support for ZSTD-compressed kernel Petr Malat
2020-03-16 13:50 ` [PATCH 2/2] x86: Enable " Petr Malat
2020-03-16 14:07   ` Greg KH
2020-03-16 14:30     ` [PATCH v2 1/2] lib: add " Petr Malat
2020-03-16 14:30       ` [PATCH v2 2/2] x86: Enable " Petr Malat
2020-03-17 21:02       ` [PATCH v2 1/2] lib: add " Kees Cook
2020-03-16 14:07 ` [PATCH " Greg KH
2020-03-16 14:35   ` Petr Malat
2020-03-17 21:00 ` Nick Terrell
  -- strict thread matches above, loose matches on Subject: below --
2017-10-12 22:11 [PATCH v2 0/2] Add " Nick Terrell
2017-10-12 22:11 ` [PATCH v2 1/2] lib: " Nick Terrell

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).