aboutsummaryrefslogtreecommitdiff
path: root/lib/sqfs/src/comp/xz.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqfs/src/comp/xz.c')
-rw-r--r--lib/sqfs/src/comp/xz.c324
1 files changed, 324 insertions, 0 deletions
diff --git a/lib/sqfs/src/comp/xz.c b/lib/sqfs/src/comp/xz.c
new file mode 100644
index 0000000..13545ed
--- /dev/null
+++ b/lib/sqfs/src/comp/xz.c
@@ -0,0 +1,324 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * xz.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <lzma.h>
+
+#include "internal.h"
+
+typedef struct {
+ sqfs_compressor_t base;
+ size_t block_size;
+ size_t dict_size;
+
+ sqfs_u8 level;
+ sqfs_u8 lc;
+ sqfs_u8 lp;
+ sqfs_u8 pb;
+
+ int flags;
+} xz_compressor_t;
+
+typedef struct {
+ sqfs_u32 dict_size;
+ sqfs_u32 flags;
+} xz_options_t;
+
+static bool is_dict_size_valid(size_t size)
+{
+ size_t x = size & (size - 1);
+
+ if (x == 0)
+ return true;
+
+ return size == (x | (x >> 1));
+}
+
+static int xz_write_options(sqfs_compressor_t *base, sqfs_file_t *file)
+{
+ xz_compressor_t *xz = (xz_compressor_t *)base;
+ xz_options_t opt;
+ sqfs_u32 flags;
+
+ if (xz->flags == 0 && xz->dict_size == xz->block_size)
+ return 0;
+
+ flags = xz->flags & SQFS_COMP_FLAG_XZ_ALL;
+ flags &= ~SQFS_COMP_FLAG_XZ_EXTREME;
+
+ opt.dict_size = htole32(xz->dict_size);
+ opt.flags = htole32(flags);
+
+ return sqfs_generic_write_options(file, &opt, sizeof(opt));
+}
+
+static int xz_read_options(sqfs_compressor_t *base, sqfs_file_t *file)
+{
+ xz_compressor_t *xz = (xz_compressor_t *)base;
+ xz_options_t opt;
+ int ret;
+
+ ret = sqfs_generic_read_options(file, &opt, sizeof(opt));
+ if (ret)
+ return ret;
+
+ opt.dict_size = le32toh(opt.dict_size);
+ opt.flags = le32toh(opt.flags);
+
+ if (!is_dict_size_valid(opt.dict_size))
+ return SQFS_ERROR_CORRUPTED;
+
+ if (opt.flags & ~SQFS_COMP_FLAG_XZ_ALL)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ xz->flags = opt.flags;
+ xz->dict_size = opt.dict_size;
+ return 0;
+}
+
+static sqfs_s32 compress(xz_compressor_t *xz, lzma_vli filter,
+ const sqfs_u8 *in, sqfs_u32 size,
+ sqfs_u8 *out, sqfs_u32 outsize,
+ sqfs_u32 presets)
+{
+ lzma_filter filters[5];
+ lzma_options_lzma opt;
+ size_t written = 0;
+ lzma_ret ret;
+ int i = 0;
+
+ if (lzma_lzma_preset(&opt, presets))
+ return SQFS_ERROR_COMPRESSOR;
+
+ opt.lc = xz->lc;
+ opt.lp = xz->lp;
+ opt.pb = xz->pb;
+ opt.dict_size = xz->dict_size;
+
+ if (filter != LZMA_VLI_UNKNOWN) {
+ filters[i].id = filter;
+ filters[i].options = NULL;
+ ++i;
+ }
+
+ filters[i].id = LZMA_FILTER_LZMA2;
+ filters[i].options = &opt;
+ ++i;
+
+ filters[i].id = LZMA_VLI_UNKNOWN;
+ filters[i].options = NULL;
+ ++i;
+
+ ret = lzma_stream_buffer_encode(filters, LZMA_CHECK_CRC32, NULL,
+ in, size, out, &written, outsize);
+
+ if (ret == LZMA_OK)
+ return (written >= size) ? 0 : written;
+
+ if (ret != LZMA_BUF_ERROR)
+ return SQFS_ERROR_COMPRESSOR;
+
+ return 0;
+}
+
+static lzma_vli flag_to_vli(int flag)
+{
+ switch (flag) {
+ case SQFS_COMP_FLAG_XZ_X86:
+ return LZMA_FILTER_X86;
+ case SQFS_COMP_FLAG_XZ_POWERPC:
+ return LZMA_FILTER_POWERPC;
+ case SQFS_COMP_FLAG_XZ_IA64:
+ return LZMA_FILTER_IA64;
+ case SQFS_COMP_FLAG_XZ_ARM:
+ return LZMA_FILTER_ARM;
+ case SQFS_COMP_FLAG_XZ_ARMTHUMB:
+ return LZMA_FILTER_ARMTHUMB;
+ case SQFS_COMP_FLAG_XZ_SPARC:
+ return LZMA_FILTER_SPARC;
+ default:
+ break;
+ }
+
+ return LZMA_VLI_UNKNOWN;
+}
+
+static sqfs_s32 xz_comp_block(sqfs_compressor_t *base, const sqfs_u8 *in,
+ sqfs_u32 size, sqfs_u8 *out, sqfs_u32 outsize)
+{
+ xz_compressor_t *xz = (xz_compressor_t *)base;
+ lzma_vli filter, selected = LZMA_VLI_UNKNOWN;
+ sqfs_s32 ret, smallest;
+ bool extreme;
+ size_t i;
+
+ if (size >= 0x7FFFFFFF)
+ return SQFS_ERROR_ARG_INVALID;
+
+ ret = compress(xz, LZMA_VLI_UNKNOWN, in, size, out,
+ outsize, xz->level);
+ if (ret < 0 || xz->flags == 0)
+ return ret;
+
+ smallest = ret;
+ extreme = false;
+
+ if (xz->flags & SQFS_COMP_FLAG_XZ_EXTREME) {
+ ret = compress(xz, LZMA_VLI_UNKNOWN, in, size, out, outsize,
+ xz->level | LZMA_PRESET_EXTREME);
+
+ if (ret > 0 && (smallest == 0 || ret < smallest)) {
+ smallest = ret;
+ extreme = true;
+ }
+ }
+
+ for (i = 1; i & SQFS_COMP_FLAG_XZ_ALL; i <<= 1) {
+ if ((i & SQFS_COMP_FLAG_XZ_EXTREME) || (xz->flags & i) == 0)
+ continue;
+
+ filter = flag_to_vli(i);
+
+ ret = compress(xz, filter, in, size, out, outsize, xz->level);
+ if (ret > 0 && (smallest == 0 || ret < smallest)) {
+ smallest = ret;
+ selected = filter;
+ extreme = false;
+ }
+
+ if (xz->flags & SQFS_COMP_FLAG_XZ_EXTREME) {
+ ret = compress(xz, filter, in, size, out, outsize,
+ xz->level | LZMA_PRESET_EXTREME);
+
+ if (ret > 0 && (smallest == 0 || ret < smallest)) {
+ smallest = ret;
+ selected = filter;
+ extreme = true;
+ }
+ }
+ }
+
+ if (smallest == 0)
+ return 0;
+
+ return compress(xz, selected, in, size, out, outsize,
+ xz->level | (extreme ? LZMA_PRESET_EXTREME : 0));
+}
+
+static sqfs_s32 xz_uncomp_block(sqfs_compressor_t *base, const sqfs_u8 *in,
+ sqfs_u32 size, sqfs_u8 *out, sqfs_u32 outsize)
+{
+ sqfs_u64 memlimit = 65 * 1024 * 1024;
+ size_t dest_pos = 0;
+ size_t src_pos = 0;
+ lzma_ret ret;
+ (void)base;
+
+ if (outsize >= 0x7FFFFFFF)
+ return SQFS_ERROR_ARG_INVALID;
+
+ ret = lzma_stream_buffer_decode(&memlimit, 0, NULL,
+ in, &src_pos, size,
+ out, &dest_pos, outsize);
+
+ if (ret == LZMA_OK && size == src_pos)
+ return dest_pos;
+
+ return SQFS_ERROR_COMPRESSOR;
+}
+
+static void xz_get_configuration(const sqfs_compressor_t *base,
+ sqfs_compressor_config_t *cfg)
+{
+ const xz_compressor_t *xz = (const xz_compressor_t *)base;
+
+ memset(cfg, 0, sizeof(*cfg));
+ cfg->id = SQFS_COMP_XZ;
+ cfg->flags = xz->flags;
+ cfg->block_size = xz->block_size;
+ cfg->level = xz->level;
+ cfg->opt.xz.dict_size = xz->dict_size;
+ cfg->opt.xz.lc = xz->lc;
+ cfg->opt.xz.lp = xz->lp;
+ cfg->opt.xz.pb = xz->pb;
+
+ if (base->do_block == xz_uncomp_block)
+ cfg->flags |= SQFS_COMP_FLAG_UNCOMPRESS;
+}
+
+static sqfs_object_t *xz_create_copy(const sqfs_object_t *cmp)
+{
+ xz_compressor_t *xz = malloc(sizeof(*xz));
+
+ if (xz == NULL)
+ return NULL;
+
+ memcpy(xz, cmp, sizeof(*xz));
+ return (sqfs_object_t *)xz;
+}
+
+static void xz_destroy(sqfs_object_t *base)
+{
+ free(base);
+}
+
+int xz_compressor_create(const sqfs_compressor_config_t *cfg,
+ sqfs_compressor_t **out)
+{
+ sqfs_compressor_t *base;
+ xz_compressor_t *xz;
+
+ if (cfg->flags & ~(SQFS_COMP_FLAG_GENERIC_ALL |
+ SQFS_COMP_FLAG_XZ_ALL)) {
+ return SQFS_ERROR_UNSUPPORTED;
+ }
+
+ if (!is_dict_size_valid(cfg->opt.xz.dict_size))
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (cfg->opt.xz.lc + cfg->opt.xz.lp > 4)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (cfg->opt.xz.pb > SQFS_XZ_MAX_PB)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (cfg->level > SQFS_XZ_MAX_LEVEL)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (cfg->opt.xz.dict_size < SQFS_XZ_MIN_DICT_SIZE)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (cfg->opt.xz.dict_size > SQFS_XZ_MAX_DICT_SIZE)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ xz = calloc(1, sizeof(*xz));
+ base = (sqfs_compressor_t *)xz;
+ if (xz == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ sqfs_object_init(xz, xz_destroy, xz_create_copy);
+
+ xz->flags = cfg->flags;
+ xz->dict_size = cfg->opt.xz.dict_size;
+ xz->block_size = cfg->block_size;
+ xz->lc = cfg->opt.xz.lc;
+ xz->lp = cfg->opt.xz.lp;
+ xz->pb = cfg->opt.xz.pb;
+ xz->level = cfg->level;
+ base->get_configuration = xz_get_configuration;
+ base->do_block = (cfg->flags & SQFS_COMP_FLAG_UNCOMPRESS) ?
+ xz_uncomp_block : xz_comp_block;
+ base->write_options = xz_write_options;
+ base->read_options = xz_read_options;
+
+ *out = base;
+ return 0;
+}