summaryrefslogtreecommitdiff
path: root/lib/sqfs/comp/gzip.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqfs/comp/gzip.c')
-rw-r--r--lib/sqfs/comp/gzip.c412
1 files changed, 412 insertions, 0 deletions
diff --git a/lib/sqfs/comp/gzip.c b/lib/sqfs/comp/gzip.c
new file mode 100644
index 0000000..1fdc051
--- /dev/null
+++ b/lib/sqfs/comp/gzip.c
@@ -0,0 +1,412 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * gzip.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "config.h"
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <zlib.h>
+
+#include "internal.h"
+
+#define GZIP_DEFAULT_LEVEL 9
+#define GZIP_DEFAULT_WINDOW 15
+#define GZIP_NUM_STRATEGIES (sizeof(strategies) / sizeof(strategies[0]))
+
+typedef enum {
+ GZIP_STRATEGY_DEFAULT = 0x01,
+ GZIP_STRATEGY_FILTERED = 0x02,
+ GZIP_STRATEGY_HUFFMAN = 0x04,
+ GZIP_STARTEGY_RLE = 0x08,
+ GZIP_STRATEGY_FIXED = 0x10,
+
+ GZIP_ALL_STRATEGIES = 0x1F,
+} GZIP_STRATEGIES;
+
+static const struct {
+ const char *name;
+ int flag;
+ int zlib;
+} strategies[] = {
+ { "default", GZIP_STRATEGY_DEFAULT, Z_DEFAULT_STRATEGY },
+ { "filtered", GZIP_STRATEGY_FILTERED, Z_FILTERED },
+ { "huffman", GZIP_STRATEGY_HUFFMAN, Z_HUFFMAN_ONLY },
+ { "rle", GZIP_STARTEGY_RLE, Z_RLE },
+ { "fixed", GZIP_STRATEGY_FIXED, Z_FIXED },
+};
+
+typedef struct {
+ uint32_t level;
+ uint16_t window;
+ uint16_t strategies;
+} gzip_options_t;
+
+typedef struct {
+ compressor_t base;
+
+ z_stream strm;
+ bool compress;
+
+ size_t block_size;
+ gzip_options_t opt;
+} gzip_compressor_t;
+
+static void gzip_destroy(compressor_t *base)
+{
+ gzip_compressor_t *gzip = (gzip_compressor_t *)base;
+
+ if (gzip->compress) {
+ deflateEnd(&gzip->strm);
+ } else {
+ inflateEnd(&gzip->strm);
+ }
+
+ free(gzip);
+}
+
+static int gzip_write_options(compressor_t *base, int fd)
+{
+ gzip_compressor_t *gzip = (gzip_compressor_t *)base;
+ gzip_options_t opt;
+
+ if (gzip->opt.level == GZIP_DEFAULT_LEVEL &&
+ gzip->opt.window == GZIP_DEFAULT_WINDOW &&
+ gzip->opt.strategies == 0) {
+ return 0;
+ }
+
+ opt.level = htole32(gzip->opt.level);
+ opt.window = htole16(gzip->opt.window);
+ opt.strategies = htole16(gzip->opt.strategies);
+
+ return generic_write_options(fd, &opt, sizeof(opt));
+}
+
+static int gzip_read_options(compressor_t *base, int fd)
+{
+ gzip_compressor_t *gzip = (gzip_compressor_t *)base;
+ gzip_options_t opt;
+
+ if (generic_read_options(fd, &opt, sizeof(opt)))
+ return -1;
+
+ gzip->opt.level = le32toh(opt.level);
+ gzip->opt.window = le16toh(opt.window);
+ gzip->opt.strategies = le16toh(opt.strategies);
+
+ if (gzip->opt.level < 1 || gzip->opt.level > 9) {
+ fprintf(stderr, "Invalid gzip compression level '%d'.\n",
+ gzip->opt.level);
+ return -1;
+ }
+
+ if (gzip->opt.window < 8 || gzip->opt.window > 15) {
+ fprintf(stderr, "Invalid gzip window size '%d'.\n",
+ gzip->opt.window);
+ return -1;
+ }
+
+ if (gzip->opt.strategies & ~GZIP_ALL_STRATEGIES) {
+ fputs("Unknown gzip strategies selected.\n", stderr);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int find_strategy(gzip_compressor_t *gzip, const uint8_t *in,
+ size_t size, uint8_t *out, size_t outsize)
+{
+ int ret, selected = Z_DEFAULT_STRATEGY;
+ size_t i, length, minlength = 0;
+
+ for (i = 0; i < GZIP_NUM_STRATEGIES; ++i) {
+ if (!(strategies[i].flag & gzip->opt.strategies))
+ continue;
+
+ ret = deflateReset(&gzip->strm);
+ if (ret != Z_OK) {
+ fputs("resetting zlib stream failed\n",
+ stderr);
+ return -1;
+ }
+
+ gzip->strm.next_in = (void *)in;
+ gzip->strm.avail_in = size;
+ gzip->strm.next_out = out;
+ gzip->strm.avail_out = outsize;
+
+ ret = deflateParams(&gzip->strm, gzip->opt.level,
+ strategies[i].zlib);
+ if (ret != Z_OK) {
+ fputs("setting deflate parameters failed\n",
+ stderr);
+ return -1;
+ }
+
+ ret = deflate(&gzip->strm, Z_FINISH);
+
+ if (ret == Z_STREAM_END) {
+ length = gzip->strm.total_out;
+
+ if (minlength == 0 || length < minlength) {
+ minlength = length;
+ selected = strategies[i].zlib;
+ }
+ } else if (ret != Z_OK && ret != Z_BUF_ERROR) {
+ fputs("gzip block processing failed\n", stderr);
+ return -1;
+ }
+ }
+
+ return selected;
+}
+
+static ssize_t gzip_do_block(compressor_t *base, const uint8_t *in,
+ size_t size, uint8_t *out, size_t outsize)
+{
+ gzip_compressor_t *gzip = (gzip_compressor_t *)base;
+ int ret, strategy = 0;
+ size_t written;
+
+ if (gzip->compress && gzip->opt.strategies != 0) {
+ strategy = find_strategy(gzip, in, size, out, outsize);
+ if (strategy < 0)
+ return -1;
+ }
+
+ if (gzip->compress) {
+ ret = deflateReset(&gzip->strm);
+ } else {
+ ret = inflateReset(&gzip->strm);
+ }
+
+ if (ret != Z_OK) {
+ fputs("resetting zlib stream failed\n", stderr);
+ return -1;
+ }
+
+ gzip->strm.next_in = (void *)in;
+ gzip->strm.avail_in = size;
+ gzip->strm.next_out = out;
+ gzip->strm.avail_out = outsize;
+
+ if (gzip->compress && gzip->opt.strategies != 0) {
+ ret = deflateParams(&gzip->strm, gzip->opt.level, strategy);
+ if (ret != Z_OK) {
+ fputs("setting selcted deflate parameters failed\n",
+ stderr);
+ return -1;
+ }
+ }
+
+ if (gzip->compress) {
+ ret = deflate(&gzip->strm, Z_FINISH);
+ } else {
+ ret = inflate(&gzip->strm, Z_FINISH);
+ }
+
+ if (ret == Z_STREAM_END) {
+ written = gzip->strm.total_out;
+
+ if (gzip->compress && written >= size)
+ return 0;
+
+ return (ssize_t)written;
+ }
+
+ if (ret != Z_OK && ret != Z_BUF_ERROR) {
+ fputs("gzip block processing failed\n", stderr);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int process_options(char *options, int *level, int *window, int *flags)
+{
+ enum {
+ OPT_WINDOW = 0,
+ OPT_LEVEL,
+ };
+ char *const token[] = {
+ [OPT_WINDOW] = (char *)"window",
+ [OPT_LEVEL] = (char *)"level",
+ NULL
+ };
+ char *subopts, *value;
+ size_t i;
+ int opt;
+
+ subopts = options;
+
+ while (*subopts != '\0') {
+ opt = getsubopt(&subopts, token, &value);
+
+ switch (opt) {
+ case OPT_WINDOW:
+ if (value == NULL)
+ goto fail_value;
+
+ for (i = 0; isdigit(value[i]); ++i)
+ ;
+
+ if (i < 1 || i > 3 || value[i] != '\0')
+ goto fail_window;
+
+ *window = atoi(value);
+
+ if (*window < 8 || *window > 15)
+ goto fail_window;
+ break;
+ case OPT_LEVEL:
+ if (value == NULL)
+ goto fail_value;
+
+ for (i = 0; isdigit(value[i]); ++i)
+ ;
+
+ if (i < 1 || i > 3 || value[i] != '\0')
+ goto fail_level;
+
+ *level = atoi(value);
+
+ if (*level < 1 || *level > 9)
+ goto fail_level;
+ break;
+ default:
+ for (i = 0; i < GZIP_NUM_STRATEGIES; ++i) {
+ if (strcmp(value, strategies[i].name) == 0) {
+ *flags |= strategies[i].flag;
+ break;
+ }
+ }
+ if (i == GZIP_NUM_STRATEGIES)
+ goto fail_opt;
+ break;
+ }
+ }
+
+ return 0;
+fail_window:
+ fputs("Window size must be a number between 8 and 15.\n", stderr);
+ return -1;
+fail_level:
+ fputs("Compression level must be a number between 1 and 9.\n", stderr);
+ return -1;
+fail_opt:
+ fprintf(stderr, "Unknown option '%s'.\n", value);
+ return -1;
+fail_value:
+ fprintf(stderr, "Missing value for '%s'.\n", token[opt]);
+ return -1;
+}
+
+static compressor_t *gzip_create_copy(compressor_t *cmp)
+{
+ gzip_compressor_t *gzip = malloc(sizeof(*gzip));
+ int ret;
+
+ if (gzip == NULL) {
+ perror("creating additional gzip compressor");
+ return NULL;
+ }
+
+ memcpy(gzip, cmp, sizeof(*gzip));
+ memset(&gzip->strm, 0, sizeof(gzip->strm));
+
+ if (gzip->compress) {
+ ret = deflateInit2(&gzip->strm, gzip->opt.level, Z_DEFLATED,
+ gzip->opt.window, 8, Z_DEFAULT_STRATEGY);
+ } else {
+ ret = inflateInit(&gzip->strm);
+ }
+
+ if (ret != Z_OK) {
+ fputs("internal error creating additional zlib stream\n",
+ stderr);
+ free(gzip);
+ return NULL;
+ }
+
+ return (compressor_t *)gzip;
+}
+
+compressor_t *create_gzip_compressor(bool compress, size_t block_size,
+ char *options)
+{
+ int window = GZIP_DEFAULT_WINDOW;
+ int level = GZIP_DEFAULT_LEVEL;
+ gzip_compressor_t *gzip;
+ compressor_t *base;
+ int flags = 0;
+ int ret;
+
+ if (options != NULL) {
+ if (process_options(options, &level, &window, &flags))
+ return NULL;
+ }
+
+ gzip = calloc(1, sizeof(*gzip));
+ base = (compressor_t *)gzip;
+
+ if (gzip == NULL) {
+ perror("creating gzip compressor");
+ return NULL;
+ }
+
+ gzip->opt.level = level;
+ gzip->opt.window = window;
+ gzip->opt.strategies = flags;
+ gzip->compress = compress;
+ gzip->block_size = block_size;
+ base->do_block = gzip_do_block;
+ base->destroy = gzip_destroy;
+ base->write_options = gzip_write_options;
+ base->read_options = gzip_read_options;
+ base->create_copy = gzip_create_copy;
+
+ if (compress) {
+ ret = deflateInit2(&gzip->strm, level, Z_DEFLATED, window, 8,
+ Z_DEFAULT_STRATEGY);
+ } else {
+ ret = inflateInit(&gzip->strm);
+ }
+
+ if (ret != Z_OK) {
+ fputs("internal error creating zlib stream\n", stderr);
+ free(gzip);
+ return NULL;
+ }
+
+ return base;
+}
+
+void compressor_gzip_print_help(void)
+{
+ size_t i;
+
+ printf(
+"Available options for gzip compressor:\n"
+"\n"
+" level=<value> Compression level. Value from 1 to 9.\n"
+" Defaults to %d.\n"
+" window=<size> Deflate compression window size. Value from 8 to 15.\n"
+" Defaults to %d.\n"
+"\n"
+"In additon to the options, one or more strategies can be specified.\n"
+"If multiple stratgies are provided, the one yielding the best compression\n"
+"ratio will be used.\n"
+"\n"
+"The following strategies are available:\n",
+ GZIP_DEFAULT_LEVEL, GZIP_DEFAULT_WINDOW);
+
+ for (i = 0; i < GZIP_NUM_STRATEGIES; ++i)
+ printf("\t%s\n", strategies[i].name);
+}