diff options
Diffstat (limited to 'lib/sqfs/data_reader.c')
-rw-r--r-- | lib/sqfs/data_reader.c | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/lib/sqfs/data_reader.c b/lib/sqfs/data_reader.c new file mode 100644 index 0000000..3de59ff --- /dev/null +++ b/lib/sqfs/data_reader.c @@ -0,0 +1,371 @@ +/* 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/block_processor.h" +#include "sqfs/data_reader.h" +#include "sqfs/compress.h" +#include "sqfs/error.h" +#include "sqfs/table.h" +#include "sqfs/inode.h" +#include "sqfs/data.h" +#include "sqfs/io.h" +#include "util.h" + +#include <stdlib.h> +#include <string.h> + +struct sqfs_data_reader_t { + sqfs_fragment_t *frag; + sqfs_compressor_t *cmp; + sqfs_block_t *data_block; + sqfs_block_t *frag_block; + + uint64_t current_block; + + sqfs_file_t *file; + + uint32_t num_fragments; + uint32_t current_frag_index; + uint32_t block_size; + + uint8_t scratch[]; +}; + +static int get_block(sqfs_data_reader_t *data, uint64_t off, uint32_t size, + size_t unpacked_size, sqfs_block_t **out) +{ + sqfs_block_t *blk = alloc_flex(sizeof(*blk), 1, unpacked_size); + size_t on_disk_size; + ssize_t ret; + int err; + + if (blk == NULL) + return SQFS_ERROR_ALLOC; + + blk->size = unpacked_size; + + if (SQFS_IS_SPARSE_BLOCK(size)) { + *out = blk; + return 0; + } + + on_disk_size = SQFS_ON_DISK_BLOCK_SIZE(size); + + if (on_disk_size > unpacked_size) + return SQFS_ERROR_OVERFLOW; + + if (SQFS_IS_BLOCK_COMPRESSED(size)) { + err = data->file->read_at(data->file, off, + data->scratch, on_disk_size); + if (err) { + free(blk); + return err; + } + + ret = data->cmp->do_block(data->cmp, data->scratch, + on_disk_size, blk->data, blk->size); + if (ret <= 0) + err = ret < 0 ? ret : SQFS_ERROR_OVERFLOW; + } else { + err = data->file->read_at(data->file, off, + blk->data, on_disk_size); + } + + if (err) { + free(blk); + return err; + } + + *out = blk; + return 0; +} + +static int precache_data_block(sqfs_data_reader_t *data, uint64_t location, + uint32_t size) +{ + int ret; + + if (data->data_block != NULL && data->current_block == location) + return 0; + + free(data->data_block); + + ret = get_block(data, location, size, data->block_size, + &data->data_block); + + if (ret < 0) { + data->data_block = NULL; + return -1; + } + + data->current_block = location; + return 0; +} + +static int precache_fragment_block(sqfs_data_reader_t *data, size_t idx) +{ + int ret; + + if (data->frag_block != NULL && idx == data->current_frag_index) + return 0; + + if (idx >= data->num_fragments) + return SQFS_ERROR_OUT_OF_BOUNDS; + + free(data->frag_block); + + ret = get_block(data, data->frag[idx].start_offset, + data->frag[idx].size, data->block_size, + &data->frag_block); + if (ret < 0) + return -1; + + data->current_frag_index = idx; + return 0; +} + +sqfs_data_reader_t *sqfs_data_reader_create(sqfs_file_t *file, + size_t block_size, + sqfs_compressor_t *cmp) +{ + sqfs_data_reader_t *data = alloc_flex(sizeof(*data), 1, block_size); + + if (data != NULL) { + data->file = file; + data->block_size = block_size; + data->cmp = cmp; + } + + return data; +} + +int sqfs_data_reader_load_fragment_table(sqfs_data_reader_t *data, + const sqfs_super_t *super) +{ + void *raw_frag; + size_t size; + uint32_t i; + int ret; + + free(data->frag_block); + free(data->frag); + + data->frag = NULL; + data->frag_block = NULL; + data->num_fragments = 0; + data->current_frag_index = 0; + + if (super->fragment_entry_count == 0 || + (super->flags & SQFS_FLAG_NO_FRAGMENTS) != 0) { + return 0; + } + + if (super->fragment_table_start >= super->bytes_used) + return SQFS_ERROR_OUT_OF_BOUNDS; + + if (SZ_MUL_OV(sizeof(data->frag[0]), super->fragment_entry_count, + &size)) { + return SQFS_ERROR_OVERFLOW; + } + + ret = sqfs_read_table(data->file, data->cmp, size, + super->fragment_table_start, + super->directory_table_start, + super->fragment_table_start, &raw_frag); + if (ret) + return ret; + + data->num_fragments = super->fragment_entry_count; + data->current_frag_index = super->fragment_entry_count; + data->frag = raw_frag; + + for (i = 0; i < data->num_fragments; ++i) { + data->frag[i].size = le32toh(data->frag[i].size); + data->frag[i].start_offset = + le64toh(data->frag[i].start_offset); + } + + return 0; +} + +void sqfs_data_reader_destroy(sqfs_data_reader_t *data) +{ + free(data->data_block); + free(data->frag_block); + free(data->frag); + free(data); +} + +int sqfs_data_reader_get_block(sqfs_data_reader_t *data, + const sqfs_inode_generic_t *inode, + size_t index, sqfs_block_t **out) +{ + size_t i, unpacked_size; + uint64_t off, filesz; + + if (inode->base.type == SQFS_INODE_FILE) { + off = inode->data.file.blocks_start; + filesz = inode->data.file.file_size; + } else if (inode->base.type == SQFS_INODE_EXT_FILE) { + off = inode->data.file_ext.blocks_start; + filesz = inode->data.file_ext.file_size; + } else { + return SQFS_ERROR_NOT_FILE; + } + + if (index >= inode->num_file_blocks) + return SQFS_ERROR_OUT_OF_BOUNDS; + + for (i = 0; i < index; ++i) { + off += SQFS_ON_DISK_BLOCK_SIZE(inode->block_sizes[i]); + filesz -= data->block_size; + } + + unpacked_size = filesz < data->block_size ? filesz : data->block_size; + + return get_block(data, off, inode->block_sizes[index], + unpacked_size, out); +} + +int sqfs_data_reader_get_fragment(sqfs_data_reader_t *data, + const sqfs_inode_generic_t *inode, + sqfs_block_t **out) +{ + uint32_t frag_idx, frag_off, frag_sz; + sqfs_block_t *blk; + uint64_t filesz; + + if (inode->base.type == SQFS_INODE_EXT_FILE) { + filesz = inode->data.file_ext.file_size; + frag_idx = inode->data.file_ext.fragment_idx; + frag_off = inode->data.file_ext.fragment_offset; + } else if (inode->base.type == SQFS_INODE_FILE) { + filesz = inode->data.file.file_size; + frag_idx = inode->data.file.fragment_index; + frag_off = inode->data.file.fragment_offset; + } else { + return -1; + } + + if (inode->num_file_blocks * data->block_size >= filesz) { + *out = NULL; + return 0; + } + + frag_sz = filesz % data->block_size; + + if (precache_fragment_block(data, frag_idx)) + return -1; + + if (frag_off + frag_sz > data->block_size) + return -1; + + blk = alloc_flex(sizeof(*blk), 1, frag_sz); + if (blk == NULL) + return -1; + + blk->size = frag_sz; + memcpy(blk->data, (char *)data->frag_block->data + frag_off, frag_sz); + + *out = blk; + return 0; +} + +ssize_t sqfs_data_reader_read(sqfs_data_reader_t *data, + const sqfs_inode_generic_t *inode, + uint64_t offset, void *buffer, size_t size) +{ + uint32_t frag_idx, frag_off; + size_t i, diff, total = 0; + uint64_t off, filesz; + char *ptr; + + /* work out file location and size */ + if (inode->base.type == SQFS_INODE_EXT_FILE) { + off = inode->data.file_ext.blocks_start; + filesz = inode->data.file_ext.file_size; + frag_idx = inode->data.file_ext.fragment_idx; + frag_off = inode->data.file_ext.fragment_offset; + } else { + off = inode->data.file.blocks_start; + filesz = inode->data.file.file_size; + frag_idx = inode->data.file.fragment_index; + frag_off = inode->data.file.fragment_offset; + } + + /* find location of the first block */ + i = 0; + + while (offset > data->block_size && i < inode->num_file_blocks) { + off += SQFS_ON_DISK_BLOCK_SIZE(inode->block_sizes[i++]); + offset -= data->block_size; + + if (filesz >= data->block_size) { + filesz -= data->block_size; + } else { + filesz = 0; + } + } + + /* copy data from blocks */ + while (i < inode->num_file_blocks && size > 0 && filesz > 0) { + diff = data->block_size - offset; + if (size < diff) + diff = size; + + if (SQFS_IS_SPARSE_BLOCK(inode->block_sizes[i])) { + memset(buffer, 0, diff); + } else { + if (precache_data_block(data, off, + inode->block_sizes[i])) { + return -1; + } + + memcpy(buffer, (char *)data->data_block->data + offset, + diff); + off += SQFS_ON_DISK_BLOCK_SIZE(inode->block_sizes[i]); + } + + if (filesz >= data->block_size) { + filesz -= data->block_size; + } else { + filesz = 0; + } + + ++i; + offset = 0; + size -= diff; + total += diff; + buffer = (char *)buffer + diff; + } + + /* copy from fragment */ + if (i == inode->num_file_blocks && size > 0 && filesz > 0) { + if (precache_fragment_block(data, frag_idx)) + return -1; + + if (frag_off + filesz > data->block_size) + return SQFS_ERROR_OUT_OF_BOUNDS; + + if (offset >= filesz) + return total; + + if (offset + size > filesz) + size = filesz - offset; + + if (size == 0) + return total; + + ptr = (char *)data->frag_block->data + frag_off + offset; + memcpy(buffer, ptr, size); + total += size; + } + + return total; +} |