diff mbox series

Add support for XZ decompression

Message ID 20250508165135.3884914-1-joakim.tjernlund@infinera.com
State Accepted
Delegated to: Stefano Babic
Headers show
Series Add support for XZ decompression | expand

Commit Message

Joakim Tjernlund May 8, 2025, 4:51 p.m. UTC
XZ compresses better than GZIP

Signed-off-by: Joakim Tjernlund <joakim.tjernlund@infinera.com>
---
 Kconfig                       |  8 ++++
 Makefile.deps                 |  4 ++
 Makefile.flags                |  4 ++
 core/cpio_utils.c             | 80 +++++++++++++++++++++++++++++++++--
 corelib/lua_interface.c       |  5 +++
 doc/source/sw-description.rst |  4 +-
 doc/source/swupdate.rst       |  2 +-
 include/util.h                |  1 +
 parser/parse_external.c       |  2 +
 parser/parser.c               |  2 +
 10 files changed, 106 insertions(+), 6 deletions(-)
diff mbox series

Patch

diff --git a/Kconfig b/Kconfig
index 79237bf5..fd74d013 100644
--- a/Kconfig
+++ b/Kconfig
@@ -81,6 +81,10 @@  config HAVE_ZLIB
 	bool
 	option env="HAVE_ZLIB"
 
+config HAVE_XZ
+	bool
+	option env="HAVE_XZ"
+
 config HAVE_ZSTD
 	bool
 	option env="HAVE_ZSTD"
@@ -529,6 +533,10 @@  config GUNZIP
 	depends on HAVE_ZLIB
 	default y
 
+config XZ
+	bool "XZ compression support"
+	depends on HAVE_XZ
+
 config ZSTD
 	bool "Zstd compression support"
 	depends on HAVE_ZSTD
diff --git a/Makefile.deps b/Makefile.deps
index c62f1af0..02e420f0 100644
--- a/Makefile.deps
+++ b/Makefile.deps
@@ -62,6 +62,10 @@  ifeq ($(HAVE_ZLIB),)
 export HAVE_ZLIB = y
 endif
 
+ifeq ($(HAVE_XZ),)
+export HAVE_XZ = y
+endif
+
 ifeq ($(HAVE_ZSTD),)
 export HAVE_ZSTD = y
 endif
diff --git a/Makefile.flags b/Makefile.flags
index 9561ec82..d65f0db4 100644
--- a/Makefile.flags
+++ b/Makefile.flags
@@ -177,6 +177,10 @@  ifeq ($(CONFIG_GUNZIP),y)
 LDLIBS += z
 endif
 
+ifeq ($(CONFIG_XZ),y)
+LDLIBS += lzma
+endif
+
 ifeq ($(CONFIG_ZSTD),y)
 LDLIBS += zstd
 endif
diff --git a/core/cpio_utils.c b/core/cpio_utils.c
index 7c4205a7..9936dd3c 100644
--- a/core/cpio_utils.c
+++ b/core/cpio_utils.c
@@ -16,6 +16,9 @@ 
 #ifdef CONFIG_GUNZIP
 #include <zlib.h>
 #endif
+#ifdef CONFIG_XZ
+#include <lzma.h>
+#endif
 #ifdef CONFIG_ZSTD
 #include <zstd.h>
 #endif
@@ -332,7 +335,7 @@  static int decrypt_step(void *state, void *buffer, size_t size)
 	return 0;
 }
 
-#if defined(CONFIG_GUNZIP) || defined(CONFIG_ZSTD)
+#if defined(CONFIG_GUNZIP) || defined(CONFIG_ZSTD) || defined(CONFIG_XZ)
 typedef int (*DecompressStep)(void *state, void *buffer, size_t size);
 
 struct DecompressState {
@@ -391,6 +394,53 @@  static int gunzip_step(void *state, void *buffer, size_t size)
 
 #endif
 
+#ifdef CONFIG_XZ
+struct XzState {
+	lzma_stream strm;
+	bool initialized;
+};
+static int xz_step(void* state, void* buffer, size_t size)
+{
+	struct DecompressState *ds = (struct DecompressState *)state;
+	struct XzState *s = (struct XzState *)ds->impl_state;
+	lzma_ret ret;
+	int outlen = 0;
+	lzma_action action = LZMA_RUN;
+
+	s->strm.next_out = buffer;
+	s->strm.avail_out = size;
+
+	while (outlen == 0) {
+		if (s->strm.avail_in == 0) {
+			ret = ds->upstream_step(ds->upstream_state, ds->input, sizeof ds->input);
+			if (ret < 0) {
+				return ret;
+			} else if (ret == 0) {
+				ds->eof = true;
+			}
+			s->strm.avail_in = ret;
+			s->strm.next_in = ds->input;
+		}
+		if (ds->eof) {
+			action = LZMA_FINISH;
+		}
+
+		ret = lzma_code(&s->strm, action);
+		outlen = size - s->strm.avail_out;
+		if (ret == LZMA_STREAM_END) {
+			ds->eof = true;
+			break;
+		}
+		if (ret != LZMA_OK && ret != LZMA_BUF_ERROR) {
+			ERROR("xz failed (returned %d)", ret);
+			return -1;
+		}
+	}
+	return outlen;
+}
+
+#endif
+
 #ifdef CONFIG_ZSTD
 
 struct ZstdState {
@@ -496,7 +546,7 @@  int copyfile(struct swupdate_copy *args)
 		.outlen = 0, .eof = false
 	};
 
-#if defined(CONFIG_GUNZIP) || defined(CONFIG_ZSTD)
+#if defined(CONFIG_GUNZIP) || defined(CONFIG_ZSTD) || defined(CONFIG_XZ)
 	struct DecompressState decompress_state = {
 		.upstream_step = NULL, .upstream_state = NULL,
 		.impl_state = NULL
@@ -513,6 +563,12 @@  int copyfile(struct swupdate_copy *args)
 		.initialized = false,
 	};
 #endif
+#ifdef CONFIG_XZ
+	struct XzState xz_state = {
+		.strm = LZMA_STREAM_INIT,
+		.initialized = false,
+	};
+#endif
 #ifdef CONFIG_ZSTD
 	struct ZstdState zstd_state = {
 		.dctx = NULL,
@@ -585,6 +641,19 @@  int copyfile(struct swupdate_copy *args)
 			decompress_state.impl_state = &gunzip_state;
 		} else
 #endif
+#ifdef CONFIG_XZ
+		if (args->compressed == COMPRESSED_XZ) {
+			if (lzma_stream_decoder(&xz_state.strm, UINT32_MAX,
+						LZMA_CONCATENATED) != LZMA_OK) {
+				ERROR("(lzma_stream_decoder failed");
+				ret = -EFAULT;
+				goto copyfile_exit;
+			}
+			xz_state.initialized = true;
+			decompress_step = &xz_step;
+			decompress_state.impl_state = &xz_state;
+		} else
+#endif
 #ifdef CONFIG_ZSTD
 		if (args->compressed == COMPRESSED_ZSTD) {
 			if ((zstd_state.dctx = ZSTD_createDStream()) == NULL) {
@@ -630,7 +699,7 @@  int copyfile(struct swupdate_copy *args)
 		state = &decrypt_state;
 	}
 
-#if defined(CONFIG_GUNZIP) || defined(CONFIG_ZSTD)
+#if defined(CONFIG_GUNZIP) || defined(CONFIG_ZSTD) || defined(CONFIG_XZ)
 	if (args->compressed) {
 		decompress_state.upstream_step = step;
 		decompress_state.upstream_state = state;
@@ -699,6 +768,11 @@  copyfile_exit:
 		inflateEnd(&gunzip_state.strm);
 	}
 #endif
+#ifdef CONFIG_XZ
+	if (xz_state.initialized) {
+		lzma_end(&xz_state.strm);
+	}
+#endif
 #ifdef CONFIG_ZSTD
 	if (zstd_state.dctx != NULL) {
 		ZSTD_freeDStream(zstd_state.dctx);
diff --git a/corelib/lua_interface.c b/corelib/lua_interface.c
index ffe62d88..e988a4ec 100644
--- a/corelib/lua_interface.c
+++ b/corelib/lua_interface.c
@@ -283,6 +283,8 @@  static void lua_string_to_img(struct img_type *img, const char *key,
 	if (!strcmp(key, "compressed")) {
 		if (!strcmp(value, "zlib")) {
 			img->compressed = COMPRESSED_ZLIB;
+		} else if (!strcmp(value, "xz")) {
+			img->compressed = COMPRESSED_XZ;
 		} else if (!strcmp(value, "zstd")) {
 			img->compressed = COMPRESSED_ZSTD;
 		} else {
@@ -530,6 +532,9 @@  static void update_table(lua_State* L, struct img_type *img)
 			case COMPRESSED_ZLIB:
 				LUA_PUSH_IMG_STRING_VALUE(img, "compressed", "zlib");
 				break;
+			case COMPRESSED_XZ:
+				LUA_PUSH_IMG_STRING_VALUE(img, "compressed", "xz");
+				break;
 			case COMPRESSED_ZSTD:
 				LUA_PUSH_IMG_STRING_VALUE(img, "compressed", "zstd");
 				break;
diff --git a/doc/source/sw-description.rst b/doc/source/sw-description.rst
index df2cb258..dbe29d3b 100644
--- a/doc/source/sw-description.rst
+++ b/doc/source/sw-description.rst
@@ -1415,8 +1415,8 @@  There are 4 main sections inside sw-description:
    |             |          | files      | compressed and must be decompressed   |
    |             |          |            | before being installed. the value     |
    |             |          |            | denotes the compression type.         |
-   |             |          |            | currently supported values are "zlib" |
-   |             |          |            | and "zstd".                           |
+   |             |          |            | currently supported values are "xz",  |
+   |             |          |            | "zlib" and "zstd".                    |
    +-------------+----------+------------+---------------------------------------+
    | compressed  | bool (dep| images     | Deprecated. Use the string form. true |
    |             | recated) | files      | is equal to 'compressed = "zlib"'.    |
diff --git a/doc/source/swupdate.rst b/doc/source/swupdate.rst
index fa43071c..694fbcfa 100644
--- a/doc/source/swupdate.rst
+++ b/doc/source/swupdate.rst
@@ -57,7 +57,7 @@  General Overview
   SWUpdate can recreate UBI volumes, resizing them and
   copying the new software.
 
-- support for compressed images, using the zlib and zstd library.
+- support for compressed images, using the xz, zlib and zstd library.
   tarball (tgz file) are supported.
 
 - support for partitioned USB-pen or unpartitioned (mainly
diff --git a/include/util.h b/include/util.h
index 07c20770..9509fdbd 100644
--- a/include/util.h
+++ b/include/util.h
@@ -61,6 +61,7 @@  enum compression_type {
   COMPRESSED_FALSE,
   COMPRESSED_TRUE,
   COMPRESSED_ZLIB,
+  COMPRESSED_XZ,
   COMPRESSED_ZSTD,
 };
 
diff --git a/parser/parse_external.c b/parser/parse_external.c
index 20ac0771..d67bcca0 100644
--- a/parser/parse_external.c
+++ b/parser/parse_external.c
@@ -87,6 +87,8 @@  static void sw_append_stream(struct img_type *img, const char *key,
 		if (value != NULL) {
 			if (!strcmp(value, "zlib")) {
 				img->compressed = COMPRESSED_ZLIB;
+			} else if (!strcmp(value, "xz")) {
+				img->compressed = COMPRESSED_XZ;
 			} else if (!strcmp(value, "zstd")) {
 				img->compressed = COMPRESSED_ZSTD;
 			} else {
diff --git a/parser/parser.c b/parser/parser.c
index a8f4a691..c702dd06 100644
--- a/parser/parser.c
+++ b/parser/parser.c
@@ -441,6 +441,8 @@  static int parse_common_attributes(parsertype p, void *elem, struct img_type *im
 	if ((compressed = get_field_string(p, elem, "compressed")) != NULL) {
 		if (!strcmp(compressed, "zlib")) {
 			image->compressed = COMPRESSED_ZLIB;
+		} else if (!strcmp(compressed, "xz")) {
+			image->compressed = COMPRESSED_XZ;
 		} else if (!strcmp(compressed, "zstd")) {
 			image->compressed = COMPRESSED_ZSTD;
 		} else {