diff options
Diffstat (limited to 'lib/sqfs/src/read_inode.c')
-rw-r--r-- | lib/sqfs/src/read_inode.c | 424 |
1 files changed, 424 insertions, 0 deletions
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; +} |