aboutsummaryrefslogtreecommitdiff
path: root/lib/sqfs/src
diff options
context:
space:
mode:
authorDavid Oberhollenzer <david.oberhollenzer@sigma-star.at>2023-01-31 11:21:30 +0100
committerDavid Oberhollenzer <david.oberhollenzer@sigma-star.at>2023-01-31 13:51:49 +0100
commitcdccc69c62579b0c13b35fad0728079652b8f3c9 (patch)
tree9fa54c710f73c5e08a9c8466e7a712eb63ee07ac /lib/sqfs/src
parent2182129c8f359c4fa1390eaba7a65b595ccd4182 (diff)
Move library source into src sub-directory
Signed-off-by: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
Diffstat (limited to 'lib/sqfs/src')
-rw-r--r--lib/sqfs/src/block_processor/backend.c335
-rw-r--r--lib/sqfs/src/block_processor/block_processor.c358
-rw-r--r--lib/sqfs/src/block_processor/frontend.c243
-rw-r--r--lib/sqfs/src/block_processor/internal.h115
-rw-r--r--lib/sqfs/src/block_writer.c238
-rw-r--r--lib/sqfs/src/comp/compressor.c218
-rw-r--r--lib/sqfs/src/comp/gzip.c307
-rw-r--r--lib/sqfs/src/comp/internal.h46
-rw-r--r--lib/sqfs/src/comp/lz4.c172
-rw-r--r--lib/sqfs/src/comp/lzma.c281
-rw-r--r--lib/sqfs/src/comp/xz.c324
-rw-r--r--lib/sqfs/src/comp/zstd.c172
-rw-r--r--lib/sqfs/src/data_reader.c374
-rw-r--r--lib/sqfs/src/dir_reader/dir_reader.c366
-rw-r--r--lib/sqfs/src/dir_reader/get_path.c81
-rw-r--r--lib/sqfs/src/dir_reader/internal.h52
-rw-r--r--lib/sqfs/src/dir_reader/read_tree.c288
-rw-r--r--lib/sqfs/src/dir_writer.c460
-rw-r--r--lib/sqfs/src/frag_table.c204
-rw-r--r--lib/sqfs/src/id_table.c162
-rw-r--r--lib/sqfs/src/inode.c380
-rw-r--r--lib/sqfs/src/meta_reader.c191
-rw-r--r--lib/sqfs/src/meta_writer.c215
-rw-r--r--lib/sqfs/src/misc.c17
-rw-r--r--lib/sqfs/src/read_inode.c424
-rw-r--r--lib/sqfs/src/read_super.c83
-rw-r--r--lib/sqfs/src/read_table.c91
-rw-r--r--lib/sqfs/src/readdir.c161
-rw-r--r--lib/sqfs/src/super.c50
-rw-r--r--lib/sqfs/src/unix/io_file.c196
-rw-r--r--lib/sqfs/src/win32/io_file.c237
-rw-r--r--lib/sqfs/src/write_inode.c220
-rw-r--r--lib/sqfs/src/write_super.c39
-rw-r--r--lib/sqfs/src/write_table.c81
-rw-r--r--lib/sqfs/src/xattr/xattr.c49
-rw-r--r--lib/sqfs/src/xattr/xattr_reader.c336
-rw-r--r--lib/sqfs/src/xattr/xattr_writer.c127
-rw-r--r--lib/sqfs/src/xattr/xattr_writer.h63
-rw-r--r--lib/sqfs/src/xattr/xattr_writer_flush.c347
-rw-r--r--lib/sqfs/src/xattr/xattr_writer_record.c145
40 files changed, 8248 insertions, 0 deletions
diff --git a/lib/sqfs/src/block_processor/backend.c b/lib/sqfs/src/block_processor/backend.c
new file mode 100644
index 0000000..b443c9d
--- /dev/null
+++ b/lib/sqfs/src/block_processor/backend.c
@@ -0,0 +1,335 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * backend.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "internal.h"
+
+static int set_block_size(sqfs_inode_generic_t **inode,
+ sqfs_u32 index, sqfs_u32 size)
+{
+ size_t min_size = (index + 1) * sizeof(sqfs_u32);
+ size_t avail = (*inode)->payload_bytes_available;
+ sqfs_inode_generic_t *new;
+ size_t newsz;
+
+ if (avail < min_size) {
+ newsz = avail ? avail : (sizeof(sqfs_u32) * 4);
+ while (newsz < min_size)
+ newsz *= 2;
+
+ if (SZ_ADD_OV(newsz, sizeof(**inode), &newsz))
+ return SQFS_ERROR_OVERFLOW;
+
+ if (sizeof(size_t) > sizeof(sqfs_u32)) {
+ if ((newsz - sizeof(**inode)) > 0x0FFFFFFFFUL)
+ return SQFS_ERROR_OVERFLOW;
+ }
+
+ new = realloc((*inode), newsz);
+ if (new == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ (*inode) = new;
+ (*inode)->payload_bytes_available = newsz - sizeof(**inode);
+ }
+
+ (*inode)->extra[index] = size;
+
+ if (min_size >= (*inode)->payload_bytes_used)
+ (*inode)->payload_bytes_used = min_size;
+
+ return 0;
+}
+
+static void release_old_block(sqfs_block_processor_t *proc, sqfs_block_t *blk)
+{
+ blk->next = proc->free_list;
+ proc->free_list = blk;
+
+ proc->backlog -= 1;
+}
+
+static int process_completed_block(sqfs_block_processor_t *proc, sqfs_block_t *blk)
+{
+ sqfs_u64 location;
+ sqfs_u32 size;
+ int err;
+
+ if (blk->flags & SQFS_BLK_FRAGMENT_BLOCK) {
+ sqfs_block_t *it = proc->fblk_in_flight, *prev = NULL;
+
+ while (it != NULL && it->index != blk->index) {
+ prev = it;
+ it = it->next;
+ }
+
+ if (it != NULL) {
+ if (prev == NULL) {
+ proc->fblk_in_flight = it->next;
+ } else {
+ prev->next = it->next;
+ }
+ free(it);
+ }
+ }
+
+ err = proc->wr->write_data_block(proc->wr, blk->user, blk->size,
+ blk->checksum,
+ blk->flags & ~BLK_FLAG_INTERNAL,
+ blk->data, &location);
+ if (err)
+ goto out;
+
+ proc->stats.output_bytes_generated += blk->size;
+
+ if (blk->flags & SQFS_BLK_IS_SPARSE) {
+ if (blk->inode != NULL) {
+ sqfs_inode_make_extended(*(blk->inode));
+ (*(blk->inode))->data.file_ext.sparse += blk->size;
+
+ err = set_block_size(blk->inode, blk->index, 0);
+ if (err)
+ goto out;
+ }
+ proc->stats.sparse_block_count += 1;
+ } else if (blk->size != 0) {
+ size = blk->size;
+ if (!(blk->flags & SQFS_BLK_IS_COMPRESSED))
+ size |= 1 << 24;
+
+ if (blk->flags & SQFS_BLK_FRAGMENT_BLOCK) {
+ if (proc->frag_tbl != NULL) {
+ err = sqfs_frag_table_set(proc->frag_tbl,
+ blk->index, location,
+ size);
+ if (err)
+ goto out;
+ }
+ proc->stats.frag_block_count += 1;
+ } else {
+ if (blk->inode != NULL) {
+ err = set_block_size(blk->inode, blk->index,
+ size);
+ if (err)
+ goto out;
+ }
+ proc->stats.data_block_count += 1;
+ }
+ }
+
+ if (blk->flags & SQFS_BLK_LAST_BLOCK && blk->inode != NULL)
+ sqfs_inode_set_file_block_start(*(blk->inode), location);
+out:
+ release_old_block(proc, blk);
+ return err;
+}
+
+static int process_completed_fragment(sqfs_block_processor_t *proc,
+ sqfs_block_t *frag)
+{
+ chunk_info_t *chunk = NULL, search;
+ struct hash_entry *entry;
+ sqfs_u32 index, offset;
+ int err;
+
+ if (frag->flags & SQFS_BLK_IS_SPARSE) {
+ if (frag->inode != NULL) {
+ sqfs_inode_make_extended(*(frag->inode));
+ set_block_size(frag->inode, frag->index, 0);
+ (*(frag->inode))->data.file_ext.sparse += frag->size;
+ }
+ proc->stats.sparse_block_count += 1;
+ release_old_block(proc, frag);
+ return 0;
+ }
+
+ proc->stats.total_frag_count += 1;
+
+ if (!(frag->flags & SQFS_BLK_DONT_DEDUPLICATE)) {
+ search.hash = frag->checksum;
+ search.size = frag->size;
+
+ proc->current_frag = frag;
+ proc->fblk_lookup_error = 0;
+ entry = hash_table_search_pre_hashed(proc->frag_ht,
+ search.hash, &search);
+ proc->current_frag = NULL;
+
+ if (proc->fblk_lookup_error != 0) {
+ err = proc->fblk_lookup_error;
+ goto fail;
+ }
+
+ if (entry != NULL) {
+ if (frag->inode != NULL) {
+ chunk = entry->data;
+ sqfs_inode_set_frag_location(*(frag->inode),
+ chunk->index,
+ chunk->offset);
+ }
+ release_old_block(proc, frag);
+ return 0;
+ }
+ }
+
+ if (proc->frag_block != NULL) {
+ size_t size = proc->frag_block->size + frag->size;
+
+ if (size > proc->max_block_size) {
+ proc->frag_block->io_seq_num = proc->io_seq_num++;
+
+ err = enqueue_block(proc, proc->frag_block);
+ proc->frag_block = NULL;
+
+ if (err)
+ goto fail;
+ }
+ }
+
+ if (proc->frag_block == NULL) {
+ if (proc->frag_tbl == NULL) {
+ index = 0;
+ } else {
+ err = sqfs_frag_table_append(proc->frag_tbl,
+ 0, 0, &index);
+ if (err)
+ goto fail;
+ }
+
+ offset = 0;
+ proc->frag_block = frag;
+ proc->frag_block->index = index;
+ proc->frag_block->flags &=
+ (SQFS_BLK_DONT_COMPRESS | SQFS_BLK_ALIGN);
+ proc->frag_block->flags |= SQFS_BLK_FRAGMENT_BLOCK;
+ } else {
+ index = proc->frag_block->index;
+ offset = proc->frag_block->size;
+
+ memcpy(proc->frag_block->data + proc->frag_block->size,
+ frag->data, frag->size);
+
+ proc->frag_block->size += frag->size;
+ proc->frag_block->flags |=
+ (frag->flags &
+ (SQFS_BLK_DONT_COMPRESS | SQFS_BLK_ALIGN));
+ }
+
+ if (proc->frag_tbl != NULL) {
+ err = SQFS_ERROR_ALLOC;
+ chunk = calloc(1, sizeof(*chunk));
+ if (chunk == NULL)
+ goto fail;
+
+ chunk->index = index;
+ chunk->offset = offset;
+ chunk->size = frag->size;
+ chunk->hash = frag->checksum;
+
+ proc->current_frag = frag;
+ proc->fblk_lookup_error = 0;
+ entry = hash_table_insert_pre_hashed(proc->frag_ht, chunk->hash,
+ chunk, chunk);
+ proc->current_frag = NULL;
+
+ if (proc->fblk_lookup_error != 0) {
+ err = proc->fblk_lookup_error;
+ goto fail;
+ }
+
+ if (entry == NULL)
+ goto fail;
+ }
+
+ if (frag->inode != NULL)
+ sqfs_inode_set_frag_location(*(frag->inode), index, offset);
+
+ if (frag != proc->frag_block)
+ release_old_block(proc, frag);
+
+ proc->stats.actual_frag_count += 1;
+ return 0;
+fail:
+ free(chunk);
+ if (frag != proc->frag_block)
+ release_old_block(proc, frag);
+ return err;
+}
+
+static void store_io_block(sqfs_block_processor_t *proc, sqfs_block_t *blk)
+{
+ sqfs_block_t *prev = NULL, *it = proc->io_queue;
+
+ while (it != NULL && (it->io_seq_num < blk->io_seq_num)) {
+ prev = it;
+ it = it->next;
+ }
+
+ if (prev == NULL) {
+ proc->io_queue = blk;
+ } else {
+ prev->next = blk;
+ }
+
+ blk->next = it;
+}
+
+int dequeue_block(sqfs_block_processor_t *proc)
+{
+ size_t backlog_old = proc->backlog;
+ sqfs_block_t *blk;
+ int status;
+
+ do {
+ while (proc->io_queue != NULL) {
+ if (proc->io_queue->io_seq_num != proc->io_deq_seq_num)
+ break;
+
+ blk = proc->io_queue;
+ proc->io_queue = blk->next;
+ proc->io_deq_seq_num += 1;
+
+ status = process_completed_block(proc, blk);
+ if (status != 0)
+ return status;
+ }
+
+ if (proc->backlog < backlog_old)
+ break;
+
+ if ((proc->backlog == 1) &&
+ (proc->frag_block != NULL || proc->blk_current != NULL)) {
+ break;
+ }
+
+ if ((proc->backlog == 2) &&
+ proc->frag_block != NULL && proc->blk_current != NULL) {
+ break;
+ }
+
+ blk = proc->pool->dequeue(proc->pool);
+
+ if (blk == NULL) {
+ status = proc->pool->get_status(proc->pool);
+ return status ? status : SQFS_ERROR_INTERNAL;
+ }
+
+ if (blk->flags & SQFS_BLK_IS_FRAGMENT) {
+ status = process_completed_fragment(proc, blk);
+ if (status != 0)
+ return status;
+ } else {
+ if (!(blk->flags & SQFS_BLK_FRAGMENT_BLOCK) ||
+ (blk->flags & BLK_FLAG_MANUAL_SUBMISSION)) {
+ blk->io_seq_num = proc->io_seq_num++;
+ }
+
+ store_io_block(proc, blk);
+ }
+ } while (proc->backlog >= backlog_old);
+
+ return 0;
+}
diff --git a/lib/sqfs/src/block_processor/block_processor.c b/lib/sqfs/src/block_processor/block_processor.c
new file mode 100644
index 0000000..d607437
--- /dev/null
+++ b/lib/sqfs/src/block_processor/block_processor.c
@@ -0,0 +1,358 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * block_processor.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "internal.h"
+
+static int process_block(void *userptr, void *workitem)
+{
+ worker_data_t *worker = userptr;
+ sqfs_block_t *block = workitem;
+ sqfs_s32 ret;
+
+ if (block->size == 0)
+ return 0;
+
+ if (!(block->flags & SQFS_BLK_IGNORE_SPARSE) &&
+ is_memory_zero(block->data, block->size)) {
+ block->flags |= SQFS_BLK_IS_SPARSE;
+ return 0;
+ }
+
+ if (block->flags & SQFS_BLK_DONT_HASH) {
+ block->checksum = 0;
+ } else {
+ block->checksum = xxh32(block->data, block->size);
+ }
+
+ if (block->flags & (SQFS_BLK_IS_FRAGMENT | SQFS_BLK_DONT_COMPRESS))
+ return 0;
+
+ ret = worker->cmp->do_block(worker->cmp, block->data, block->size,
+ worker->scratch, worker->scratch_size);
+ if (ret < 0)
+ return ret;
+
+ if (ret > 0) {
+ memcpy(block->data, worker->scratch, ret);
+ block->size = ret;
+ block->flags |= SQFS_BLK_IS_COMPRESSED;
+ }
+ return 0;
+}
+
+static int load_frag_block(sqfs_block_processor_t *proc, sqfs_u32 index)
+{
+ sqfs_fragment_t info;
+ size_t size;
+ int ret;
+
+ if (proc->cached_frag_blk == NULL) {
+ size = sizeof(*proc->cached_frag_blk);
+
+ proc->cached_frag_blk = alloc_flex(size, 1,
+ proc->max_block_size);
+
+ if (proc->cached_frag_blk == NULL)
+ return SQFS_ERROR_ALLOC;
+ } else {
+ if (proc->cached_frag_blk->index == index)
+ return 0;
+ }
+
+ ret = sqfs_frag_table_lookup(proc->frag_tbl, index, &info);
+ if (ret != 0)
+ return ret;
+
+ size = SQFS_ON_DISK_BLOCK_SIZE(info.size);
+ if (size > proc->max_block_size)
+ return SQFS_ERROR_CORRUPTED;
+
+ if (SQFS_IS_BLOCK_COMPRESSED(info.size)) {
+ ret = proc->file->read_at(proc->file, info.start_offset,
+ proc->scratch, size);
+ if (ret != 0)
+ return ret;
+
+ ret = proc->uncmp->do_block(proc->uncmp, proc->scratch, size,
+ proc->cached_frag_blk->data,
+ proc->max_block_size);
+ if (ret <= 0)
+ return ret ? ret : SQFS_ERROR_OVERFLOW;
+
+ size = ret;
+ } else {
+ ret = proc->file->read_at(proc->file, info.start_offset,
+ proc->cached_frag_blk->data, size);
+ if (ret != 0)
+ return ret;
+ }
+
+ proc->cached_frag_blk->size = size;
+ proc->cached_frag_blk->index = index;
+ return 0;
+}
+
+static bool chunk_info_equals(void *user, const void *k, const void *c)
+{
+ const chunk_info_t *key = k, *cmp = c;
+ sqfs_block_processor_t *proc = user;
+ sqfs_block_t *it;
+ int ret;
+
+ if (key->size != cmp->size || key->hash != cmp->hash)
+ return false;
+
+ if (proc->uncmp == NULL || proc->file == NULL)
+ return true;
+
+ if (proc->current_frag == NULL || proc->frag_tbl == NULL)
+ return true;
+
+ if (proc->fblk_lookup_error != 0)
+ return false;
+
+ for (it = proc->fblk_in_flight; it != NULL; it = it->next) {
+ if (it->index == cmp->index)
+ break;
+ }
+
+ if (it == NULL && proc->frag_block != NULL) {
+ if (proc->frag_block->index == cmp->index)
+ it = proc->frag_block;
+ }
+
+ if (it == NULL) {
+ ret = load_frag_block(proc, cmp->index);
+ if (ret != 0) {
+ proc->fblk_lookup_error = ret;
+ return false;
+ }
+
+ it = proc->cached_frag_blk;
+ }
+
+ if (cmp->offset >= it->size || (it->size - cmp->offset) < cmp->size) {
+ proc->fblk_lookup_error = SQFS_ERROR_CORRUPTED;
+ return false;
+ }
+
+ if (cmp->size != proc->current_frag->size) {
+ proc->fblk_lookup_error = SQFS_ERROR_CORRUPTED;
+ return false;
+ }
+
+ return memcmp(it->data + cmp->offset,
+ proc->current_frag->data, cmp->size) == 0;
+}
+
+static void ht_delete_function(struct hash_entry *entry)
+{
+ free(entry->data);
+}
+
+static void free_block_list(sqfs_block_t *list)
+{
+ while (list != NULL) {
+ sqfs_block_t *it = list;
+ list = it->next;
+ free(it);
+ }
+}
+
+static void block_processor_destroy(sqfs_object_t *base)
+{
+ sqfs_block_processor_t *proc = (sqfs_block_processor_t *)base;
+
+ free(proc->frag_block);
+ free(proc->blk_current);
+ free(proc->cached_frag_blk);
+
+ free_block_list(proc->free_list);
+ free_block_list(proc->io_queue);
+ free_block_list(proc->fblk_in_flight);
+
+ if (proc->frag_ht != NULL)
+ hash_table_destroy(proc->frag_ht, ht_delete_function);
+
+ /* XXX: shut down the pool first before cleaning up the worker data */
+ if (proc->pool != NULL)
+ proc->pool->destroy(proc->pool);
+
+ while (proc->workers != NULL) {
+ worker_data_t *worker = proc->workers;
+ proc->workers = worker->next;
+
+ sqfs_drop(worker->cmp);
+ free(worker);
+ }
+
+ sqfs_drop(proc->frag_tbl);
+ sqfs_drop(proc->wr);
+ sqfs_drop(proc->file);
+ sqfs_drop(proc->uncmp);
+ free(proc);
+}
+
+int sqfs_block_processor_sync(sqfs_block_processor_t *proc)
+{
+ int ret;
+
+ for (;;) {
+ if (proc->backlog == 0)
+ break;
+
+ if ((proc->backlog == 1) &&
+ (proc->frag_block != NULL || proc->blk_current != NULL)) {
+ break;
+ }
+
+ if ((proc->backlog == 2) &&
+ proc->frag_block != NULL && proc->blk_current != NULL) {
+ break;
+ }
+
+ ret = dequeue_block(proc);
+ if (ret != 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+int sqfs_block_processor_finish(sqfs_block_processor_t *proc)
+{
+ sqfs_block_t *blk;
+ int status;
+
+ status = sqfs_block_processor_sync(proc);
+ if (status != 0)
+ return status;
+
+ if (proc->frag_block != NULL) {
+ blk = proc->frag_block;
+ blk->next = NULL;
+ proc->frag_block = NULL;
+
+ blk->io_seq_num = proc->io_seq_num++;
+
+ status = enqueue_block(proc, blk);
+ if (status != 0)
+ return status;
+
+ status = sqfs_block_processor_sync(proc);
+ }
+
+ return status;
+}
+
+const sqfs_block_processor_stats_t
+*sqfs_block_processor_get_stats(const sqfs_block_processor_t *proc)
+{
+ return &proc->stats;
+}
+
+int sqfs_block_processor_create_ex(const sqfs_block_processor_desc_t *desc,
+ sqfs_block_processor_t **out)
+{
+ size_t i, count, scratch_size = 0;
+ sqfs_block_processor_t *proc;
+ int ret;
+
+ if (desc->size != sizeof(sqfs_block_processor_desc_t))
+ return SQFS_ERROR_ARG_INVALID;
+
+ if (desc->file != NULL && desc->uncmp != NULL)
+ scratch_size = desc->max_block_size;
+
+ proc = alloc_flex(sizeof(*proc), 1, scratch_size);
+ if (proc == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ sqfs_object_init(proc, block_processor_destroy, NULL);
+
+ proc->max_backlog = desc->max_backlog;
+ proc->max_block_size = desc->max_block_size;
+ proc->frag_tbl = sqfs_grab(desc->tbl);
+ proc->wr = sqfs_grab(desc->wr);
+ proc->file = sqfs_grab(desc->file);
+ proc->uncmp = sqfs_grab(desc->uncmp);
+ proc->stats.size = sizeof(proc->stats);
+
+ /* we need at least one current data block + one fragment block */
+ if (proc->max_backlog < 3)
+ proc->max_backlog = 3;
+
+ /* create the thread pool */
+ proc->pool = thread_pool_create(desc->num_workers, process_block);
+ if (proc->pool == NULL) {
+ ret = SQFS_ERROR_INTERNAL;
+ goto fail_pool;
+ }
+
+ /* create the worker compressors & scratch buffer */
+ count = proc->pool->get_worker_count(proc->pool);
+
+ for (i = 0; i < count; ++i) {
+ worker_data_t *worker = alloc_flex(sizeof(*worker), 1,
+ desc->max_block_size);
+ if (worker == NULL) {
+ ret = SQFS_ERROR_ALLOC;
+ goto fail_pool;
+ }
+
+ worker->scratch_size = desc->max_block_size;
+ worker->next = proc->workers;
+ proc->workers = worker;
+
+ worker->cmp = sqfs_copy(desc->cmp);
+ if (worker->cmp == NULL) {
+ ret = SQFS_ERROR_ALLOC;
+ goto fail_pool;
+ }
+
+ proc->pool->set_worker_ptr(proc->pool, i, worker);
+ }
+
+ /* create the fragment hash table */
+ proc->frag_ht = hash_table_create(NULL, chunk_info_equals);
+ if (proc->frag_ht == NULL) {
+ ret = SQFS_ERROR_ALLOC;
+ goto fail_pool;
+ }
+
+ proc->frag_ht->user = proc;
+ *out = proc;
+ return 0;
+fail_pool:
+ block_processor_destroy((sqfs_object_t *)proc);
+ return ret;
+}
+
+sqfs_block_processor_t *sqfs_block_processor_create(size_t max_block_size,
+ sqfs_compressor_t *cmp,
+ unsigned int num_workers,
+ size_t max_backlog,
+ sqfs_block_writer_t *wr,
+ sqfs_frag_table_t *tbl)
+{
+ sqfs_block_processor_desc_t desc;
+ sqfs_block_processor_t *out;
+
+ memset(&desc, 0, sizeof(desc));
+ desc.size = sizeof(desc);
+ desc.max_block_size = max_block_size;
+ desc.num_workers = num_workers;
+ desc.max_backlog = max_backlog;
+ desc.cmp = cmp;
+ desc.wr = wr;
+ desc.tbl = tbl;
+
+ if (sqfs_block_processor_create_ex(&desc, &out) != 0)
+ return NULL;
+
+ return out;
+}
diff --git a/lib/sqfs/src/block_processor/frontend.c b/lib/sqfs/src/block_processor/frontend.c
new file mode 100644
index 0000000..e8a4207
--- /dev/null
+++ b/lib/sqfs/src/block_processor/frontend.c
@@ -0,0 +1,243 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * frontend.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "internal.h"
+
+static int get_new_block(sqfs_block_processor_t *proc, sqfs_block_t **out)
+{
+ sqfs_block_t *blk;
+
+ while (proc->backlog >= proc->max_backlog) {
+ int ret = dequeue_block(proc);
+ if (ret != 0)
+ return ret;
+ }
+
+ if (proc->free_list != NULL) {
+ blk = proc->free_list;
+ proc->free_list = blk->next;
+ } else {
+ blk = malloc(sizeof(*blk) + proc->max_block_size);
+ if (blk == NULL)
+ return SQFS_ERROR_ALLOC;
+ }
+
+ memset(blk, 0, sizeof(*blk));
+ *out = blk;
+
+ proc->backlog += 1;
+ return 0;
+}
+
+static int add_sentinel_block(sqfs_block_processor_t *proc)
+{
+ sqfs_block_t *blk;
+ int ret;
+
+ ret = get_new_block(proc, &blk);
+ if (ret != 0)
+ return ret;
+
+ blk->inode = proc->inode;
+ blk->flags = proc->blk_flags | SQFS_BLK_LAST_BLOCK;
+
+ return enqueue_block(proc, blk);
+}
+
+int enqueue_block(sqfs_block_processor_t *proc, sqfs_block_t *blk)
+{
+ int status;
+
+ if ((blk->flags & SQFS_BLK_FRAGMENT_BLOCK) &&
+ proc->file != NULL && proc->uncmp != NULL) {
+ sqfs_block_t *copy = alloc_flex(sizeof(*copy), 1, blk->size);
+
+ if (copy == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ copy->size = blk->size;
+ copy->index = blk->index;
+ memcpy(copy->data, blk->data, blk->size);
+
+ copy->next = proc->fblk_in_flight;
+ proc->fblk_in_flight = copy;
+ }
+
+ if (proc->pool->submit(proc->pool, blk) != 0) {
+ status = proc->pool->get_status(proc->pool);
+
+ if (status == 0)
+ status = SQFS_ERROR_ALLOC;
+
+ blk->next = proc->free_list;
+ proc->free_list = blk;
+ return status;
+ }
+
+ return 0;
+}
+
+int sqfs_block_processor_begin_file(sqfs_block_processor_t *proc,
+ sqfs_inode_generic_t **inode,
+ void *user, sqfs_u32 flags)
+{
+ if (proc->begin_called)
+ return SQFS_ERROR_SEQUENCE;
+
+ if (flags & ~SQFS_BLK_USER_SETTABLE_FLAGS)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (inode != NULL) {
+ (*inode) = calloc(1, sizeof(sqfs_inode_generic_t));
+ if ((*inode) == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ (*inode)->base.type = SQFS_INODE_FILE;
+ sqfs_inode_set_frag_location(*inode, 0xFFFFFFFF, 0xFFFFFFFF);
+ }
+
+ proc->begin_called = true;
+ proc->inode = inode;
+ proc->blk_flags = flags | SQFS_BLK_FIRST_BLOCK;
+ proc->blk_index = 0;
+ proc->user = user;
+ return 0;
+}
+
+int sqfs_block_processor_append(sqfs_block_processor_t *proc, const void *data,
+ size_t size)
+{
+ sqfs_block_t *new;
+ sqfs_u64 filesize;
+ size_t diff;
+ int err;
+
+ if (!proc->begin_called)
+ return SQFS_ERROR_SEQUENCE;
+
+ if (proc->inode != NULL) {
+ sqfs_inode_get_file_size(*(proc->inode), &filesize);
+ sqfs_inode_set_file_size(*(proc->inode), filesize + size);
+ }
+
+ while (size > 0) {
+ if (proc->blk_current == NULL) {
+ err = get_new_block(proc, &new);
+ if (err != 0)
+ return err;
+
+ proc->blk_current = new;
+ proc->blk_current->flags = proc->blk_flags;
+ proc->blk_current->inode = proc->inode;
+ proc->blk_current->user = proc->user;
+ proc->blk_current->index = proc->blk_index++;
+ proc->blk_flags &= ~SQFS_BLK_FIRST_BLOCK;
+ }
+
+ diff = proc->max_block_size - proc->blk_current->size;
+
+ if (diff == 0) {
+ err = enqueue_block(proc, proc->blk_current);
+ proc->blk_current = NULL;
+
+ if (err)
+ return err;
+ continue;
+ }
+
+ if (diff > size)
+ diff = size;
+
+ memcpy(proc->blk_current->data + proc->blk_current->size,
+ data, diff);
+
+ size -= diff;
+ proc->blk_current->size += diff;
+ data = (const char *)data + diff;
+
+ proc->stats.input_bytes_read += diff;
+ }
+
+ if (proc->blk_current->size == proc->max_block_size) {
+ err = enqueue_block(proc, proc->blk_current);
+ proc->blk_current = NULL;
+
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+int sqfs_block_processor_end_file(sqfs_block_processor_t *proc)
+{
+ int err;
+
+ if (!proc->begin_called)
+ return SQFS_ERROR_SEQUENCE;
+
+ if (proc->blk_current == NULL) {
+ if (!(proc->blk_flags & SQFS_BLK_FIRST_BLOCK)) {
+ err = add_sentinel_block(proc);
+ if (err)
+ return err;
+ }
+ } else {
+ if (proc->blk_flags & SQFS_BLK_DONT_FRAGMENT) {
+ proc->blk_current->flags |= SQFS_BLK_LAST_BLOCK;
+ } else {
+ if (!(proc->blk_current->flags &
+ SQFS_BLK_FIRST_BLOCK)) {
+ err = add_sentinel_block(proc);
+ if (err)
+ return err;
+ }
+
+ proc->blk_current->flags |= SQFS_BLK_IS_FRAGMENT;
+ }
+
+ err = enqueue_block(proc, proc->blk_current);
+ proc->blk_current = NULL;
+
+ if (err)
+ return err;
+ }
+
+ proc->begin_called = false;
+ proc->inode = NULL;
+ proc->user = NULL;
+ proc->blk_flags = 0;
+ return 0;
+}
+
+int sqfs_block_processor_submit_block(sqfs_block_processor_t *proc, void *user,
+ sqfs_u32 flags, const void *data,
+ size_t size)
+{
+ sqfs_block_t *blk;
+ int ret;
+
+ if (proc->begin_called)
+ return SQFS_ERROR_SEQUENCE;
+
+ if (size > proc->max_block_size)
+ return SQFS_ERROR_OVERFLOW;
+
+ if (flags & ~SQFS_BLK_FLAGS_ALL)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ ret = get_new_block(proc, &blk);
+ if (ret != 0)
+ return ret;
+
+ blk->flags = flags | BLK_FLAG_MANUAL_SUBMISSION;
+ blk->user = user;
+ blk->size = size;
+ memcpy(blk->data, data, size);
+
+ return enqueue_block(proc, blk);
+}
diff --git a/lib/sqfs/src/block_processor/internal.h b/lib/sqfs/src/block_processor/internal.h
new file mode 100644
index 0000000..0b2c88d
--- /dev/null
+++ b/lib/sqfs/src/block_processor/internal.h
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * internal.h
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#ifndef INTERNAL_H
+#define INTERNAL_H
+
+#include "config.h"
+
+#include "sqfs/block_processor.h"
+#include "sqfs/block_writer.h"
+#include "sqfs/frag_table.h"
+#include "sqfs/compressor.h"
+#include "sqfs/inode.h"
+#include "sqfs/table.h"
+#include "sqfs/error.h"
+#include "sqfs/block.h"
+#include "sqfs/io.h"
+
+#include "util/hash_table.h"
+#include "util/threadpool.h"
+#include "util/util.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+typedef struct {
+ sqfs_u32 index;
+ sqfs_u32 offset;
+ sqfs_u32 size;
+ sqfs_u32 hash;
+} chunk_info_t;
+
+enum {
+ BLK_FLAG_MANUAL_SUBMISSION = 0x10000000,
+ BLK_FLAG_INTERNAL = 0x10000000,
+};
+
+typedef struct sqfs_block_t {
+ struct sqfs_block_t *next;
+ sqfs_inode_generic_t **inode;
+
+ sqfs_u32 io_seq_num;
+ sqfs_u32 flags;
+ sqfs_u32 size;
+ sqfs_u32 checksum;
+
+ /* For data blocks: index within the inode.
+ For fragment fragment blocks: fragment table index. */
+ sqfs_u32 index;
+
+ /* User data pointer */
+ void *user;
+
+ sqfs_u8 data[];
+} sqfs_block_t;
+
+typedef struct worker_data_t {
+ struct worker_data_t *next;
+ sqfs_compressor_t *cmp;
+
+ size_t scratch_size;
+ sqfs_u8 scratch[];
+} worker_data_t;
+
+struct sqfs_block_processor_t {
+ sqfs_object_t obj;
+
+ sqfs_frag_table_t *frag_tbl;
+ sqfs_block_t *frag_block;
+ sqfs_block_writer_t *wr;
+
+ sqfs_block_processor_stats_t stats;
+
+ sqfs_inode_generic_t **inode;
+ sqfs_block_t *blk_current;
+ sqfs_u32 blk_flags;
+ sqfs_u32 blk_index;
+ void *user;
+
+ struct hash_table *frag_ht;
+ sqfs_block_t *free_list;
+
+ size_t max_block_size;
+ size_t max_backlog;
+ size_t backlog;
+
+ bool begin_called;
+
+ sqfs_file_t *file;
+ sqfs_compressor_t *uncmp;
+
+ thread_pool_t *pool;
+ worker_data_t *workers;
+
+ sqfs_block_t *io_queue;
+ sqfs_u32 io_seq_num;
+ sqfs_u32 io_deq_seq_num;
+
+ sqfs_block_t *current_frag;
+ sqfs_block_t *cached_frag_blk;
+ sqfs_block_t *fblk_in_flight;
+ int fblk_lookup_error;
+
+ sqfs_u8 scratch[];
+};
+
+SQFS_INTERNAL int enqueue_block(sqfs_block_processor_t *proc,
+ sqfs_block_t *blk);
+
+SQFS_INTERNAL int dequeue_block(sqfs_block_processor_t *proc);
+
+#endif /* INTERNAL_H */
diff --git a/lib/sqfs/src/block_writer.c b/lib/sqfs/src/block_writer.c
new file mode 100644
index 0000000..a5135bc
--- /dev/null
+++ b/lib/sqfs/src/block_writer.c
@@ -0,0 +1,238 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * block_writer.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/block_writer.h"
+#include "sqfs/error.h"
+#include "sqfs/block.h"
+#include "sqfs/io.h"
+
+#include "util/array.h"
+#include "util/util.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#define MK_BLK_HASH(chksum, size) \
+ (((sqfs_u64)(size) << 32) | (sqfs_u64)(chksum))
+
+#define SIZE_FROM_HASH(hash) ((hash >> 32) & ((1 << 24) - 1))
+
+#define INIT_BLOCK_COUNT (128)
+#define SCRATCH_SIZE (8192)
+
+typedef struct {
+ sqfs_u64 offset;
+ sqfs_u64 hash;
+} blk_info_t;
+
+typedef struct {
+ sqfs_block_writer_t base;
+ sqfs_file_t *file;
+
+ array_t blocks;
+ size_t devblksz;
+
+ size_t file_start;
+
+ sqfs_u32 flags;
+
+ sqfs_u8 scratch[];
+} block_writer_default_t;
+
+static int store_block_location(block_writer_default_t *wr, sqfs_u64 offset,
+ sqfs_u32 size, sqfs_u32 chksum)
+{
+ blk_info_t info = { offset, MK_BLK_HASH(chksum, size) };
+
+ return array_append(&(wr->blocks), &info);
+}
+
+static int deduplicate_blocks(block_writer_default_t *wr, sqfs_u32 flags, sqfs_u64 *out)
+{
+ const blk_info_t *blocks = wr->blocks.data;
+ sqfs_u64 loc_a, loc_b, sz;
+ size_t i, j, count;
+ int ret;
+
+ count = wr->blocks.used - wr->file_start;
+ if (count == 0) {
+ *out = 0;
+ return 0;
+ }
+
+ if (flags & SQFS_BLK_DONT_DEDUPLICATE) {
+ *out = blocks[wr->file_start].offset;
+ return 0;
+ }
+
+ sz = 0;
+ loc_a = blocks[wr->file_start].offset;
+
+ for (i = 0; i < count; ++i)
+ sz += SIZE_FROM_HASH(blocks[wr->file_start + i].hash);
+
+ for (i = 0; i < wr->file_start; ++i) {
+ for (j = 0; j < count; ++j) {
+ if (blocks[i + j].hash == 0)
+ break;
+
+ if (blocks[i + j].hash !=
+ blocks[wr->file_start + j].hash)
+ break;
+ }
+
+ if (j != count)
+ continue;
+
+ if (wr->flags & SQFS_BLOCK_WRITER_HASH_COMPARE_ONLY)
+ break;
+
+ loc_b = blocks[i].offset;
+
+ ret = check_file_range_equal(wr->file, wr->scratch,
+ SCRATCH_SIZE, loc_a, loc_b, sz);
+ if (ret == 0)
+ break;
+ if (ret < 0)
+ return ret;
+ }
+
+ *out = blocks[i].offset;
+ if (i >= wr->file_start)
+ return 0;
+
+ if (count >= (wr->file_start - i)) {
+ wr->blocks.used = i + count;
+ } else {
+ wr->blocks.used = wr->file_start;
+ }
+
+ sz = blocks[wr->blocks.used - 1].offset +
+ SIZE_FROM_HASH(blocks[wr->blocks.used - 1].hash);
+
+ return wr->file->truncate(wr->file, sz);
+}
+
+static int align_file(block_writer_default_t *wr, sqfs_u32 flags)
+{
+ void *padding;
+ sqfs_u64 size;
+ size_t diff;
+ int ret;
+
+ if (!(flags & SQFS_BLK_ALIGN))
+ return 0;
+
+ size = wr->file->get_size(wr->file);
+ diff = size % wr->devblksz;
+ if (diff == 0)
+ return 0;
+
+ padding = calloc(1, diff);
+ if (padding == 0)
+ return SQFS_ERROR_ALLOC;
+
+ ret = wr->file->write_at(wr->file, size, padding, diff);
+ free(padding);
+ if (ret)
+ return ret;
+
+ return store_block_location(wr, size, 0, 0);
+}
+
+static void block_writer_destroy(sqfs_object_t *wr)
+{
+ sqfs_drop(((block_writer_default_t *)wr)->file);
+ array_cleanup(&(((block_writer_default_t *)wr)->blocks));
+ free(wr);
+}
+
+static int write_data_block(sqfs_block_writer_t *base, void *user,
+ sqfs_u32 size, sqfs_u32 checksum, sqfs_u32 flags,
+ const sqfs_u8 *data, sqfs_u64 *location)
+{
+ block_writer_default_t *wr = (block_writer_default_t *)base;
+ int err;
+ (void)user;
+
+ if (flags & (SQFS_BLK_FIRST_BLOCK | SQFS_BLK_FRAGMENT_BLOCK)) {
+ err = align_file(wr, flags);
+ if (err)
+ return err;
+
+ if (flags & SQFS_BLK_FIRST_BLOCK)
+ wr->file_start = wr->blocks.used;
+ }
+
+ *location = wr->file->get_size(wr->file);
+
+ if (size != 0 && !(flags & SQFS_BLK_IS_SPARSE)) {
+ sqfs_u32 out = size;
+ if (!(flags & SQFS_BLK_IS_COMPRESSED))
+ out |= 1 << 24;
+
+ err = store_block_location(wr, *location, out, checksum);
+ if (err)
+ return err;
+
+ err = wr->file->write_at(wr->file, *location, data, size);
+ if (err)
+ return err;
+ }
+
+ if (flags & (SQFS_BLK_LAST_BLOCK | SQFS_BLK_FRAGMENT_BLOCK)) {
+ err = align_file(wr, flags);
+ if (err)
+ return err;
+
+ if (flags & SQFS_BLK_LAST_BLOCK)
+ return deduplicate_blocks(wr, flags, location);
+ }
+
+ return 0;
+}
+
+static sqfs_u64 get_block_count(const sqfs_block_writer_t *wr)
+{
+ return ((const block_writer_default_t *)wr)->blocks.used;
+}
+
+sqfs_block_writer_t *sqfs_block_writer_create(sqfs_file_t *file,
+ size_t devblksz, sqfs_u32 flags)
+{
+ block_writer_default_t *wr;
+
+ if (flags & ~SQFS_BLOCK_WRITER_ALL_FLAGS)
+ return NULL;
+
+ if (flags & SQFS_BLOCK_WRITER_HASH_COMPARE_ONLY) {
+ wr = calloc(1, sizeof(*wr));
+ } else {
+ wr = alloc_flex(sizeof(*wr), 1, SCRATCH_SIZE);
+ }
+
+ if (wr == NULL)
+ return NULL;
+
+ sqfs_object_init(wr, block_writer_destroy, NULL);
+
+ ((sqfs_block_writer_t *)wr)->write_data_block = write_data_block;
+ ((sqfs_block_writer_t *)wr)->get_block_count = get_block_count;
+ wr->flags = flags;
+ wr->file = sqfs_grab(file);
+ wr->devblksz = devblksz;
+
+ if (array_init(&(wr->blocks), sizeof(blk_info_t), INIT_BLOCK_COUNT)) {
+ sqfs_drop(wr->file);
+ free(wr);
+ return NULL;
+ }
+
+ return (sqfs_block_writer_t *)wr;
+}
diff --git a/lib/sqfs/src/comp/compressor.c b/lib/sqfs/src/comp/compressor.c
new file mode 100644
index 0000000..4c4d73c
--- /dev/null
+++ b/lib/sqfs/src/comp/compressor.c
@@ -0,0 +1,218 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * compressor.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "internal.h"
+
+typedef int (*compressor_fun_t)(const sqfs_compressor_config_t *cfg,
+ sqfs_compressor_t **out);
+
+static compressor_fun_t compressors[SQFS_COMP_MAX + 1] = {
+#ifdef WITH_GZIP
+ [SQFS_COMP_GZIP] = gzip_compressor_create,
+#endif
+#ifdef WITH_XZ
+ [SQFS_COMP_XZ] = xz_compressor_create,
+ [SQFS_COMP_LZMA] = lzma_compressor_create,
+#endif
+#ifdef WITH_LZ4
+ [SQFS_COMP_LZ4] = lz4_compressor_create,
+#endif
+#ifdef WITH_ZSTD
+ [SQFS_COMP_ZSTD] = zstd_compressor_create,
+#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 sqfs_generic_write_options(sqfs_file_t *file, const void *data, size_t size)
+{
+ sqfs_u8 buffer[64];
+ sqfs_u16 header;
+ int ret;
+
+ /* XXX: options for all known compressors should fit into this */
+ if (size >= (sizeof(buffer) - sizeof(header)))
+ return SQFS_ERROR_INTERNAL;
+
+ header = htole16(0x8000 | size);
+ memcpy(buffer, &header, sizeof(header));
+ memcpy(buffer + sizeof(header), data, size);
+
+ ret = file->write_at(file, sizeof(sqfs_super_t),
+ buffer, sizeof(header) + size);
+ if (ret)
+ return ret;
+
+ return sizeof(header) + size;
+}
+
+int sqfs_generic_read_options(sqfs_file_t *file, void *data, size_t size)
+{
+ sqfs_u8 buffer[64];
+ sqfs_u16 header;
+ int ret;
+
+ /* XXX: options for all known compressors should fit into this */
+ if (size >= (sizeof(buffer) - sizeof(header)))
+ return SQFS_ERROR_INTERNAL;
+
+ ret = file->read_at(file, sizeof(sqfs_super_t),
+ buffer, sizeof(header) + size);
+ if (ret)
+ return ret;
+
+ memcpy(&header, buffer, sizeof(header));
+
+ if (le16toh(header) != (0x8000 | size))
+ return SQFS_ERROR_CORRUPTED;
+
+ memcpy(data, buffer + 2, size);
+ return 0;
+}
+
+int sqfs_compressor_create(const sqfs_compressor_config_t *cfg,
+ sqfs_compressor_t **out)
+{
+ sqfs_u8 padd0[sizeof(cfg->opt)];
+ int ret;
+
+ *out = NULL;
+
+ /* check compressor ID */
+ if (cfg == NULL)
+ return SQFS_ERROR_ARG_INVALID;
+
+ if (cfg->id < SQFS_COMP_MIN || cfg->id > SQFS_COMP_MAX)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (compressors[cfg->id] == NULL)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ /* make sure the padding bytes are cleared, so we could theoretically
+ turn them into option fields in the future and remain compatible */
+ memset(padd0, 0, sizeof(padd0));
+
+ switch (cfg->id) {
+ case SQFS_COMP_XZ:
+ ret = memcmp(cfg->opt.xz.padd0, padd0,
+ sizeof(cfg->opt.xz.padd0));
+ break;
+ case SQFS_COMP_LZMA:
+ ret = memcmp(cfg->opt.lzma.padd0, padd0,
+ sizeof(cfg->opt.lzma.padd0));
+ break;
+ case SQFS_COMP_LZO:
+ ret = memcmp(cfg->opt.lzo.padd0, padd0,
+ sizeof(cfg->opt.lzo.padd0));
+ break;
+ case SQFS_COMP_GZIP:
+ ret = memcmp(cfg->opt.gzip.padd0, padd0,
+ sizeof(cfg->opt.gzip.padd0));
+ break;
+ default:
+ ret = memcmp(cfg->opt.padd0, padd0, sizeof(cfg->opt.padd0));
+ break;
+ }
+
+ if (ret != 0)
+ return SQFS_ERROR_ARG_INVALID;
+
+ return compressors[cfg->id](cfg, out);
+}
+
+const char *sqfs_compressor_name_from_id(SQFS_COMPRESSOR id)
+{
+ if (id < 0 || (size_t)id >= sizeof(names) / sizeof(names[0]))
+ return NULL;
+
+ return names[id];
+}
+
+int sqfs_compressor_id_from_name(const char *name)
+{
+ size_t i;
+
+ for (i = 0; i < sizeof(names) / sizeof(names[0]); ++i) {
+ if (names[i] != NULL && strcmp(names[i], name) == 0)
+ return i;
+ }
+
+ return SQFS_ERROR_UNSUPPORTED;
+}
+
+int sqfs_compressor_config_init(sqfs_compressor_config_t *cfg,
+ SQFS_COMPRESSOR id,
+ size_t block_size, sqfs_u16 flags)
+{
+ sqfs_u32 flag_mask = SQFS_COMP_FLAG_GENERIC_ALL;
+
+ memset(cfg, 0, sizeof(*cfg));
+
+ switch (id) {
+ case SQFS_COMP_GZIP:
+ flag_mask |= SQFS_COMP_FLAG_GZIP_ALL;
+ cfg->level = SQFS_GZIP_DEFAULT_LEVEL;
+ cfg->opt.gzip.window_size = SQFS_GZIP_DEFAULT_WINDOW;
+ break;
+ case SQFS_COMP_LZO:
+ cfg->opt.lzo.algorithm = SQFS_LZO_DEFAULT_ALG;
+ cfg->level = SQFS_LZO_DEFAULT_LEVEL;
+ break;
+ case SQFS_COMP_ZSTD:
+ cfg->level = SQFS_ZSTD_DEFAULT_LEVEL;
+ break;
+ case SQFS_COMP_XZ:
+ flag_mask |= SQFS_COMP_FLAG_XZ_ALL;
+ cfg->level = SQFS_XZ_DEFAULT_LEVEL;
+ cfg->opt.xz.dict_size = block_size;
+ cfg->opt.xz.lc = SQFS_XZ_DEFAULT_LC;
+ cfg->opt.xz.lp = SQFS_XZ_DEFAULT_LP;
+ cfg->opt.xz.pb = SQFS_XZ_DEFAULT_PB;
+
+ if (block_size < SQFS_XZ_MIN_DICT_SIZE)
+ cfg->opt.xz.dict_size = SQFS_XZ_MIN_DICT_SIZE;
+ break;
+ case SQFS_COMP_LZMA:
+ flag_mask |= SQFS_COMP_FLAG_LZMA_ALL;
+ cfg->level = SQFS_LZMA_DEFAULT_LEVEL;
+ cfg->opt.lzma.dict_size = block_size;
+ cfg->opt.lzma.lc = SQFS_LZMA_DEFAULT_LC;
+ cfg->opt.lzma.lp = SQFS_LZMA_DEFAULT_LP;
+ cfg->opt.lzma.pb = SQFS_LZMA_DEFAULT_PB;
+
+ if (block_size < SQFS_LZMA_MIN_DICT_SIZE)
+ cfg->opt.lzma.dict_size = SQFS_LZMA_MIN_DICT_SIZE;
+ break;
+ case SQFS_COMP_LZ4:
+ flag_mask |= SQFS_COMP_FLAG_LZ4_ALL;
+ break;
+ default:
+ return SQFS_ERROR_UNSUPPORTED;
+ }
+
+ if (flags & ~flag_mask) {
+ memset(cfg, 0, sizeof(*cfg));
+ return SQFS_ERROR_UNSUPPORTED;
+ }
+
+ cfg->id = id;
+ cfg->flags = flags;
+ cfg->block_size = block_size;
+ return 0;
+}
diff --git a/lib/sqfs/src/comp/gzip.c b/lib/sqfs/src/comp/gzip.c
new file mode 100644
index 0000000..beacfb8
--- /dev/null
+++ b/lib/sqfs/src/comp/gzip.c
@@ -0,0 +1,307 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * gzip.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <zlib.h>
+
+#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;
+ default:
+ break;
+ }
+
+ 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;
+
+ sqfs_object_init(gzip, gzip_destroy, gzip_create_copy);
+
+ 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;
+
+ 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;
+}
diff --git a/lib/sqfs/src/comp/internal.h b/lib/sqfs/src/comp/internal.h
new file mode 100644
index 0000000..e4c3dd8
--- /dev/null
+++ b/lib/sqfs/src/comp/internal.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * internal.h
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#ifndef INTERNAL_H
+#define INTERNAL_H
+
+#include "config.h"
+
+#include "sqfs/predef.h"
+#include "sqfs/compressor.h"
+#include "sqfs/error.h"
+#include "sqfs/block.h"
+#include "sqfs/io.h"
+#include "util/util.h"
+
+SQFS_INTERNAL
+int sqfs_generic_write_options(sqfs_file_t *file, const void *data,
+ size_t size);
+
+SQFS_INTERNAL
+int sqfs_generic_read_options(sqfs_file_t *file, void *data, size_t size);
+
+SQFS_INTERNAL
+int xz_compressor_create(const sqfs_compressor_config_t *cfg,
+ sqfs_compressor_t **out);
+
+SQFS_INTERNAL
+int gzip_compressor_create(const sqfs_compressor_config_t *cfg,
+ sqfs_compressor_t **out);
+
+SQFS_INTERNAL
+int lz4_compressor_create(const sqfs_compressor_config_t *cfg,
+ sqfs_compressor_t **out);
+
+SQFS_INTERNAL
+int zstd_compressor_create(const sqfs_compressor_config_t *cfg,
+ sqfs_compressor_t **out);
+
+SQFS_INTERNAL
+int lzma_compressor_create(const sqfs_compressor_config_t *cfg,
+ sqfs_compressor_t **out);
+
+#endif /* INTERNAL_H */
diff --git a/lib/sqfs/src/comp/lz4.c b/lib/sqfs/src/comp/lz4.c
new file mode 100644
index 0000000..77f4a6e
--- /dev/null
+++ b/lib/sqfs/src/comp/lz4.c
@@ -0,0 +1,172 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * lz4.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <lz4.h>
+#include <lz4hc.h>
+
+#include "internal.h"
+
+typedef struct {
+ sqfs_compressor_t base;
+ size_t block_size;
+ bool high_compression;
+} lz4_compressor_t;
+
+typedef struct {
+ sqfs_u32 version;
+ sqfs_u32 flags;
+} lz4_options;
+
+#define LZ4LEGACY 1
+
+/* old verions of liblz4 don't have this */
+#ifndef LZ4HC_CLEVEL_MAX
+#define LZ4HC_CLEVEL_MAX 12
+#endif
+
+static int lz4_write_options(sqfs_compressor_t *base, sqfs_file_t *file)
+{
+ lz4_compressor_t *lz4 = (lz4_compressor_t *)base;
+ lz4_options opt = {
+ .version = htole32(LZ4LEGACY),
+ .flags = htole32(lz4->high_compression ?
+ SQFS_COMP_FLAG_LZ4_HC : 0),
+ };
+
+ return sqfs_generic_write_options(file, &opt, sizeof(opt));
+}
+
+static int lz4_read_options(sqfs_compressor_t *base, sqfs_file_t *file)
+{
+ lz4_options opt;
+ int ret;
+ (void)base;
+
+ ret = sqfs_generic_read_options(file, &opt, sizeof(opt));
+ if (ret)
+ return ret;
+
+ opt.version = le32toh(opt.version);
+ opt.flags = le32toh(opt.flags);
+
+ if (opt.version != LZ4LEGACY)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ return 0;
+}
+
+static sqfs_s32 lz4_comp_block(sqfs_compressor_t *base, const sqfs_u8 *in,
+ sqfs_u32 size, sqfs_u8 *out, sqfs_u32 outsize)
+{
+ lz4_compressor_t *lz4 = (lz4_compressor_t *)base;
+ int ret;
+
+ if (size >= 0x7FFFFFFF)
+ return SQFS_ERROR_ARG_INVALID;
+
+ if (lz4->high_compression) {
+ ret = LZ4_compress_HC((const void *)in, (void *)out,
+ size, outsize, LZ4HC_CLEVEL_MAX);
+ } else {
+ ret = LZ4_compress_default((const void *)in, (void *)out,
+ size, outsize);
+ }
+
+ if (ret < 0)
+ return SQFS_ERROR_COMPRESSOR;
+
+ return ret;
+}
+
+static sqfs_s32 lz4_uncomp_block(sqfs_compressor_t *base, const sqfs_u8 *in,
+ sqfs_u32 size, sqfs_u8 *out, sqfs_u32 outsize)
+{
+ int ret;
+ (void)base;
+
+ if (outsize >= 0x7FFFFFFF)
+ return SQFS_ERROR_ARG_INVALID;
+
+ ret = LZ4_decompress_safe((const void *)in, (void *)out, size, outsize);
+
+ if (ret < 0)
+ return SQFS_ERROR_COMPRESSOR;
+
+ return ret;
+}
+
+static void lz4_get_configuration(const sqfs_compressor_t *base,
+ sqfs_compressor_config_t *cfg)
+{
+ const lz4_compressor_t *lz4 = (const lz4_compressor_t *)base;
+
+ memset(cfg, 0, sizeof(*cfg));
+ cfg->id = SQFS_COMP_LZ4;
+ cfg->block_size = lz4->block_size;
+
+ if (lz4->high_compression)
+ cfg->flags |= SQFS_COMP_FLAG_LZ4_HC;
+
+ if (base->do_block == lz4_uncomp_block)
+ cfg->flags |= SQFS_COMP_FLAG_UNCOMPRESS;
+}
+
+static sqfs_object_t *lz4_create_copy(const sqfs_object_t *cmp)
+{
+ lz4_compressor_t *lz4 = malloc(sizeof(*lz4));
+
+ if (lz4 == NULL)
+ return NULL;
+
+ memcpy(lz4, cmp, sizeof(*lz4));
+ return (sqfs_object_t *)lz4;
+}
+
+static void lz4_destroy(sqfs_object_t *base)
+{
+ free(base);
+}
+
+int lz4_compressor_create(const sqfs_compressor_config_t *cfg,
+ sqfs_compressor_t **out)
+{
+ sqfs_compressor_t *base;
+ lz4_compressor_t *lz4;
+
+ if (cfg->flags & ~(SQFS_COMP_FLAG_LZ4_ALL |
+ SQFS_COMP_FLAG_GENERIC_ALL)) {
+ return SQFS_ERROR_UNSUPPORTED;
+ }
+
+ if (cfg->level != 0)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ lz4 = calloc(1, sizeof(*lz4));
+ base = (sqfs_compressor_t *)lz4;
+ if (lz4 == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ sqfs_object_init(lz4, lz4_destroy, lz4_create_copy);
+
+ lz4->high_compression = (cfg->flags & SQFS_COMP_FLAG_LZ4_HC) != 0;
+ lz4->block_size = cfg->block_size;
+
+ base->get_configuration = lz4_get_configuration;
+ base->do_block = (cfg->flags & SQFS_COMP_FLAG_UNCOMPRESS) ?
+ lz4_uncomp_block : lz4_comp_block;
+ base->write_options = lz4_write_options;
+ base->read_options = lz4_read_options;
+
+ *out = base;
+ return 0;
+}
diff --git a/lib/sqfs/src/comp/lzma.c b/lib/sqfs/src/comp/lzma.c
new file mode 100644
index 0000000..5456603
--- /dev/null
+++ b/lib/sqfs/src/comp/lzma.c
@@ -0,0 +1,281 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * lzma.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <lzma.h>
+
+#include "internal.h"
+
+#define LZMA_SIZE_OFFSET (5)
+#define LZMA_SIZE_BYTES (8)
+#define LZMA_HEADER_SIZE (13)
+
+#define MEMLIMIT (64 * 1024 * 1024)
+
+typedef struct {
+ sqfs_compressor_t base;
+ size_t block_size;
+ size_t dict_size;
+
+ sqfs_u32 flags;
+ sqfs_u8 level;
+ sqfs_u8 lc;
+ sqfs_u8 lp;
+ sqfs_u8 pb;
+} lzma_compressor_t;
+
+static int lzma_write_options(sqfs_compressor_t *base, sqfs_file_t *file)
+{
+ (void)base; (void)file;
+ return 0;
+}
+
+static int lzma_read_options(sqfs_compressor_t *base, sqfs_file_t *file)
+{
+ (void)base;
+ (void)file;
+ return SQFS_ERROR_UNSUPPORTED;
+}
+
+static sqfs_s32 try_compress(lzma_compressor_t *lzma, sqfs_u32 preset,
+ const sqfs_u8 *in, size_t size,
+ sqfs_u8 *out, size_t outsize)
+{
+ lzma_stream strm = LZMA_STREAM_INIT;
+ lzma_options_lzma opt;
+ int ret;
+
+ lzma_lzma_preset(&opt, preset);
+ opt.dict_size = lzma->block_size;
+ opt.lc = lzma->lc;
+ opt.lp = lzma->lp;
+ opt.pb = lzma->pb;
+
+ if (lzma_alone_encoder(&strm, &opt) != LZMA_OK) {
+ lzma_end(&strm);
+ return SQFS_ERROR_COMPRESSOR;
+ }
+
+ strm.next_out = out;
+ strm.avail_out = outsize;
+ strm.next_in = in;
+ strm.avail_in = size;
+
+ ret = lzma_code(&strm, LZMA_FINISH);
+ lzma_end(&strm);
+
+ if (ret != LZMA_STREAM_END)
+ return ret == LZMA_OK ? 0 : SQFS_ERROR_COMPRESSOR;
+
+ if (strm.total_out > size)
+ return 0;
+
+ out[LZMA_SIZE_OFFSET ] = size & 0xFF;
+ out[LZMA_SIZE_OFFSET + 1] = (size >> 8) & 0xFF;
+ out[LZMA_SIZE_OFFSET + 2] = (size >> 16) & 0xFF;
+ out[LZMA_SIZE_OFFSET + 3] = (size >> 24) & 0xFF;
+ out[LZMA_SIZE_OFFSET + 4] = 0;
+ out[LZMA_SIZE_OFFSET + 5] = 0;
+ out[LZMA_SIZE_OFFSET + 6] = 0;
+ out[LZMA_SIZE_OFFSET + 7] = 0;
+ return strm.total_out;
+}
+
+static sqfs_s32 lzma_comp_block(sqfs_compressor_t *base, const sqfs_u8 *in,
+ sqfs_u32 size, sqfs_u8 *out, sqfs_u32 outsize)
+{
+ lzma_compressor_t *lzma = (lzma_compressor_t *)base;
+ sqfs_s32 ret, smallest;
+ sqfs_u32 preset;
+
+ if (outsize < LZMA_HEADER_SIZE || size >= 0x7FFFFFFF)
+ return SQFS_ERROR_ARG_INVALID;
+
+ preset = lzma->level;
+ ret = try_compress(lzma, preset, in, size, out, outsize);
+ if (ret < 0 || !(lzma->flags & SQFS_COMP_FLAG_LZMA_EXTREME))
+ return ret;
+
+ preset |= LZMA_PRESET_EXTREME;
+ smallest = ret;
+
+ ret = try_compress(lzma, preset, in, size, out, outsize);
+ if (ret < 0 || (ret > 0 && (smallest == 0 || ret < smallest)))
+ return ret;
+
+ preset &= ~LZMA_PRESET_EXTREME;
+ return smallest == 0 ? 0 :
+ try_compress(lzma, preset, in, size, out, outsize);
+}
+
+static sqfs_s32 lzma_uncomp_block(sqfs_compressor_t *base, const sqfs_u8 *in,
+ sqfs_u32 size, sqfs_u8 *out, sqfs_u32 outsize)
+{
+ sqfs_u8 lzma_header[LZMA_HEADER_SIZE];
+ lzma_stream strm = LZMA_STREAM_INIT;
+ size_t hdrsize;
+ int ret;
+ (void)base;
+
+ if (size >= 0x7FFFFFFF)
+ return SQFS_ERROR_ARG_INVALID;
+
+ if (size < sizeof(lzma_header))
+ return SQFS_ERROR_CORRUPTED;
+
+ hdrsize = (size_t)in[LZMA_SIZE_OFFSET] |
+ ((size_t)in[LZMA_SIZE_OFFSET + 1] << 8) |
+ ((size_t)in[LZMA_SIZE_OFFSET + 2] << 16) |
+ ((size_t)in[LZMA_SIZE_OFFSET + 3] << 24);
+
+ if (hdrsize > outsize)
+ return 0;
+
+ if (lzma_alone_decoder(&strm, MEMLIMIT) != LZMA_OK) {
+ lzma_end(&strm);
+ return SQFS_ERROR_COMPRESSOR;
+ }
+
+ memcpy(lzma_header, in, sizeof(lzma_header));
+ memset(lzma_header + LZMA_SIZE_OFFSET, 0xFF, LZMA_SIZE_BYTES);
+
+ strm.next_out = out;
+ strm.avail_out = outsize;
+ strm.next_in = lzma_header;
+ strm.avail_in = sizeof(lzma_header);
+
+ ret = lzma_code(&strm, LZMA_RUN);
+
+ if (ret != LZMA_OK || strm.avail_in != 0) {
+ lzma_end(&strm);
+ return SQFS_ERROR_COMPRESSOR;
+ }
+
+ strm.next_in = in + sizeof(lzma_header);
+ strm.avail_in = size - sizeof(lzma_header);
+
+ ret = lzma_code(&strm, LZMA_FINISH);
+ lzma_end(&strm);
+
+ if (ret != LZMA_STREAM_END && ret != LZMA_OK)
+ return SQFS_ERROR_COMPRESSOR;
+
+ if (ret == LZMA_OK) {
+ if (strm.total_out < hdrsize || strm.avail_in != 0)
+ return 0;
+ }
+
+ return hdrsize;
+}
+
+static void lzma_get_configuration(const sqfs_compressor_t *base,
+ sqfs_compressor_config_t *cfg)
+{
+ const lzma_compressor_t *lzma = (const lzma_compressor_t *)base;
+
+ memset(cfg, 0, sizeof(*cfg));
+ cfg->id = SQFS_COMP_LZMA;
+ cfg->block_size = lzma->block_size;
+ cfg->flags = lzma->flags;
+ cfg->level = lzma->level;
+ cfg->opt.lzma.dict_size = lzma->dict_size;
+ cfg->opt.lzma.lc = lzma->lc;
+ cfg->opt.lzma.lp = lzma->lp;
+ cfg->opt.lzma.pb = lzma->pb;
+}
+
+static sqfs_object_t *lzma_create_copy(const sqfs_object_t *cmp)
+{
+ lzma_compressor_t *copy = malloc(sizeof(*copy));
+
+ if (copy != NULL)
+ memcpy(copy, cmp, sizeof(*copy));
+
+ return (sqfs_object_t *)copy;
+}
+
+static void lzma_destroy(sqfs_object_t *base)
+{
+ free(base);
+}
+
+int lzma_compressor_create(const sqfs_compressor_config_t *cfg,
+ sqfs_compressor_t **out)
+{
+ sqfs_compressor_t *base;
+ lzma_compressor_t *lzma;
+ sqfs_u32 mask;
+
+ mask = SQFS_COMP_FLAG_GENERIC_ALL | SQFS_COMP_FLAG_LZMA_ALL;
+
+ if (cfg->flags & ~mask)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ /* XXX: values are unsigned and minimum is 0 */
+ if (cfg->level > SQFS_LZMA_MAX_LEVEL)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (cfg->opt.lzma.lc > SQFS_LZMA_MAX_LC)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (cfg->opt.lzma.lp > SQFS_LZMA_MAX_LP)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (cfg->opt.lzma.pb > SQFS_LZMA_MAX_PB)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (cfg->opt.lzma.lc + cfg->opt.lzma.lp > 4)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (cfg->opt.lzma.dict_size == 0)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (cfg->opt.lzma.dict_size < SQFS_LZMA_MIN_DICT_SIZE)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (cfg->opt.lzma.dict_size > SQFS_LZMA_MAX_DICT_SIZE)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ mask = cfg->opt.lzma.dict_size;
+ mask &= mask - 1;
+
+ if (mask != 0) {
+ if ((mask & (mask - 1)) != 0)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (cfg->opt.lzma.dict_size != (mask | mask >> 1))
+ return SQFS_ERROR_UNSUPPORTED;
+ }
+
+ lzma = calloc(1, sizeof(*lzma));
+ base = (sqfs_compressor_t *)lzma;
+ if (lzma == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ sqfs_object_init(lzma, lzma_destroy, lzma_create_copy);
+
+ lzma->block_size = cfg->block_size;
+ lzma->flags = cfg->flags;
+ lzma->level = cfg->level;
+ lzma->dict_size = cfg->opt.lzma.dict_size;
+ lzma->lc = cfg->opt.lzma.lc;
+ lzma->lp = cfg->opt.lzma.lp;
+ lzma->pb = cfg->opt.lzma.pb;
+
+ base->get_configuration = lzma_get_configuration;
+ base->do_block = (cfg->flags & SQFS_COMP_FLAG_UNCOMPRESS) ?
+ lzma_uncomp_block : lzma_comp_block;
+ base->write_options = lzma_write_options;
+ base->read_options = lzma_read_options;
+
+ *out = base;
+ return 0;
+}
diff --git a/lib/sqfs/src/comp/xz.c b/lib/sqfs/src/comp/xz.c
new file mode 100644
index 0000000..13545ed
--- /dev/null
+++ b/lib/sqfs/src/comp/xz.c
@@ -0,0 +1,324 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * xz.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <lzma.h>
+
+#include "internal.h"
+
+typedef struct {
+ sqfs_compressor_t base;
+ size_t block_size;
+ size_t dict_size;
+
+ sqfs_u8 level;
+ sqfs_u8 lc;
+ sqfs_u8 lp;
+ sqfs_u8 pb;
+
+ int flags;
+} xz_compressor_t;
+
+typedef struct {
+ sqfs_u32 dict_size;
+ sqfs_u32 flags;
+} xz_options_t;
+
+static bool is_dict_size_valid(size_t size)
+{
+ size_t x = size & (size - 1);
+
+ if (x == 0)
+ return true;
+
+ return size == (x | (x >> 1));
+}
+
+static int xz_write_options(sqfs_compressor_t *base, sqfs_file_t *file)
+{
+ xz_compressor_t *xz = (xz_compressor_t *)base;
+ xz_options_t opt;
+ sqfs_u32 flags;
+
+ if (xz->flags == 0 && xz->dict_size == xz->block_size)
+ return 0;
+
+ flags = xz->flags & SQFS_COMP_FLAG_XZ_ALL;
+ flags &= ~SQFS_COMP_FLAG_XZ_EXTREME;
+
+ opt.dict_size = htole32(xz->dict_size);
+ opt.flags = htole32(flags);
+
+ return sqfs_generic_write_options(file, &opt, sizeof(opt));
+}
+
+static int xz_read_options(sqfs_compressor_t *base, sqfs_file_t *file)
+{
+ xz_compressor_t *xz = (xz_compressor_t *)base;
+ xz_options_t opt;
+ int ret;
+
+ ret = sqfs_generic_read_options(file, &opt, sizeof(opt));
+ if (ret)
+ return ret;
+
+ opt.dict_size = le32toh(opt.dict_size);
+ opt.flags = le32toh(opt.flags);
+
+ if (!is_dict_size_valid(opt.dict_size))
+ return SQFS_ERROR_CORRUPTED;
+
+ if (opt.flags & ~SQFS_COMP_FLAG_XZ_ALL)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ xz->flags = opt.flags;
+ xz->dict_size = opt.dict_size;
+ return 0;
+}
+
+static sqfs_s32 compress(xz_compressor_t *xz, lzma_vli filter,
+ const sqfs_u8 *in, sqfs_u32 size,
+ sqfs_u8 *out, sqfs_u32 outsize,
+ sqfs_u32 presets)
+{
+ lzma_filter filters[5];
+ lzma_options_lzma opt;
+ size_t written = 0;
+ lzma_ret ret;
+ int i = 0;
+
+ if (lzma_lzma_preset(&opt, presets))
+ return SQFS_ERROR_COMPRESSOR;
+
+ opt.lc = xz->lc;
+ opt.lp = xz->lp;
+ opt.pb = xz->pb;
+ 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)
+ return SQFS_ERROR_COMPRESSOR;
+
+ return 0;
+}
+
+static lzma_vli flag_to_vli(int flag)
+{
+ switch (flag) {
+ case SQFS_COMP_FLAG_XZ_X86:
+ return LZMA_FILTER_X86;
+ case SQFS_COMP_FLAG_XZ_POWERPC:
+ return LZMA_FILTER_POWERPC;
+ case SQFS_COMP_FLAG_XZ_IA64:
+ return LZMA_FILTER_IA64;
+ case SQFS_COMP_FLAG_XZ_ARM:
+ return LZMA_FILTER_ARM;
+ case SQFS_COMP_FLAG_XZ_ARMTHUMB:
+ return LZMA_FILTER_ARMTHUMB;
+ case SQFS_COMP_FLAG_XZ_SPARC:
+ return LZMA_FILTER_SPARC;
+ default:
+ break;
+ }
+
+ return LZMA_VLI_UNKNOWN;
+}
+
+static sqfs_s32 xz_comp_block(sqfs_compressor_t *base, const sqfs_u8 *in,
+ sqfs_u32 size, sqfs_u8 *out, sqfs_u32 outsize)
+{
+ xz_compressor_t *xz = (xz_compressor_t *)base;
+ lzma_vli filter, selected = LZMA_VLI_UNKNOWN;
+ sqfs_s32 ret, smallest;
+ bool extreme;
+ size_t i;
+
+ if (size >= 0x7FFFFFFF)
+ return SQFS_ERROR_ARG_INVALID;
+
+ ret = compress(xz, LZMA_VLI_UNKNOWN, in, size, out,
+ outsize, xz->level);
+ if (ret < 0 || xz->flags == 0)
+ return ret;
+
+ smallest = ret;
+ extreme = false;
+
+ if (xz->flags & SQFS_COMP_FLAG_XZ_EXTREME) {
+ ret = compress(xz, LZMA_VLI_UNKNOWN, in, size, out, outsize,
+ xz->level | LZMA_PRESET_EXTREME);
+
+ if (ret > 0 && (smallest == 0 || ret < smallest)) {
+ smallest = ret;
+ extreme = true;
+ }
+ }
+
+ for (i = 1; i & SQFS_COMP_FLAG_XZ_ALL; i <<= 1) {
+ if ((i & SQFS_COMP_FLAG_XZ_EXTREME) || (xz->flags & i) == 0)
+ continue;
+
+ filter = flag_to_vli(i);
+
+ ret = compress(xz, filter, in, size, out, outsize, xz->level);
+ if (ret > 0 && (smallest == 0 || ret < smallest)) {
+ smallest = ret;
+ selected = filter;
+ extreme = false;
+ }
+
+ if (xz->flags & SQFS_COMP_FLAG_XZ_EXTREME) {
+ ret = compress(xz, filter, in, size, out, outsize,
+ xz->level | LZMA_PRESET_EXTREME);
+
+ if (ret > 0 && (smallest == 0 || ret < smallest)) {
+ smallest = ret;
+ selected = filter;
+ extreme = true;
+ }
+ }
+ }
+
+ if (smallest == 0)
+ return 0;
+
+ return compress(xz, selected, in, size, out, outsize,
+ xz->level | (extreme ? LZMA_PRESET_EXTREME : 0));
+}
+
+static sqfs_s32 xz_uncomp_block(sqfs_compressor_t *base, const sqfs_u8 *in,
+ sqfs_u32 size, sqfs_u8 *out, sqfs_u32 outsize)
+{
+ sqfs_u64 memlimit = 65 * 1024 * 1024;
+ size_t dest_pos = 0;
+ size_t src_pos = 0;
+ lzma_ret ret;
+ (void)base;
+
+ if (outsize >= 0x7FFFFFFF)
+ return SQFS_ERROR_ARG_INVALID;
+
+ ret = lzma_stream_buffer_decode(&memlimit, 0, NULL,
+ in, &src_pos, size,
+ out, &dest_pos, outsize);
+
+ if (ret == LZMA_OK && size == src_pos)
+ return dest_pos;
+
+ return SQFS_ERROR_COMPRESSOR;
+}
+
+static void xz_get_configuration(const sqfs_compressor_t *base,
+ sqfs_compressor_config_t *cfg)
+{
+ const xz_compressor_t *xz = (const xz_compressor_t *)base;
+
+ memset(cfg, 0, sizeof(*cfg));
+ cfg->id = SQFS_COMP_XZ;
+ cfg->flags = xz->flags;
+ cfg->block_size = xz->block_size;
+ cfg->level = xz->level;
+ cfg->opt.xz.dict_size = xz->dict_size;
+ cfg->opt.xz.lc = xz->lc;
+ cfg->opt.xz.lp = xz->lp;
+ cfg->opt.xz.pb = xz->pb;
+
+ if (base->do_block == xz_uncomp_block)
+ cfg->flags |= SQFS_COMP_FLAG_UNCOMPRESS;
+}
+
+static sqfs_object_t *xz_create_copy(const sqfs_object_t *cmp)
+{
+ xz_compressor_t *xz = malloc(sizeof(*xz));
+
+ if (xz == NULL)
+ return NULL;
+
+ memcpy(xz, cmp, sizeof(*xz));
+ return (sqfs_object_t *)xz;
+}
+
+static void xz_destroy(sqfs_object_t *base)
+{
+ free(base);
+}
+
+int xz_compressor_create(const sqfs_compressor_config_t *cfg,
+ sqfs_compressor_t **out)
+{
+ sqfs_compressor_t *base;
+ xz_compressor_t *xz;
+
+ if (cfg->flags & ~(SQFS_COMP_FLAG_GENERIC_ALL |
+ SQFS_COMP_FLAG_XZ_ALL)) {
+ return SQFS_ERROR_UNSUPPORTED;
+ }
+
+ if (!is_dict_size_valid(cfg->opt.xz.dict_size))
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (cfg->opt.xz.lc + cfg->opt.xz.lp > 4)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (cfg->opt.xz.pb > SQFS_XZ_MAX_PB)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (cfg->level > SQFS_XZ_MAX_LEVEL)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (cfg->opt.xz.dict_size < SQFS_XZ_MIN_DICT_SIZE)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (cfg->opt.xz.dict_size > SQFS_XZ_MAX_DICT_SIZE)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ xz = calloc(1, sizeof(*xz));
+ base = (sqfs_compressor_t *)xz;
+ if (xz == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ sqfs_object_init(xz, xz_destroy, xz_create_copy);
+
+ xz->flags = cfg->flags;
+ xz->dict_size = cfg->opt.xz.dict_size;
+ xz->block_size = cfg->block_size;
+ xz->lc = cfg->opt.xz.lc;
+ xz->lp = cfg->opt.xz.lp;
+ xz->pb = cfg->opt.xz.pb;
+ xz->level = cfg->level;
+ base->get_configuration = xz_get_configuration;
+ base->do_block = (cfg->flags & SQFS_COMP_FLAG_UNCOMPRESS) ?
+ xz_uncomp_block : xz_comp_block;
+ base->write_options = xz_write_options;
+ base->read_options = xz_read_options;
+
+ *out = base;
+ return 0;
+}
diff --git a/lib/sqfs/src/comp/zstd.c b/lib/sqfs/src/comp/zstd.c
new file mode 100644
index 0000000..a6d7975
--- /dev/null
+++ b/lib/sqfs/src/comp/zstd.c
@@ -0,0 +1,172 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * zstd.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <zstd.h>
+#include <zstd_errors.h>
+
+#include "internal.h"
+
+typedef struct {
+ sqfs_compressor_t base;
+ size_t block_size;
+ ZSTD_CCtx *zctx;
+ int level;
+} zstd_compressor_t;
+
+typedef struct {
+ sqfs_u32 level;
+} zstd_options_t;
+
+static int zstd_write_options(sqfs_compressor_t *base, sqfs_file_t *file)
+{
+ zstd_compressor_t *zstd = (zstd_compressor_t *)base;
+ zstd_options_t opt;
+
+ if (zstd->level == SQFS_ZSTD_DEFAULT_LEVEL)
+ return 0;
+
+ opt.level = htole32(zstd->level);
+ return sqfs_generic_write_options(file, &opt, sizeof(opt));
+}
+
+static int zstd_read_options(sqfs_compressor_t *base, sqfs_file_t *file)
+{
+ zstd_options_t opt;
+ int ret;
+ (void)base;
+
+ ret = sqfs_generic_read_options(file, &opt, sizeof(opt));
+ if (ret)
+ return ret;
+
+ opt.level = le32toh(opt.level);
+ return 0;
+}
+
+static sqfs_s32 zstd_comp_block(sqfs_compressor_t *base, const sqfs_u8 *in,
+ sqfs_u32 size, sqfs_u8 *out, sqfs_u32 outsize)
+{
+ zstd_compressor_t *zstd = (zstd_compressor_t *)base;
+ size_t ret;
+
+ if (size >= 0x7FFFFFFF)
+ return SQFS_ERROR_ARG_INVALID;
+
+ ret = ZSTD_compressCCtx(zstd->zctx, out, outsize, in, size,
+ zstd->level);
+
+ if (ZSTD_isError(ret)) {
+ if (ZSTD_getErrorCode(ret) == ZSTD_error_dstSize_tooSmall)
+ return 0;
+
+ return SQFS_ERROR_COMPRESSOR;
+ }
+
+ return ret < size ? ret : 0;
+}
+
+static sqfs_s32 zstd_uncomp_block(sqfs_compressor_t *base, const sqfs_u8 *in,
+ sqfs_u32 size, sqfs_u8 *out, sqfs_u32 outsize)
+{
+ size_t ret;
+ (void)base;
+
+ if (outsize >= 0x7FFFFFFF)
+ return SQFS_ERROR_ARG_INVALID;
+
+ ret = ZSTD_decompress(out, outsize, in, size);
+
+ if (ZSTD_isError(ret))
+ return SQFS_ERROR_COMPRESSOR;
+
+ return ret;
+}
+
+static void zstd_get_configuration(const sqfs_compressor_t *base,
+ sqfs_compressor_config_t *cfg)
+{
+ const zstd_compressor_t *zstd = (const zstd_compressor_t *)base;
+
+ memset(cfg, 0, sizeof(*cfg));
+ cfg->id = SQFS_COMP_ZSTD;
+
+ cfg->block_size = zstd->block_size;
+ cfg->level = zstd->level;
+
+ if (base->do_block == zstd_uncomp_block)
+ cfg->flags |= SQFS_COMP_FLAG_UNCOMPRESS;
+}
+
+static sqfs_object_t *zstd_create_copy(const sqfs_object_t *cmp)
+{
+ zstd_compressor_t *zstd = malloc(sizeof(*zstd));
+
+ if (zstd == NULL)
+ return NULL;
+
+ memcpy(zstd, cmp, sizeof(*zstd));
+
+ zstd->zctx = ZSTD_createCCtx();
+
+ if (zstd->zctx == NULL) {
+ free(zstd);
+ return NULL;
+ }
+
+ return (sqfs_object_t *)zstd;
+}
+
+static void zstd_destroy(sqfs_object_t *base)
+{
+ zstd_compressor_t *zstd = (zstd_compressor_t *)base;
+
+ ZSTD_freeCCtx(zstd->zctx);
+ free(zstd);
+}
+
+int zstd_compressor_create(const sqfs_compressor_config_t *cfg,
+ sqfs_compressor_t **out)
+{
+ zstd_compressor_t *zstd;
+ sqfs_compressor_t *base;
+
+ if (cfg->flags & ~SQFS_COMP_FLAG_GENERIC_ALL)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (cfg->level < 1 || cfg->level > (unsigned int)ZSTD_maxCLevel())
+ return SQFS_ERROR_UNSUPPORTED;
+
+ zstd = calloc(1, sizeof(*zstd));
+ base = (sqfs_compressor_t *)zstd;
+ if (zstd == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ sqfs_object_init(zstd, zstd_destroy, zstd_create_copy);
+
+ zstd->block_size = cfg->block_size;
+ zstd->level = cfg->level;
+ zstd->zctx = ZSTD_createCCtx();
+ if (zstd->zctx == NULL) {
+ free(zstd);
+ return SQFS_ERROR_COMPRESSOR;
+ }
+
+ base->get_configuration = zstd_get_configuration;
+ base->do_block = cfg->flags & SQFS_COMP_FLAG_UNCOMPRESS ?
+ zstd_uncomp_block : zstd_comp_block;
+ base->write_options = zstd_write_options;
+ base->read_options = zstd_read_options;
+
+ *out = base;
+ return 0;
+}
diff --git a/lib/sqfs/src/data_reader.c b/lib/sqfs/src/data_reader.c
new file mode 100644
index 0000000..3f0cd74
--- /dev/null
+++ b/lib/sqfs/src/data_reader.c
@@ -0,0 +1,374 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * data_reader.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/data_reader.h"
+#include "sqfs/compressor.h"
+#include "sqfs/frag_table.h"
+#include "sqfs/block.h"
+#include "sqfs/error.h"
+#include "sqfs/table.h"
+#include "sqfs/inode.h"
+#include "sqfs/io.h"
+#include "util/util.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+struct sqfs_data_reader_t {
+ sqfs_object_t obj;
+
+ sqfs_frag_table_t *frag_tbl;
+ sqfs_compressor_t *cmp;
+ sqfs_file_t *file;
+
+ sqfs_u8 *data_block;
+ size_t data_blk_size;
+ sqfs_u64 current_block;
+
+ sqfs_u8 *frag_block;
+ size_t frag_blk_size;
+ sqfs_u32 current_frag_index;
+ sqfs_u32 block_size;
+
+ sqfs_u8 scratch[];
+};
+
+static int get_block(sqfs_data_reader_t *data, sqfs_u64 off, sqfs_u32 size,
+ sqfs_u32 max_size, size_t *out_sz, sqfs_u8 **out)
+{
+ sqfs_u32 on_disk_size;
+ sqfs_s32 ret;
+ int err;
+
+ *out = alloc_array(1, max_size);
+ *out_sz = max_size;
+
+ if (*out == NULL) {
+ err = SQFS_ERROR_ALLOC;
+ goto fail;
+ }
+
+ if (SQFS_IS_SPARSE_BLOCK(size))
+ return 0;
+
+ on_disk_size = SQFS_ON_DISK_BLOCK_SIZE(size);
+
+ if (on_disk_size > max_size) {
+ err = SQFS_ERROR_OVERFLOW;
+ goto fail;
+ }
+
+ if (SQFS_IS_BLOCK_COMPRESSED(size)) {
+ err = data->file->read_at(data->file, off,
+ data->scratch, on_disk_size);
+ if (err)
+ goto fail;
+
+ ret = data->cmp->do_block(data->cmp, data->scratch,
+ on_disk_size, *out, max_size);
+ if (ret <= 0) {
+ err = ret < 0 ? ret : SQFS_ERROR_OVERFLOW;
+ goto fail;
+ }
+
+ *out_sz = ret;
+ } else {
+ err = data->file->read_at(data->file, off,
+ *out, on_disk_size);
+ if (err)
+ goto fail;
+
+ *out_sz = on_disk_size;
+ }
+
+ return 0;
+fail:
+ free(*out);
+ *out = NULL;
+ *out_sz = 0;
+ return err;
+}
+
+static int precache_data_block(sqfs_data_reader_t *data, sqfs_u64 location,
+ sqfs_u32 size)
+{
+ if (data->data_block != NULL && data->current_block == location)
+ return 0;
+
+ free(data->data_block);
+ data->current_block = location;
+
+ return get_block(data, location, size, data->block_size,
+ &data->data_blk_size, &data->data_block);
+}
+
+static int precache_fragment_block(sqfs_data_reader_t *data, size_t idx)
+{
+ sqfs_fragment_t ent;
+ int ret;
+
+ if (data->frag_block != NULL && idx == data->current_frag_index)
+ return 0;
+
+ ret = sqfs_frag_table_lookup(data->frag_tbl, idx, &ent);
+ if (ret != 0)
+ return ret;
+
+ free(data->frag_block);
+ data->current_frag_index = idx;
+
+ return get_block(data, ent.start_offset, ent.size, data->block_size,
+ &data->frag_blk_size, &data->frag_block);
+}
+
+static void data_reader_destroy(sqfs_object_t *obj)
+{
+ sqfs_data_reader_t *data = (sqfs_data_reader_t *)obj;
+
+ sqfs_drop(data->cmp);
+ sqfs_drop(data->file);
+ sqfs_drop(data->frag_tbl);
+ free(data->data_block);
+ free(data->frag_block);
+ free(data);
+}
+
+static sqfs_object_t *data_reader_copy(const sqfs_object_t *obj)
+{
+ const sqfs_data_reader_t *data = (const sqfs_data_reader_t *)obj;
+ sqfs_data_reader_t *copy;
+
+ copy = alloc_flex(sizeof(*data), 1, data->block_size);
+ if (copy == NULL)
+ return NULL;
+
+ memcpy(copy, data, sizeof(*data) + data->block_size);
+
+ copy->frag_tbl = sqfs_copy(data->frag_tbl);
+ if (copy->frag_tbl == NULL)
+ goto fail_ftbl;
+
+ if (data->data_block != NULL) {
+ copy->data_block = malloc(data->data_blk_size);
+ if (copy->data_block == NULL)
+ goto fail_dblk;
+
+ memcpy(copy->data_block, data->data_block,
+ data->data_blk_size);
+ }
+
+ if (copy->frag_block != NULL) {
+ copy->frag_block = malloc(copy->frag_blk_size);
+ if (copy->frag_block == NULL)
+ goto fail_fblk;
+
+ memcpy(copy->frag_block, data->frag_block,
+ data->frag_blk_size);
+ }
+
+ /* duplicate references */
+ copy->file = sqfs_grab(copy->file);
+ copy->cmp = sqfs_grab(copy->cmp);
+ return (sqfs_object_t *)copy;
+fail_fblk:
+ free(copy->data_block);
+fail_dblk:
+ sqfs_drop(copy->frag_tbl);
+fail_ftbl:
+ free(copy);
+ return NULL;
+}
+
+sqfs_data_reader_t *sqfs_data_reader_create(sqfs_file_t *file,
+ size_t block_size,
+ sqfs_compressor_t *cmp,
+ sqfs_u32 flags)
+{
+ sqfs_data_reader_t *data;
+
+ if (flags != 0)
+ return NULL;
+
+ data = alloc_flex(sizeof(*data), 1, block_size);
+ if (data == NULL)
+ return NULL;
+
+ sqfs_object_init(data, data_reader_destroy, data_reader_copy);
+
+ data->frag_tbl = sqfs_frag_table_create(0);
+ if (data->frag_tbl == NULL) {
+ free(data);
+ return NULL;
+ }
+
+ data->file = sqfs_grab(file);
+ data->block_size = block_size;
+ data->cmp = sqfs_grab(cmp);
+ return data;
+}
+
+int sqfs_data_reader_load_fragment_table(sqfs_data_reader_t *data,
+ const sqfs_super_t *super)
+{
+ int ret;
+
+ free(data->frag_block);
+ data->frag_block = NULL;
+ data->current_frag_index = 0;
+
+ ret = sqfs_frag_table_read(data->frag_tbl, data->file,
+ super, data->cmp);
+ if (ret != 0)
+ return ret;
+
+ data->current_frag_index = sqfs_frag_table_get_size(data->frag_tbl);
+ return 0;
+}
+
+int sqfs_data_reader_get_block(sqfs_data_reader_t *data,
+ const sqfs_inode_generic_t *inode,
+ size_t index, size_t *size, sqfs_u8 **out)
+{
+ size_t i, unpacked_size;
+ sqfs_u64 off, filesz;
+
+ sqfs_inode_get_file_block_start(inode, &off);
+ sqfs_inode_get_file_size(inode, &filesz);
+
+ if (index >= sqfs_inode_get_file_block_count(inode))
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ for (i = 0; i < index; ++i) {
+ off += SQFS_ON_DISK_BLOCK_SIZE(inode->extra[i]);
+ filesz -= data->block_size;
+ }
+
+ unpacked_size = filesz < data->block_size ? filesz : data->block_size;
+
+ return get_block(data, off, inode->extra[index],
+ unpacked_size, size, out);
+}
+
+int sqfs_data_reader_get_fragment(sqfs_data_reader_t *data,
+ const sqfs_inode_generic_t *inode,
+ size_t *size, sqfs_u8 **out)
+{
+ sqfs_u32 frag_idx, frag_off, frag_sz;
+ size_t block_count;
+ sqfs_u64 filesz;
+ int err;
+
+ sqfs_inode_get_file_size(inode, &filesz);
+ sqfs_inode_get_frag_location(inode, &frag_idx, &frag_off);
+ *size = 0;
+ *out = NULL;
+
+ block_count = sqfs_inode_get_file_block_count(inode);
+
+ if (block_count > (UINT64_MAX / data->block_size))
+ return SQFS_ERROR_OVERFLOW;
+
+ if ((sqfs_u64)block_count * data->block_size >= filesz)
+ return 0;
+
+ frag_sz = filesz % data->block_size;
+
+ err = precache_fragment_block(data, frag_idx);
+ if (err)
+ return err;
+
+ if (frag_off + frag_sz > data->block_size)
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ *out = alloc_array(1, frag_sz);
+ if (*out == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ *size = frag_sz;
+ memcpy(*out, (char *)data->frag_block + frag_off, frag_sz);
+ return 0;
+}
+
+sqfs_s32 sqfs_data_reader_read(sqfs_data_reader_t *data,
+ const sqfs_inode_generic_t *inode,
+ sqfs_u64 offset, void *buffer, sqfs_u32 size)
+{
+ sqfs_u32 frag_idx, frag_off, diff, total = 0;
+ size_t i, block_count;
+ sqfs_u64 off, filesz;
+ char *ptr;
+ int err;
+
+ if (size >= 0x7FFFFFFF)
+ size = 0x7FFFFFFE;
+
+ /* work out file location and size */
+ sqfs_inode_get_file_size(inode, &filesz);
+ sqfs_inode_get_frag_location(inode, &frag_idx, &frag_off);
+ sqfs_inode_get_file_block_start(inode, &off);
+ block_count = sqfs_inode_get_file_block_count(inode);
+
+ if (offset >= filesz)
+ return 0;
+
+ if ((filesz - offset) < (sqfs_u64)size)
+ size = filesz - offset;
+
+ if (size == 0)
+ return 0;
+
+ /* find location of the first block */
+ for (i = 0; offset > data->block_size && i < block_count; ++i) {
+ off += SQFS_ON_DISK_BLOCK_SIZE(inode->extra[i]);
+ offset -= data->block_size;
+ }
+
+ /* copy data from blocks */
+ while (i < block_count && size > 0) {
+ diff = data->block_size - offset;
+ if (size < diff)
+ diff = size;
+
+ if (SQFS_IS_SPARSE_BLOCK(inode->extra[i])) {
+ memset(buffer, 0, diff);
+ } else {
+ err = precache_data_block(data, off, inode->extra[i]);
+ if (err)
+ return err;
+
+ memcpy(buffer, (char *)data->data_block + offset, diff);
+ off += SQFS_ON_DISK_BLOCK_SIZE(inode->extra[i]);
+ }
+
+ ++i;
+ offset = 0;
+ size -= diff;
+ total += diff;
+ buffer = (char *)buffer + diff;
+ }
+
+ /* copy from fragment */
+ if (size > 0) {
+ err = precache_fragment_block(data, frag_idx);
+ if (err)
+ return err;
+
+ if ((frag_off + offset) >= data->frag_blk_size)
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ if ((data->frag_blk_size - (frag_off + offset)) < size)
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ ptr = (char *)data->frag_block + frag_off + offset;
+ memcpy(buffer, ptr, size);
+ total += size;
+ }
+
+ return total;
+}
diff --git a/lib/sqfs/src/dir_reader/dir_reader.c b/lib/sqfs/src/dir_reader/dir_reader.c
new file mode 100644
index 0000000..d70f729
--- /dev/null
+++ b/lib/sqfs/src/dir_reader/dir_reader.c
@@ -0,0 +1,366 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * fs_reader.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "internal.h"
+
+static int inode_copy(const sqfs_inode_generic_t *inode,
+ sqfs_inode_generic_t **out)
+{
+ *out = alloc_flex(sizeof(*inode), 1, inode->payload_bytes_used);
+ if (*out == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ memcpy(*out, inode, sizeof(*inode) + inode->payload_bytes_used);
+ return 0;
+}
+
+static int dcache_key_compare(const void *ctx, const void *l, const void *r)
+{
+ sqfs_u32 lhs = *((const sqfs_u32 *)l), rhs = *((const sqfs_u32 *)r);
+ (void)ctx;
+
+ return lhs < rhs ? -1 : (lhs > rhs ? 1 : 0);
+}
+
+static int dcache_add(sqfs_dir_reader_t *rd,
+ const sqfs_inode_generic_t *inode, sqfs_u64 ref)
+{
+ sqfs_u32 inum = inode->base.inode_number;
+
+ if (!(rd->flags & SQFS_DIR_READER_DOT_ENTRIES))
+ return 0;
+
+ if (inode->base.type != SQFS_INODE_DIR &&
+ inode->base.type != SQFS_INODE_EXT_DIR) {
+ return 0;
+ }
+
+ if (rbtree_lookup(&rd->dcache, &inum) != NULL)
+ return 0;
+
+ return rbtree_insert(&rd->dcache, &inum, &ref);
+}
+
+static int dcache_find(sqfs_dir_reader_t *rd, sqfs_u32 inode, sqfs_u64 *ref)
+{
+ rbtree_node_t *node;
+
+ if (!(rd->flags & SQFS_DIR_READER_DOT_ENTRIES))
+ return SQFS_ERROR_NO_ENTRY;
+
+ node = rbtree_lookup(&rd->dcache, &inode);
+ if (node == NULL)
+ return SQFS_ERROR_NO_ENTRY;
+
+ *ref = *((sqfs_u64 *)rbtree_node_value(node));
+ return 0;
+}
+
+static void dir_reader_destroy(sqfs_object_t *obj)
+{
+ sqfs_dir_reader_t *rd = (sqfs_dir_reader_t *)obj;
+
+ if (rd->flags & SQFS_DIR_READER_DOT_ENTRIES)
+ rbtree_cleanup(&rd->dcache);
+
+ sqfs_drop(rd->meta_inode);
+ sqfs_drop(rd->meta_dir);
+ free(rd);
+}
+
+static sqfs_object_t *dir_reader_copy(const sqfs_object_t *obj)
+{
+ const sqfs_dir_reader_t *rd = (const sqfs_dir_reader_t *)obj;
+ sqfs_dir_reader_t *copy = malloc(sizeof(*copy));
+
+ if (copy == NULL)
+ return NULL;
+
+ memcpy(copy, rd, sizeof(*copy));
+
+ if (rd->flags & SQFS_DIR_READER_DOT_ENTRIES) {
+ if (rbtree_copy(&rd->dcache, &copy->dcache))
+ goto fail_cache;
+ }
+
+ copy->meta_inode = sqfs_copy(rd->meta_inode);
+ if (copy->meta_inode == NULL)
+ goto fail_mino;
+
+ copy->meta_dir = sqfs_copy(rd->meta_dir);
+ if (copy->meta_dir == NULL)
+ goto fail_mdir;
+
+ return (sqfs_object_t *)copy;
+fail_mdir:
+ sqfs_drop(copy->meta_inode);
+fail_mino:
+ if (copy->flags & SQFS_DIR_READER_DOT_ENTRIES)
+ rbtree_cleanup(&copy->dcache);
+fail_cache:
+ free(copy);
+ return NULL;
+}
+
+sqfs_dir_reader_t *sqfs_dir_reader_create(const sqfs_super_t *super,
+ sqfs_compressor_t *cmp,
+ sqfs_file_t *file,
+ sqfs_u32 flags)
+{
+ sqfs_dir_reader_t *rd;
+ sqfs_u64 start, limit;
+ int ret;
+
+ if (flags & ~SQFS_DIR_READER_ALL_FLAGS)
+ return NULL;
+
+ rd = calloc(1, sizeof(*rd));
+ if (rd == NULL)
+ return NULL;
+
+ sqfs_object_init(rd, dir_reader_destroy, dir_reader_copy);
+
+ if (flags & SQFS_DIR_READER_DOT_ENTRIES) {
+ ret = rbtree_init(&rd->dcache, sizeof(sqfs_u32),
+ sizeof(sqfs_u64), dcache_key_compare);
+
+ if (ret != 0)
+ goto fail_dcache;
+ }
+
+ start = super->inode_table_start;
+ limit = super->directory_table_start;
+
+ rd->meta_inode = sqfs_meta_reader_create(file, cmp, start, limit);
+ if (rd->meta_inode == NULL)
+ goto fail_mino;
+
+ start = super->directory_table_start;
+ limit = super->id_table_start;
+
+ if (super->fragment_table_start < limit)
+ limit = super->fragment_table_start;
+
+ if (super->export_table_start < limit)
+ limit = super->export_table_start;
+
+ rd->meta_dir = sqfs_meta_reader_create(file, cmp, start, limit);
+ if (rd->meta_dir == NULL)
+ goto fail_mdir;
+
+ rd->super = *super;
+ rd->flags = flags;
+ rd->state = DIR_STATE_NONE;
+ return rd;
+fail_mdir:
+ sqfs_drop(rd->meta_inode);
+fail_mino:
+ if (flags & SQFS_DIR_READER_DOT_ENTRIES)
+ rbtree_cleanup(&rd->dcache);
+fail_dcache:
+ free(rd);
+ return NULL;
+}
+
+int sqfs_dir_reader_open_dir(sqfs_dir_reader_t *rd,
+ const sqfs_inode_generic_t *inode,
+ sqfs_u32 flags)
+{
+ sqfs_u32 parent;
+ int ret;
+
+ if (flags & (~SQFS_DIR_OPEN_ALL_FLAGS))
+ return SQFS_ERROR_UNSUPPORTED;
+
+ ret = sqfs_readdir_state_init(&rd->it, &rd->super, inode);
+ if (ret)
+ return ret;
+
+ if ((rd->flags & SQFS_DIR_READER_DOT_ENTRIES) &&
+ !(flags & SQFS_DIR_OPEN_NO_DOT_ENTRIES)) {
+ if (inode->base.type == SQFS_INODE_EXT_DIR) {
+ parent = inode->data.dir_ext.parent_inode;
+ } else {
+ parent = inode->data.dir.parent_inode;
+ }
+
+ if (dcache_find(rd, inode->base.inode_number, &rd->cur_ref))
+ return SQFS_ERROR_NO_ENTRY;
+
+ if (rd->cur_ref == rd->super.root_inode_ref) {
+ rd->parent_ref = rd->cur_ref;
+ } else if (dcache_find(rd, parent, &rd->parent_ref)) {
+ return SQFS_ERROR_NO_ENTRY;
+ }
+
+ rd->state = DIR_STATE_OPENED;
+ } else {
+ rd->state = DIR_STATE_ENTRIES;
+ }
+
+ rd->start_state = rd->state;
+ return 0;
+}
+
+static int mk_dummy_entry(const char *str, sqfs_dir_entry_t **out)
+{
+ size_t len = strlen(str);
+ sqfs_dir_entry_t *ent;
+
+ ent = calloc(1, sizeof(sqfs_dir_entry_t) + len + 1);
+ if (ent == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ ent->type = SQFS_INODE_DIR;
+ ent->size = len - 1;
+
+ strcpy((char *)ent->name, str);
+
+ *out = ent;
+ return 0;
+}
+
+int sqfs_dir_reader_read(sqfs_dir_reader_t *rd, sqfs_dir_entry_t **out)
+{
+ int err;
+
+ switch (rd->state) {
+ case DIR_STATE_OPENED:
+ err = mk_dummy_entry(".", out);
+ if (err == 0) {
+ rd->state = DIR_STATE_DOT;
+ rd->ent_ref = rd->cur_ref;
+ }
+ return err;
+ case DIR_STATE_DOT:
+ err = mk_dummy_entry("..", out);
+ if (err == 0) {
+ rd->state = DIR_STATE_ENTRIES;
+ rd->ent_ref = rd->parent_ref;
+ }
+ return err;
+ case DIR_STATE_ENTRIES:
+ break;
+ default:
+ return SQFS_ERROR_SEQUENCE;
+ }
+
+ return sqfs_meta_reader_readdir(rd->meta_dir, &rd->it,
+ out, NULL, &rd->ent_ref);
+}
+
+int sqfs_dir_reader_rewind(sqfs_dir_reader_t *rd)
+{
+ if (rd->state == DIR_STATE_NONE)
+ return SQFS_ERROR_SEQUENCE;
+
+ sqfs_readdir_state_reset(&rd->it);
+ rd->state = rd->start_state;
+ return 0;
+}
+
+int sqfs_dir_reader_find(sqfs_dir_reader_t *rd, const char *name)
+{
+ sqfs_dir_entry_t *ent;
+ int ret;
+
+ ret = sqfs_dir_reader_rewind(rd);
+ if (ret != 0)
+ return ret;
+
+ do {
+ ret = sqfs_dir_reader_read(rd, &ent);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ return SQFS_ERROR_NO_ENTRY;
+
+ ret = strcmp((const char *)ent->name, name);
+ free(ent);
+ } while (ret < 0);
+
+ return ret == 0 ? 0 : SQFS_ERROR_NO_ENTRY;
+}
+
+int sqfs_dir_reader_get_inode(sqfs_dir_reader_t *rd,
+ sqfs_inode_generic_t **inode)
+{
+ int ret;
+
+ ret = sqfs_meta_reader_read_inode(rd->meta_inode, &rd->super,
+ rd->ent_ref >> 16,
+ rd->ent_ref & 0x0FFFF, inode);
+ if (ret != 0)
+ return ret;
+
+ return dcache_add(rd, *inode, rd->ent_ref);
+}
+
+int sqfs_dir_reader_get_root_inode(sqfs_dir_reader_t *rd,
+ sqfs_inode_generic_t **inode)
+{
+ sqfs_u64 block_start = rd->super.root_inode_ref >> 16;
+ sqfs_u16 offset = rd->super.root_inode_ref & 0xFFFF;
+ int ret;
+
+ ret = sqfs_meta_reader_read_inode(rd->meta_inode, &rd->super,
+ block_start, offset, inode);
+ if (ret != 0)
+ return ret;
+
+ return dcache_add(rd, *inode, rd->super.root_inode_ref);
+}
+
+int sqfs_dir_reader_find_by_path(sqfs_dir_reader_t *rd,
+ const sqfs_inode_generic_t *start,
+ const char *path, sqfs_inode_generic_t **out)
+{
+ sqfs_inode_generic_t *inode;
+ const char *ptr;
+ int ret = 0;
+ char *name;
+
+ if (start == NULL) {
+ ret = sqfs_dir_reader_get_root_inode(rd, &inode);
+ } else {
+ ret = inode_copy(start, &inode);
+ }
+
+ if (ret)
+ return ret;
+
+ for (; *path != '\0'; path = ptr) {
+ if (*path == '/') {
+ for (ptr = path; *ptr == '/'; ++ptr)
+ ;
+ continue;
+ }
+
+ ret = sqfs_dir_reader_open_dir(rd, inode, 0);
+ free(inode);
+ if (ret)
+ return ret;
+
+ ptr = strchrnul(path, '/');
+
+ name = strndup(path, ptr - path);
+ if (name == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ ret = sqfs_dir_reader_find(rd, name);
+ free(name);
+ if (ret)
+ return ret;
+
+ ret = sqfs_dir_reader_get_inode(rd, &inode);
+ if (ret)
+ return ret;
+ }
+
+ *out = inode;
+ return 0;
+}
diff --git a/lib/sqfs/src/dir_reader/get_path.c b/lib/sqfs/src/dir_reader/get_path.c
new file mode 100644
index 0000000..847bfd3
--- /dev/null
+++ b/lib/sqfs/src/dir_reader/get_path.c
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * get_path.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "internal.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+int sqfs_tree_node_get_path(const sqfs_tree_node_t *node, char **out)
+{
+ const sqfs_tree_node_t *it;
+ size_t clen, len = 0;
+ char *str, *ptr;
+
+ *out = NULL;
+
+ if (node == NULL)
+ return SQFS_ERROR_ARG_INVALID;
+
+ for (it = node; it->parent != NULL; it = it->parent) {
+ if (it->parent == node)
+ return SQFS_ERROR_LINK_LOOP;
+
+ /* non-root nodes must have a valid name */
+ clen = strlen((const char *)it->name);
+
+ if (clen == 0)
+ return SQFS_ERROR_CORRUPTED;
+
+ if (strchr((const char *)it->name, '/') != NULL)
+ return SQFS_ERROR_CORRUPTED;
+
+ if (it->name[0] == '.') {
+ if (clen == 1 || (clen == 2 && it->name[1] == '.'))
+ return SQFS_ERROR_CORRUPTED;
+ }
+
+ /* compute total path length */
+ if (SZ_ADD_OV(clen, 1, &clen))
+ return SQFS_ERROR_OVERFLOW;
+
+ if (SZ_ADD_OV(len, clen, &len))
+ return SQFS_ERROR_OVERFLOW;
+ }
+
+ /* root node must not have a name */
+ if (it->name[0] != '\0')
+ return SQFS_ERROR_ARG_INVALID;
+
+ /* generate the path */
+ if (node->parent == NULL) {
+ str = strdup("/");
+ if (str == NULL)
+ return SQFS_ERROR_ALLOC;
+ } else {
+ if (SZ_ADD_OV(len, 1, &len))
+ return SQFS_ERROR_OVERFLOW;
+
+ str = malloc(len);
+ if (str == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ ptr = str + len - 1;
+ *ptr = '\0';
+
+ for (it = node; it->parent != NULL; it = it->parent) {
+ len = strlen((const char *)it->name);
+ ptr -= len;
+
+ memcpy(ptr, (const char *)it->name, len);
+ *(--ptr) = '/';
+ }
+ }
+
+ *out = str;
+ return 0;
+}
diff --git a/lib/sqfs/src/dir_reader/internal.h b/lib/sqfs/src/dir_reader/internal.h
new file mode 100644
index 0000000..471d197
--- /dev/null
+++ b/lib/sqfs/src/dir_reader/internal.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * internal.h
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#ifndef DIR_READER_INTERNAL_H
+#define DIR_READER_INTERNAL_H
+
+#include "config.h"
+
+#include "sqfs/meta_reader.h"
+#include "sqfs/dir_reader.h"
+#include "sqfs/compressor.h"
+#include "sqfs/id_table.h"
+#include "sqfs/super.h"
+#include "sqfs/inode.h"
+#include "sqfs/error.h"
+#include "sqfs/dir.h"
+#include "util/rbtree.h"
+#include "util/util.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+enum {
+ DIR_STATE_NONE = 0,
+ DIR_STATE_OPENED = 1,
+ DIR_STATE_DOT = 2,
+ DIR_STATE_ENTRIES = 3,
+};
+
+struct sqfs_dir_reader_t {
+ sqfs_object_t base;
+
+ sqfs_meta_reader_t *meta_dir;
+ sqfs_meta_reader_t *meta_inode;
+ sqfs_super_t super;
+
+ sqfs_readdir_state_t it;
+
+ sqfs_u32 flags;
+
+ int start_state;
+ int state;
+ sqfs_u64 parent_ref;
+ sqfs_u64 cur_ref;
+ sqfs_u64 ent_ref;
+ rbtree_t dcache;
+};
+
+#endif /* DIR_READER_INTERNAL_H */
diff --git a/lib/sqfs/src/dir_reader/read_tree.c b/lib/sqfs/src/dir_reader/read_tree.c
new file mode 100644
index 0000000..91cc2c0
--- /dev/null
+++ b/lib/sqfs/src/dir_reader/read_tree.c
@@ -0,0 +1,288 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * read_tree.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "internal.h"
+
+static int should_skip(int type, unsigned int flags)
+{
+ switch (type) {
+ case SQFS_INODE_BDEV:
+ case SQFS_INODE_CDEV:
+ case SQFS_INODE_EXT_CDEV:
+ case SQFS_INODE_EXT_BDEV:
+ return (flags & SQFS_TREE_NO_DEVICES);
+ case SQFS_INODE_SLINK:
+ case SQFS_INODE_EXT_SLINK:
+ return (flags & SQFS_TREE_NO_SLINKS);
+ case SQFS_INODE_SOCKET:
+ case SQFS_INODE_EXT_SOCKET:
+ return(flags & SQFS_TREE_NO_SOCKETS);
+ case SQFS_INODE_FIFO:
+ case SQFS_INODE_EXT_FIFO:
+ return (flags & SQFS_TREE_NO_FIFO);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static bool would_be_own_parent(sqfs_tree_node_t *parent, sqfs_tree_node_t *n)
+{
+ sqfs_u32 inum = n->inode->base.inode_number;
+
+ while (parent != NULL) {
+ if (parent->inode->base.inode_number == inum)
+ return true;
+
+ parent = parent->parent;
+ }
+
+ return false;
+}
+
+static sqfs_tree_node_t *create_node(sqfs_inode_generic_t *inode,
+ const char *name)
+{
+ sqfs_tree_node_t *n;
+
+ n = alloc_flex(sizeof(*n), 1, strlen(name) + 1);
+ if (n == NULL)
+ return NULL;
+
+ n->inode = inode;
+ strcpy((char *)n->name, name);
+ return n;
+}
+
+static int fill_dir(sqfs_dir_reader_t *dr, sqfs_tree_node_t *root,
+ unsigned int flags)
+{
+ sqfs_tree_node_t *n, *prev, **tail;
+ sqfs_inode_generic_t *inode;
+ sqfs_dir_entry_t *ent;
+ int err;
+
+ tail = &root->children;
+
+ for (;;) {
+ err = sqfs_dir_reader_read(dr, &ent);
+ if (err > 0)
+ break;
+ if (err < 0)
+ return err;
+
+ if (should_skip(ent->type, flags)) {
+ free(ent);
+ continue;
+ }
+
+ err = sqfs_dir_reader_get_inode(dr, &inode);
+ if (err) {
+ free(ent);
+ return err;
+ }
+
+ n = create_node(inode, (const char *)ent->name);
+ free(ent);
+
+ if (n == NULL) {
+ free(inode);
+ return SQFS_ERROR_ALLOC;
+ }
+
+ if (would_be_own_parent(root, n)) {
+ free(n);
+ free(inode);
+ return SQFS_ERROR_LINK_LOOP;
+ }
+
+ *tail = n;
+ tail = &n->next;
+ n->parent = root;
+ }
+
+ n = root->children;
+ prev = NULL;
+
+ while (n != NULL) {
+ if (n->inode->base.type == SQFS_INODE_DIR ||
+ n->inode->base.type == SQFS_INODE_EXT_DIR) {
+ if (!(flags & SQFS_TREE_NO_RECURSE)) {
+ err = sqfs_dir_reader_open_dir(dr, n->inode,
+ SQFS_DIR_OPEN_NO_DOT_ENTRIES);
+ if (err)
+ return err;
+
+ err = fill_dir(dr, n, flags);
+ if (err)
+ return err;
+ }
+
+ if (n->children == NULL &&
+ (flags & SQFS_TREE_NO_EMPTY)) {
+ free(n->inode);
+ if (prev == NULL) {
+ root->children = root->children->next;
+ free(n);
+ n = root->children;
+ } else {
+ prev->next = n->next;
+ free(n);
+ n = prev->next;
+ }
+ continue;
+ }
+ }
+
+ prev = n;
+ n = n->next;
+ }
+
+ return 0;
+}
+
+static int resolve_ids(sqfs_tree_node_t *root, const sqfs_id_table_t *idtbl)
+{
+ sqfs_tree_node_t *it;
+ int err;
+
+ for (it = root->children; it != NULL; it = it->next)
+ resolve_ids(it, idtbl);
+
+ err = sqfs_id_table_index_to_id(idtbl, root->inode->base.uid_idx,
+ &root->uid);
+ if (err)
+ return err;
+
+ return sqfs_id_table_index_to_id(idtbl, root->inode->base.gid_idx,
+ &root->gid);
+}
+
+void sqfs_dir_tree_destroy(sqfs_tree_node_t *root)
+{
+ sqfs_tree_node_t *it;
+
+ if (!root)
+ return;
+
+ while (root->children != NULL) {
+ it = root->children;
+ root->children = it->next;
+
+ sqfs_dir_tree_destroy(it);
+ }
+
+ free(root->inode);
+ free(root);
+}
+
+int sqfs_dir_reader_get_full_hierarchy(sqfs_dir_reader_t *rd,
+ const sqfs_id_table_t *idtbl,
+ const char *path, unsigned int flags,
+ sqfs_tree_node_t **out)
+{
+ sqfs_tree_node_t *root, *tail, *new;
+ sqfs_inode_generic_t *inode;
+ sqfs_dir_entry_t *ent;
+ const char *ptr;
+ int ret;
+
+ if (flags & ~SQFS_TREE_ALL_FLAGS)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ ret = sqfs_dir_reader_get_root_inode(rd, &inode);
+ if (ret)
+ return ret;
+
+ root = tail = create_node(inode, "");
+ if (root == NULL) {
+ free(inode);
+ return SQFS_ERROR_ALLOC;
+ }
+ inode = NULL;
+
+ while (path != NULL && *path != '\0') {
+ if (*path == '/') {
+ while (*path == '/')
+ ++path;
+ continue;
+ }
+
+ ret = sqfs_dir_reader_open_dir(rd, tail->inode,
+ SQFS_DIR_OPEN_NO_DOT_ENTRIES);
+ if (ret)
+ goto fail;
+
+ ptr = strchrnul(path, '/');
+
+ for (;;) {
+ ret = sqfs_dir_reader_read(rd, &ent);
+ if (ret < 0)
+ goto fail;
+ if (ret > 0) {
+ ret = SQFS_ERROR_NO_ENTRY;
+ goto fail;
+ }
+
+ ret = strncmp((const char *)ent->name,
+ path, ptr - path);
+ if (ret == 0 && ent->name[ptr - path] == '\0')
+ break;
+ free(ent);
+ }
+
+ ret = sqfs_dir_reader_get_inode(rd, &inode);
+ if (ret) {
+ free(ent);
+ goto fail;
+ }
+
+ new = create_node(inode, (const char *)ent->name);
+ free(ent);
+
+ if (new == NULL) {
+ free(inode);
+ ret = SQFS_ERROR_ALLOC;
+ goto fail;
+ }
+
+ inode = NULL;
+ path = ptr;
+
+ if (flags & SQFS_TREE_STORE_PARENTS) {
+ tail->children = new;
+ new->parent = tail;
+ tail = new;
+ } else {
+ sqfs_dir_tree_destroy(root);
+ root = tail = new;
+ }
+ }
+
+ if (tail->inode->base.type == SQFS_INODE_DIR ||
+ tail->inode->base.type == SQFS_INODE_EXT_DIR) {
+ ret = sqfs_dir_reader_open_dir(rd, tail->inode,
+ SQFS_DIR_OPEN_NO_DOT_ENTRIES);
+ if (ret)
+ goto fail;
+
+ ret = fill_dir(rd, tail, flags);
+ if (ret)
+ goto fail;
+ }
+
+ ret = resolve_ids(root, idtbl);
+ if (ret)
+ goto fail;
+
+ *out = root;
+ return 0;
+fail:
+ sqfs_dir_tree_destroy(root);
+ return ret;
+}
diff --git a/lib/sqfs/src/dir_writer.c b/lib/sqfs/src/dir_writer.c
new file mode 100644
index 0000000..d2b72df
--- /dev/null
+++ b/lib/sqfs/src/dir_writer.c
@@ -0,0 +1,460 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * dir_writer.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/meta_writer.h"
+#include "sqfs/dir_writer.h"
+#include "sqfs/super.h"
+#include "sqfs/table.h"
+#include "sqfs/inode.h"
+#include "sqfs/error.h"
+#include "sqfs/block.h"
+#include "sqfs/dir.h"
+#include "util/array.h"
+#include "util/util.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#define DIR_INDEX_THRESHOLD (256)
+
+typedef struct dir_entry_t {
+ struct dir_entry_t *next;
+ sqfs_u64 inode_ref;
+ sqfs_u32 inode_num;
+ sqfs_u16 type;
+ size_t name_len;
+ char name[];
+} dir_entry_t;
+
+typedef struct index_ent_t {
+ struct index_ent_t *next;
+ dir_entry_t *ent;
+ sqfs_u64 block;
+ sqfs_u32 index;
+} index_ent_t;
+
+struct sqfs_dir_writer_t {
+ sqfs_object_t base;
+
+ dir_entry_t *list;
+ dir_entry_t *list_end;
+
+ index_ent_t *idx;
+ index_ent_t *idx_end;
+
+ sqfs_u64 dir_ref;
+ size_t dir_size;
+ size_t ent_count;
+ sqfs_meta_writer_t *dm;
+
+ array_t export_tbl;
+};
+
+static int get_type(sqfs_u16 mode)
+{
+ switch (mode & S_IFMT) {
+ case S_IFSOCK: return SQFS_INODE_SOCKET;
+ case S_IFIFO: return SQFS_INODE_FIFO;
+ case S_IFLNK: return SQFS_INODE_SLINK;
+ case S_IFBLK: return SQFS_INODE_BDEV;
+ case S_IFCHR: return SQFS_INODE_CDEV;
+ case S_IFDIR: return SQFS_INODE_DIR;
+ case S_IFREG: return SQFS_INODE_FILE;
+ default:
+ break;
+ }
+
+ return SQFS_ERROR_UNSUPPORTED;
+}
+
+static void writer_reset(sqfs_dir_writer_t *writer)
+{
+ dir_entry_t *ent;
+ index_ent_t *idx;
+
+ while (writer->idx != NULL) {
+ idx = writer->idx;
+ writer->idx = idx->next;
+ free(idx);
+ }
+
+ while (writer->list != NULL) {
+ ent = writer->list;
+ writer->list = ent->next;
+ free(ent);
+ }
+
+ writer->list_end = NULL;
+ writer->idx_end = NULL;
+ writer->dir_ref = 0;
+ writer->dir_size = 0;
+ writer->ent_count = 0;
+}
+
+static int add_export_table_entry(sqfs_dir_writer_t *writer,
+ sqfs_u32 inum, sqfs_u64 iref)
+{
+ sqfs_u64 *ptr;
+ int ret;
+
+ if (writer->export_tbl.data == NULL)
+ return 0;
+
+ if (inum < 1)
+ return SQFS_ERROR_ARG_INVALID;
+
+ ret = array_set_capacity(&writer->export_tbl, inum);
+ if (ret != 0)
+ return ret;
+
+ ptr = (sqfs_u64 *)writer->export_tbl.data;
+
+ if ((inum - 1) >= writer->export_tbl.used) {
+ memset(ptr + writer->export_tbl.used, 0xFF,
+ (inum - writer->export_tbl.used) * sizeof(*ptr));
+
+ writer->export_tbl.used = inum;
+ }
+
+ ptr[inum - 1] = iref;
+ return 0;
+}
+
+static void dir_writer_destroy(sqfs_object_t *obj)
+{
+ sqfs_dir_writer_t *writer = (sqfs_dir_writer_t *)obj;
+
+ sqfs_drop(writer->dm);
+ writer_reset(writer);
+ array_cleanup(&writer->export_tbl);
+ free(writer);
+}
+
+sqfs_dir_writer_t *sqfs_dir_writer_create(sqfs_meta_writer_t *dm,
+ sqfs_u32 flags)
+{
+ sqfs_dir_writer_t *writer;
+
+ if (flags & ~SQFS_DIR_WRITER_CREATE_ALL_FLAGS)
+ return NULL;
+
+ writer = calloc(1, sizeof(*writer));
+ if (writer == NULL)
+ return NULL;
+
+ sqfs_object_init(writer, dir_writer_destroy, NULL);
+
+ if (flags & SQFS_DIR_WRITER_CREATE_EXPORT_TABLE) {
+ if (array_init(&writer->export_tbl, sizeof(sqfs_u64), 512)) {
+ free(writer);
+ return NULL;
+ }
+
+ memset(writer->export_tbl.data, 0xFF,
+ writer->export_tbl.size * writer->export_tbl.count);
+ }
+
+ writer->dm = sqfs_grab(dm);
+ return writer;
+}
+
+int sqfs_dir_writer_begin(sqfs_dir_writer_t *writer, sqfs_u32 flags)
+{
+ sqfs_u32 offset;
+ sqfs_u64 block;
+
+ if (flags != 0)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ writer_reset(writer);
+
+ sqfs_meta_writer_get_position(writer->dm, &block, &offset);
+ writer->dir_ref = (block << 16) | offset;
+ return 0;
+}
+
+int sqfs_dir_writer_add_entry(sqfs_dir_writer_t *writer, const char *name,
+ sqfs_u32 inode_num, sqfs_u64 inode_ref,
+ sqfs_u16 mode)
+{
+ dir_entry_t *ent;
+ int type, err;
+
+ type = get_type(mode);
+ if (type < 0)
+ return type;
+
+ if (name[0] == '\0' || inode_num < 1)
+ return SQFS_ERROR_ARG_INVALID;
+
+ err = add_export_table_entry(writer, inode_num, inode_ref);
+ if (err)
+ return err;
+
+ ent = alloc_flex(sizeof(*ent), 1, strlen(name));
+ if (ent == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ ent->inode_ref = inode_ref;
+ ent->inode_num = inode_num;
+ ent->type = type;
+ ent->name_len = strlen(name);
+ memcpy(ent->name, name, ent->name_len);
+
+ if (writer->list_end == NULL) {
+ writer->list = writer->list_end = ent;
+ } else {
+ writer->list_end->next = ent;
+ writer->list_end = ent;
+ }
+
+ writer->ent_count += 1;
+ return 0;
+}
+
+static size_t get_conseq_entry_count(sqfs_u32 offset, dir_entry_t *head)
+{
+ size_t size, count = 0;
+ dir_entry_t *it;
+ sqfs_s32 diff;
+
+ size = (offset + sizeof(sqfs_dir_header_t)) % SQFS_META_BLOCK_SIZE;
+
+ for (it = head; it != NULL; it = it->next) {
+ if ((it->inode_ref >> 16) != (head->inode_ref >> 16))
+ break;
+
+ diff = it->inode_num - head->inode_num;
+
+ if (diff > 32767 || diff < -32767)
+ break;
+
+ size += sizeof(sqfs_dir_entry_t) + it->name_len;
+
+ if (count > 0 && size > SQFS_META_BLOCK_SIZE)
+ break;
+
+ count += 1;
+
+ if (count == SQFS_MAX_DIR_ENT)
+ break;
+ }
+
+ return count;
+}
+
+static int add_header(sqfs_dir_writer_t *writer, size_t count,
+ dir_entry_t *ref, sqfs_u64 block)
+{
+ sqfs_dir_header_t hdr;
+ index_ent_t *idx;
+ int err;
+
+ hdr.count = htole32(count - 1);
+ hdr.start_block = htole32(ref->inode_ref >> 16);
+ hdr.inode_number = htole32(ref->inode_num);
+
+ err = sqfs_meta_writer_append(writer->dm, &hdr, sizeof(hdr));
+ if (err)
+ return err;
+
+ idx = calloc(1, sizeof(*idx));
+ if (idx == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ idx->ent = ref;
+ idx->block = block;
+ idx->index = writer->dir_size;
+
+ if (writer->idx_end == NULL) {
+ writer->idx = writer->idx_end = idx;
+ } else {
+ writer->idx_end->next = idx;
+ writer->idx_end = idx;
+ }
+
+ writer->dir_size += sizeof(hdr);
+ return 0;
+}
+
+int sqfs_dir_writer_end(sqfs_dir_writer_t *writer)
+{
+ dir_entry_t *it, *first;
+ sqfs_dir_entry_t ent;
+ sqfs_u16 *diff_u16;
+ size_t i, count;
+ sqfs_u32 offset;
+ sqfs_u64 block;
+ int err;
+
+ for (it = writer->list; it != NULL; ) {
+ sqfs_meta_writer_get_position(writer->dm, &block, &offset);
+ count = get_conseq_entry_count(offset, it);
+
+ err = add_header(writer, count, it, block);
+ if (err)
+ return err;
+
+ first = it;
+
+ for (i = 0; i < count; ++i) {
+ ent.offset = htole16(it->inode_ref & 0x0000FFFF);
+ ent.inode_diff = it->inode_num - first->inode_num;
+ ent.type = htole16(it->type);
+ ent.size = htole16(it->name_len - 1);
+
+ diff_u16 = (sqfs_u16 *)&ent.inode_diff;
+ *diff_u16 = htole16(*diff_u16);
+
+ err = sqfs_meta_writer_append(writer->dm, &ent,
+ sizeof(ent));
+ if (err)
+ return err;
+
+ err = sqfs_meta_writer_append(writer->dm, it->name,
+ it->name_len);
+ if (err)
+ return err;
+
+ writer->dir_size += sizeof(ent) + it->name_len;
+ it = it->next;
+ }
+ }
+
+ return 0;
+}
+
+size_t sqfs_dir_writer_get_size(const sqfs_dir_writer_t *writer)
+{
+ return writer->dir_size;
+}
+
+sqfs_u64 sqfs_dir_writer_get_dir_reference(const sqfs_dir_writer_t *writer)
+{
+ return writer->dir_ref;
+}
+
+size_t sqfs_dir_writer_get_index_size(const sqfs_dir_writer_t *writer)
+{
+ size_t index_size = 0;
+ index_ent_t *idx;
+
+ for (idx = writer->idx; idx != NULL; idx = idx->next)
+ index_size += sizeof(sqfs_dir_index_t) + idx->ent->name_len;
+
+ return index_size;
+}
+
+size_t sqfs_dir_writer_get_entry_count(const sqfs_dir_writer_t *writer)
+{
+ return writer->ent_count;
+}
+
+sqfs_inode_generic_t
+*sqfs_dir_writer_create_inode(const sqfs_dir_writer_t *writer,
+ size_t hlinks, sqfs_u32 xattr,
+ sqfs_u32 parent_ino)
+{
+ sqfs_inode_generic_t *inode;
+ sqfs_dir_index_t ent;
+ sqfs_u64 start_block;
+ sqfs_u16 block_offset;
+ size_t index_size;
+ index_ent_t *idx;
+ sqfs_u8 *ptr;
+
+ index_size = 0;
+
+ for (idx = writer->idx; idx != NULL; idx = idx->next)
+ index_size += sizeof(ent) + idx->ent->name_len;
+
+ inode = alloc_flex(sizeof(*inode), 1, index_size);
+ if (inode == NULL)
+ return NULL;
+
+ inode->payload_bytes_available = index_size;
+ start_block = writer->dir_ref >> 16;
+ block_offset = writer->dir_ref & 0xFFFF;
+
+ if (xattr != 0xFFFFFFFF || start_block > 0xFFFFFFFFUL ||
+ writer->dir_size > (0xFFFF - 3)) {
+ inode->base.type = SQFS_INODE_EXT_DIR;
+ } else {
+ inode->base.type = SQFS_INODE_DIR;
+ }
+
+ if (writer->ent_count >= DIR_INDEX_THRESHOLD)
+ inode->base.type = SQFS_INODE_EXT_DIR;
+
+ if (inode->base.type == SQFS_INODE_DIR) {
+ inode->data.dir.start_block = start_block;
+ inode->data.dir.nlink = writer->ent_count + hlinks + 2;
+ inode->data.dir.size = writer->dir_size + 3;
+ inode->data.dir.offset = block_offset;
+ inode->data.dir.parent_inode = parent_ino;
+ } else {
+ inode->data.dir_ext.nlink = writer->ent_count + hlinks + 2;
+ inode->data.dir_ext.size = writer->dir_size + 3;
+ inode->data.dir_ext.start_block = start_block;
+ inode->data.dir_ext.parent_inode = parent_ino;
+ inode->data.dir_ext.offset = block_offset;
+ inode->data.dir_ext.xattr_idx = xattr;
+ inode->data.dir_ext.inodex_count = 0;
+ inode->payload_bytes_used = 0;
+
+ for (idx = writer->idx; idx != NULL; idx = idx->next) {
+ memset(&ent, 0, sizeof(ent));
+ ent.start_block = idx->block;
+ ent.index = idx->index;
+ ent.size = idx->ent->name_len - 1;
+
+ ptr = (sqfs_u8 *)inode->extra +
+ inode->payload_bytes_used;
+ memcpy(ptr, &ent, sizeof(ent));
+ memcpy(ptr + sizeof(ent), idx->ent->name,
+ idx->ent->name_len);
+
+ inode->data.dir_ext.inodex_count += 1;
+ inode->payload_bytes_used += sizeof(ent);
+ inode->payload_bytes_used += idx->ent->name_len;
+ }
+ }
+
+ return inode;
+}
+
+int sqfs_dir_writer_write_export_table(sqfs_dir_writer_t *writer,
+ sqfs_file_t *file,
+ sqfs_compressor_t *cmp,
+ sqfs_u32 root_inode_num,
+ sqfs_u64 root_inode_ref,
+ sqfs_super_t *super)
+{
+ sqfs_u64 start;
+ size_t size;
+ int ret;
+
+ ret = add_export_table_entry(writer, root_inode_num, root_inode_ref);
+ if (ret)
+ return 0;
+
+ if (writer->export_tbl.data == NULL)
+ return 0;
+
+ size = writer->export_tbl.size * writer->export_tbl.used;
+
+ ret = sqfs_write_table(file, cmp, writer->export_tbl.data,
+ size, &start);
+ if (ret)
+ return ret;
+
+ super->export_table_start = start;
+ super->flags |= SQFS_FLAG_EXPORTABLE;
+ return 0;
+}
diff --git a/lib/sqfs/src/frag_table.c b/lib/sqfs/src/frag_table.c
new file mode 100644
index 0000000..151df28
--- /dev/null
+++ b/lib/sqfs/src/frag_table.c
@@ -0,0 +1,204 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * frag_table.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/frag_table.h"
+#include "sqfs/super.h"
+#include "sqfs/table.h"
+#include "sqfs/error.h"
+#include "sqfs/block.h"
+#include "compat.h"
+#include "util/array.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+struct sqfs_frag_table_t {
+ sqfs_object_t base;
+
+ array_t table;
+};
+
+static void frag_table_destroy(sqfs_object_t *obj)
+{
+ sqfs_frag_table_t *tbl = (sqfs_frag_table_t *)obj;
+
+ array_cleanup(&tbl->table);
+ free(tbl);
+}
+
+static sqfs_object_t *frag_table_copy(const sqfs_object_t *obj)
+{
+ const sqfs_frag_table_t *tbl = (const sqfs_frag_table_t *)obj;
+ sqfs_frag_table_t *copy = calloc(1, sizeof(*copy));
+
+ if (copy == NULL)
+ return NULL;
+
+ if (array_init_copy(&copy->table, &tbl->table)) {
+ free(copy);
+ return NULL;
+ }
+
+ return (sqfs_object_t *)copy;
+}
+
+sqfs_frag_table_t *sqfs_frag_table_create(sqfs_u32 flags)
+{
+ sqfs_frag_table_t *tbl;
+
+ if (flags != 0)
+ return NULL;
+
+ tbl = calloc(1, sizeof(*tbl));
+ if (tbl == NULL)
+ return NULL;
+
+ sqfs_object_init(tbl, frag_table_destroy, frag_table_copy);
+
+ array_init(&tbl->table, sizeof(sqfs_fragment_t), 0);
+ return tbl;
+}
+
+int sqfs_frag_table_read(sqfs_frag_table_t *tbl, sqfs_file_t *file,
+ const sqfs_super_t *super, sqfs_compressor_t *cmp)
+{
+ sqfs_u64 location, lower, upper;
+ void *raw = NULL;
+ size_t size;
+ int err;
+
+ array_cleanup(&tbl->table);
+ tbl->table.size = sizeof(sqfs_fragment_t);
+
+ if (super->flags & SQFS_FLAG_NO_FRAGMENTS)
+ return 0;
+
+ if (super->fragment_table_start == 0xFFFFFFFFFFFFFFFFUL)
+ return 0;
+
+ if (super->fragment_entry_count == 0)
+ return 0;
+
+ if (super->fragment_table_start >= super->bytes_used)
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ /* location must be after inode & directory table,
+ but before the ID table */
+ if (super->fragment_table_start < super->directory_table_start)
+ return SQFS_ERROR_CORRUPTED;
+
+ if (super->fragment_table_start >= super->id_table_start)
+ return SQFS_ERROR_CORRUPTED;
+
+ location = super->fragment_table_start;
+ lower = super->directory_table_start;
+ upper = super->id_table_start;
+
+ if (super->export_table_start < super->id_table_start)
+ upper = super->export_table_start;
+
+ if (SZ_MUL_OV(super->fragment_entry_count, sizeof(sqfs_fragment_t),
+ &size)) {
+ return SQFS_ERROR_OVERFLOW;
+ }
+
+ err = sqfs_read_table(file, cmp, size, location, lower, upper, &raw);
+ if (err) {
+ free(raw);
+ return err;
+ }
+
+ tbl->table.data = raw;
+ tbl->table.count = super->fragment_entry_count;
+ tbl->table.used = super->fragment_entry_count;
+ return 0;
+}
+
+int sqfs_frag_table_write(sqfs_frag_table_t *tbl, sqfs_file_t *file,
+ sqfs_super_t *super, sqfs_compressor_t *cmp)
+{
+ size_t i;
+ int err;
+
+ if (tbl->table.used == 0) {
+ super->fragment_table_start = 0xFFFFFFFFFFFFFFFF;
+ super->flags |= SQFS_FLAG_NO_FRAGMENTS;
+ super->flags &= ~SQFS_FLAG_ALWAYS_FRAGMENTS;
+ super->flags &= ~SQFS_FLAG_UNCOMPRESSED_FRAGMENTS;
+ return 0;
+ }
+
+ err = sqfs_write_table(file, cmp, tbl->table.data,
+ tbl->table.size * tbl->table.used,
+ &super->fragment_table_start);
+ if (err)
+ return err;
+
+ super->fragment_entry_count = tbl->table.used;
+ super->flags &= ~SQFS_FLAG_NO_FRAGMENTS;
+ super->flags |= SQFS_FLAG_ALWAYS_FRAGMENTS;
+ super->flags |= SQFS_FLAG_UNCOMPRESSED_FRAGMENTS;
+
+ for (i = 0; i < tbl->table.used; ++i) {
+ sqfs_u32 sz = ((sqfs_fragment_t *)tbl->table.data)[i].size;
+
+ if (SQFS_IS_BLOCK_COMPRESSED(le32toh(sz))) {
+ super->flags &= ~SQFS_FLAG_UNCOMPRESSED_FRAGMENTS;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int sqfs_frag_table_lookup(sqfs_frag_table_t *tbl, sqfs_u32 index,
+ sqfs_fragment_t *out)
+{
+ sqfs_fragment_t *frag = array_get(&tbl->table, index);
+
+ if (frag == NULL)
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ out->start_offset = le64toh(frag->start_offset);
+ out->size = le32toh(frag->size);
+ out->pad0 = le32toh(frag->pad0);
+ return 0;
+}
+
+int sqfs_frag_table_append(sqfs_frag_table_t *tbl, sqfs_u64 location,
+ sqfs_u32 size, sqfs_u32 *index)
+{
+ sqfs_fragment_t frag;
+
+ if (index != NULL)
+ *index = tbl->table.used;
+
+ memset(&frag, 0, sizeof(frag));
+ frag.start_offset = htole64(location);
+ frag.size = htole32(size);
+
+ return array_append(&tbl->table, &frag);
+}
+
+int sqfs_frag_table_set(sqfs_frag_table_t *tbl, sqfs_u32 index,
+ sqfs_u64 location, sqfs_u32 size)
+{
+ sqfs_fragment_t frag;
+
+ memset(&frag, 0, sizeof(frag));
+ frag.start_offset = htole64(location);
+ frag.size = htole32(size);
+
+ return array_set(&tbl->table, index, &frag);
+}
+
+size_t sqfs_frag_table_get_size(sqfs_frag_table_t *tbl)
+{
+ return tbl->table.used;
+}
diff --git a/lib/sqfs/src/id_table.c b/lib/sqfs/src/id_table.c
new file mode 100644
index 0000000..ec3fdfe
--- /dev/null
+++ b/lib/sqfs/src/id_table.c
@@ -0,0 +1,162 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * id_table.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/id_table.h"
+#include "sqfs/super.h"
+#include "sqfs/table.h"
+#include "sqfs/error.h"
+#include "compat.h"
+#include "util/array.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+struct sqfs_id_table_t {
+ sqfs_object_t base;
+
+ array_t ids;
+};
+
+static void id_table_destroy(sqfs_object_t *obj)
+{
+ sqfs_id_table_t *tbl = (sqfs_id_table_t *)obj;
+
+ array_cleanup(&tbl->ids);
+ free(tbl);
+}
+
+static sqfs_object_t *id_table_copy(const sqfs_object_t *obj)
+{
+ const sqfs_id_table_t *tbl = (const sqfs_id_table_t *)obj;
+ sqfs_id_table_t *copy = calloc(1, sizeof(*copy));
+
+ if (copy == NULL)
+ return NULL;
+
+ if (array_init_copy(&copy->ids, &tbl->ids) != 0) {
+ free(copy);
+ return NULL;
+ }
+
+ return (sqfs_object_t *)copy;
+}
+
+sqfs_id_table_t *sqfs_id_table_create(sqfs_u32 flags)
+{
+ sqfs_id_table_t *tbl;
+
+ if (flags != 0)
+ return NULL;
+
+ tbl = calloc(1, sizeof(sqfs_id_table_t));
+
+ if (tbl != NULL) {
+ array_init(&tbl->ids, sizeof(sqfs_u32), 0);
+ sqfs_object_init(tbl, id_table_destroy, id_table_copy);
+ }
+
+ return tbl;
+}
+
+int sqfs_id_table_id_to_index(sqfs_id_table_t *tbl, sqfs_u32 id, sqfs_u16 *out)
+{
+ size_t i;
+
+ for (i = 0; i < tbl->ids.used; ++i) {
+ if (((sqfs_u32 *)tbl->ids.data)[i] == id) {
+ *out = i;
+ return 0;
+ }
+ }
+
+ if (tbl->ids.used == 0x10000)
+ return SQFS_ERROR_OVERFLOW;
+
+ *out = tbl->ids.used;
+ return array_append(&tbl->ids, &id);
+}
+
+int sqfs_id_table_index_to_id(const sqfs_id_table_t *tbl, sqfs_u16 index,
+ sqfs_u32 *out)
+{
+ if (index >= tbl->ids.used)
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ *out = ((sqfs_u32 *)tbl->ids.data)[index];
+ return 0;
+}
+
+int sqfs_id_table_read(sqfs_id_table_t *tbl, sqfs_file_t *file,
+ const sqfs_super_t *super, sqfs_compressor_t *cmp)
+{
+ sqfs_u64 upper_limit, lower_limit;
+ void *raw_ids;
+ size_t i;
+ int ret;
+
+ if (!super->id_count || super->id_table_start >= super->bytes_used)
+ return SQFS_ERROR_CORRUPTED;
+
+ upper_limit = super->id_table_start;
+ lower_limit = super->directory_table_start;
+
+ if (super->fragment_table_start > lower_limit &&
+ super->fragment_table_start < upper_limit) {
+ lower_limit = super->fragment_table_start;
+ }
+
+ if (super->export_table_start > lower_limit &&
+ super->export_table_start < upper_limit) {
+ lower_limit = super->export_table_start;
+ }
+
+ array_cleanup(&tbl->ids);
+ tbl->ids.size = sizeof(sqfs_u32);
+
+ ret = sqfs_read_table(file, cmp, super->id_count * sizeof(sqfs_u32),
+ super->id_table_start, lower_limit,
+ upper_limit, &raw_ids);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < super->id_count; ++i)
+ ((sqfs_u32 *)raw_ids)[i] = le32toh(((sqfs_u32 *)raw_ids)[i]);
+
+ tbl->ids.data = raw_ids;
+ tbl->ids.used = super->id_count;
+ tbl->ids.count = super->id_count;
+ return 0;
+}
+
+int sqfs_id_table_write(sqfs_id_table_t *tbl, sqfs_file_t *file,
+ sqfs_super_t *super, sqfs_compressor_t *cmp)
+{
+ sqfs_u64 start;
+ size_t i;
+ int ret;
+
+ for (i = 0; i < tbl->ids.used; ++i) {
+ ((sqfs_u32 *)tbl->ids.data)[i] =
+ htole32(((sqfs_u32 *)tbl->ids.data)[i]);
+ }
+
+ super->id_count = tbl->ids.used;
+
+ ret = sqfs_write_table(file, cmp, tbl->ids.data,
+ sizeof(sqfs_u32) * tbl->ids.used, &start);
+
+ super->id_table_start = start;
+
+ for (i = 0; i < tbl->ids.used; ++i) {
+ ((sqfs_u32 *)tbl->ids.data)[i] =
+ le32toh(((sqfs_u32 *)tbl->ids.data)[i]);
+ }
+
+ return ret;
+}
diff --git a/lib/sqfs/src/inode.c b/lib/sqfs/src/inode.c
new file mode 100644
index 0000000..ce51cf5
--- /dev/null
+++ b/lib/sqfs/src/inode.c
@@ -0,0 +1,380 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * inode.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/inode.h"
+#include "sqfs/error.h"
+#include "sqfs/dir.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "util/util.h"
+
+static int inverse_type[] = {
+ [SQFS_INODE_DIR] = SQFS_INODE_EXT_DIR,
+ [SQFS_INODE_FILE] = SQFS_INODE_EXT_FILE,
+ [SQFS_INODE_SLINK] = SQFS_INODE_EXT_SLINK,
+ [SQFS_INODE_BDEV] = SQFS_INODE_EXT_BDEV,
+ [SQFS_INODE_CDEV] = SQFS_INODE_EXT_CDEV,
+ [SQFS_INODE_FIFO] = SQFS_INODE_EXT_FIFO,
+ [SQFS_INODE_SOCKET] = SQFS_INODE_EXT_SOCKET,
+ [SQFS_INODE_EXT_DIR] = SQFS_INODE_DIR,
+ [SQFS_INODE_EXT_FILE] = SQFS_INODE_FILE,
+ [SQFS_INODE_EXT_SLINK] = SQFS_INODE_SLINK,
+ [SQFS_INODE_EXT_BDEV] = SQFS_INODE_BDEV,
+ [SQFS_INODE_EXT_CDEV] = SQFS_INODE_CDEV,
+ [SQFS_INODE_EXT_FIFO] = SQFS_INODE_FIFO,
+ [SQFS_INODE_EXT_SOCKET] = SQFS_INODE_SOCKET,
+};
+
+int sqfs_inode_get_xattr_index(const sqfs_inode_generic_t *inode,
+ sqfs_u32 *out)
+{
+ switch (inode->base.type) {
+ case SQFS_INODE_DIR:
+ case SQFS_INODE_FILE:
+ case SQFS_INODE_SLINK:
+ case SQFS_INODE_BDEV:
+ case SQFS_INODE_CDEV:
+ case SQFS_INODE_FIFO:
+ case SQFS_INODE_SOCKET:
+ *out = 0xFFFFFFFF;
+ break;
+ case SQFS_INODE_EXT_DIR:
+ *out = inode->data.dir_ext.xattr_idx;
+ break;
+ case SQFS_INODE_EXT_FILE:
+ *out = inode->data.file_ext.xattr_idx;
+ break;
+ case SQFS_INODE_EXT_SLINK:
+ *out = inode->data.slink_ext.xattr_idx;
+ break;
+ case SQFS_INODE_EXT_BDEV:
+ case SQFS_INODE_EXT_CDEV:
+ *out = inode->data.dev_ext.xattr_idx;
+ break;
+ case SQFS_INODE_EXT_FIFO:
+ case SQFS_INODE_EXT_SOCKET:
+ *out = inode->data.ipc_ext.xattr_idx;
+ break;
+ default:
+ return SQFS_ERROR_CORRUPTED;
+ }
+
+ return 0;
+}
+
+int sqfs_inode_set_xattr_index(sqfs_inode_generic_t *inode, sqfs_u32 index)
+{
+ int err;
+
+ if (index != 0xFFFFFFFF) {
+ err = sqfs_inode_make_extended(inode);
+ if (err)
+ return err;
+ }
+
+ switch (inode->base.type) {
+ case SQFS_INODE_DIR:
+ case SQFS_INODE_FILE:
+ case SQFS_INODE_SLINK:
+ case SQFS_INODE_BDEV:
+ case SQFS_INODE_CDEV:
+ case SQFS_INODE_FIFO:
+ case SQFS_INODE_SOCKET:
+ break;
+ case SQFS_INODE_EXT_DIR:
+ inode->data.dir_ext.xattr_idx = index;
+ break;
+ case SQFS_INODE_EXT_FILE:
+ inode->data.file_ext.xattr_idx = index;
+ break;
+ case SQFS_INODE_EXT_SLINK:
+ inode->data.slink_ext.xattr_idx = index;
+ break;
+ case SQFS_INODE_EXT_BDEV:
+ case SQFS_INODE_EXT_CDEV:
+ inode->data.dev_ext.xattr_idx = index;
+ break;
+ case SQFS_INODE_EXT_FIFO:
+ case SQFS_INODE_EXT_SOCKET:
+ inode->data.ipc_ext.xattr_idx = index;
+ break;
+ default:
+ return SQFS_ERROR_CORRUPTED;
+ }
+
+ return 0;
+}
+
+int sqfs_inode_make_extended(sqfs_inode_generic_t *inode)
+{
+ switch (inode->base.type) {
+ case SQFS_INODE_DIR: {
+ sqfs_inode_dir_ext_t temp = {
+ .nlink = inode->data.dir.nlink,
+ .size = inode->data.dir.size,
+ .start_block = inode->data.dir.start_block,
+ .parent_inode = inode->data.dir.parent_inode,
+ .inodex_count = 0,
+ .offset = inode->data.dir.offset,
+ .xattr_idx = 0xFFFFFFFF,
+ };
+ inode->data.dir_ext = temp;
+ break;
+ }
+ case SQFS_INODE_FILE: {
+ sqfs_inode_file_ext_t temp = {
+ .blocks_start = inode->data.file.blocks_start,
+ .file_size = inode->data.file.file_size,
+ .sparse = 0,
+ .nlink = 1,
+ .fragment_idx = inode->data.file.fragment_index,
+ .fragment_offset = inode->data.file.fragment_offset,
+ .xattr_idx = 0xFFFFFFFF,
+ };
+ inode->data.file_ext = temp;
+ break;
+ }
+ case SQFS_INODE_SLINK:
+ inode->data.slink_ext.xattr_idx = 0xFFFFFFFF;
+ break;
+ case SQFS_INODE_BDEV:
+ case SQFS_INODE_CDEV:
+ inode->data.dev_ext.xattr_idx = 0xFFFFFFFF;
+ break;
+ case SQFS_INODE_FIFO:
+ case SQFS_INODE_SOCKET:
+ inode->data.dev_ext.xattr_idx = 0xFFFFFFFF;
+ break;
+ case SQFS_INODE_EXT_DIR:
+ case SQFS_INODE_EXT_FILE:
+ case SQFS_INODE_EXT_SLINK:
+ case SQFS_INODE_EXT_BDEV:
+ case SQFS_INODE_EXT_CDEV:
+ case SQFS_INODE_EXT_FIFO:
+ case SQFS_INODE_EXT_SOCKET:
+ return 0;
+ default:
+ return SQFS_ERROR_CORRUPTED;
+ }
+
+ inode->base.type = inverse_type[inode->base.type];
+ return 0;
+}
+
+int sqfs_inode_make_basic(sqfs_inode_generic_t *inode)
+{
+ sqfs_u32 xattr;
+ int err;
+
+ err = sqfs_inode_get_xattr_index(inode, &xattr);
+ if (err != 0 || xattr != 0xFFFFFFFF)
+ return err;
+
+ switch (inode->base.type) {
+ case SQFS_INODE_DIR:
+ case SQFS_INODE_FILE:
+ case SQFS_INODE_SLINK:
+ case SQFS_INODE_BDEV:
+ case SQFS_INODE_CDEV:
+ case SQFS_INODE_FIFO:
+ case SQFS_INODE_SOCKET:
+ return 0;
+ case SQFS_INODE_EXT_DIR: {
+ sqfs_inode_dir_t temp = {
+ .start_block = inode->data.dir_ext.start_block,
+ .nlink = inode->data.dir_ext.nlink,
+ .size = inode->data.dir_ext.size,
+ .offset = inode->data.dir_ext.offset,
+ .parent_inode = inode->data.dir_ext.parent_inode,
+ };
+
+ if (inode->data.dir_ext.size > 0x0FFFF)
+ return 0;
+
+ inode->data.dir = temp;
+ break;
+ }
+ case SQFS_INODE_EXT_FILE: {
+ sqfs_inode_file_t temp = {
+ .blocks_start = inode->data.file_ext.blocks_start,
+ .fragment_index = inode->data.file_ext.fragment_idx,
+ .fragment_offset = inode->data.file_ext.fragment_offset,
+ .file_size = inode->data.file_ext.file_size,
+ };
+
+ if (inode->data.file_ext.blocks_start > 0x0FFFFFFFFUL)
+ return 0;
+ if (inode->data.file_ext.file_size > 0x0FFFFFFFFUL)
+ return 0;
+ if (inode->data.file_ext.sparse > 0)
+ return 0;
+ if (inode->data.file_ext.nlink > 1)
+ return 0;
+
+ inode->data.file = temp;
+ break;
+ }
+ case SQFS_INODE_EXT_SLINK:
+ case SQFS_INODE_EXT_BDEV:
+ case SQFS_INODE_EXT_CDEV:
+ case SQFS_INODE_EXT_FIFO:
+ case SQFS_INODE_EXT_SOCKET:
+ break;
+ default:
+ return SQFS_ERROR_CORRUPTED;
+ }
+
+ inode->base.type = inverse_type[inode->base.type];
+ return 0;
+}
+
+int sqfs_inode_set_file_size(sqfs_inode_generic_t *inode, sqfs_u64 size)
+{
+ if (inode->base.type == SQFS_INODE_EXT_FILE) {
+ inode->data.file_ext.file_size = size;
+
+ if (size < 0x0FFFFFFFFUL)
+ sqfs_inode_make_basic(inode);
+ } else if (inode->base.type == SQFS_INODE_FILE) {
+ if (size > 0x0FFFFFFFFUL) {
+ sqfs_inode_make_extended(inode);
+ inode->data.file_ext.file_size = size;
+ } else {
+ inode->data.file.file_size = size;
+ }
+ } else {
+ return SQFS_ERROR_NOT_FILE;
+ }
+
+ return 0;
+}
+
+int sqfs_inode_set_frag_location(sqfs_inode_generic_t *inode,
+ sqfs_u32 index, sqfs_u32 offset)
+{
+ if (inode->base.type == SQFS_INODE_EXT_FILE) {
+ inode->data.file_ext.fragment_idx = index;
+ inode->data.file_ext.fragment_offset = offset;
+ } else if (inode->base.type == SQFS_INODE_FILE) {
+ inode->data.file.fragment_index = index;
+ inode->data.file.fragment_offset = offset;
+ } else {
+ return SQFS_ERROR_NOT_FILE;
+ }
+
+ return 0;
+}
+
+int sqfs_inode_set_file_block_start(sqfs_inode_generic_t *inode,
+ sqfs_u64 location)
+{
+ if (inode->base.type == SQFS_INODE_EXT_FILE) {
+ inode->data.file_ext.blocks_start = location;
+
+ if (location < 0x0FFFFFFFFUL)
+ sqfs_inode_make_basic(inode);
+ } else if (inode->base.type == SQFS_INODE_FILE) {
+ if (location > 0x0FFFFFFFFUL) {
+ sqfs_inode_make_extended(inode);
+ inode->data.file_ext.blocks_start = location;
+ } else {
+ inode->data.file.blocks_start = location;
+ }
+ } else {
+ return SQFS_ERROR_NOT_FILE;
+ }
+
+ return 0;
+}
+
+int sqfs_inode_get_file_size(const sqfs_inode_generic_t *inode, sqfs_u64 *size)
+{
+ if (inode->base.type == SQFS_INODE_EXT_FILE) {
+ *size = inode->data.file_ext.file_size;
+ } else if (inode->base.type == SQFS_INODE_FILE) {
+ *size = inode->data.file.file_size;
+ } else {
+ return SQFS_ERROR_NOT_FILE;
+ }
+
+ return 0;
+}
+
+int sqfs_inode_get_frag_location(const sqfs_inode_generic_t *inode,
+ sqfs_u32 *index, sqfs_u32 *offset)
+{
+ if (inode->base.type == SQFS_INODE_EXT_FILE) {
+ *index = inode->data.file_ext.fragment_idx;
+ *offset = inode->data.file_ext.fragment_offset;
+ } else if (inode->base.type == SQFS_INODE_FILE) {
+ *index = inode->data.file.fragment_index;
+ *offset = inode->data.file.fragment_offset;
+ } else {
+ return SQFS_ERROR_NOT_FILE;
+ }
+
+ return 0;
+}
+
+int sqfs_inode_get_file_block_start(const sqfs_inode_generic_t *inode,
+ sqfs_u64 *location)
+{
+ if (inode->base.type == SQFS_INODE_EXT_FILE) {
+ *location = inode->data.file_ext.blocks_start;
+ } else if (inode->base.type == SQFS_INODE_FILE) {
+ *location = inode->data.file.blocks_start;
+ } else {
+ return SQFS_ERROR_NOT_FILE;
+ }
+
+ return 0;
+}
+
+int sqfs_inode_unpack_dir_index_entry(const sqfs_inode_generic_t *inode,
+ sqfs_dir_index_t **out,
+ size_t index)
+{
+ sqfs_dir_index_t ent;
+ const char *ptr;
+ size_t offset;
+
+ if (inode->base.type != SQFS_INODE_EXT_DIR) {
+ if (inode->base.type == SQFS_INODE_DIR)
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ return SQFS_ERROR_NOT_DIR;
+ }
+
+ offset = 0;
+ ptr = (const char *)inode->extra;
+
+ for (;;) {
+ if (offset >= inode->payload_bytes_used)
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ if (index == 0)
+ break;
+
+ memcpy(&ent, ptr + offset, sizeof(ent));
+ offset += sizeof(ent) + ent.size + 1;
+ index -= 1;
+ }
+
+ memcpy(&ent, ptr + offset, sizeof(ent));
+
+ *out = alloc_flex(sizeof(ent), 1, ent.size + 2);
+ if (*out == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ memcpy(*out, &ent, sizeof(ent));
+ memcpy((*out)->name, ptr + offset + sizeof(ent), ent.size + 1);
+ return 0;
+}
diff --git a/lib/sqfs/src/meta_reader.c b/lib/sqfs/src/meta_reader.c
new file mode 100644
index 0000000..e431d40
--- /dev/null
+++ b/lib/sqfs/src/meta_reader.c
@@ -0,0 +1,191 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * meta_reader.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/meta_reader.h"
+#include "sqfs/compressor.h"
+#include "sqfs/error.h"
+#include "sqfs/block.h"
+#include "sqfs/io.h"
+#include "util/util.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+struct sqfs_meta_reader_t {
+ sqfs_object_t base;
+
+ sqfs_u64 start;
+ sqfs_u64 limit;
+ size_t data_used;
+
+ /* The location of the current block in the image */
+ sqfs_u64 block_offset;
+
+ /* The location of the next block after the current one */
+ sqfs_u64 next_block;
+
+ /* A byte offset into the uncompressed data of the current block */
+ size_t offset;
+
+ /* The underlying file descriptor to read from */
+ sqfs_file_t *file;
+
+ /* A pointer to the compressor to use for extracting data */
+ sqfs_compressor_t *cmp;
+
+ /* The raw data read from the input file */
+ sqfs_u8 data[SQFS_META_BLOCK_SIZE];
+
+ /* The uncompressed data read from the input file */
+ sqfs_u8 scratch[SQFS_META_BLOCK_SIZE];
+};
+
+static void meta_reader_destroy(sqfs_object_t *m)
+{
+ sqfs_meta_reader_t *mr = (sqfs_meta_reader_t *)m;
+
+ sqfs_drop(mr->file);
+ sqfs_drop(mr->cmp);
+ free(m);
+}
+
+static sqfs_object_t *meta_reader_copy(const sqfs_object_t *obj)
+{
+ const sqfs_meta_reader_t *m = (const sqfs_meta_reader_t *)obj;
+ sqfs_meta_reader_t *copy = malloc(sizeof(*copy));
+
+ if (copy != NULL) {
+ memcpy(copy, m, sizeof(*m));
+
+ /* duplicate references */
+ copy->cmp = sqfs_grab(copy->cmp);
+ copy->file = sqfs_grab(copy->file);
+ }
+
+ return (sqfs_object_t *)copy;
+}
+
+sqfs_meta_reader_t *sqfs_meta_reader_create(sqfs_file_t *file,
+ sqfs_compressor_t *cmp,
+ sqfs_u64 start, sqfs_u64 limit)
+{
+ sqfs_meta_reader_t *m = calloc(1, sizeof(*m));
+
+ if (m == NULL)
+ return NULL;
+
+ sqfs_object_init(m, meta_reader_destroy, meta_reader_copy);
+
+ m->block_offset = 0xFFFFFFFFFFFFFFFFUL;
+ m->start = start;
+ m->limit = limit;
+ m->file = sqfs_grab(file);
+ m->cmp = sqfs_grab(cmp);
+ return m;
+}
+
+int sqfs_meta_reader_seek(sqfs_meta_reader_t *m, sqfs_u64 block_start,
+ size_t offset)
+{
+ bool compressed;
+ sqfs_u16 header;
+ sqfs_u32 size;
+ sqfs_s32 ret;
+ int err;
+
+ if (block_start < m->start || block_start >= m->limit)
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ if (block_start == m->block_offset) {
+ if (offset >= m->data_used)
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ m->offset = offset;
+ return 0;
+ }
+
+ err = m->file->read_at(m->file, block_start, &header, 2);
+ if (err)
+ return err;
+
+ header = le16toh(header);
+ compressed = (header & 0x8000) == 0;
+ size = header & 0x7FFF;
+
+ if (size > sizeof(m->data))
+ return SQFS_ERROR_CORRUPTED;
+
+ if ((block_start + 2 + size) > m->limit)
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ err = m->file->read_at(m->file, block_start + 2, m->data, size);
+ if (err)
+ return err;
+
+ if (compressed) {
+ ret = m->cmp->do_block(m->cmp, m->data, size,
+ m->scratch, sizeof(m->scratch));
+
+ if (ret < 0)
+ return ret;
+
+ memcpy(m->data, m->scratch, ret);
+ m->data_used = ret;
+ } else {
+ m->data_used = size;
+ }
+
+ if (offset >= m->data_used)
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ m->block_offset = block_start;
+ m->next_block = block_start + size + 2;
+ m->offset = offset;
+ return 0;
+}
+
+void sqfs_meta_reader_get_position(const sqfs_meta_reader_t *m,
+ sqfs_u64 *block_start, size_t *offset)
+{
+ if (m->offset == m->data_used) {
+ *block_start = m->next_block;
+ *offset = 0;
+ } else {
+ *block_start = m->block_offset;
+ *offset = m->offset;
+ }
+}
+
+int sqfs_meta_reader_read(sqfs_meta_reader_t *m, void *data, size_t size)
+{
+ size_t diff;
+ int ret;
+
+ while (size != 0) {
+ diff = m->data_used - m->offset;
+
+ if (diff == 0) {
+ ret = sqfs_meta_reader_seek(m, m->next_block, 0);
+ if (ret)
+ return ret;
+ diff = m->data_used;
+ }
+
+ if (diff > size)
+ diff = size;
+
+ memcpy(data, m->data + m->offset, diff);
+
+ m->offset += diff;
+ data = (char *)data + diff;
+ size -= diff;
+ }
+
+ return 0;
+}
diff --git a/lib/sqfs/src/meta_writer.c b/lib/sqfs/src/meta_writer.c
new file mode 100644
index 0000000..bf3f426
--- /dev/null
+++ b/lib/sqfs/src/meta_writer.c
@@ -0,0 +1,215 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * meta_writer.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/meta_writer.h"
+#include "sqfs/compressor.h"
+#include "sqfs/error.h"
+#include "sqfs/block.h"
+#include "sqfs/io.h"
+#include "util/util.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+typedef struct meta_block_t {
+ struct meta_block_t *next;
+
+ /* possibly compressed data with 2 byte header */
+ sqfs_u8 data[SQFS_META_BLOCK_SIZE + 2];
+} meta_block_t;
+
+struct sqfs_meta_writer_t {
+ sqfs_object_t base;
+
+ /* A byte offset into the uncompressed data of the current block */
+ size_t offset;
+
+ /* The location of the current block in the file */
+ size_t block_offset;
+
+ /* The underlying file descriptor to write to */
+ sqfs_file_t *file;
+
+ /* A pointer to the compressor to use for compressing the data */
+ sqfs_compressor_t *cmp;
+
+ /* The raw data chunk that data is appended to */
+ sqfs_u8 data[SQFS_META_BLOCK_SIZE];
+
+ sqfs_u32 flags;
+ meta_block_t *list;
+ meta_block_t *list_end;
+};
+
+static int write_block(sqfs_file_t *file, meta_block_t *outblk)
+{
+ sqfs_u16 header;
+ size_t count;
+ sqfs_u64 off;
+
+ memcpy(&header, outblk->data, sizeof(header));
+ count = le16toh(header) & 0x7FFF;
+ off = file->get_size(file);
+
+ return file->write_at(file, off, outblk->data, count + 2);
+}
+
+static void meta_writer_destroy(sqfs_object_t *obj)
+{
+ sqfs_meta_writer_t *m = (sqfs_meta_writer_t *)obj;
+ meta_block_t *blk;
+
+ while (m->list != NULL) {
+ blk = m->list;
+ m->list = blk->next;
+ free(blk);
+ }
+
+ sqfs_drop(m->file);
+ sqfs_drop(m->cmp);
+ free(m);
+}
+
+sqfs_meta_writer_t *sqfs_meta_writer_create(sqfs_file_t *file,
+ sqfs_compressor_t *cmp,
+ sqfs_u32 flags)
+{
+ sqfs_meta_writer_t *m;
+
+ if (flags & ~SQFS_META_WRITER_ALL_FLAGS)
+ return NULL;
+
+ m = calloc(1, sizeof(*m));
+ if (m == NULL)
+ return NULL;
+
+ sqfs_object_init(m, meta_writer_destroy, NULL);
+
+ m->cmp = sqfs_grab(cmp);
+ m->file = sqfs_grab(file);
+ m->flags = flags;
+ return m;
+}
+
+int sqfs_meta_writer_flush(sqfs_meta_writer_t *m)
+{
+ meta_block_t *outblk;
+ sqfs_u16 header;
+ sqfs_u32 count;
+ sqfs_s32 ret;
+
+ if (m->offset == 0)
+ return 0;
+
+ outblk = calloc(1, sizeof(*outblk));
+ if (outblk == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ ret = m->cmp->do_block(m->cmp, m->data, m->offset,
+ outblk->data + 2, sizeof(outblk->data) - 2);
+ if (ret < 0) {
+ free(outblk);
+ return ret;
+ }
+
+ if (ret > 0) {
+ header = htole16(ret);
+ count = ret + 2;
+ } else {
+ header = htole16(m->offset | 0x8000);
+ memcpy(outblk->data + 2, m->data, m->offset);
+ count = m->offset + 2;
+ }
+
+ memcpy(outblk->data, &header, sizeof(header));
+
+ ret = 0;
+
+ if (m->flags & SQFS_META_WRITER_KEEP_IN_MEMORY) {
+ if (m->list == NULL) {
+ m->list = outblk;
+ } else {
+ m->list_end->next = outblk;
+ }
+ m->list_end = outblk;
+ } else {
+ ret = write_block(m->file, outblk);
+ free(outblk);
+ }
+
+ memset(m->data, 0, sizeof(m->data));
+ m->offset = 0;
+ m->block_offset += count;
+ return ret;
+}
+
+int sqfs_meta_writer_append(sqfs_meta_writer_t *m, const void *data,
+ size_t size)
+{
+ size_t diff;
+ int ret;
+
+ while (size != 0) {
+ diff = sizeof(m->data) - m->offset;
+
+ if (diff == 0) {
+ ret = sqfs_meta_writer_flush(m);
+ if (ret)
+ return ret;
+ diff = sizeof(m->data);
+ }
+
+ if (diff > size)
+ diff = size;
+
+ memcpy(m->data + m->offset, data, diff);
+ m->offset += diff;
+ size -= diff;
+ data = (const char *)data + diff;
+ }
+
+ if (m->offset == sizeof(m->data))
+ return sqfs_meta_writer_flush(m);
+
+ return 0;
+}
+
+void sqfs_meta_writer_get_position(const sqfs_meta_writer_t *m,
+ sqfs_u64 *block_start,
+ sqfs_u32 *offset)
+{
+ *block_start = m->block_offset;
+ *offset = m->offset;
+}
+
+void sqfs_meta_writer_reset(sqfs_meta_writer_t *m)
+{
+ m->block_offset = 0;
+ m->offset = 0;
+}
+
+int sqfs_meta_write_write_to_file(sqfs_meta_writer_t *m)
+{
+ meta_block_t *blk;
+ int ret;
+
+ while (m->list != NULL) {
+ blk = m->list;
+
+ ret = write_block(m->file, blk);
+ if (ret)
+ return ret;
+
+ m->list = blk->next;
+ free(blk);
+ }
+
+ m->list_end = NULL;
+ return 0;
+}
diff --git a/lib/sqfs/src/misc.c b/lib/sqfs/src/misc.c
new file mode 100644
index 0000000..74a4203
--- /dev/null
+++ b/lib/sqfs/src/misc.c
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * misc.c
+ *
+ * Copyright (C) 2021 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/predef.h"
+
+#include <stdlib.h>
+
+void sqfs_free(void *ptr)
+{
+ free(ptr);
+}
diff --git a/lib/sqfs/src/read_inode.c b/lib/sqfs/src/read_inode.c
new file mode 100644
index 0000000..12bef48
--- /dev/null
+++ b/lib/sqfs/src/read_inode.c
@@ -0,0 +1,424 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * read_inode.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/meta_reader.h"
+#include "sqfs/error.h"
+#include "sqfs/super.h"
+#include "sqfs/inode.h"
+#include "sqfs/dir.h"
+#include "util/util.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#define SWAB16(x) x = le16toh(x)
+#define SWAB32(x) x = le32toh(x)
+#define SWAB64(x) x = le64toh(x)
+
+static int set_mode(sqfs_inode_t *inode)
+{
+ inode->mode &= ~S_IFMT;
+
+ switch (inode->type) {
+ case SQFS_INODE_SOCKET:
+ case SQFS_INODE_EXT_SOCKET:
+ inode->mode |= S_IFSOCK;
+ break;
+ case SQFS_INODE_SLINK:
+ case SQFS_INODE_EXT_SLINK:
+ inode->mode |= S_IFLNK;
+ break;
+ case SQFS_INODE_FILE:
+ case SQFS_INODE_EXT_FILE:
+ inode->mode |= S_IFREG;
+ break;
+ case SQFS_INODE_BDEV:
+ case SQFS_INODE_EXT_BDEV:
+ inode->mode |= S_IFBLK;
+ break;
+ case SQFS_INODE_DIR:
+ case SQFS_INODE_EXT_DIR:
+ inode->mode |= S_IFDIR;
+ break;
+ case SQFS_INODE_CDEV:
+ case SQFS_INODE_EXT_CDEV:
+ inode->mode |= S_IFCHR;
+ break;
+ case SQFS_INODE_FIFO:
+ case SQFS_INODE_EXT_FIFO:
+ inode->mode |= S_IFIFO;
+ break;
+ default:
+ return SQFS_ERROR_UNSUPPORTED;
+ }
+
+ return 0;
+}
+
+static sqfs_u64 get_block_count(sqfs_u64 size, sqfs_u64 block_size,
+ sqfs_u32 frag_index, sqfs_u32 frag_offset)
+{
+ sqfs_u64 count = size / block_size;
+
+ if ((size % block_size) != 0 &&
+ (frag_index == 0xFFFFFFFF || frag_offset == 0xFFFFFFFF)) {
+ ++count;
+ }
+
+ return count;
+}
+
+static int read_inode_file(sqfs_meta_reader_t *ir, sqfs_inode_t *base,
+ size_t block_size, sqfs_inode_generic_t **result)
+{
+ sqfs_inode_generic_t *out;
+ sqfs_inode_file_t file;
+ sqfs_u64 i, count;
+ int err;
+
+ err = sqfs_meta_reader_read(ir, &file, sizeof(file));
+ if (err)
+ return err;
+
+ SWAB32(file.blocks_start);
+ SWAB32(file.fragment_index);
+ SWAB32(file.fragment_offset);
+ SWAB32(file.file_size);
+
+ count = get_block_count(file.file_size, block_size,
+ file.fragment_index, file.fragment_offset);
+
+ out = alloc_flex(sizeof(*out), sizeof(sqfs_u32), count);
+ if (out == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ out->base = *base;
+ out->data.file = file;
+ out->payload_bytes_available = count * sizeof(sqfs_u32);
+ out->payload_bytes_used = count * sizeof(sqfs_u32);
+
+ err = sqfs_meta_reader_read(ir, out->extra, count * sizeof(sqfs_u32));
+ if (err) {
+ free(out);
+ return err;
+ }
+
+ for (i = 0; i < count; ++i)
+ SWAB32(out->extra[i]);
+
+ *result = out;
+ return 0;
+}
+
+static int read_inode_file_ext(sqfs_meta_reader_t *ir, sqfs_inode_t *base,
+ size_t block_size, sqfs_inode_generic_t **result)
+{
+ sqfs_inode_file_ext_t file;
+ sqfs_inode_generic_t *out;
+ sqfs_u64 i, count;
+ int err;
+
+ err = sqfs_meta_reader_read(ir, &file, sizeof(file));
+ if (err)
+ return err;
+
+ SWAB64(file.blocks_start);
+ SWAB64(file.file_size);
+ SWAB64(file.sparse);
+ SWAB32(file.nlink);
+ SWAB32(file.fragment_idx);
+ SWAB32(file.fragment_offset);
+ SWAB32(file.xattr_idx);
+
+ count = get_block_count(file.file_size, block_size,
+ file.fragment_idx, file.fragment_offset);
+
+ out = alloc_flex(sizeof(*out), sizeof(sqfs_u32), count);
+ if (out == NULL) {
+ return errno == EOVERFLOW ? SQFS_ERROR_OVERFLOW :
+ SQFS_ERROR_ALLOC;
+ }
+
+ out->base = *base;
+ out->data.file_ext = file;
+ out->payload_bytes_available = count * sizeof(sqfs_u32);
+ out->payload_bytes_used = count * sizeof(sqfs_u32);
+
+ err = sqfs_meta_reader_read(ir, out->extra, count * sizeof(sqfs_u32));
+ if (err) {
+ free(out);
+ return err;
+ }
+
+ for (i = 0; i < count; ++i)
+ SWAB32(out->extra[i]);
+
+ *result = out;
+ return 0;
+}
+
+static int read_inode_slink(sqfs_meta_reader_t *ir, sqfs_inode_t *base,
+ sqfs_inode_generic_t **result)
+{
+ sqfs_inode_generic_t *out;
+ sqfs_inode_slink_t slink;
+ size_t size;
+ int err;
+
+ err = sqfs_meta_reader_read(ir, &slink, sizeof(slink));
+ if (err)
+ return err;
+
+ SWAB32(slink.nlink);
+ SWAB32(slink.target_size);
+
+ if (SZ_ADD_OV(slink.target_size, 1, &size) ||
+ SZ_ADD_OV(sizeof(*out), size, &size)) {
+ return SQFS_ERROR_OVERFLOW;
+ }
+
+ out = calloc(1, size);
+ if (out == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ out->payload_bytes_available = size - sizeof(*out);
+ out->payload_bytes_used = size - sizeof(*out) - 1;
+ out->base = *base;
+ out->data.slink = slink;
+
+ err = sqfs_meta_reader_read(ir, (void *)out->extra, slink.target_size);
+ if (err) {
+ free(out);
+ return err;
+ }
+
+ *result = out;
+ return 0;
+}
+
+static int read_inode_slink_ext(sqfs_meta_reader_t *ir, sqfs_inode_t *base,
+ sqfs_inode_generic_t **result)
+{
+ sqfs_u32 xattr;
+ int err;
+
+ err = read_inode_slink(ir, base, result);
+ if (err)
+ return err;
+
+ err = sqfs_meta_reader_read(ir, &xattr, sizeof(xattr));
+ if (err) {
+ free(*result);
+ return err;
+ }
+
+ (*result)->data.slink_ext.xattr_idx = le32toh(xattr);
+ return 0;
+}
+
+static int read_inode_dir_ext(sqfs_meta_reader_t *ir, sqfs_inode_t *base,
+ sqfs_inode_generic_t **result)
+{
+ size_t i, new_sz, index_max, index_used;
+ sqfs_inode_generic_t *out, *new;
+ sqfs_inode_dir_ext_t dir;
+ sqfs_dir_index_t ent;
+ int err;
+
+ err = sqfs_meta_reader_read(ir, &dir, sizeof(dir));
+ if (err)
+ return err;
+
+ SWAB32(dir.nlink);
+ SWAB32(dir.size);
+ SWAB32(dir.start_block);
+ SWAB32(dir.parent_inode);
+ SWAB16(dir.inodex_count);
+ SWAB16(dir.offset);
+ SWAB32(dir.xattr_idx);
+
+ index_max = dir.size ? 128 : 0;
+ index_used = 0;
+
+ out = alloc_flex(sizeof(*out), 1, index_max);
+ if (out == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ out->base = *base;
+ out->data.dir_ext = dir;
+
+ if (dir.size == 0) {
+ *result = out;
+ return 0;
+ }
+
+ for (i = 0; i < dir.inodex_count; ++i) {
+ err = sqfs_meta_reader_read(ir, &ent, sizeof(ent));
+ if (err) {
+ free(out);
+ return err;
+ }
+
+ SWAB32(ent.start_block);
+ SWAB32(ent.index);
+ SWAB32(ent.size);
+
+ new_sz = index_max;
+ while (sizeof(ent) + ent.size + 1 > new_sz - index_used) {
+ if (SZ_MUL_OV(new_sz, 2, &new_sz)) {
+ free(out);
+ return SQFS_ERROR_OVERFLOW;
+ }
+ }
+
+ if (new_sz > index_max) {
+ new = realloc(out, sizeof(*out) + new_sz);
+ if (new == NULL) {
+ free(out);
+ return SQFS_ERROR_ALLOC;
+ }
+ out = new;
+ index_max = new_sz;
+ }
+
+ memcpy((char *)out->extra + index_used, &ent, sizeof(ent));
+ index_used += sizeof(ent);
+
+ err = sqfs_meta_reader_read(ir, (char *)out->extra + index_used,
+ ent.size + 1);
+ if (err) {
+ free(out);
+ return err;
+ }
+
+ index_used += ent.size + 1;
+ }
+
+ out->payload_bytes_used = index_used;
+ out->payload_bytes_available = index_used;
+ *result = out;
+ return 0;
+}
+
+int sqfs_meta_reader_read_inode(sqfs_meta_reader_t *ir,
+ const sqfs_super_t *super,
+ sqfs_u64 block_start, size_t offset,
+ sqfs_inode_generic_t **result)
+{
+ sqfs_inode_generic_t *out;
+ sqfs_inode_t inode;
+ int err;
+
+ /* read base inode */
+ block_start += super->inode_table_start;
+
+ err = sqfs_meta_reader_seek(ir, block_start, offset);
+ if (err)
+ return err;
+
+ err = sqfs_meta_reader_read(ir, &inode, sizeof(inode));
+ if (err)
+ return err;
+
+ SWAB16(inode.type);
+ SWAB16(inode.mode);
+ SWAB16(inode.uid_idx);
+ SWAB16(inode.gid_idx);
+ SWAB32(inode.mod_time);
+ SWAB32(inode.inode_number);
+
+ err = set_mode(&inode);
+ if (err)
+ return err;
+
+ /* inode types where the size is variable */
+ switch (inode.type) {
+ case SQFS_INODE_FILE:
+ return read_inode_file(ir, &inode, super->block_size, result);
+ case SQFS_INODE_SLINK:
+ return read_inode_slink(ir, &inode, result);
+ case SQFS_INODE_EXT_FILE:
+ return read_inode_file_ext(ir, &inode, super->block_size,
+ result);
+ case SQFS_INODE_EXT_SLINK:
+ return read_inode_slink_ext(ir, &inode, result);
+ case SQFS_INODE_EXT_DIR:
+ return read_inode_dir_ext(ir, &inode, result);
+ default:
+ break;
+ }
+
+ /* everything else */
+ out = calloc(1, sizeof(*out));
+ if (out == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ out->base = inode;
+
+ switch (inode.type) {
+ case SQFS_INODE_DIR:
+ err = sqfs_meta_reader_read(ir, &out->data.dir,
+ sizeof(out->data.dir));
+ if (err)
+ goto fail_free;
+
+ SWAB32(out->data.dir.start_block);
+ SWAB32(out->data.dir.nlink);
+ SWAB16(out->data.dir.size);
+ SWAB16(out->data.dir.offset);
+ SWAB32(out->data.dir.parent_inode);
+ break;
+ case SQFS_INODE_BDEV:
+ case SQFS_INODE_CDEV:
+ err = sqfs_meta_reader_read(ir, &out->data.dev,
+ sizeof(out->data.dev));
+ if (err)
+ goto fail_free;
+ SWAB32(out->data.dev.nlink);
+ SWAB32(out->data.dev.devno);
+ break;
+ case SQFS_INODE_FIFO:
+ case SQFS_INODE_SOCKET:
+ err = sqfs_meta_reader_read(ir, &out->data.ipc,
+ sizeof(out->data.ipc));
+ if (err)
+ goto fail_free;
+ SWAB32(out->data.ipc.nlink);
+ break;
+ case SQFS_INODE_EXT_BDEV:
+ case SQFS_INODE_EXT_CDEV:
+ err = sqfs_meta_reader_read(ir, &out->data.dev_ext,
+ sizeof(out->data.dev_ext));
+ if (err)
+ goto fail_free;
+ SWAB32(out->data.dev_ext.nlink);
+ SWAB32(out->data.dev_ext.devno);
+ SWAB32(out->data.dev_ext.xattr_idx);
+ break;
+ case SQFS_INODE_EXT_FIFO:
+ case SQFS_INODE_EXT_SOCKET:
+ err = sqfs_meta_reader_read(ir, &out->data.ipc_ext,
+ sizeof(out->data.ipc_ext));
+ if (err)
+ goto fail_free;
+ SWAB32(out->data.ipc_ext.nlink);
+ SWAB32(out->data.ipc_ext.xattr_idx);
+ break;
+ default:
+ err = SQFS_ERROR_UNSUPPORTED;
+ goto fail_free;
+ }
+
+ *result = out;
+ return 0;
+fail_free:
+ free(out);
+ return err;
+}
diff --git a/lib/sqfs/src/read_super.c b/lib/sqfs/src/read_super.c
new file mode 100644
index 0000000..11bc314
--- /dev/null
+++ b/lib/sqfs/src/read_super.c
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * read_super.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/super.h"
+#include "sqfs/error.h"
+#include "sqfs/io.h"
+#include "util/util.h"
+
+#include <string.h>
+
+int sqfs_super_read(sqfs_super_t *super, sqfs_file_t *file)
+{
+ size_t block_size = 0;
+ sqfs_super_t temp;
+ int i, ret;
+
+ ret = file->read_at(file, 0, &temp, sizeof(temp));
+ if (ret)
+ return ret;
+
+ temp.magic = le32toh(temp.magic);
+ temp.inode_count = le32toh(temp.inode_count);
+ temp.modification_time = le32toh(temp.modification_time);
+ temp.block_size = le32toh(temp.block_size);
+ temp.fragment_entry_count = le32toh(temp.fragment_entry_count);
+ temp.compression_id = le16toh(temp.compression_id);
+ temp.block_log = le16toh(temp.block_log);
+ temp.flags = le16toh(temp.flags);
+ temp.id_count = le16toh(temp.id_count);
+ temp.version_major = le16toh(temp.version_major);
+ temp.version_minor = le16toh(temp.version_minor);
+ temp.root_inode_ref = le64toh(temp.root_inode_ref);
+ temp.bytes_used = le64toh(temp.bytes_used);
+ temp.id_table_start = le64toh(temp.id_table_start);
+ temp.xattr_id_table_start = le64toh(temp.xattr_id_table_start);
+ temp.inode_table_start = le64toh(temp.inode_table_start);
+ temp.directory_table_start = le64toh(temp.directory_table_start);
+ temp.fragment_table_start = le64toh(temp.fragment_table_start);
+ temp.export_table_start = le64toh(temp.export_table_start);
+
+ if (temp.magic != SQFS_MAGIC)
+ return SFQS_ERROR_SUPER_MAGIC;
+
+ if ((temp.version_major != SQFS_VERSION_MAJOR) ||
+ (temp.version_minor != SQFS_VERSION_MINOR))
+ return SFQS_ERROR_SUPER_VERSION;
+
+ if ((temp.block_size - 1) & temp.block_size)
+ return SQFS_ERROR_SUPER_BLOCK_SIZE;
+
+ if (temp.block_size < SQFS_MIN_BLOCK_SIZE)
+ return SQFS_ERROR_SUPER_BLOCK_SIZE;
+
+ if (temp.block_size > SQFS_MAX_BLOCK_SIZE)
+ return SQFS_ERROR_SUPER_BLOCK_SIZE;
+
+ if (temp.block_log < 12 || temp.block_log > 20)
+ return SQFS_ERROR_CORRUPTED;
+
+ block_size = 1;
+
+ for (i = 0; i < temp.block_log; ++i)
+ block_size <<= 1;
+
+ if (temp.block_size != block_size)
+ return SQFS_ERROR_CORRUPTED;
+
+ if (temp.compression_id < SQFS_COMP_MIN ||
+ temp.compression_id > SQFS_COMP_MAX)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ if (temp.id_count == 0)
+ return SQFS_ERROR_CORRUPTED;
+
+ memcpy(super, &temp, sizeof(temp));
+ return 0;
+}
diff --git a/lib/sqfs/src/read_table.c b/lib/sqfs/src/read_table.c
new file mode 100644
index 0000000..c6a9bbe
--- /dev/null
+++ b/lib/sqfs/src/read_table.c
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * read_table.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/meta_reader.h"
+#include "sqfs/error.h"
+#include "sqfs/table.h"
+#include "sqfs/block.h"
+#include "sqfs/io.h"
+#include "util/util.h"
+
+#include <stdlib.h>
+
+int sqfs_read_table(sqfs_file_t *file, sqfs_compressor_t *cmp,
+ size_t table_size, sqfs_u64 location, sqfs_u64 lower_limit,
+ sqfs_u64 upper_limit, void **out)
+{
+ size_t diff, block_count, blk_idx = 0;
+ sqfs_u64 start, *locations;
+ sqfs_meta_reader_t *m;
+ void *data, *ptr;
+ int err;
+
+ data = malloc(table_size);
+ if (data == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ /* restore list from image */
+ block_count = table_size / SQFS_META_BLOCK_SIZE;
+
+ if ((table_size % SQFS_META_BLOCK_SIZE) != 0)
+ ++block_count;
+
+ locations = alloc_array(sizeof(sqfs_u64), block_count);
+
+ if (locations == NULL) {
+ err = SQFS_ERROR_ALLOC;
+ goto fail_data;
+ }
+
+ err = file->read_at(file, location, locations,
+ sizeof(sqfs_u64) * block_count);
+ if (err)
+ goto fail_idx;
+
+ /* Read the actual data */
+ m = sqfs_meta_reader_create(file, cmp, lower_limit, upper_limit);
+ if (m == NULL) {
+ err = SQFS_ERROR_ALLOC;
+ goto fail_idx;
+ }
+
+ ptr = data;
+
+ while (table_size > 0) {
+ start = le64toh(locations[blk_idx++]);
+
+ err = sqfs_meta_reader_seek(m, start, 0);
+ if (err)
+ goto fail;
+
+ diff = SQFS_META_BLOCK_SIZE;
+ if (diff > table_size)
+ diff = table_size;
+
+ err = sqfs_meta_reader_read(m, ptr, diff);
+ if (err)
+ goto fail;
+
+ ptr = (char *)ptr + diff;
+ table_size -= diff;
+ }
+
+ sqfs_drop(m);
+ free(locations);
+ *out = data;
+ return 0;
+fail:
+ sqfs_drop(m);
+fail_idx:
+ free(locations);
+fail_data:
+ free(data);
+ *out = NULL;
+ return err;
+}
diff --git a/lib/sqfs/src/readdir.c b/lib/sqfs/src/readdir.c
new file mode 100644
index 0000000..e2dbcd4
--- /dev/null
+++ b/lib/sqfs/src/readdir.c
@@ -0,0 +1,161 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * readdir.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/meta_reader.h"
+#include "sqfs/error.h"
+#include "sqfs/super.h"
+#include "sqfs/inode.h"
+#include "sqfs/dir.h"
+#include "compat.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+int sqfs_meta_reader_read_dir_header(sqfs_meta_reader_t *m,
+ sqfs_dir_header_t *hdr)
+{
+ int err = sqfs_meta_reader_read(m, hdr, sizeof(*hdr));
+ if (err)
+ return err;
+
+ hdr->count = le32toh(hdr->count);
+ hdr->start_block = le32toh(hdr->start_block);
+ hdr->inode_number = le32toh(hdr->inode_number);
+
+ if (hdr->count > (SQFS_MAX_DIR_ENT - 1))
+ return SQFS_ERROR_CORRUPTED;
+
+ return 0;
+}
+
+int sqfs_meta_reader_read_dir_ent(sqfs_meta_reader_t *m,
+ sqfs_dir_entry_t **result)
+{
+ sqfs_dir_entry_t ent, *out;
+ sqfs_u16 *diff_u16;
+ int err;
+
+ err = sqfs_meta_reader_read(m, &ent, sizeof(ent));
+ if (err)
+ return err;
+
+ diff_u16 = (sqfs_u16 *)&ent.inode_diff;
+ *diff_u16 = le16toh(*diff_u16);
+
+ ent.offset = le16toh(ent.offset);
+ ent.type = le16toh(ent.type);
+ ent.size = le16toh(ent.size);
+
+ out = calloc(1, sizeof(*out) + ent.size + 2);
+ if (out == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ *out = ent;
+ err = sqfs_meta_reader_read(m, out->name, ent.size + 1);
+ if (err) {
+ free(out);
+ return err;
+ }
+
+ *result = out;
+ return 0;
+}
+
+int sqfs_readdir_state_init(sqfs_readdir_state_t *s, const sqfs_super_t *super,
+ const sqfs_inode_generic_t *inode)
+{
+ memset(s, 0, sizeof(*s));
+
+ if (inode->base.type == SQFS_INODE_DIR) {
+ s->init.block = inode->data.dir.start_block;
+ s->init.offset = inode->data.dir.offset;
+ s->init.size = inode->data.dir.size;
+ } else if (inode->base.type == SQFS_INODE_EXT_DIR) {
+ s->init.block = inode->data.dir_ext.start_block;
+ s->init.offset = inode->data.dir_ext.offset;
+ s->init.size = inode->data.dir_ext.size;
+ } else {
+ return SQFS_ERROR_NOT_DIR;
+ }
+
+ s->init.block += super->directory_table_start;
+ s->current = s->init;
+ return 0;
+}
+
+int sqfs_meta_reader_readdir(sqfs_meta_reader_t *m, sqfs_readdir_state_t *it,
+ sqfs_dir_entry_t **ent,
+ sqfs_u32 *inum, sqfs_u64 *iref)
+{
+ size_t count;
+ int ret;
+
+ if (it->entries == 0) {
+ sqfs_dir_header_t hdr;
+
+ if (it->current.size <= sizeof(hdr))
+ goto out_eof;
+
+ ret = sqfs_meta_reader_seek(m, it->current.block,
+ it->current.offset);
+ if (ret != 0)
+ return ret;
+
+ ret = sqfs_meta_reader_read_dir_header(m, &hdr);
+ if (ret != 0)
+ return ret;
+
+ sqfs_meta_reader_get_position(m, &it->current.block,
+ &it->current.offset);
+
+ it->current.size -= sizeof(hdr);
+ it->entries = hdr.count + 1;
+ it->inum_base = hdr.inode_number;
+ it->inode_block = hdr.start_block;
+ }
+
+ if (it->current.size <= sizeof(**ent))
+ goto out_eof;
+
+ ret = sqfs_meta_reader_seek(m, it->current.block, it->current.offset);
+ if (ret != 0)
+ return ret;
+
+ ret = sqfs_meta_reader_read_dir_ent(m, ent);
+ if (ret)
+ return ret;
+
+ sqfs_meta_reader_get_position(m, &it->current.block,
+ &it->current.offset);
+
+ it->current.size -= sizeof(**ent);
+ it->entries -= 1;
+
+ count = (*ent)->size + 1;
+
+ if (count >= it->current.size) {
+ it->current.size = 0;
+ } else {
+ it->current.size -= count;
+ }
+
+ if (inum != NULL)
+ *inum = it->inum_base + (*ent)->inode_diff;
+
+ if (iref != NULL) {
+ *iref = (sqfs_u64)it->inode_block << 16UL;
+ *iref |= (*ent)->offset;
+ }
+
+ return 0;
+out_eof:
+ it->current.size = 0;
+ it->entries = 0;
+ return 1;
+}
diff --git a/lib/sqfs/src/super.c b/lib/sqfs/src/super.c
new file mode 100644
index 0000000..470c06a
--- /dev/null
+++ b/lib/sqfs/src/super.c
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * super.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/super.h"
+#include "sqfs/error.h"
+
+#include <string.h>
+
+int sqfs_super_init(sqfs_super_t *super, size_t block_size, sqfs_u32 mtime,
+ SQFS_COMPRESSOR compressor)
+{
+ unsigned int i;
+
+ if (block_size & (block_size - 1))
+ return SQFS_ERROR_SUPER_BLOCK_SIZE;
+
+ if (block_size < SQFS_MIN_BLOCK_SIZE)
+ return SQFS_ERROR_SUPER_BLOCK_SIZE;
+
+ if (block_size > SQFS_MAX_BLOCK_SIZE)
+ return SQFS_ERROR_SUPER_BLOCK_SIZE;
+
+ memset(super, 0, sizeof(*super));
+ super->magic = SQFS_MAGIC;
+ super->modification_time = mtime;
+ super->block_size = block_size;
+ super->compression_id = compressor;
+ super->flags = SQFS_FLAG_NO_FRAGMENTS | SQFS_FLAG_NO_XATTRS;
+ super->flags |= SQFS_FLAG_NO_DUPLICATES;
+ super->version_major = SQFS_VERSION_MAJOR;
+ super->version_minor = SQFS_VERSION_MINOR;
+ super->bytes_used = sizeof(*super);
+ super->id_table_start = 0xFFFFFFFFFFFFFFFFUL;
+ super->xattr_id_table_start = 0xFFFFFFFFFFFFFFFFUL;
+ super->inode_table_start = 0xFFFFFFFFFFFFFFFFUL;
+ super->directory_table_start = 0xFFFFFFFFFFFFFFFFUL;
+ super->fragment_table_start = 0xFFFFFFFFFFFFFFFFUL;
+ super->export_table_start = 0xFFFFFFFFFFFFFFFFUL;
+
+ for (i = block_size; i != 0x01; i >>= 1)
+ super->block_log += 1;
+
+ return 0;
+}
diff --git a/lib/sqfs/src/unix/io_file.c b/lib/sqfs/src/unix/io_file.c
new file mode 100644
index 0000000..e1fb9db
--- /dev/null
+++ b/lib/sqfs/src/unix/io_file.c
@@ -0,0 +1,196 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * io_file.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/io.h"
+#include "sqfs/error.h"
+
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+
+
+typedef struct {
+ sqfs_file_t base;
+
+ bool readonly;
+ sqfs_u64 size;
+ int fd;
+} sqfs_file_stdio_t;
+
+
+static void stdio_destroy(sqfs_object_t *base)
+{
+ sqfs_file_stdio_t *file = (sqfs_file_stdio_t *)base;
+
+ close(file->fd);
+ free(file);
+}
+
+static sqfs_object_t *stdio_copy(const sqfs_object_t *base)
+{
+ const sqfs_file_stdio_t *file = (const sqfs_file_stdio_t *)base;
+ sqfs_file_stdio_t *copy;
+ int err;
+
+ if (!file->readonly) {
+ errno = ENOTSUP;
+ return NULL;
+ }
+
+ copy = calloc(1, sizeof(*copy));
+ if (copy == NULL)
+ return NULL;
+
+ memcpy(copy, file, sizeof(*file));
+
+ copy->fd = dup(file->fd);
+ if (copy->fd < 0) {
+ err = errno;
+ free(copy);
+ copy = NULL;
+ errno = err;
+ }
+
+ return (sqfs_object_t *)copy;
+}
+
+static int stdio_read_at(sqfs_file_t *base, sqfs_u64 offset,
+ void *buffer, size_t size)
+{
+ sqfs_file_stdio_t *file = (sqfs_file_stdio_t *)base;
+ ssize_t ret;
+
+ while (size > 0) {
+ ret = pread(file->fd, buffer, size, offset);
+
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ return SQFS_ERROR_IO;
+ }
+
+ if (ret == 0)
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ buffer = (char *)buffer + ret;
+ size -= ret;
+ offset += ret;
+ }
+
+ return 0;
+}
+
+static int stdio_write_at(sqfs_file_t *base, sqfs_u64 offset,
+ const void *buffer, size_t size)
+{
+ sqfs_file_stdio_t *file = (sqfs_file_stdio_t *)base;
+ ssize_t ret;
+
+ while (size > 0) {
+ ret = pwrite(file->fd, buffer, size, offset);
+
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ return SQFS_ERROR_IO;
+ }
+
+ if (ret == 0)
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ buffer = (const char *)buffer + ret;
+ size -= ret;
+ offset += ret;
+ }
+
+ if (offset >= file->size)
+ file->size = offset;
+
+ return 0;
+}
+
+static sqfs_u64 stdio_get_size(const sqfs_file_t *base)
+{
+ const sqfs_file_stdio_t *file = (const sqfs_file_stdio_t *)base;
+
+ return file->size;
+}
+
+static int stdio_truncate(sqfs_file_t *base, sqfs_u64 size)
+{
+ sqfs_file_stdio_t *file = (sqfs_file_stdio_t *)base;
+
+ if (ftruncate(file->fd, size))
+ return SQFS_ERROR_IO;
+
+ file->size = size;
+ return 0;
+}
+
+
+sqfs_file_t *sqfs_open_file(const char *filename, sqfs_u32 flags)
+{
+ sqfs_file_stdio_t *file;
+ int open_mode, temp;
+ sqfs_file_t *base;
+ struct stat sb;
+
+ if (flags & ~SQFS_FILE_OPEN_ALL_FLAGS) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ file = calloc(1, sizeof(*file));
+ base = (sqfs_file_t *)file;
+ if (file == NULL)
+ return NULL;
+
+ sqfs_object_init(file, stdio_destroy, stdio_copy);
+
+ if (flags & SQFS_FILE_OPEN_READ_ONLY) {
+ file->readonly = true;
+ open_mode = O_RDONLY;
+ } else {
+ file->readonly = false;
+ open_mode = O_CREAT | O_RDWR;
+
+ if (flags & SQFS_FILE_OPEN_OVERWRITE) {
+ open_mode |= O_TRUNC;
+ } else {
+ open_mode |= O_EXCL;
+ }
+ }
+
+ file->fd = open(filename, open_mode, 0644);
+ if (file->fd < 0) {
+ temp = errno;
+ free(file);
+ errno = temp;
+ return NULL;
+ }
+
+ if (fstat(file->fd, &sb)) {
+ temp = errno;
+ close(file->fd);
+ free(file);
+ errno = temp;
+ return NULL;
+ }
+
+ file->size = sb.st_size;
+
+ base->read_at = stdio_read_at;
+ base->write_at = stdio_write_at;
+ base->get_size = stdio_get_size;
+ base->truncate = stdio_truncate;
+ return base;
+}
diff --git a/lib/sqfs/src/win32/io_file.c b/lib/sqfs/src/win32/io_file.c
new file mode 100644
index 0000000..548a246
--- /dev/null
+++ b/lib/sqfs/src/win32/io_file.c
@@ -0,0 +1,237 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * io_file.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/io.h"
+#include "sqfs/error.h"
+
+#include <stdlib.h>
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+
+typedef struct {
+ sqfs_file_t base;
+
+ bool readonly;
+ sqfs_u64 size;
+ HANDLE fd;
+} sqfs_file_stdio_t;
+
+
+static void stdio_destroy(sqfs_object_t *base)
+{
+ sqfs_file_stdio_t *file = (sqfs_file_stdio_t *)base;
+
+ CloseHandle(file->fd);
+ free(file);
+}
+
+static sqfs_object_t *stdio_copy(const sqfs_object_t *base)
+{
+ const sqfs_file_stdio_t *file = (const sqfs_file_stdio_t *)base;
+ sqfs_file_stdio_t *copy;
+ BOOL ret;
+
+ if (!file->readonly) {
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return NULL;
+ }
+
+ copy = calloc(1, sizeof(*copy));
+ if (copy == NULL)
+ return NULL;
+
+ memcpy(copy, file, sizeof(*file));
+
+ ret = DuplicateHandle(GetCurrentProcess(), file->fd,
+ GetCurrentProcess(), &copy->fd,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+
+ if (!ret) {
+ free(copy);
+ return NULL;
+ }
+
+ return (sqfs_object_t *)copy;
+}
+
+static int stdio_read_at(sqfs_file_t *base, sqfs_u64 offset,
+ void *buffer, size_t size)
+{
+ sqfs_file_stdio_t *file = (sqfs_file_stdio_t *)base;
+ DWORD actually_read;
+ LARGE_INTEGER pos;
+
+ if (offset >= file->size)
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ if (size == 0)
+ return 0;
+
+ if ((offset + size - 1) >= file->size)
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ pos.QuadPart = offset;
+
+ if (!SetFilePointerEx(file->fd, pos, NULL, FILE_BEGIN))
+ return SQFS_ERROR_IO;
+
+ while (size > 0) {
+ if (!ReadFile(file->fd, buffer, size, &actually_read, NULL))
+ return SQFS_ERROR_IO;
+
+ size -= actually_read;
+ buffer = (char *)buffer + actually_read;
+ }
+
+ return 0;
+}
+
+static int stdio_write_at(sqfs_file_t *base, sqfs_u64 offset,
+ const void *buffer, size_t size)
+{
+ sqfs_file_stdio_t *file = (sqfs_file_stdio_t *)base;
+ DWORD actually_read;
+ LARGE_INTEGER pos;
+
+ if (size == 0)
+ return 0;
+
+ pos.QuadPart = offset;
+
+ if (!SetFilePointerEx(file->fd, pos, NULL, FILE_BEGIN))
+ return SQFS_ERROR_IO;
+
+ while (size > 0) {
+ if (!WriteFile(file->fd, buffer, size, &actually_read, NULL))
+ return SQFS_ERROR_IO;
+
+ size -= actually_read;
+ buffer = (char *)buffer + actually_read;
+ offset += actually_read;
+
+ if (offset > file->size)
+ file->size = offset;
+ }
+
+ return 0;
+}
+
+static sqfs_u64 stdio_get_size(const sqfs_file_t *base)
+{
+ const sqfs_file_stdio_t *file = (const sqfs_file_stdio_t *)base;
+
+ return file->size;
+}
+
+static int stdio_truncate(sqfs_file_t *base, sqfs_u64 size)
+{
+ sqfs_file_stdio_t *file = (sqfs_file_stdio_t *)base;
+ LARGE_INTEGER pos;
+
+ pos.QuadPart = size;
+
+ if (!SetFilePointerEx(file->fd, pos, NULL, FILE_BEGIN))
+ return SQFS_ERROR_IO;
+
+ if (!SetEndOfFile(file->fd))
+ return SQFS_ERROR_IO;
+
+ file->size = size;
+ return 0;
+}
+
+
+sqfs_file_t *sqfs_open_file(const char *filename, sqfs_u32 flags)
+{
+ int access_flags, creation_mode, share_mode;
+ sqfs_file_stdio_t *file;
+ LARGE_INTEGER size;
+ sqfs_file_t *base;
+ WCHAR *wpath = NULL;
+ DWORD length;
+
+ if (flags & ~SQFS_FILE_OPEN_ALL_FLAGS) {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return NULL;
+ }
+
+ if (!(flags & SQFS_FILE_OPEN_NO_CHARSET_XFRM)) {
+ length = MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0);
+ if (length <= 0)
+ return NULL;
+
+ wpath = calloc(sizeof(wpath[0]), length + 1);
+ if (wpath == NULL) {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return NULL;
+ }
+
+ MultiByteToWideChar(CP_UTF8, 0, filename, -1,
+ wpath, length + 1);
+ wpath[length] = '\0';
+ }
+
+ file = calloc(1, sizeof(*file));
+ base = (sqfs_file_t *)file;
+ if (file == NULL) {
+ free(wpath);
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return NULL;
+ }
+
+ sqfs_object_init(file, stdio_destroy, stdio_copy);
+
+ if (flags & SQFS_FILE_OPEN_READ_ONLY) {
+ file->readonly = true;
+ access_flags = GENERIC_READ;
+ creation_mode = OPEN_EXISTING;
+ share_mode = FILE_SHARE_READ;
+ } else {
+ file->readonly = false;
+ access_flags = GENERIC_READ | GENERIC_WRITE;
+ share_mode = 0;
+
+ if (flags & SQFS_FILE_OPEN_OVERWRITE) {
+ creation_mode = CREATE_ALWAYS;
+ } else {
+ creation_mode = CREATE_NEW;
+ }
+ }
+
+ if (flags & SQFS_FILE_OPEN_NO_CHARSET_XFRM) {
+ file->fd = CreateFileA(filename, access_flags, share_mode, NULL,
+ creation_mode, FILE_ATTRIBUTE_NORMAL,
+ NULL);
+ } else {
+ file->fd = CreateFileW(wpath, access_flags, share_mode, NULL,
+ creation_mode, FILE_ATTRIBUTE_NORMAL,
+ NULL);
+ }
+
+ free(wpath);
+
+ if (file->fd == INVALID_HANDLE_VALUE) {
+ free(file);
+ return NULL;
+ }
+
+ if (!GetFileSizeEx(file->fd, &size)) {
+ free(file);
+ return NULL;
+ }
+
+ file->size = size.QuadPart;
+ base->read_at = stdio_read_at;
+ base->write_at = stdio_write_at;
+ base->get_size = stdio_get_size;
+ base->truncate = stdio_truncate;
+ return base;
+}
diff --git a/lib/sqfs/src/write_inode.c b/lib/sqfs/src/write_inode.c
new file mode 100644
index 0000000..118b713
--- /dev/null
+++ b/lib/sqfs/src/write_inode.c
@@ -0,0 +1,220 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * write_inode.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/meta_writer.h"
+#include "sqfs/error.h"
+#include "sqfs/inode.h"
+#include "sqfs/dir.h"
+#include "compat.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#if defined(_WIN32) || defined(__WINDOWS__)
+# include <malloc.h>
+# ifdef _MSC_VER
+# define alloca _alloca
+# endif
+#elif defined(HAVE_ALLOCA_H)
+# include <alloca.h>
+#endif
+
+static int write_block_sizes(sqfs_meta_writer_t *ir,
+ const sqfs_inode_generic_t *n)
+{
+ sqfs_u32 *sizes;
+ size_t i;
+
+ if (n->payload_bytes_used < sizeof(sizes[0]))
+ return 0;
+
+ if ((n->payload_bytes_used % sizeof(sizes[0])) != 0)
+ return SQFS_ERROR_CORRUPTED;
+
+ sizes = alloca(n->payload_bytes_used);
+
+ for (i = 0; i < (n->payload_bytes_used / sizeof(sizes[0])); ++i)
+ sizes[i] = htole32(n->extra[i]);
+
+ return sqfs_meta_writer_append(ir, sizes, n->payload_bytes_used);
+}
+
+static int write_dir_index(sqfs_meta_writer_t *ir, const sqfs_u8 *data,
+ size_t count)
+{
+ sqfs_dir_index_t ent;
+ size_t len;
+ int err;
+
+ while (count > sizeof(ent)) {
+ memcpy(&ent, data, sizeof(ent));
+ data += sizeof(ent);
+ count -= sizeof(ent);
+ len = ent.size + 1;
+
+ if (len > count)
+ return SQFS_ERROR_CORRUPTED;
+
+ ent.start_block = htole32(ent.start_block);
+ ent.index = htole32(ent.index);
+ ent.size = htole32(ent.size);
+
+ err = sqfs_meta_writer_append(ir, &ent, sizeof(ent));
+ if (err)
+ return err;
+
+ err = sqfs_meta_writer_append(ir, data, len);
+ if (err)
+ return err;
+
+ data += len;
+ count -= len;
+ }
+
+ return 0;
+}
+
+int sqfs_meta_writer_write_inode(sqfs_meta_writer_t *ir,
+ const sqfs_inode_generic_t *n)
+{
+ sqfs_inode_t base;
+ int ret;
+
+ base.type = htole16(n->base.type);
+ base.mode = htole16(n->base.mode & ~SQFS_INODE_MODE_MASK);
+ base.uid_idx = htole16(n->base.uid_idx);
+ base.gid_idx = htole16(n->base.gid_idx);
+ base.mod_time = htole32(n->base.mod_time);
+ base.inode_number = htole32(n->base.inode_number);
+
+ ret = sqfs_meta_writer_append(ir, &base, sizeof(base));
+ if (ret)
+ return ret;
+
+ switch (n->base.type) {
+ case SQFS_INODE_DIR: {
+ sqfs_inode_dir_t dir = {
+ .start_block = htole32(n->data.dir.start_block),
+ .nlink = htole32(n->data.dir.nlink),
+ .size = htole16(n->data.dir.size),
+ .offset = htole16(n->data.dir.offset),
+ .parent_inode = htole32(n->data.dir.parent_inode),
+ };
+ return sqfs_meta_writer_append(ir, &dir, sizeof(dir));
+ }
+ case SQFS_INODE_FILE: {
+ sqfs_inode_file_t file = {
+ .blocks_start = htole32(n->data.file.blocks_start),
+ .fragment_index = htole32(n->data.file.fragment_index),
+ .fragment_offset =
+ htole32(n->data.file.fragment_offset),
+ .file_size = htole32(n->data.file.file_size),
+ };
+ ret = sqfs_meta_writer_append(ir, &file, sizeof(file));
+ if (ret)
+ return ret;
+ return write_block_sizes(ir, n);
+ }
+ case SQFS_INODE_SLINK: {
+ sqfs_inode_slink_t slink = {
+ .nlink = htole32(n->data.slink.nlink),
+ .target_size = htole32(n->data.slink.target_size),
+ };
+ ret = sqfs_meta_writer_append(ir, &slink, sizeof(slink));
+ if (ret)
+ return ret;
+ return sqfs_meta_writer_append(ir, n->extra,
+ n->data.slink.target_size);
+ }
+ case SQFS_INODE_BDEV:
+ case SQFS_INODE_CDEV: {
+ sqfs_inode_dev_t dev = {
+ .nlink = htole32(n->data.dev.nlink),
+ .devno = htole32(n->data.dev.devno),
+ };
+ return sqfs_meta_writer_append(ir, &dev, sizeof(dev));
+ }
+ case SQFS_INODE_FIFO:
+ case SQFS_INODE_SOCKET: {
+ sqfs_inode_ipc_t ipc = {
+ .nlink = htole32(n->data.ipc.nlink),
+ };
+ return sqfs_meta_writer_append(ir, &ipc, sizeof(ipc));
+ }
+ case SQFS_INODE_EXT_DIR: {
+ sqfs_inode_dir_ext_t dir = {
+ .nlink = htole32(n->data.dir_ext.nlink),
+ .size = htole32(n->data.dir_ext.size),
+ .start_block = htole32(n->data.dir_ext.start_block),
+ .parent_inode = htole32(n->data.dir_ext.parent_inode),
+ .inodex_count = htole16(n->data.dir_ext.inodex_count),
+ .offset = htole16(n->data.dir_ext.offset),
+ .xattr_idx = htole32(n->data.dir_ext.xattr_idx),
+ };
+ ret = sqfs_meta_writer_append(ir, &dir, sizeof(dir));
+ if (ret)
+ return ret;
+ return write_dir_index(ir, (const sqfs_u8 *)n->extra,
+ n->payload_bytes_used);
+ }
+ case SQFS_INODE_EXT_FILE: {
+ sqfs_inode_file_ext_t file = {
+ .blocks_start = htole64(n->data.file_ext.blocks_start),
+ .file_size = htole64(n->data.file_ext.file_size),
+ .sparse = htole64(n->data.file_ext.sparse),
+ .nlink = htole32(n->data.file_ext.nlink),
+ .fragment_idx = htole32(n->data.file_ext.fragment_idx),
+ .fragment_offset =
+ htole32(n->data.file_ext.fragment_offset),
+ .xattr_idx = htole32(n->data.file_ext.xattr_idx),
+ };
+ ret = sqfs_meta_writer_append(ir, &file, sizeof(file));
+ if (ret)
+ return ret;
+ return write_block_sizes(ir, n);
+ }
+ case SQFS_INODE_EXT_SLINK: {
+ sqfs_inode_slink_t slink = {
+ .nlink = htole32(n->data.slink_ext.nlink),
+ .target_size = htole32(n->data.slink_ext.target_size),
+ };
+ sqfs_u32 xattr = htole32(n->data.slink_ext.xattr_idx);
+
+ ret = sqfs_meta_writer_append(ir, &slink, sizeof(slink));
+ if (ret)
+ return ret;
+ ret = sqfs_meta_writer_append(ir, n->extra,
+ n->data.slink_ext.target_size);
+ if (ret)
+ return ret;
+ return sqfs_meta_writer_append(ir, &xattr, sizeof(xattr));
+ }
+ case SQFS_INODE_EXT_BDEV:
+ case SQFS_INODE_EXT_CDEV: {
+ sqfs_inode_dev_ext_t dev = {
+ .nlink = htole32(n->data.dev_ext.nlink),
+ .devno = htole32(n->data.dev_ext.devno),
+ .xattr_idx = htole32(n->data.dev_ext.xattr_idx),
+ };
+ return sqfs_meta_writer_append(ir, &dev, sizeof(dev));
+ }
+ case SQFS_INODE_EXT_FIFO:
+ case SQFS_INODE_EXT_SOCKET: {
+ sqfs_inode_ipc_ext_t ipc = {
+ .nlink = htole32(n->data.ipc_ext.nlink),
+ .xattr_idx = htole32(n->data.ipc_ext.xattr_idx),
+ };
+ return sqfs_meta_writer_append(ir, &ipc, sizeof(ipc));
+ }
+ default:
+ break;
+ }
+
+ return SQFS_ERROR_UNSUPPORTED;
+}
diff --git a/lib/sqfs/src/write_super.c b/lib/sqfs/src/write_super.c
new file mode 100644
index 0000000..35127da
--- /dev/null
+++ b/lib/sqfs/src/write_super.c
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * write_super.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/super.h"
+#include "sqfs/io.h"
+#include "compat.h"
+
+int sqfs_super_write(const sqfs_super_t *super, sqfs_file_t *file)
+{
+ sqfs_super_t copy;
+
+ copy.magic = htole32(super->magic);
+ copy.inode_count = htole32(super->inode_count);
+ copy.modification_time = htole32(super->modification_time);
+ copy.block_size = htole32(super->block_size);
+ copy.fragment_entry_count = htole32(super->fragment_entry_count);
+ copy.compression_id = htole16(super->compression_id);
+ copy.block_log = htole16(super->block_log);
+ copy.flags = htole16(super->flags);
+ copy.id_count = htole16(super->id_count);
+ copy.version_major = htole16(super->version_major);
+ copy.version_minor = htole16(super->version_minor);
+ copy.root_inode_ref = htole64(super->root_inode_ref);
+ copy.bytes_used = htole64(super->bytes_used);
+ copy.id_table_start = htole64(super->id_table_start);
+ copy.xattr_id_table_start = htole64(super->xattr_id_table_start);
+ copy.inode_table_start = htole64(super->inode_table_start);
+ copy.directory_table_start = htole64(super->directory_table_start);
+ copy.fragment_table_start = htole64(super->fragment_table_start);
+ copy.export_table_start = htole64(super->export_table_start);
+
+ return file->write_at(file, 0, &copy, sizeof(copy));
+}
diff --git a/lib/sqfs/src/write_table.c b/lib/sqfs/src/write_table.c
new file mode 100644
index 0000000..6f28a75
--- /dev/null
+++ b/lib/sqfs/src/write_table.c
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * write_table.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/meta_writer.h"
+#include "sqfs/error.h"
+#include "sqfs/super.h"
+#include "sqfs/table.h"
+#include "sqfs/block.h"
+#include "sqfs/io.h"
+#include "util/util.h"
+
+#include <stdlib.h>
+
+int sqfs_write_table(sqfs_file_t *file, sqfs_compressor_t *cmp,
+ const void *data, size_t table_size, sqfs_u64 *start)
+{
+ size_t block_count, list_size, diff, blkidx = 0;
+ sqfs_u64 off, *locations;
+ sqfs_meta_writer_t *m;
+ int ret;
+
+ block_count = table_size / SQFS_META_BLOCK_SIZE;
+ if ((table_size % SQFS_META_BLOCK_SIZE) != 0)
+ ++block_count;
+
+ locations = alloc_array(sizeof(sqfs_u64), block_count);
+
+ if (locations == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ /* Write actual data */
+ m = sqfs_meta_writer_create(file, cmp, 0);
+ if (m == NULL) {
+ ret = SQFS_ERROR_ALLOC;
+ goto out_idx;
+ }
+
+ while (table_size > 0) {
+ locations[blkidx++] = htole64(file->get_size(file));
+
+ diff = SQFS_META_BLOCK_SIZE;
+ if (diff > table_size)
+ diff = table_size;
+
+ ret = sqfs_meta_writer_append(m, data, diff);
+ if (ret)
+ goto out;
+
+ data = (const char *)data + diff;
+ table_size -= diff;
+ }
+
+ ret = sqfs_meta_writer_flush(m);
+ if (ret)
+ goto out;
+
+ /* write location list */
+ *start = file->get_size(file);
+
+ list_size = sizeof(sqfs_u64) * block_count;
+
+ off = file->get_size(file);
+
+ ret = file->write_at(file, off, locations, list_size);
+ if (ret)
+ goto out;
+
+ /* cleanup */
+ ret = 0;
+out:
+ sqfs_drop(m);
+out_idx:
+ free(locations);
+ return ret;
+}
diff --git a/lib/sqfs/src/xattr/xattr.c b/lib/sqfs/src/xattr/xattr.c
new file mode 100644
index 0000000..29ecebf
--- /dev/null
+++ b/lib/sqfs/src/xattr/xattr.c
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * write_xattr.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+#include "sqfs/xattr.h"
+#include "sqfs/error.h"
+
+#include <string.h>
+
+static const struct {
+ const char *prefix;
+ SQFS_XATTR_TYPE type;
+} xattr_types[] = {
+ { "user.", SQFS_XATTR_USER },
+ { "trusted.", SQFS_XATTR_TRUSTED },
+ { "security.", SQFS_XATTR_SECURITY },
+};
+
+int sqfs_get_xattr_prefix_id(const char *key)
+{
+ size_t i, len;
+
+ for (i = 0; i < sizeof(xattr_types) / sizeof(xattr_types[0]); ++i) {
+ len = strlen(xattr_types[i].prefix);
+
+ if (strncmp(key, xattr_types[i].prefix, len) == 0 &&
+ strlen(key) > len) {
+ return xattr_types[i].type;
+ }
+ }
+
+ return SQFS_ERROR_UNSUPPORTED;
+}
+
+const char *sqfs_get_xattr_prefix(SQFS_XATTR_TYPE id)
+{
+ size_t i;
+
+ for (i = 0; i < sizeof(xattr_types) / sizeof(xattr_types[0]); ++i) {
+ if (xattr_types[i].type == id)
+ return xattr_types[i].prefix;
+ }
+
+ return NULL;
+}
diff --git a/lib/sqfs/src/xattr/xattr_reader.c b/lib/sqfs/src/xattr/xattr_reader.c
new file mode 100644
index 0000000..9e3ea76
--- /dev/null
+++ b/lib/sqfs/src/xattr/xattr_reader.c
@@ -0,0 +1,336 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * xattr_reader.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/xattr_reader.h"
+#include "sqfs/meta_reader.h"
+#include "sqfs/super.h"
+#include "sqfs/xattr.h"
+#include "sqfs/error.h"
+#include "sqfs/block.h"
+#include "sqfs/io.h"
+#include "util/util.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+struct sqfs_xattr_reader_t {
+ sqfs_object_t base;
+
+ sqfs_u64 xattr_start;
+ sqfs_u64 xattr_end;
+
+ size_t num_id_blocks;
+ size_t num_ids;
+
+ sqfs_u64 *id_block_starts;
+
+ sqfs_meta_reader_t *idrd;
+ sqfs_meta_reader_t *kvrd;
+};
+
+static sqfs_object_t *xattr_reader_copy(const sqfs_object_t *obj)
+{
+ const sqfs_xattr_reader_t *xr = (const sqfs_xattr_reader_t *)obj;
+ sqfs_xattr_reader_t *copy = malloc(sizeof(*copy));
+
+ if (copy == NULL)
+ return NULL;
+
+ memcpy(copy, xr, sizeof(*xr));
+
+ if (xr->kvrd != NULL) {
+ copy->kvrd = sqfs_copy(xr->kvrd);
+ if (copy->kvrd == NULL)
+ goto fail;
+ }
+
+ if (xr->idrd != NULL) {
+ copy->idrd = sqfs_copy(xr->idrd);
+ if (copy->idrd == NULL)
+ goto fail;
+ }
+
+ if (xr->id_block_starts != NULL) {
+ copy->id_block_starts = alloc_array(sizeof(sqfs_u64),
+ xr->num_id_blocks);
+ if (copy->id_block_starts == NULL)
+ goto fail;
+
+ memcpy(copy->id_block_starts, xr->id_block_starts,
+ sizeof(sqfs_u64) * xr->num_id_blocks);
+ }
+
+ return (sqfs_object_t *)copy;
+fail:
+ sqfs_drop(copy->idrd);
+ sqfs_drop(copy->kvrd);
+ free(copy);
+ return NULL;
+}
+
+static void xattr_reader_destroy(sqfs_object_t *obj)
+{
+ sqfs_xattr_reader_t *xr = (sqfs_xattr_reader_t *)obj;
+
+ sqfs_drop(xr->kvrd);
+ sqfs_drop(xr->idrd);
+ free(xr->id_block_starts);
+ free(xr);
+}
+
+int sqfs_xattr_reader_load(sqfs_xattr_reader_t *xr, const sqfs_super_t *super,
+ sqfs_file_t *file, sqfs_compressor_t *cmp)
+{
+ sqfs_xattr_id_table_t idtbl;
+ size_t i;
+ int err;
+
+ /* sanity check */
+ if (super->flags & SQFS_FLAG_NO_XATTRS)
+ return 0;
+
+ if (super->xattr_id_table_start == 0xFFFFFFFFFFFFFFFF)
+ return 0;
+
+ if (super->xattr_id_table_start >= super->bytes_used)
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ /* cleanup pre-existing data */
+ xr->idrd = sqfs_drop(xr->idrd);
+ xr->kvrd = sqfs_drop(xr->kvrd);
+
+ free(xr->id_block_starts);
+ xr->id_block_starts = NULL;
+
+ /* read the locations table */
+ err = file->read_at(file, super->xattr_id_table_start,
+ &idtbl, sizeof(idtbl));
+ if (err)
+ return err;
+
+ xr->xattr_start = le64toh(idtbl.xattr_table_start);
+ xr->num_ids = le32toh(idtbl.xattr_ids);
+ xr->num_id_blocks =
+ (xr->num_ids * sizeof(sqfs_xattr_id_t)) / SQFS_META_BLOCK_SIZE;
+
+ if ((xr->num_ids * sizeof(sqfs_xattr_id_t)) % SQFS_META_BLOCK_SIZE)
+ xr->num_id_blocks += 1;
+
+ xr->id_block_starts = alloc_array(sizeof(sqfs_u64), xr->num_id_blocks);
+ if (xr->id_block_starts == NULL) {
+ if (errno == EOVERFLOW)
+ return SQFS_ERROR_OVERFLOW;
+ return SQFS_ERROR_ALLOC;
+ }
+
+ err = file->read_at(file, super->xattr_id_table_start + sizeof(idtbl),
+ xr->id_block_starts,
+ sizeof(sqfs_u64) * xr->num_id_blocks);
+ if (err)
+ goto fail_blocks;
+
+ for (i = 0; i < xr->num_id_blocks; ++i) {
+ xr->id_block_starts[i] = le64toh(xr->id_block_starts[i]);
+
+ if (xr->id_block_starts[i] > super->bytes_used) {
+ err = SQFS_ERROR_OUT_OF_BOUNDS;
+ goto fail_blocks;
+ }
+ }
+
+ /* create the meta data readers */
+ xr->idrd = sqfs_meta_reader_create(file, cmp, super->id_table_start,
+ super->bytes_used);
+ if (xr->idrd == NULL)
+ goto fail_blocks;
+
+ xr->kvrd = sqfs_meta_reader_create(file, cmp, super->id_table_start,
+ super->bytes_used);
+ if (xr->kvrd == NULL)
+ goto fail_idrd;
+
+ xr->xattr_end = super->bytes_used;
+ return 0;
+fail_idrd:
+ xr->idrd = sqfs_drop(xr->idrd);
+fail_blocks:
+ free(xr->id_block_starts);
+ xr->id_block_starts = NULL;
+ return err;
+}
+
+int sqfs_xattr_reader_read_key(sqfs_xattr_reader_t *xr,
+ sqfs_xattr_entry_t **key_out)
+{
+ sqfs_xattr_entry_t key, *out;
+ const char *prefix;
+ size_t plen, total;
+ int ret;
+
+ ret = sqfs_meta_reader_read(xr->kvrd, &key, sizeof(key));
+ if (ret)
+ return ret;
+
+ key.type = le16toh(key.type);
+ key.size = le16toh(key.size);
+
+ prefix = sqfs_get_xattr_prefix(key.type & SQFS_XATTR_PREFIX_MASK);
+ if (prefix == NULL)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ plen = strlen(prefix);
+
+ if (SZ_ADD_OV(plen, key.size, &total) || SZ_ADD_OV(total, 1, &total) ||
+ SZ_ADD_OV(sizeof(*out), total, &total)) {
+ return SQFS_ERROR_OVERFLOW;
+ }
+
+ out = calloc(1, total);
+ if (out == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ *out = key;
+ memcpy(out->key, prefix, plen);
+
+ ret = sqfs_meta_reader_read(xr->kvrd, out->key + plen, key.size);
+ if (ret) {
+ free(out);
+ return ret;
+ }
+
+ *key_out = out;
+ return 0;
+}
+
+int sqfs_xattr_reader_read_value(sqfs_xattr_reader_t *xr,
+ const sqfs_xattr_entry_t *key,
+ sqfs_xattr_value_t **val_out)
+{
+ size_t offset, new_offset, size;
+ sqfs_xattr_value_t value, *out;
+ sqfs_u64 ref, start, new_start;
+ int ret;
+
+ ret = sqfs_meta_reader_read(xr->kvrd, &value, sizeof(value));
+ if (ret)
+ return ret;
+
+ if (key->type & SQFS_XATTR_FLAG_OOL) {
+ ret = sqfs_meta_reader_read(xr->kvrd, &ref, sizeof(ref));
+ if (ret)
+ return ret;
+
+ sqfs_meta_reader_get_position(xr->kvrd, &start, &offset);
+
+ new_start = xr->xattr_start + (ref >> 16);
+ if (new_start >= xr->xattr_end)
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ new_offset = ref & 0xFFFF;
+ if (new_offset >= SQFS_META_BLOCK_SIZE)
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ ret = sqfs_meta_reader_seek(xr->kvrd, new_start, new_offset);
+ if (ret)
+ return ret;
+
+ ret = sqfs_meta_reader_read(xr->kvrd, &value, sizeof(value));
+ if (ret)
+ return ret;
+ }
+
+ value.size = le32toh(value.size);
+
+ if (SZ_ADD_OV(sizeof(*out), value.size, &size) ||
+ SZ_ADD_OV(size, 1, &size)) {
+ return SQFS_ERROR_OVERFLOW;
+ }
+
+ out = calloc(1, size);
+ if (out == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ *out = value;
+
+ ret = sqfs_meta_reader_read(xr->kvrd, out->value, value.size);
+ if (ret)
+ goto fail;
+
+ if (key->type & SQFS_XATTR_FLAG_OOL) {
+ ret = sqfs_meta_reader_seek(xr->kvrd, start, offset);
+ if (ret)
+ goto fail;
+ }
+
+ *val_out = out;
+ return 0;
+fail:
+ free(out);
+ return ret;
+}
+
+int sqfs_xattr_reader_seek_kv(sqfs_xattr_reader_t *xr,
+ const sqfs_xattr_id_t *desc)
+{
+ sqfs_u32 offset = desc->xattr & 0xFFFF;
+ sqfs_u64 block = xr->xattr_start + (desc->xattr >> 16);
+
+ return sqfs_meta_reader_seek(xr->kvrd, block, offset);
+}
+
+int sqfs_xattr_reader_get_desc(sqfs_xattr_reader_t *xr, sqfs_u32 idx,
+ sqfs_xattr_id_t *desc)
+{
+ size_t block, offset;
+ int ret;
+
+ memset(desc, 0, sizeof(*desc));
+
+ if (idx == 0xFFFFFFFF)
+ return 0;
+
+ if (xr->kvrd == NULL || xr->idrd == NULL)
+ return idx == 0 ? 0 : SQFS_ERROR_OUT_OF_BOUNDS;
+
+ if (idx >= xr->num_ids)
+ return SQFS_ERROR_OUT_OF_BOUNDS;
+
+ offset = (idx * sizeof(*desc)) % SQFS_META_BLOCK_SIZE;
+ block = (idx * sizeof(*desc)) / SQFS_META_BLOCK_SIZE;
+
+ ret = sqfs_meta_reader_seek(xr->idrd, xr->id_block_starts[block],
+ offset);
+ if (ret)
+ return ret;
+
+ ret = sqfs_meta_reader_read(xr->idrd, desc, sizeof(*desc));
+ if (ret)
+ return ret;
+
+ desc->xattr = le64toh(desc->xattr);
+ desc->count = le32toh(desc->count);
+ desc->size = le32toh(desc->size);
+ return 0;
+}
+
+sqfs_xattr_reader_t *sqfs_xattr_reader_create(sqfs_u32 flags)
+{
+ sqfs_xattr_reader_t *xr;
+
+ if (flags != 0)
+ return NULL;
+
+ xr = calloc(1, sizeof(*xr));
+ if (xr == NULL)
+ return NULL;
+
+ sqfs_object_init(xr, xattr_reader_destroy, xattr_reader_copy);
+ return xr;
+}
diff --git a/lib/sqfs/src/xattr/xattr_writer.c b/lib/sqfs/src/xattr/xattr_writer.c
new file mode 100644
index 0000000..39e1b05
--- /dev/null
+++ b/lib/sqfs/src/xattr/xattr_writer.c
@@ -0,0 +1,127 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * xattr_writer.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "xattr_writer.h"
+
+static sqfs_object_t *xattr_writer_copy(const sqfs_object_t *obj)
+{
+ const sqfs_xattr_writer_t *xwr = (const sqfs_xattr_writer_t *)obj;
+ sqfs_xattr_writer_t *copy;
+ kv_block_desc_t *it;
+
+ copy = calloc(1, sizeof(*copy));
+ if (copy == NULL)
+ return NULL;
+
+ memcpy(copy, xwr, sizeof(*xwr));
+
+ if (str_table_copy(&copy->keys, &xwr->keys))
+ goto fail_keys;
+
+ if (str_table_copy(&copy->values, &xwr->values))
+ goto fail_values;
+
+ if (array_init_copy(&copy->kv_pairs, &xwr->kv_pairs))
+ goto fail_pairs;
+
+ if (rbtree_copy(&xwr->kv_block_tree, &copy->kv_block_tree) != 0)
+ goto fail_tree;
+
+ for (it = xwr->kv_block_first; it != NULL; it = it->next) {
+ rbtree_node_t *n = rbtree_lookup(&copy->kv_block_tree, it);
+
+ if (copy->kv_block_last == NULL) {
+ copy->kv_block_first = rbtree_node_key(n);
+ copy->kv_block_last = copy->kv_block_first;
+ } else {
+ copy->kv_block_last->next = rbtree_node_key(n);
+ copy->kv_block_last = copy->kv_block_last->next;
+ }
+
+ copy->kv_block_last->next = NULL;
+ }
+
+ return (sqfs_object_t *)copy;
+fail_tree:
+ array_cleanup(&copy->kv_pairs);
+fail_pairs:
+ str_table_cleanup(&copy->values);
+fail_values:
+ str_table_cleanup(&copy->keys);
+fail_keys:
+ free(copy);
+ return NULL;
+}
+
+static void xattr_writer_destroy(sqfs_object_t *obj)
+{
+ sqfs_xattr_writer_t *xwr = (sqfs_xattr_writer_t *)obj;
+
+ rbtree_cleanup(&xwr->kv_block_tree);
+ array_cleanup(&xwr->kv_pairs);
+ str_table_cleanup(&xwr->values);
+ str_table_cleanup(&xwr->keys);
+ free(xwr);
+}
+
+static int block_compare(const void *context,
+ const void *lhs, const void *rhs)
+{
+ const sqfs_xattr_writer_t *xwr = context;
+ const kv_block_desc_t *l = lhs, *r = rhs;
+
+ if (l->count != r->count)
+ return l->count < r->count ? -1 : 1;
+
+ if (l->start == r->start)
+ return 0;
+
+ return memcmp((sqfs_u64 *)xwr->kv_pairs.data + l->start,
+ (sqfs_u64 *)xwr->kv_pairs.data + r->start,
+ l->count * xwr->kv_pairs.size);
+}
+
+sqfs_xattr_writer_t *sqfs_xattr_writer_create(sqfs_u32 flags)
+{
+ sqfs_xattr_writer_t *xwr;
+
+ if (flags != 0)
+ return NULL;
+
+ xwr = calloc(1, sizeof(*xwr));
+ if (xwr == NULL)
+ return NULL;
+
+ sqfs_object_init(xwr, xattr_writer_destroy, xattr_writer_copy);
+
+ if (str_table_init(&xwr->keys))
+ goto fail_keys;
+
+ if (str_table_init(&xwr->values))
+ goto fail_values;
+
+ if (array_init(&xwr->kv_pairs, sizeof(sqfs_u64),
+ XATTR_INITIAL_PAIR_CAP)) {
+ goto fail_pairs;
+ }
+
+ if (rbtree_init(&xwr->kv_block_tree, sizeof(kv_block_desc_t),
+ sizeof(sqfs_u32), block_compare)) {
+ goto fail_tree;
+ }
+
+ xwr->kv_block_tree.key_context = xwr;
+ return xwr;
+fail_tree:
+ array_cleanup(&xwr->kv_pairs);
+fail_pairs:
+ str_table_cleanup(&xwr->values);
+fail_values:
+ str_table_cleanup(&xwr->keys);
+fail_keys:
+ free(xwr);
+ return NULL;
+}
diff --git a/lib/sqfs/src/xattr/xattr_writer.h b/lib/sqfs/src/xattr/xattr_writer.h
new file mode 100644
index 0000000..792cfae
--- /dev/null
+++ b/lib/sqfs/src/xattr/xattr_writer.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * xattr_writer.h
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#ifndef XATTR_WRITER_H
+#define XATTR_WRITER_H
+
+#define SQFS_BUILDING_DLL
+#include "config.h"
+
+#include "sqfs/xattr_writer.h"
+#include "sqfs/meta_writer.h"
+#include "sqfs/super.h"
+#include "sqfs/xattr.h"
+#include "sqfs/error.h"
+#include "sqfs/block.h"
+#include "sqfs/io.h"
+
+#include "util/str_table.h"
+#include "util/rbtree.h"
+#include "util/array.h"
+#include "util/util.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+
+#define XATTR_INITIAL_PAIR_CAP 128
+
+#define MK_PAIR(key, value) (((sqfs_u64)(key) << 32UL) | (sqfs_u64)(value))
+#define GET_KEY(pair) ((pair >> 32UL) & 0x0FFFFFFFFUL)
+#define GET_VALUE(pair) (pair & 0x0FFFFFFFFUL)
+
+
+typedef struct kv_block_desc_t {
+ struct kv_block_desc_t *next;
+ size_t start;
+ size_t count;
+
+ sqfs_u64 start_ref;
+ size_t size_bytes;
+} kv_block_desc_t;
+
+struct sqfs_xattr_writer_t {
+ sqfs_object_t base;
+
+ str_table_t keys;
+ str_table_t values;
+
+ array_t kv_pairs;
+
+ size_t kv_start;
+
+ rbtree_t kv_block_tree;
+ kv_block_desc_t *kv_block_first;
+ kv_block_desc_t *kv_block_last;
+ size_t num_blocks;
+};
+
+#endif /* XATTR_WRITER_H */
diff --git a/lib/sqfs/src/xattr/xattr_writer_flush.c b/lib/sqfs/src/xattr/xattr_writer_flush.c
new file mode 100644
index 0000000..a06463f
--- /dev/null
+++ b/lib/sqfs/src/xattr/xattr_writer_flush.c
@@ -0,0 +1,347 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * xattr_writer_flush.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "xattr_writer.h"
+
+static const char *hexmap = "0123456789ABCDEF";
+
+static void *from_base32(const char *input, size_t *size_out)
+{
+ sqfs_u8 lo, hi, *out, *ptr;
+ size_t len;
+
+ len = strlen(input);
+ *size_out = len / 2;
+
+ out = malloc(*size_out);
+ if (out == NULL)
+ return NULL;
+
+ ptr = out;
+
+ while (*input != '\0') {
+ lo = strchr(hexmap, *(input++)) - hexmap;
+ hi = strchr(hexmap, *(input++)) - hexmap;
+
+ *(ptr++) = lo | (hi << 4);
+ }
+
+ return out;
+}
+
+static sqfs_s32 write_key(sqfs_meta_writer_t *mw, const char *key,
+ bool value_is_ool)
+{
+ sqfs_xattr_entry_t kent;
+ int type, err;
+ size_t len;
+
+ type = sqfs_get_xattr_prefix_id(key);
+ assert(type >= 0);
+
+ key = strchr(key, '.');
+ assert(key != NULL);
+ ++key;
+ len = strlen(key);
+
+ if (value_is_ool)
+ type |= SQFS_XATTR_FLAG_OOL;
+
+ memset(&kent, 0, sizeof(kent));
+ kent.type = htole16(type);
+ kent.size = htole16(len);
+
+ err = sqfs_meta_writer_append(mw, &kent, sizeof(kent));
+ if (err)
+ return err;
+
+ err = sqfs_meta_writer_append(mw, key, len);
+ if (err)
+ return err;
+
+ return sizeof(kent) + len;
+}
+
+static sqfs_s32 write_value(sqfs_meta_writer_t *mw, const char *value_str,
+ sqfs_u64 *value_ref_out)
+{
+ sqfs_xattr_value_t vent;
+ sqfs_u32 offset;
+ sqfs_u64 block;
+ size_t size;
+ void *value;
+ int err;
+
+ value = from_base32(value_str, &size);
+ if (value == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ memset(&vent, 0, sizeof(vent));
+ vent.size = htole32(size);
+
+ sqfs_meta_writer_get_position(mw, &block, &offset);
+ *value_ref_out = (block << 16) | (offset & 0xFFFF);
+
+ err = sqfs_meta_writer_append(mw, &vent, sizeof(vent));
+ if (err)
+ goto fail;
+
+ err = sqfs_meta_writer_append(mw, value, size);
+ if (err)
+ goto fail;
+
+ free(value);
+ return sizeof(vent) + size;
+fail:
+ free(value);
+ return err;
+}
+
+static sqfs_s32 write_value_ool(sqfs_meta_writer_t *mw, sqfs_u64 location)
+{
+ sqfs_xattr_value_t vent;
+ sqfs_u64 ref;
+ int err;
+
+ memset(&vent, 0, sizeof(vent));
+ vent.size = htole32(sizeof(location));
+ ref = htole64(location);
+
+ err = sqfs_meta_writer_append(mw, &vent, sizeof(vent));
+ if (err)
+ return err;
+
+ err = sqfs_meta_writer_append(mw, &ref, sizeof(ref));
+ if (err)
+ return err;
+
+ return sizeof(vent) + sizeof(ref);
+}
+
+static bool should_store_ool(const char *val_str, size_t refcount)
+{
+ if (refcount < 2)
+ return false;
+
+ /*
+ Storing in line needs this many bytes: refcount * len
+
+ Storing out-of-line needs this many: len + (refcount - 1) * 8
+
+ Out-of-line prefereable iff refcount > 1 and:
+ refcount * len > len + (refcount - 1) * 8
+ => refcount * len - len > (refcount - 1) * 8
+ => (refcount - 1) * len > (refcount - 1) * 8
+ => len > 8
+ */
+ return (strlen(val_str) / 2) > sizeof(sqfs_u64);
+}
+
+static int write_block_pairs(const sqfs_xattr_writer_t *xwr,
+ sqfs_meta_writer_t *mw,
+ const kv_block_desc_t *blk,
+ sqfs_u64 *ool_locations)
+{
+ const char *key_str, *value_str;
+ sqfs_s32 diff, total = 0;
+ size_t i, refcount;
+ sqfs_u64 ref;
+
+ for (i = 0; i < blk->count; ++i) {
+ sqfs_u64 ent = ((sqfs_u64 *)xwr->kv_pairs.data)[blk->start + i];
+ sqfs_u32 key_idx = GET_KEY(ent);
+ sqfs_u32 val_idx = GET_VALUE(ent);
+
+ key_str = str_table_get_string(&xwr->keys, key_idx);
+ value_str = str_table_get_string(&xwr->values, val_idx);
+
+ if (ool_locations[val_idx] == 0xFFFFFFFFFFFFFFFFUL) {
+ diff = write_key(mw, key_str, false);
+ if (diff < 0)
+ return diff;
+ total += diff;
+
+ diff = write_value(mw, value_str, &ref);
+ if (diff < 0)
+ return diff;
+ total += diff;
+
+ refcount = str_table_get_ref_count(&xwr->values,
+ val_idx);
+
+ if (should_store_ool(value_str, refcount))
+ ool_locations[val_idx] = ref;
+ } else {
+ diff = write_key(mw, key_str, true);
+ if (diff < 0)
+ return diff;
+ total += diff;
+
+ diff = write_value_ool(mw, ool_locations[val_idx]);
+ if (diff < 0)
+ return diff;
+ total += diff;
+ }
+ }
+
+ return total;
+}
+
+static int write_kv_pairs(const sqfs_xattr_writer_t *xwr,
+ sqfs_meta_writer_t *mw)
+{
+ sqfs_u64 block, *ool_locations;
+ kv_block_desc_t *blk;
+ sqfs_u32 offset;
+ sqfs_s32 size;
+ size_t i;
+
+ ool_locations = alloc_array(sizeof(ool_locations[0]),
+ str_table_count(&xwr->values));
+ if (ool_locations == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ for (i = 0; i < str_table_count(&xwr->values); ++i)
+ ool_locations[i] = 0xFFFFFFFFFFFFFFFFUL;
+
+ for (blk = xwr->kv_block_first; blk != NULL; blk = blk->next) {
+ sqfs_meta_writer_get_position(mw, &block, &offset);
+ blk->start_ref = (block << 16) | (offset & 0xFFFF);
+
+ size = write_block_pairs(xwr, mw, blk, ool_locations);
+ if (size < 0) {
+ free(ool_locations);
+ return size;
+ }
+
+ blk->size_bytes = size;
+ }
+
+ free(ool_locations);
+ return sqfs_meta_writer_flush(mw);
+}
+
+static int write_id_table(const sqfs_xattr_writer_t *xwr,
+ sqfs_meta_writer_t *mw,
+ sqfs_u64 *locations)
+{
+ sqfs_xattr_id_t id_ent;
+ kv_block_desc_t *blk;
+ sqfs_u32 offset;
+ sqfs_u64 block;
+ size_t i = 0;
+ int err;
+
+ locations[i++] = 0;
+
+ for (blk = xwr->kv_block_first; blk != NULL; blk = blk->next) {
+ memset(&id_ent, 0, sizeof(id_ent));
+ id_ent.xattr = htole64(blk->start_ref);
+ id_ent.count = htole32(blk->count);
+ id_ent.size = htole32(blk->size_bytes);
+
+ err = sqfs_meta_writer_append(mw, &id_ent, sizeof(id_ent));
+ if (err)
+ return err;
+
+ sqfs_meta_writer_get_position(mw, &block, &offset);
+ if (block != locations[i - 1])
+ locations[i++] = block;
+ }
+
+ return sqfs_meta_writer_flush(mw);
+}
+
+static int write_location_table(const sqfs_xattr_writer_t *xwr,
+ sqfs_u64 kv_start, sqfs_file_t *file,
+ const sqfs_super_t *super, sqfs_u64 *locations,
+ size_t loc_count)
+{
+ sqfs_xattr_id_table_t idtbl;
+ int err;
+
+ memset(&idtbl, 0, sizeof(idtbl));
+ idtbl.xattr_table_start = htole64(kv_start);
+ idtbl.xattr_ids = htole32(xwr->num_blocks);
+
+ err = file->write_at(file, super->xattr_id_table_start,
+ &idtbl, sizeof(idtbl));
+ if (err)
+ return err;
+
+ return file->write_at(file, super->xattr_id_table_start + sizeof(idtbl),
+ locations, sizeof(locations[0]) * loc_count);
+}
+
+static int alloc_location_table(const sqfs_xattr_writer_t *xwr,
+ sqfs_u64 **tbl_out, size_t *szout)
+{
+ sqfs_u64 *locations;
+ size_t size, count;
+
+ if (SZ_MUL_OV(xwr->num_blocks, sizeof(sqfs_xattr_id_t), &size))
+ return SQFS_ERROR_OVERFLOW;
+
+ count = size / SQFS_META_BLOCK_SIZE;
+ if (size % SQFS_META_BLOCK_SIZE)
+ ++count;
+
+ locations = alloc_array(sizeof(sqfs_u64), count);
+ if (locations == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ *tbl_out = locations;
+ *szout = count;
+ return 0;
+}
+
+int sqfs_xattr_writer_flush(const sqfs_xattr_writer_t *xwr, sqfs_file_t *file,
+ sqfs_super_t *super, sqfs_compressor_t *cmp)
+{
+ sqfs_u64 *locations = NULL, kv_start, id_start;
+ sqfs_meta_writer_t *mw;
+ size_t i, count;
+ int err;
+
+ if (xwr->kv_pairs.used == 0 || xwr->num_blocks == 0) {
+ super->xattr_id_table_start = 0xFFFFFFFFFFFFFFFFUL;
+ super->flags |= SQFS_FLAG_NO_XATTRS;
+ return 0;
+ }
+
+ mw = sqfs_meta_writer_create(file, cmp, 0);
+ if (mw == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ kv_start = file->get_size(file);
+ err = write_kv_pairs(xwr, mw);
+ if (err)
+ goto out;
+
+ sqfs_meta_writer_reset(mw);
+
+ id_start = file->get_size(file);
+ err = alloc_location_table(xwr, &locations, &count);
+ if (err)
+ goto out;
+
+ err = write_id_table(xwr, mw, locations);
+ if (err)
+ goto out;
+
+ super->xattr_id_table_start = file->get_size(file);
+ super->flags &= ~SQFS_FLAG_NO_XATTRS;
+
+ for (i = 0; i < count; ++i)
+ locations[i] = htole64(locations[i] + id_start);
+
+ err = write_location_table(xwr, kv_start, file, super,
+ locations, count);
+out:
+ free(locations);
+ sqfs_drop(mw);
+ return err;
+}
diff --git a/lib/sqfs/src/xattr/xattr_writer_record.c b/lib/sqfs/src/xattr/xattr_writer_record.c
new file mode 100644
index 0000000..81bbf6b
--- /dev/null
+++ b/lib/sqfs/src/xattr/xattr_writer_record.c
@@ -0,0 +1,145 @@
+/* SPDX-License-Identifier: LGPL-3.0-or-later */
+/*
+ * xattr_writer_record.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "xattr_writer.h"
+
+static const char *hexmap = "0123456789ABCDEF";
+
+static char *to_base32(const void *input, size_t size)
+{
+ const sqfs_u8 *in = input;
+ char *out, *ptr;
+ size_t i;
+
+ out = malloc(2 * size + 1);
+ if (out == NULL)
+ return NULL;
+
+ ptr = out;
+
+ for (i = 0; i < size; ++i) {
+ *(ptr++) = hexmap[ in[i] & 0x0F];
+ *(ptr++) = hexmap[(in[i] >> 4) & 0x0F];
+ }
+
+ *ptr = '\0';
+ return out;
+}
+
+static int compare_u64(const void *a, const void *b)
+{
+ sqfs_u64 lhs = *((const sqfs_u64 *)a);
+ sqfs_u64 rhs = *((const sqfs_u64 *)b);
+
+ return (lhs < rhs ? -1 : (lhs > rhs ? 1 : 0));
+}
+
+int sqfs_xattr_writer_begin(sqfs_xattr_writer_t *xwr, sqfs_u32 flags)
+{
+ if (flags != 0)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ xwr->kv_start = xwr->kv_pairs.used;
+ return 0;
+}
+
+int sqfs_xattr_writer_add(sqfs_xattr_writer_t *xwr, const char *key,
+ const void *value, size_t size)
+{
+ size_t i, key_index, old_value_index, value_index;
+ sqfs_u64 kv_pair;
+ char *value_str;
+ int err;
+
+ if (sqfs_get_xattr_prefix_id(key) < 0)
+ return SQFS_ERROR_UNSUPPORTED;
+
+ err = str_table_get_index(&xwr->keys, key, &key_index);
+ if (err)
+ return err;
+
+ value_str = to_base32(value, size);
+ if (value_str == NULL)
+ return SQFS_ERROR_ALLOC;
+
+ err = str_table_get_index(&xwr->values, value_str, &value_index);
+ free(value_str);
+ if (err)
+ return err;
+
+ str_table_add_ref(&xwr->values, value_index);
+
+ if (sizeof(size_t) > sizeof(sqfs_u32)) {
+ if (key_index > 0x0FFFFFFFFUL || value_index > 0x0FFFFFFFFUL)
+ return SQFS_ERROR_OVERFLOW;
+ }
+
+ kv_pair = MK_PAIR(key_index, value_index);
+
+ for (i = xwr->kv_start; i < xwr->kv_pairs.used; ++i) {
+ sqfs_u64 ent = ((sqfs_u64 *)xwr->kv_pairs.data)[i];
+
+ if (ent == kv_pair)
+ return 0;
+
+ if (GET_KEY(ent) == key_index) {
+ old_value_index = GET_VALUE(ent);
+
+ str_table_del_ref(&xwr->values, old_value_index);
+
+ ((sqfs_u64 *)xwr->kv_pairs.data)[i] = kv_pair;
+ return 0;
+ }
+ }
+
+ return array_append(&xwr->kv_pairs, &kv_pair);
+}
+
+int sqfs_xattr_writer_end(sqfs_xattr_writer_t *xwr, sqfs_u32 *out)
+{
+ kv_block_desc_t blk;
+ rbtree_node_t *n;
+ sqfs_u32 index;
+ int ret;
+
+ memset(&blk, 0, sizeof(blk));
+ blk.start = xwr->kv_start;
+ blk.count = xwr->kv_pairs.used - xwr->kv_start;
+
+ if (blk.count == 0) {
+ *out = 0xFFFFFFFF;
+ return 0;
+ }
+
+ array_sort_range(&xwr->kv_pairs, blk.start, blk.count, compare_u64);
+
+ n = rbtree_lookup(&xwr->kv_block_tree, &blk);
+
+ if (n != NULL) {
+ index = *((sqfs_u32 *)rbtree_node_value(n));
+ xwr->kv_pairs.used = xwr->kv_start;
+ } else {
+ index = xwr->num_blocks;
+
+ ret = rbtree_insert(&xwr->kv_block_tree, &blk, &index);
+ if (ret != 0)
+ return ret;
+
+ xwr->num_blocks += 1;
+ n = rbtree_lookup(&xwr->kv_block_tree, &blk);
+
+ if (xwr->kv_block_last == NULL) {
+ xwr->kv_block_first = rbtree_node_key(n);
+ xwr->kv_block_last = xwr->kv_block_first;
+ } else {
+ xwr->kv_block_last->next = rbtree_node_key(n);
+ xwr->kv_block_last = xwr->kv_block_last->next;
+ }
+ }
+
+ *out = index;
+ return 0;
+}