/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * gzip.c * * Copyright (C) 2019 David Oberhollenzer */ #define SQFS_BUILDING_DLL #include "config.h" #include #include #include #include #include #include "internal.h" typedef struct { sqfs_u32 level; sqfs_u16 window; sqfs_u16 strategies; } gzip_options_t; typedef struct { sqfs_compressor_t base; z_stream strm; bool compress; size_t block_size; gzip_options_t opt; } gzip_compressor_t; static void gzip_destroy(sqfs_object_t *base) { gzip_compressor_t *gzip = (gzip_compressor_t *)base; if (gzip->compress) { deflateEnd(&gzip->strm); } else { inflateEnd(&gzip->strm); } free(gzip); } static void gzip_get_configuration(const sqfs_compressor_t *base, sqfs_compressor_config_t *cfg) { const gzip_compressor_t *gzip = (const gzip_compressor_t *)base; memset(cfg, 0, sizeof(*cfg)); cfg->id = SQFS_COMP_GZIP; cfg->flags = gzip->opt.strategies; cfg->block_size = gzip->block_size; cfg->level = gzip->opt.level; cfg->opt.gzip.window_size = gzip->opt.window; if (!gzip->compress) cfg->flags |= SQFS_COMP_FLAG_UNCOMPRESS; } static int gzip_write_options(sqfs_compressor_t *base, sqfs_file_t *file) { gzip_compressor_t *gzip = (gzip_compressor_t *)base; gzip_options_t opt; if (gzip->opt.level == SQFS_GZIP_DEFAULT_LEVEL && gzip->opt.window == SQFS_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 sqfs_generic_write_options(file, &opt, sizeof(opt)); } static int gzip_read_options(sqfs_compressor_t *base, sqfs_file_t *file) { gzip_compressor_t *gzip = (gzip_compressor_t *)base; gzip_options_t opt; int ret; ret = sqfs_generic_read_options(file, &opt, sizeof(opt)); if (ret) return ret; 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) return SQFS_ERROR_UNSUPPORTED; if (gzip->opt.window < 8 || gzip->opt.window > 15) return SQFS_ERROR_UNSUPPORTED; if (gzip->opt.strategies & ~SQFS_COMP_FLAG_GZIP_ALL) return SQFS_ERROR_UNSUPPORTED; return 0; } static int flag_to_zlib_strategy(int flag) { switch (flag) { case SQFS_COMP_FLAG_GZIP_DEFAULT: return Z_DEFAULT_STRATEGY; case SQFS_COMP_FLAG_GZIP_FILTERED: return Z_FILTERED; case SQFS_COMP_FLAG_GZIP_HUFFMAN: return Z_HUFFMAN_ONLY; case SQFS_COMP_FLAG_GZIP_RLE: return Z_RLE; case SQFS_COMP_FLAG_GZIP_FIXED: return Z_FIXED; } return 0; } static int find_strategy(gzip_compressor_t *gzip, const sqfs_u8 *in, sqfs_u32 size, sqfs_u8 *out, sqfs_u32 outsize) { int ret, strategy, selected = Z_DEFAULT_STRATEGY; size_t i, length, minlength = 0; for (i = 0x01; i & SQFS_COMP_FLAG_GZIP_ALL; i <<= 1) { if ((gzip->opt.strategies & i) == 0) continue; ret = deflateReset(&gzip->strm); if (ret != Z_OK) return SQFS_ERROR_COMPRESSOR; strategy = flag_to_zlib_strategy(i); gzip->strm.next_in = (z_const Bytef *)in; gzip->strm.avail_in = size; gzip->strm.next_out = out; gzip->strm.avail_out = outsize; ret = deflateParams(&gzip->strm, gzip->opt.level, strategy); if (ret != Z_OK) return SQFS_ERROR_COMPRESSOR; ret = deflate(&gzip->strm, Z_FINISH); if (ret == Z_STREAM_END) { length = gzip->strm.total_out; if (minlength == 0 || length < minlength) { minlength = length; selected = strategy; } } else if (ret != Z_OK && ret != Z_BUF_ERROR) { return SQFS_ERROR_COMPRESSOR; } } return selected; } static sqfs_s32 gzip_do_block(sqfs_compressor_t *base, const sqfs_u8 *in, sqfs_u32 size, sqfs_u8 *out, sqfs_u32 outsize) { gzip_compressor_t *gzip = (gzip_compressor_t *)base; int ret, strategy = 0; size_t written; if (size >= 0x7FFFFFFF) return SQFS_ERROR_ARG_INVALID; if (gzip->compress && gzip->opt.strategies != 0) { strategy = find_strategy(gzip, in, size, out, outsize); if (strategy < 0) return strategy; } if (gzip->compress) { ret = deflateReset(&gzip->strm); } else { ret = inflateReset(&gzip->strm); } if (ret != Z_OK) return SQFS_ERROR_COMPRESSOR; gzip->strm.next_in = (const 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) return SQFS_ERROR_COMPRESSOR; } 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 written; } if (ret != Z_OK && ret != Z_BUF_ERROR) return SQFS_ERROR_COMPRESSOR; return 0; } static sqfs_object_t *gzip_create_copy(const sqfs_object_t *cmp) { gzip_compressor_t *gzip = malloc(sizeof(*gzip)); int ret; if (gzip == NULL) 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) { free(gzip); return NULL; } return (sqfs_object_t *)gzip; } int gzip_compressor_create(const sqfs_compressor_config_t *cfg, sqfs_compressor_t **out) { gzip_compressor_t *gzip; sqfs_compressor_t *base; int ret; if (cfg->flags & ~(SQFS_COMP_FLAG_GZIP_ALL | SQFS_COMP_FLAG_GENERIC_ALL)) { return SQFS_ERROR_UNSUPPORTED; } if (cfg->level < SQFS_GZIP_MIN_LEVEL || cfg->level > SQFS_GZIP_MAX_LEVEL) { return SQFS_ERROR_UNSUPPORTED; } if (cfg->opt.gzip.window_size < SQFS_GZIP_MIN_WINDOW || cfg->opt.gzip.window_size > SQFS_GZIP_MAX_WINDOW) { return SQFS_ERROR_UNSUPPORTED; } gzip = calloc(1, sizeof(*gzip)); base = (sqfs_compressor_t *)gzip; if (gzip == NULL) return SQFS_ERROR_ALLOC; gzip->opt.level = cfg->level; gzip->opt.window = cfg->opt.gzip.window_size; gzip->opt.strategies = cfg->flags & SQFS_COMP_FLAG_GZIP_ALL; gzip->compress = (cfg->flags & SQFS_COMP_FLAG_UNCOMPRESS) == 0; gzip->block_size = cfg->block_size; base->get_configuration = gzip_get_configuration; base->do_block = gzip_do_block; base->write_options = gzip_write_options; base->read_options = gzip_read_options; ((sqfs_object_t *)base)->copy = gzip_create_copy; ((sqfs_object_t *)base)->destroy = gzip_destroy; if (gzip->compress) { ret = deflateInit2(&gzip->strm, cfg->level, Z_DEFLATED, cfg->opt.gzip.window_size, 8, Z_DEFAULT_STRATEGY); } else { ret = inflateInit(&gzip->strm); } if (ret != Z_OK) { free(gzip); return SQFS_ERROR_COMPRESSOR; } *out = base; return 0; }