From 94481929885bbed5b738c05c616d4b2e71383873 Mon Sep 17 00:00:00 2001 From: David Oberhollenzer Date: Thu, 23 May 2019 17:27:45 +0200 Subject: Add options for deflate compressor Signed-off-by: David Oberhollenzer --- lib/comp/gzip.c | 265 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 255 insertions(+), 10 deletions(-) (limited to 'lib/comp') diff --git a/lib/comp/gzip.c b/lib/comp/gzip.c index bc70bcf..732245f 100644 --- a/lib/comp/gzip.c +++ b/lib/comp/gzip.c @@ -3,10 +3,43 @@ #include #include #include +#include #include #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; @@ -14,6 +47,7 @@ typedef struct { bool compress; size_t block_size; + gzip_options_t opt; } gzip_compressor_t; static void gzip_destroy(compressor_t *base) @@ -31,25 +65,114 @@ static void gzip_destroy(compressor_t *base) static int gzip_write_options(compressor_t *base, int fd) { - (void)base; - (void)fd; - return 0; + 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) { - (void)base; - (void)fd; - fputs("gzip extra options are not yet implemented\n", stderr); - return -1; + 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; - int ret; + + 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); @@ -67,6 +190,15 @@ static ssize_t gzip_do_block(compressor_t *base, const uint8_t *in, 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 { @@ -82,7 +214,7 @@ static ssize_t gzip_do_block(compressor_t *base, const uint8_t *in, return (ssize_t)written; } - if (ret != Z_OK) { + if (ret != Z_OK && ret != Z_BUF_ERROR) { fputs("gzip block processing failed\n", stderr); return -1; } @@ -90,18 +222,111 @@ static ssize_t gzip_do_block(compressor_t *base, const uint8_t *in, 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; +} + compressor_t *create_gzip_compressor(bool compress, size_t block_size, char *options) { gzip_compressor_t *gzip = calloc(1, sizeof(*gzip)); compressor_t *base = (compressor_t *)gzip; + int window = GZIP_DEFAULT_WINDOW; + int level = GZIP_DEFAULT_LEVEL; + 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; @@ -110,7 +335,8 @@ compressor_t *create_gzip_compressor(bool compress, size_t block_size, base->read_options = gzip_read_options; if (compress) { - ret = deflateInit(&gzip->strm, Z_BEST_COMPRESSION); + ret = deflateInit2(&gzip->strm, level, Z_DEFLATED, window, 8, + Z_DEFAULT_STRATEGY); } else { ret = inflateInit(&gzip->strm); } @@ -126,4 +352,23 @@ compressor_t *create_gzip_compressor(bool compress, size_t block_size, void compressor_gzip_print_help(void) { + size_t i; + + printf( +"Available options for gzip compressor:\n" +"\n" +" level= Compression level. Value from 1 to 9.\n" +" Defaults to %d.\n" +" window= 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); } -- cgit v1.2.3