summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/comp/gzip.c265
1 files changed, 255 insertions, 10 deletions
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 <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;
@@ -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=<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);
}