diff options
author | David Oberhollenzer <david.oberhollenzer@sigma-star.at> | 2023-01-31 11:21:30 +0100 |
---|---|---|
committer | David Oberhollenzer <david.oberhollenzer@sigma-star.at> | 2023-01-31 13:51:49 +0100 |
commit | cdccc69c62579b0c13b35fad0728079652b8f3c9 (patch) | |
tree | 9fa54c710f73c5e08a9c8466e7a712eb63ee07ac /lib/sqfs/src/data_reader.c | |
parent | 2182129c8f359c4fa1390eaba7a65b595ccd4182 (diff) |
Move library source into src sub-directory
Signed-off-by: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
Diffstat (limited to 'lib/sqfs/src/data_reader.c')
-rw-r--r-- | lib/sqfs/src/data_reader.c | 374 |
1 files changed, 374 insertions, 0 deletions
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; +} |