diff options
Diffstat (limited to 'lib/sqfs')
-rw-r--r-- | lib/sqfs/comp/block_processor.c | 72 | ||||
-rw-r--r-- | lib/sqfs/comp/block_processor_parallel.c | 340 | ||||
-rw-r--r-- | lib/sqfs/comp/compressor.c | 183 | ||||
-rw-r--r-- | lib/sqfs/comp/create_block.c | 37 | ||||
-rw-r--r-- | lib/sqfs/comp/gzip.c | 412 | ||||
-rw-r--r-- | lib/sqfs/comp/internal.h | 44 | ||||
-rw-r--r-- | lib/sqfs/comp/lz4.c | 159 | ||||
-rw-r--r-- | lib/sqfs/comp/lzo.c | 321 | ||||
-rw-r--r-- | lib/sqfs/comp/process_block.c | 38 | ||||
-rw-r--r-- | lib/sqfs/comp/xz.c | 360 | ||||
-rw-r--r-- | lib/sqfs/comp/zstd.c | 188 |
11 files changed, 2154 insertions, 0 deletions
diff --git a/lib/sqfs/comp/block_processor.c b/lib/sqfs/comp/block_processor.c new file mode 100644 index 0000000..06dc384 --- /dev/null +++ b/lib/sqfs/comp/block_processor.c @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * block_processor.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "config.h" + +#include "block_processor.h" +#include "util.h" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +struct block_processor_t { + size_t max_block_size; + compressor_t *cmp; + block_cb cb; + void *user; + int status; + + uint8_t scratch[]; +}; + +block_processor_t *block_processor_create(size_t max_block_size, + compressor_t *cmp, + unsigned int num_workers, + void *user, + block_cb callback) +{ + block_processor_t *proc = alloc_flex(sizeof(*proc), 1, max_block_size); + (void)num_workers; + + if (proc == NULL) { + perror("Creating block processor"); + return NULL; + } + + proc->max_block_size = max_block_size; + proc->cmp = cmp; + proc->cb = callback; + proc->user = user; + return proc; +} + +void block_processor_destroy(block_processor_t *proc) +{ + free(proc); +} + +int block_processor_enqueue(block_processor_t *proc, block_t *block) +{ + if (process_block(block, proc->cmp, + proc->scratch, proc->max_block_size)) + goto fail; + + if (proc->cb(proc->user, block)) + goto fail; + + free(block); + return 0; +fail: + free(block); + proc->status = -1; + return -1; +} + +int block_processor_finish(block_processor_t *proc) +{ + return proc->status; +} diff --git a/lib/sqfs/comp/block_processor_parallel.c b/lib/sqfs/comp/block_processor_parallel.c new file mode 100644 index 0000000..b58ad5c --- /dev/null +++ b/lib/sqfs/comp/block_processor_parallel.c @@ -0,0 +1,340 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * block_processor.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "config.h" + +#include "block_processor.h" +#include "util.h" + +#include <pthread.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#define MAX_BACKLOG_FACTOR (10) + +typedef struct { + block_processor_t *shared; + compressor_t *cmp; + pthread_t thread; + uint8_t scratch[]; +} compress_worker_t; + +struct block_processor_t { + pthread_mutex_t mtx; + pthread_cond_t queue_cond; + pthread_cond_t done_cond; + + /* needs rw access by worker and main thread */ + block_t *queue; + block_t *queue_last; + + block_t *done; + bool terminate; + size_t backlog; + + /* used by main thread only */ + uint32_t enqueue_id; + uint32_t dequeue_id; + + unsigned int num_workers; + block_cb cb; + void *user; + int status; + + /* used only by workers */ + size_t max_block_size; + + compress_worker_t *workers[]; +}; + +static void store_completed_block(block_processor_t *shared, block_t *blk) +{ + block_t *it = shared->done, *prev = NULL; + + while (it != NULL) { + if (it->sequence_number >= blk->sequence_number) + break; + prev = it; + it = it->next; + } + + if (prev == NULL) { + blk->next = shared->done; + shared->done = blk; + } else { + blk->next = prev->next; + prev->next = blk; + } +} + +static void *worker_proc(void *arg) +{ + compress_worker_t *worker = arg; + block_processor_t *shared = worker->shared; + block_t *blk = NULL; + + for (;;) { + pthread_mutex_lock(&shared->mtx); + if (blk != NULL) { + store_completed_block(shared, blk); + shared->backlog -= 1; + pthread_cond_broadcast(&shared->done_cond); + } + + while (shared->queue == NULL && !shared->terminate) { + pthread_cond_wait(&shared->queue_cond, + &shared->mtx); + } + + if (shared->terminate) { + pthread_mutex_unlock(&shared->mtx); + break; + } + + blk = shared->queue; + shared->queue = blk->next; + blk->next = NULL; + + if (shared->queue == NULL) + shared->queue_last = NULL; + pthread_mutex_unlock(&shared->mtx); + + if (process_block(blk, worker->cmp, worker->scratch, + shared->max_block_size)) { + blk->flags |= BLK_COMPRESS_ERROR; + } + } + return NULL; +} + +block_processor_t *block_processor_create(size_t max_block_size, + compressor_t *cmp, + unsigned int num_workers, + void *user, + block_cb callback) +{ + block_processor_t *proc; + unsigned int i; + int ret; + + if (num_workers < 1) + num_workers = 1; + + proc = alloc_flex(sizeof(*proc), + sizeof(proc->workers[0]), num_workers); + if (proc == NULL) { + perror("Creating block processor"); + return NULL; + } + + proc->max_block_size = max_block_size; + proc->cb = callback; + proc->user = user; + proc->num_workers = num_workers; + + if (pthread_mutex_init(&proc->mtx, NULL)) { + perror("Creating block processor mutex"); + goto fail_free; + } + + if (pthread_cond_init(&proc->queue_cond, NULL)) { + perror("Creating block processor conditional"); + goto fail_mtx; + } + + if (pthread_cond_init(&proc->done_cond, NULL)) { + perror("Creating block processor completion conditional"); + goto fail_cond; + } + + for (i = 0; i < num_workers; ++i) { + proc->workers[i] = alloc_flex(sizeof(compress_worker_t), + 1, max_block_size); + + if (proc->workers[i] == NULL) { + perror("Creating block worker data"); + goto fail_init; + } + + proc->workers[i]->shared = proc; + proc->workers[i]->cmp = cmp->create_copy(cmp); + + if (proc->workers[i]->cmp == NULL) + goto fail_init; + } + + for (i = 0; i < num_workers; ++i) { + ret = pthread_create(&proc->workers[i]->thread, NULL, + worker_proc, proc->workers[i]); + + if (ret != 0) { + perror("Creating block processor thread"); + goto fail_thread; + } + } + + return proc; +fail_thread: + pthread_mutex_lock(&proc->mtx); + proc->terminate = true; + pthread_cond_broadcast(&proc->queue_cond); + pthread_mutex_unlock(&proc->mtx); + + for (i = 0; i < num_workers; ++i) { + if (proc->workers[i]->thread > 0) { + pthread_join(proc->workers[i]->thread, NULL); + } + } +fail_init: + for (i = 0; i < num_workers; ++i) { + if (proc->workers[i] != NULL) { + if (proc->workers[i]->cmp != NULL) { + proc->workers[i]->cmp-> + destroy(proc->workers[i]->cmp); + } + + free(proc->workers[i]); + } + } + pthread_cond_destroy(&proc->done_cond); +fail_cond: + pthread_cond_destroy(&proc->queue_cond); +fail_mtx: + pthread_mutex_destroy(&proc->mtx); +fail_free: + free(proc); + return NULL; +} + +void block_processor_destroy(block_processor_t *proc) +{ + unsigned int i; + block_t *blk; + + pthread_mutex_lock(&proc->mtx); + proc->terminate = true; + pthread_cond_broadcast(&proc->queue_cond); + pthread_mutex_unlock(&proc->mtx); + + for (i = 0; i < proc->num_workers; ++i) { + pthread_join(proc->workers[i]->thread, NULL); + + proc->workers[i]->cmp->destroy(proc->workers[i]->cmp); + free(proc->workers[i]); + } + + pthread_cond_destroy(&proc->done_cond); + pthread_cond_destroy(&proc->queue_cond); + pthread_mutex_destroy(&proc->mtx); + + while (proc->queue != NULL) { + blk = proc->queue; + proc->queue = blk->next; + free(blk); + } + + while (proc->done != NULL) { + blk = proc->done; + proc->done = blk->next; + free(blk); + } + + free(proc); +} + +static int process_completed_blocks(block_processor_t *proc, block_t *queue) +{ + block_t *it; + + while (queue != NULL) { + it = queue; + queue = queue->next; + + if (it->flags & BLK_COMPRESS_ERROR) { + proc->status = -1; + } else { + if (proc->cb(proc->user, it)) + proc->status = -1; + } + + free(it); + } + + return proc->status; +} + +int block_processor_enqueue(block_processor_t *proc, block_t *block) +{ + block_t *queue = NULL, *it, *prev; + + block->sequence_number = proc->enqueue_id++; + block->next = NULL; + + pthread_mutex_lock(&proc->mtx); + if ((block->flags & BLK_DONT_COMPRESS) && + (block->flags & BLK_DONT_CHECKSUM)) { + store_completed_block(proc, block); + } else { + while (proc->backlog > proc->num_workers * MAX_BACKLOG_FACTOR) + pthread_cond_wait(&proc->done_cond, &proc->mtx); + + if (proc->queue_last == NULL) { + proc->queue = proc->queue_last = block; + } else { + proc->queue_last->next = block; + proc->queue_last = block; + } + + proc->backlog += 1; + } + + it = proc->done; + prev = NULL; + + while (it != NULL && it->sequence_number == proc->dequeue_id) { + prev = it; + it = it->next; + proc->dequeue_id += 1; + } + + if (prev != NULL) { + queue = proc->done; + prev->next = NULL; + proc->done = it; + } + + pthread_cond_broadcast(&proc->queue_cond); + pthread_mutex_unlock(&proc->mtx); + + return process_completed_blocks(proc, queue); +} + +int block_processor_finish(block_processor_t *proc) +{ + block_t *queue, *it; + + pthread_mutex_lock(&proc->mtx); + while (proc->backlog > 0) + pthread_cond_wait(&proc->done_cond, &proc->mtx); + + for (it = proc->done; it != NULL; it = it->next) { + if (it->sequence_number != proc->dequeue_id++) { + pthread_mutex_unlock(&proc->mtx); + goto bug_seqnum; + } + } + + queue = proc->done; + proc->done = NULL; + pthread_mutex_unlock(&proc->mtx); + + return process_completed_blocks(proc, queue); +bug_seqnum: + fputs("[BUG][parallel block processor] " + "gap in sequence numbers!\n", stderr); + return -1; +} diff --git a/lib/sqfs/comp/compressor.c b/lib/sqfs/comp/compressor.c new file mode 100644 index 0000000..e59d948 --- /dev/null +++ b/lib/sqfs/comp/compressor.c @@ -0,0 +1,183 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * compressor.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "config.h" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include "internal.h" +#include "util.h" + +typedef compressor_t *(*compressor_fun_t)(bool compress, size_t block_size, + char *options); + +typedef void (*compressor_help_fun_t)(void); + +static compressor_fun_t compressors[SQFS_COMP_MAX + 1] = { +#ifdef WITH_GZIP + [SQFS_COMP_GZIP] = create_gzip_compressor, +#endif +#ifdef WITH_XZ + [SQFS_COMP_XZ] = create_xz_compressor, +#endif +#ifdef WITH_LZO + [SQFS_COMP_LZO] = create_lzo_compressor, +#endif +#ifdef WITH_LZ4 + [SQFS_COMP_LZ4] = create_lz4_compressor, +#endif +#ifdef WITH_ZSTD + [SQFS_COMP_ZSTD] = create_zstd_compressor, +#endif +}; + +static const compressor_help_fun_t helpfuns[SQFS_COMP_MAX + 1] = { +#ifdef WITH_GZIP + [SQFS_COMP_GZIP] = compressor_gzip_print_help, +#endif +#ifdef WITH_XZ + [SQFS_COMP_XZ] = compressor_xz_print_help, +#endif +#ifdef WITH_LZO + [SQFS_COMP_LZO] = compressor_lzo_print_help, +#endif +#ifdef WITH_LZ4 + [SQFS_COMP_LZ4] = compressor_lz4_print_help, +#endif +#ifdef WITH_ZSTD + [SQFS_COMP_ZSTD] = compressor_zstd_print_help, +#endif +}; + +static const char *names[] = { + [SQFS_COMP_GZIP] = "gzip", + [SQFS_COMP_LZMA] = "lzma", + [SQFS_COMP_LZO] = "lzo", + [SQFS_COMP_XZ] = "xz", + [SQFS_COMP_LZ4] = "lz4", + [SQFS_COMP_ZSTD] = "zstd", +}; + +int generic_write_options(int fd, const void *data, size_t size) +{ + uint8_t buffer[size + 2]; + + *((uint16_t *)buffer) = htole16(0x8000 | size); + memcpy(buffer + 2, data, size); + + if (write_data("writing compressor options", + fd, buffer, sizeof(buffer))) { + return -1; + } + + return sizeof(buffer); +} + +int generic_read_options(int fd, void *data, size_t size) +{ + uint8_t buffer[size + 2]; + + if (read_data_at("reading compressor options", sizeof(sqfs_super_t), + fd, buffer, sizeof(buffer))) { + return -1; + } + + if (le16toh(*((uint16_t *)buffer)) != (0x8000 | size)) { + fputs("reading compressor options: invalid meta data header\n", + stderr); + return -1; + } + + memcpy(data, buffer + 2, size); + return 0; +} + +bool compressor_exists(E_SQFS_COMPRESSOR id) +{ + if (id < SQFS_COMP_MIN || id > SQFS_COMP_MAX) + return false; + + return (compressors[id] != NULL); +} + +compressor_t *compressor_create(E_SQFS_COMPRESSOR id, bool compress, + size_t block_size, char *options) +{ + if (id < SQFS_COMP_MIN || id > SQFS_COMP_MAX) + return NULL; + + if (compressors[id] == NULL) + return NULL; + + return compressors[id](compress, block_size, options); +} + +void compressor_print_help(E_SQFS_COMPRESSOR id) +{ + if (id < SQFS_COMP_MIN || id > SQFS_COMP_MAX) + return; + + if (compressors[id] == NULL) + return; + + helpfuns[id](); +} + +void compressor_print_available(void) +{ + size_t i; + + fputs("Available compressors:\n", stdout); + + for (i = 0; i < sizeof(names) / sizeof(names[0]); ++i) { + if (compressor_exists(i)) + printf("\t%s\n", names[i]); + } + + printf("\nDefault compressor: %s\n", names[compressor_get_default()]); +} + +const char *compressor_name_from_id(E_SQFS_COMPRESSOR id) +{ + if (id < 0 || (size_t)id >= sizeof(names) / sizeof(names[0])) + return NULL; + + return names[id]; +} + +int compressor_id_from_name(const char *name, E_SQFS_COMPRESSOR *out) +{ + size_t i; + + for (i = 0; i < sizeof(names) / sizeof(names[0]); ++i) { + if (names[i] != NULL && strcmp(names[i], name) == 0) { + *out = i; + return 0; + } + } + + return -1; +} + +E_SQFS_COMPRESSOR compressor_get_default(void) +{ +#if defined(WITH_XZ) + return SQFS_COMP_XZ; +#elif defined(WITH_ZSTD) + return SQFS_COMP_ZSTD; +#elif defined(WITH_GZIP) + return SQFS_COMP_GZIP; +#elif defined(WITH_LZO) + return SQFS_COMP_LZO; +#elif defined(WITH_LZ4) + return SQFS_COMP_LZ4; +#else + fputs("No compressor implementation available!\n", stderr); + exit(EXIT_FAILURE); +#endif +} diff --git a/lib/sqfs/comp/create_block.c b/lib/sqfs/comp/create_block.c new file mode 100644 index 0000000..90344bb --- /dev/null +++ b/lib/sqfs/comp/create_block.c @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * create_block.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "config.h" + +#include "block_processor.h" +#include "util.h" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +block_t *create_block(const char *filename, int fd, size_t size, + void *user, uint32_t flags) +{ + block_t *blk = alloc_flex(sizeof(*blk), 1, size); + + if (blk == NULL) { + perror(filename); + return NULL; + } + + if (fd >= 0) { + if (read_data(filename, fd, blk->data, size)) { + free(blk); + return NULL; + } + } + + blk->size = size; + blk->user = user; + blk->flags = flags; + return blk; +} 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); +} diff --git a/lib/sqfs/comp/internal.h b/lib/sqfs/comp/internal.h new file mode 100644 index 0000000..986266d --- /dev/null +++ b/lib/sqfs/comp/internal.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * internal.h + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#ifndef INTERNAL_H +#define INTERNAL_H + +#include "config.h" + +#include "compress.h" +#include "util.h" + +int generic_write_options(int fd, const void *data, size_t size); + +int generic_read_options(int fd, void *data, size_t size); + +compressor_t *create_xz_compressor(bool compress, size_t block_size, + char *options); + +compressor_t *create_gzip_compressor(bool compress, size_t block_size, + char *options); + +compressor_t *create_lzo_compressor(bool compress, size_t block_size, + char *options); + +compressor_t *create_lz4_compressor(bool compress, size_t block_size, + char *options); + +compressor_t *create_zstd_compressor(bool compress, size_t block_size, + char *options); + +void compressor_xz_print_help(void); + +void compressor_gzip_print_help(void); + +void compressor_lzo_print_help(void); + +void compressor_lz4_print_help(void); + +void compressor_zstd_print_help(void); + +#endif /* INTERNAL_H */ diff --git a/lib/sqfs/comp/lz4.c b/lib/sqfs/comp/lz4.c new file mode 100644 index 0000000..abb6c5c --- /dev/null +++ b/lib/sqfs/comp/lz4.c @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * lz4.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 <lz4.h> +#include <lz4hc.h> + +#include "internal.h" + +typedef struct { + compressor_t base; + bool high_compression; +} lz4_compressor_t; + +typedef struct { + uint32_t version; + uint32_t flags; +} lz4_options; + +#define LZ4LEGACY 1 +#define LZ4_FLAG_HC 0x01 + +static int lz4_write_options(compressor_t *base, int fd) +{ + lz4_compressor_t *lz4 = (lz4_compressor_t *)base; + lz4_options opt = { + .version = htole32(LZ4LEGACY), + .flags = htole32(lz4->high_compression ? LZ4_FLAG_HC : 0), + }; + + return generic_write_options(fd, &opt, sizeof(opt)); +} + +static int lz4_read_options(compressor_t *base, int fd) +{ + lz4_options opt; + (void)base; + + if (generic_read_options(fd, &opt, sizeof(opt))) + return -1; + + opt.version = le32toh(opt.version); + opt.flags = le32toh(opt.flags); + + if (opt.version != LZ4LEGACY) { + fprintf(stderr, "unsupported lz4 version '%d'\n", opt.version); + return -1; + } + + return 0; +} + +static ssize_t lz4_comp_block(compressor_t *base, const uint8_t *in, + size_t size, uint8_t *out, size_t outsize) +{ + lz4_compressor_t *lz4 = (lz4_compressor_t *)base; + int ret; + + if (lz4->high_compression) { + ret = LZ4_compress_HC((void *)in, (void *)out, + size, outsize, LZ4HC_CLEVEL_MAX); + } else { + ret = LZ4_compress_default((void *)in, (void *)out, + size, outsize); + } + + if (ret < 0) { + fputs("internal error in lz4 compressor\n", stderr); + return -1; + } + + return ret; +} + +static ssize_t lz4_uncomp_block(compressor_t *base, const uint8_t *in, + size_t size, uint8_t *out, size_t outsize) +{ + int ret; + (void)base; + + ret = LZ4_decompress_safe((void *)in, (void *)out, size, outsize); + + if (ret < 0) { + fputs("internal error in lz4 decompressor\n", stderr); + return -1; + } + + return ret; +} + +static compressor_t *lz4_create_copy(compressor_t *cmp) +{ + lz4_compressor_t *lz4 = malloc(sizeof(*lz4)); + + if (lz4 == NULL) { + perror("creating additional lz4 compressor"); + return NULL; + } + + memcpy(lz4, cmp, sizeof(*lz4)); + return (compressor_t *)lz4; +} + +static void lz4_destroy(compressor_t *base) +{ + free(base); +} + +compressor_t *create_lz4_compressor(bool compress, size_t block_size, + char *options) +{ + lz4_compressor_t *lz4 = calloc(1, sizeof(*lz4)); + compressor_t *base = (compressor_t *)lz4; + (void)block_size; + + if (lz4 == NULL) { + perror("creating lz4 compressor"); + return NULL; + } + + lz4->high_compression = false; + + if (options != NULL) { + if (strcmp(options, "hc") == 0) { + lz4->high_compression = true; + } else { + fputs("Unsupported extra options for lz4 " + "compressor.\n", stderr); + free(lz4); + return NULL; + } + } + + base->destroy = lz4_destroy; + base->do_block = compress ? lz4_comp_block : lz4_uncomp_block; + base->write_options = lz4_write_options; + base->read_options = lz4_read_options; + base->create_copy = lz4_create_copy; + return base; +} + +void compressor_lz4_print_help(void) +{ + fputs("Available options for lz4 compressor:\n" + "\n" + " hc If present, use slower but better compressing\n" + " variant of lz4.\n" + "\n", + stdout); +} diff --git a/lib/sqfs/comp/lzo.c b/lib/sqfs/comp/lzo.c new file mode 100644 index 0000000..09ef75c --- /dev/null +++ b/lib/sqfs/comp/lzo.c @@ -0,0 +1,321 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * lzo.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 <lzo/lzo1x.h> + +#include "internal.h" + + +typedef enum { + LZO_ALGORITHM_LZO1X_1 = 0, + LZO_ALGORITHM_LZO1X_1_11 = 1, + LZO_ALGORITHM_LZO1X_1_12 = 2, + LZO_ALGORITHM_LZO1X_1_15 = 3, + LZO_ALGORITHM_LZO1X_999 = 4, +} LZO_ALGORITHM; + +#define LZO_DEFAULT_ALG LZO_ALGORITHM_LZO1X_999 +#define LZO_DEFAULT_LEVEL 8 +#define LZO_NUM_ALGS (sizeof(lzo_algs) / sizeof(lzo_algs[0])) + +typedef int (*lzo_cb_t)(const lzo_bytep src, lzo_uint src_len, lzo_bytep dst, + lzo_uintp dst_len, lzo_voidp wrkmem); + +static const struct { + const char *name; + lzo_cb_t compress; + size_t bufsize; +} lzo_algs[] = { + [LZO_ALGORITHM_LZO1X_1] = { + .name = "lzo1x_1", + .compress = lzo1x_1_compress, + .bufsize = LZO1X_1_MEM_COMPRESS, + }, + [LZO_ALGORITHM_LZO1X_1_11] = { + .name = "lzo1x_1_11", + .compress = lzo1x_1_11_compress, + .bufsize = LZO1X_1_11_MEM_COMPRESS, + }, + [LZO_ALGORITHM_LZO1X_1_12] = { + .name = "lzo1x_1_12", + .compress = lzo1x_1_12_compress, + .bufsize = LZO1X_1_12_MEM_COMPRESS, + }, + [LZO_ALGORITHM_LZO1X_1_15] = { + .name = "lzo1x_1_15", + .compress = lzo1x_1_15_compress, + .bufsize = LZO1X_1_15_MEM_COMPRESS, + }, + [LZO_ALGORITHM_LZO1X_999] = { + .name = "lzo1x_999", + .compress = lzo1x_999_compress, + .bufsize = LZO1X_999_MEM_COMPRESS, + }, +}; + +typedef struct { + compressor_t base; + int algorithm; + int level; + + uint8_t buffer[]; +} lzo_compressor_t; + +typedef struct { + uint32_t algorithm; + uint32_t level; +} lzo_options_t; + +static int lzo_write_options(compressor_t *base, int fd) +{ + lzo_compressor_t *lzo = (lzo_compressor_t *)base; + lzo_options_t opt; + + if (lzo->algorithm == LZO_DEFAULT_ALG && + lzo->level == LZO_DEFAULT_LEVEL) { + return 0; + } + + opt.algorithm = htole32(lzo->algorithm); + + if (lzo->algorithm == LZO_ALGORITHM_LZO1X_999) { + opt.level = htole32(lzo->level); + } else { + opt.level = 0; + } + + return generic_write_options(fd, &opt, sizeof(opt)); +} + +static int lzo_read_options(compressor_t *base, int fd) +{ + lzo_compressor_t *lzo = (lzo_compressor_t *)base; + lzo_options_t opt; + + if (generic_read_options(fd, &opt, sizeof(opt))) + return -1; + + lzo->algorithm = le32toh(opt.algorithm); + lzo->level = le32toh(opt.level); + + switch(lzo->algorithm) { + case LZO_ALGORITHM_LZO1X_1: + case LZO_ALGORITHM_LZO1X_1_11: + case LZO_ALGORITHM_LZO1X_1_12: + case LZO_ALGORITHM_LZO1X_1_15: + if (lzo->level != 0) + goto fail_level; + break; + case LZO_ALGORITHM_LZO1X_999: + if (lzo->level < 1 || lzo->level > 9) + goto fail_level; + break; + default: + fputs("Unsupported LZO variant specified.\n", stderr); + return -1; + } + + return 0; +fail_level: + fputs("Unsupported LZO compression level specified.\n", stderr); + return -1; +} + +static ssize_t lzo_comp_block(compressor_t *base, const uint8_t *in, + size_t size, uint8_t *out, size_t outsize) +{ + lzo_compressor_t *lzo = (lzo_compressor_t *)base; + lzo_uint len = outsize; + int ret; + + if (lzo->algorithm == LZO_ALGORITHM_LZO1X_999 && + lzo->level != LZO_DEFAULT_LEVEL) { + ret = lzo1x_999_compress_level(in, size, out, &len, + lzo->buffer, NULL, 0, 0, + lzo->level); + } else { + ret = lzo_algs[lzo->algorithm].compress(in, size, out, + &len, lzo->buffer); + } + + if (ret != LZO_E_OK) { + fputs("LZO compression failed.\n", stderr); + return -1; + } + + if (len < size) + return len; + + return 0; +} + +static ssize_t lzo_uncomp_block(compressor_t *base, const uint8_t *in, + size_t size, uint8_t *out, size_t outsize) +{ + lzo_compressor_t *lzo = (lzo_compressor_t *)base; + lzo_uint len = outsize; + int ret; + + ret = lzo1x_decompress_safe(in, size, out, &len, lzo->buffer); + + if (ret != LZO_E_OK) { + fputs("lzo decompress: input data is corrupted\n", stderr); + return -1; + } + + return len; +} + +static compressor_t *lzo_create_copy(compressor_t *cmp) +{ + lzo_compressor_t *other = (lzo_compressor_t *)cmp; + lzo_compressor_t *lzo; + + lzo = alloc_flex(sizeof(*lzo), 1, lzo_algs[other->algorithm].bufsize); + + if (lzo == NULL) { + perror("creating additional lzo compressor"); + return NULL; + } + + memcpy(lzo, other, sizeof(*lzo)); + return (compressor_t *)lzo; +} + +static void lzo_destroy(compressor_t *base) +{ + free(base); +} + +static int process_options(char *options, int *algorithm, int *level) +{ + enum { + OPT_ALG = 0, + OPT_LEVEL, + }; + char *const token[] = { + [OPT_ALG] = (char *)"algorithm", + [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_ALG: + if (value == NULL) + goto fail_value; + + for (i = 0; i < LZO_NUM_ALGS; ++i) { + if (strcmp(lzo_algs[i].name, value) == 0) { + *algorithm = i; + break; + } + } + + if (i == LZO_NUM_ALGS) { + fprintf(stderr, "Unknown lzo variant '%s'.\n", + value); + return -1; + } + 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: + goto fail_opt; + } + } + + return 0; +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_lzo_compressor(bool compress, size_t block_size, + char *options) +{ + lzo_compressor_t *lzo; + compressor_t *base; + int level, alg; + (void)block_size; + + alg = LZO_DEFAULT_ALG; + level = LZO_DEFAULT_LEVEL; + + if (options != NULL && process_options(options, &alg, &level) != 0) + return NULL; + + lzo = alloc_flex(sizeof(*lzo), 1, lzo_algs[alg].bufsize); + base = (compressor_t *)lzo; + + if (lzo == NULL) { + perror("creating lzo compressor"); + return NULL; + } + + lzo->algorithm = alg; + lzo->level = level; + + base->destroy = lzo_destroy; + base->do_block = compress ? lzo_comp_block : lzo_uncomp_block; + base->write_options = lzo_write_options; + base->read_options = lzo_read_options; + base->create_copy = lzo_create_copy; + return base; +} + +void compressor_lzo_print_help(void) +{ + size_t i; + + fputs("Available options for lzo compressor:\n" + "\n" + " algorithm=<name> Specify the variant of lzo to use.\n" + " Defaults to 'lzo1x_999'.\n" + " level=<value> For lzo1x_999, the compression level.\n" + " Value from 1 to 9. Defaults to 8.\n" + " Ignored if algorithm is not lzo1x_999.\n" + "\n" + "Available algorithms:\n", + stdout); + + for (i = 0; i < LZO_NUM_ALGS; ++i) + printf("\t%s\n", lzo_algs[i].name); +} diff --git a/lib/sqfs/comp/process_block.c b/lib/sqfs/comp/process_block.c new file mode 100644 index 0000000..0fcbae0 --- /dev/null +++ b/lib/sqfs/comp/process_block.c @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * process_block.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "config.h" + +#include "block_processor.h" +#include "util.h" + +#include <string.h> +#include <zlib.h> + +int process_block(block_t *block, compressor_t *cmp, + uint8_t *scratch, size_t scratch_size) +{ + ssize_t ret; + + if (!(block->flags & BLK_DONT_CHECKSUM)) + block->checksum = crc32(0, block->data, block->size); + + if (!(block->flags & BLK_DONT_COMPRESS)) { + ret = cmp->do_block(cmp, block->data, block->size, + scratch, scratch_size); + + if (ret < 0) + return -1; + + if (ret > 0) { + memcpy(block->data, scratch, ret); + block->size = ret; + block->flags |= BLK_IS_COMPRESSED; + } + } + + return 0; +} diff --git a/lib/sqfs/comp/xz.c b/lib/sqfs/comp/xz.c new file mode 100644 index 0000000..d38aab6 --- /dev/null +++ b/lib/sqfs/comp/xz.c @@ -0,0 +1,360 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * xz.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 <lzma.h> + +#include "internal.h" + +typedef enum { + XZ_FILTER_X86 = 0x01, + XZ_FILTER_POWERPC = 0x02, + XZ_FILTER_IA64 = 0x04, + XZ_FILTER_ARM = 0x08, + XZ_FILTER_ARMTHUMB = 0x10, + XZ_FILTER_SPARC = 0x20, + + XZ_FILTER_ALL = 0x3F, +} XZ_FILTER_FLAG; + +typedef struct { + compressor_t base; + size_t block_size; + size_t dict_size; + int flags; +} xz_compressor_t; + +typedef struct { + uint32_t dict_size; + uint32_t flags; +} xz_options_t; + +static const struct { + const char *name; + lzma_vli filter; + int flag; +} xz_filters[] = { + { "x86", LZMA_FILTER_X86, XZ_FILTER_X86 }, + { "powerpc", LZMA_FILTER_POWERPC, XZ_FILTER_POWERPC }, + { "ia64", LZMA_FILTER_IA64, XZ_FILTER_IA64 }, + { "arm", LZMA_FILTER_ARM, XZ_FILTER_ARM }, + { "armthumb", LZMA_FILTER_ARMTHUMB, XZ_FILTER_ARMTHUMB }, + { "sparc", LZMA_FILTER_SPARC, XZ_FILTER_SPARC }, +}; + +#define XZ_NUM_FILTERS (sizeof(xz_filters) / sizeof(xz_filters[0])) + +static int xz_write_options(compressor_t *base, int fd) +{ + xz_compressor_t *xz = (xz_compressor_t *)base; + xz_options_t opt; + + if (xz->flags == 0 && xz->dict_size == xz->block_size) + return 0; + + opt.dict_size = htole32(xz->dict_size); + opt.flags = htole32(xz->flags); + + return generic_write_options(fd, &opt, sizeof(opt)); +} + +static int xz_read_options(compressor_t *base, int fd) +{ + xz_compressor_t *xz = (xz_compressor_t *)base; + xz_options_t opt; + uint32_t mask; + + if (generic_read_options(fd, &opt, sizeof(opt))) + return -1; + + opt.dict_size = le32toh(opt.dict_size); + opt.flags = le32toh(opt.flags); + + mask = opt.dict_size & (opt.dict_size - 1); + + if (mask != 0 && ((mask & (mask - 1)) != 0)) { + fputs("Invalid lzma dictionary size.\n", stderr); + return -1; + } + + if (opt.flags & ~XZ_FILTER_ALL) { + fputs("Unknown BCJ filter used.\n", stderr); + return -1; + } + + xz->flags = opt.flags; + xz->dict_size = opt.dict_size; + return 0; +} + +static ssize_t compress(xz_compressor_t *xz, lzma_vli filter, + const uint8_t *in, size_t size, + uint8_t *out, size_t outsize) +{ + lzma_filter filters[5]; + lzma_options_lzma opt; + size_t written = 0; + lzma_ret ret; + int i = 0; + + if (lzma_lzma_preset(&opt, LZMA_PRESET_DEFAULT)) { + fputs("error initializing xz options\n", stderr); + return -1; + } + + 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) { + fputs("xz block compress failed\n", stderr); + return -1; + } + + return 0; +} + +static ssize_t xz_comp_block(compressor_t *base, const uint8_t *in, + size_t size, uint8_t *out, size_t outsize) +{ + xz_compressor_t *xz = (xz_compressor_t *)base; + lzma_vli selected = LZMA_VLI_UNKNOWN; + size_t i, smallest; + ssize_t ret; + + ret = compress(xz, LZMA_VLI_UNKNOWN, in, size, out, outsize); + if (ret < 0 || xz->flags == 0) + return ret; + + smallest = ret; + + for (i = 0; i < XZ_NUM_FILTERS; ++i) { + if (!(xz->flags & xz_filters[i].flag)) + continue; + + ret = compress(xz, xz_filters[i].filter, in, size, out, outsize); + if (ret < 0) + return -1; + + if (ret > 0 && (smallest == 0 || (size_t)ret < smallest)) { + smallest = ret; + selected = xz_filters[i].filter; + } + } + + if (smallest == 0) + return 0; + + return compress(xz, selected, in, size, out, outsize); +} + +static ssize_t xz_uncomp_block(compressor_t *base, const uint8_t *in, + size_t size, uint8_t *out, size_t outsize) +{ + uint64_t memlimit = 32 * 1024 * 1024; + size_t dest_pos = 0; + size_t src_pos = 0; + lzma_ret ret; + (void)base; + + ret = lzma_stream_buffer_decode(&memlimit, 0, NULL, + in, &src_pos, size, + out, &dest_pos, outsize); + + if (ret == LZMA_OK && size == src_pos) + return (ssize_t)dest_pos; + + fputs("xz block extract failed\n", stderr); + return -1; +} + +static compressor_t *xz_create_copy(compressor_t *cmp) +{ + xz_compressor_t *xz = malloc(sizeof(*xz)); + + if (xz == NULL) { + perror("creating additional xz compressor"); + return NULL; + } + + memcpy(xz, cmp, sizeof(*xz)); + return (compressor_t *)xz; +} + +static void xz_destroy(compressor_t *base) +{ + free(base); +} + +static int process_options(char *options, size_t blocksize, + int *flags, uint64_t *dictsize) +{ + enum { + OPT_DICT = 0, + }; + char *const token[] = { + [OPT_DICT] = (char *)"dictsize", + NULL + }; + char *subopts, *value; + uint64_t mask; + size_t i; + int opt; + + subopts = options; + + while (*subopts != '\0') { + opt = getsubopt(&subopts, token, &value); + + switch (opt) { + case OPT_DICT: + if (value == NULL) + goto fail_value; + + for (i = 0; isdigit(value[i]); ++i) + ; + + if (i < 1 || i > 9) + goto fail_dict; + + *dictsize = atol(value); + + switch (value[i]) { + case '\0': + break; + case 'm': + case 'M': + *dictsize <<= 20; + break; + case 'k': + case 'K': + *dictsize <<= 10; + break; + case '%': + *dictsize = ((*dictsize) * blocksize) / 100; + break; + default: + goto fail_dict; + } + + if (*dictsize > 0x0FFFFFFFFUL) + goto fail_dict_ov; + + mask = *dictsize & (*dictsize - 1); + + if (mask != 0 && ((mask & (mask - 1)) != 0)) + goto fail_dict_pot; + break; + default: + for (i = 0; i < XZ_NUM_FILTERS; ++i) { + if (strcmp(value, xz_filters[i].name) == 0) { + *flags |= xz_filters[i].flag; + break; + } + } + if (i == XZ_NUM_FILTERS) + goto fail_opt; + break; + } + } + + return 0; +fail_dict_pot: + fputs("dictionary size must be either 2^n or 2^n + 2^(n-1)\n", stderr); + return -1; +fail_dict_ov: + fputs("dictionary size too large.\n", stderr); + return -1; +fail_dict: + fputs("dictionary size must be a number with the optional " + "suffix 'm','k' or '%'.\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_xz_compressor(bool compress, size_t block_size, + char *options) +{ + uint64_t dictsize = block_size; + xz_compressor_t *xz; + compressor_t *base; + int flags = 0; + + if (options != NULL) { + if (process_options(options, block_size, &flags, &dictsize)) + return NULL; + } + + xz = calloc(1, sizeof(*xz)); + base = (compressor_t *)xz; + if (xz == NULL) { + perror("creating xz compressor"); + return NULL; + } + + xz->flags = flags; + xz->dict_size = dictsize; + xz->block_size = block_size; + base->destroy = xz_destroy; + base->do_block = compress ? xz_comp_block : xz_uncomp_block; + base->write_options = xz_write_options; + base->read_options = xz_read_options; + base->create_copy = xz_create_copy; + return base; +} + +void compressor_xz_print_help(void) +{ + size_t i; + + fputs( +"Available options for xz compressor:\n" +"\n" +" dictsize=<value> Dictionary size. Either a value in bytes or a\n" +" percentage of the block size. Defaults to 100%.\n" +" The suffix '%' indicates a percentage. 'K' and 'M'\n" +" can also be used for kibi and mebi bytes\n" +" respecitively.\n" +"\n" +"In additon to the options, one or more bcj filters can be specified.\n" +"If multiple filters are provided, the one yielding the best compression\n" +"ratio will be used.\n" +"\n" +"The following filters are available:\n", + stdout); + + for (i = 0; i < XZ_NUM_FILTERS; ++i) + printf("\t%s\n", xz_filters[i].name); +} diff --git a/lib/sqfs/comp/zstd.c b/lib/sqfs/comp/zstd.c new file mode 100644 index 0000000..e206338 --- /dev/null +++ b/lib/sqfs/comp/zstd.c @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * zstd.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 <zstd.h> + +#include "internal.h" + +#define ZSTD_DEFAULT_COMPRESSION_LEVEL 15 + +typedef struct { + compressor_t base; + ZSTD_CCtx *zctx; + int level; +} zstd_compressor_t; + +typedef struct { + uint32_t level; +} zstd_options_t; + +static int zstd_write_options(compressor_t *base, int fd) +{ + zstd_compressor_t *zstd = (zstd_compressor_t *)base; + zstd_options_t opt; + (void)fd; + + if (zstd->level == ZSTD_DEFAULT_COMPRESSION_LEVEL) + return 0; + + opt.level = htole32(zstd->level); + return generic_write_options(fd, &opt, sizeof(opt)); +} + +static int zstd_read_options(compressor_t *base, int fd) +{ + zstd_options_t opt; + (void)base; + + if (generic_read_options(fd, &opt, sizeof(opt))) + return -1; + + opt.level = le32toh(opt.level); + return 0; +} + +static ssize_t zstd_comp_block(compressor_t *base, const uint8_t *in, + size_t size, uint8_t *out, size_t outsize) +{ + zstd_compressor_t *zstd = (zstd_compressor_t *)base; + size_t ret; + + ret = ZSTD_compressCCtx(zstd->zctx, out, outsize, in, size, + zstd->level); + + if (ZSTD_isError(ret)) { + fprintf(stderr, "internal error in ZSTD compressor: %s\n", + ZSTD_getErrorName(ret)); + return -1; + } + + return ret < size ? ret : 0; +} + +static ssize_t zstd_uncomp_block(compressor_t *base, const uint8_t *in, + size_t size, uint8_t *out, size_t outsize) +{ + size_t ret; + (void)base; + + ret = ZSTD_decompress(out, outsize, in, size); + + if (ZSTD_isError(ret)) { + fprintf(stderr, "error uncompressing ZSTD compressed data: %s", + ZSTD_getErrorName(ret)); + return -1; + } + + return ret; +} + +static compressor_t *zstd_create_copy(compressor_t *cmp) +{ + zstd_compressor_t *zstd = malloc(sizeof(*zstd)); + + if (zstd == NULL) { + perror("creating additional zstd compressor"); + return NULL; + } + + memcpy(zstd, cmp, sizeof(*zstd)); + + zstd->zctx = ZSTD_createCCtx(); + + if (zstd->zctx == NULL) { + fputs("error creating addtional zstd compression context\n", + stderr); + free(zstd); + return NULL; + } + + return (compressor_t *)zstd; +} + +static void zstd_destroy(compressor_t *base) +{ + zstd_compressor_t *zstd = (zstd_compressor_t *)base; + + ZSTD_freeCCtx(zstd->zctx); + free(zstd); +} + +compressor_t *create_zstd_compressor(bool compress, size_t block_size, + char *options) +{ + zstd_compressor_t *zstd = calloc(1, sizeof(*zstd)); + compressor_t *base = (compressor_t *)zstd; + size_t i; + (void)block_size; + + if (zstd == NULL) { + perror("creating zstd compressor"); + return NULL; + } + + zstd->level = ZSTD_DEFAULT_COMPRESSION_LEVEL; + + if (options != NULL) { + if (strncmp(options, "level=", 6) == 0) { + options += 6; + + for (i = 0; isdigit(options[i]); ++i) + ; + + if (i == 0 || options[i] != '\0' || i > 6) + goto fail_level; + + zstd->level = atoi(options); + + if (zstd->level < 1 || zstd->level > ZSTD_maxCLevel()) + goto fail_level; + } else { + goto fail_opt; + } + } + + zstd->zctx = ZSTD_createCCtx(); + if (zstd->zctx == NULL) { + fputs("error creating zstd compression context\n", stderr); + free(zstd); + return NULL; + } + + base->destroy = zstd_destroy; + base->do_block = compress ? zstd_comp_block : zstd_uncomp_block; + base->write_options = zstd_write_options; + base->read_options = zstd_read_options; + base->create_copy = zstd_create_copy; + return base; +fail_level: + fprintf(stderr, "zstd compression level must be a number in the range " + "1...%d\n", ZSTD_maxCLevel()); + free(zstd); + return NULL; +fail_opt: + fputs("Unsupported extra options for zstd compressor\n", stderr); + free(zstd); + return NULL; +} + +void compressor_zstd_print_help(void) +{ + printf("Available options for zstd compressor:\n" + "\n" + " level=<value> Set compression level. Defaults to %d.\n" + " Maximum is %d.\n" + "\n", + ZSTD_DEFAULT_COMPRESSION_LEVEL, ZSTD_maxCLevel()); +} |