diff options
-rw-r--r-- | include/fstree.h | 10 | ||||
-rw-r--r-- | include/sqfs/dir.h | 159 | ||||
-rw-r--r-- | include/sqfs/error.h | 4 | ||||
-rw-r--r-- | include/sqfs/predef.h | 1 | ||||
-rw-r--r-- | lib/sqfs/Makemodule.am | 1 | ||||
-rw-r--r-- | lib/sqfs/dir_reader.c | 266 | ||||
-rw-r--r-- | lib/sqfshelper/deserialize_fstree.c | 165 | ||||
-rw-r--r-- | lib/sqfshelper/tree_node_from_inode.c | 4 |
8 files changed, 499 insertions, 111 deletions
diff --git a/include/fstree.h b/include/fstree.h index 4c02ce5..880a870 100644 --- a/include/fstree.h +++ b/include/fstree.h @@ -16,6 +16,8 @@ #include <stddef.h> #include <stdio.h> +#include "sqfs/inode.h" + #include "str_table.h" #define FSTREE_XATTR_KEY_BUCKETS 31 @@ -105,12 +107,6 @@ struct dir_info_t { directory meta data to disk. */ uint64_t size; - /* Start block offset, relative to directory table start. */ - uint64_t start_block; - - /* Byte offset into the uncompressed meta data block. */ - uint32_t block_offset; - /* Set to true for implicitly generated directories. */ bool created_implicitly; }; @@ -126,6 +122,8 @@ struct tree_node_t { /* For the root node, this points to an empty string. */ char *name; + sqfs_inode_generic_t *inode; + /* A pointer to an extended attribute array or NULL if unused. diff --git a/include/sqfs/dir.h b/include/sqfs/dir.h index 18950df..d8603d7 100644 --- a/include/sqfs/dir.h +++ b/include/sqfs/dir.h @@ -59,6 +59,29 @@ * writer used for inodes. */ +/** + * @struct sqfs_dir_reader_t + * + * @brief Abstracts reading of directory entries + * + * SquashFS stores directory listings and inode structures seperated from + * each other in meta data blocks. + * + * The sqfs_dir_reader_t abstracts access to the filesystem tree in a SquashFS + * through a fairly simple interface. It keeps two meta data readers internally + * for reading directory listings and inodes. Externally, it offers a few + * simple functions for iterating over the contents of a directory that + * completely take care of fetching/decoding headers and sifting through the + * multi level hierarchie used for storing them on disk. + * + * See @ref sqfs_dir_writer_t for an overview on how directory entries are + * stored in SquashFS. + * + * The reader also abstracts easy access to the underlying inodes, allowing + * direct access to the inode referred to by a directory entry. + */ + + #define SQFS_MAX_DIR_ENT 256 /** @@ -341,6 +364,142 @@ SQFS_API sqfs_inode_generic_t *sqfs_dir_writer_create_inode(const sqfs_dir_writer_t *writer, size_t hlinks, uint32_t xattr, uint32_t parent_ino); +/** + * @brief Create a directory reader. + * + * @memberof sqfs_dir_reader_t + * + * @param super A pointer to the super block. Kept internally an used for + * resolving table positions. + * @param cmp A compressor to use for unpacking meta data blocks. + * @param file The input file to read from. + * + * @return A new directory reader on success, NULL on allocation failure. + */ +SQFS_API sqfs_dir_reader_t *sqfs_dir_reader_create(const sqfs_super_t *super, + sqfs_compressor_t *cmp, + sqfs_file_t *file); + +/** + * @brief Cleanup a directory reader and free all its memory. + * + * @memberof sqfs_dir_reader_t + */ +SQFS_API void sqfs_dir_reader_destroy(sqfs_dir_reader_t *rd); + +/** + * @brief Navigate a directory reader to the location of a directory + * represented by an inode. + * + * @memberof sqfs_dir_reader_t + * + * This function seeks to the meta data block containing the directory + * listing that the given inode referes to and resets the internal state. + * After that, consequtive cals to @ref sqfs_dir_reader_read can be made + * to iterate over the directory contents. + * + * @param rd A pointer to a directory reader. + * @param inode An directory or extended directory inode. + * + * @return Zero on success, an @ref E_SQFS_ERROR value on failure. + */ +SQFS_API int sqfs_dir_reader_open_dir(sqfs_dir_reader_t *rd, + const sqfs_inode_generic_t *inode); + +/** + * @brief Reset a directory reader back to the beginning of the listing. + * + * @memberof sqfs_dir_reader_t + * + * @param rd A pointer to a directory reader. + * + * @return Zero on success, an @ref E_SQFS_ERROR value on failure. + */ +SQFS_API int sqfs_dir_reader_rewind(sqfs_dir_reader_t *rd); + +/** + * @brief Seek through the current directory listing to locate an + * entry by name. + * + * @memberof sqfs_dir_reader_t + * + * @param rd A pointer to a directory reader. + * @param name The name of the entry to find. + * + * @return Zero on success, an @ref E_SQFS_ERROR value on failure. + */ +SQFS_API int sqfs_dir_reader_find(sqfs_dir_reader_t *rd, const char *name); + +/** + * @brief Read a directory entry and advance the internal position indicator + * to the next one. + * + * @memberof sqfs_dir_reader_t + * + * Call this function repeatedly to iterate over a directory listing. It + * returns a positive number to indicate that it couldn't fetch any more data + * because the end of the listing was reached. A negative value indicates an + * error. + * + * After calling this function, you can use @ref sqfs_dir_reader_get_inode to + * read the full inode structure that the current entry referes to. + * + * @param rd A pointer to a directory reader. + * @param out Returns a pointer to a directory entry on success that can be + * freed with a single free call. + * + * @return Zero on success, an @ref E_SQFS_ERROR value on failure, a positive + * number if the end of the current directory listing has been reached. + */ +SQFS_API int sqfs_dir_reader_read(sqfs_dir_reader_t *rd, + sqfs_dir_entry_t **out); + +/** + * @brief Read the inode that the current directory entry points to. + * + * @memberof sqfs_dir_reader_t + * + * @param rd A pointer to a directory reader. + * @param out Returns a pointer to a generic inode that can be freed with a + * single free call. + * + * @return Zero on success, an @ref E_SQFS_ERROR value on failure. + */ +SQFS_API int sqfs_dir_reader_get_inode(sqfs_dir_reader_t *rd, + sqfs_inode_generic_t **inode); + +/** + * @brief Read the root inode using the location given by the super block. + * + * @memberof sqfs_dir_reader_t + * + * @param rd A pointer to a directory reader. + * @param out Returns a pointer to a generic inode that can be freed with a + * single free call. + * + * @return Zero on success, an @ref E_SQFS_ERROR value on failure. + */ +SQFS_API int sqfs_dir_reader_get_root_inode(sqfs_dir_reader_t *rd, + sqfs_inode_generic_t **inode); + +/** + * @brief Find an inode through path traversal from the root node downwards. + * + * @memberof sqfs_dir_reader_t + * + * @param rd A pointer to a directory reader. + * @param path A path to resolve into an inode. Forward or backward slashes can + * be used to seperate path components. Resolving '.' or '..' is + * not supported. + * @param out Returns a pointer to a generic inode that can be freed with a + * single free call. + * + * @return Zero on success, an @ref E_SQFS_ERROR value on failure. + */ +SQFS_API int sqfs_dir_reader_find_by_path(sqfs_dir_reader_t *rd, + const char *path, + sqfs_inode_generic_t **out); + #ifdef __cplusplus } #endif diff --git a/include/sqfs/error.h b/include/sqfs/error.h index a9a0acf..ddfeadd 100644 --- a/include/sqfs/error.h +++ b/include/sqfs/error.h @@ -103,6 +103,10 @@ typedef enum { * legal range (4k to 1M). */ SQFS_ERROR_SUPER_BLOCK_SIZE = -11, + + SQFS_ERROR_NOT_DIR = -12, + + SQFS_ERROR_NO_ENTRY = -13, } E_SQFS_ERROR; #endif /* SQFS_ERROR_H */ diff --git a/include/sqfs/predef.h b/include/sqfs/predef.h index 95d907f..4041a57 100644 --- a/include/sqfs/predef.h +++ b/include/sqfs/predef.h @@ -63,6 +63,7 @@ typedef struct sqfs_block_processor_t sqfs_block_processor_t; typedef struct sqfs_compressor_config_t sqfs_compressor_config_t; typedef struct sqfs_compressor_t sqfs_compressor_t; typedef struct sqfs_dir_writer_t sqfs_dir_writer_t; +typedef struct sqfs_dir_reader_t sqfs_dir_reader_t; typedef struct sqfs_id_table_t sqfs_id_table_t; typedef struct sqfs_meta_reader_t sqfs_meta_reader_t; typedef struct sqfs_meta_writer_t sqfs_meta_writer_t; diff --git a/lib/sqfs/Makemodule.am b/lib/sqfs/Makemodule.am index 7d228a4..807c104 100644 --- a/lib/sqfs/Makemodule.am +++ b/lib/sqfs/Makemodule.am @@ -14,6 +14,7 @@ libsquashfs_la_SOURCES += lib/sqfs/read_inode.c lib/sqfs/write_inode.c libsquashfs_la_SOURCES += lib/sqfs/dir_writer.c lib/sqfs/xattr_reader.c libsquashfs_la_SOURCES += lib/sqfs/read_table.c lib/sqfs/comp/compressor.c libsquashfs_la_SOURCES += lib/sqfs/io_stdin.c lib/sqfs/comp/internal.h +libsquashfs_la_SOURCES += lib/sqfs/dir_reader.c libsquashfs_la_SOURCES += lib/sqfs/blk_proc/process_block.c lib/sqfs/io.c libsquashfs_la_SOURCES += lib/sqfs/blk_proc/internal.h libsquashfs_la_CPPFLAGS = $(AM_CPPFLAGS) diff --git a/lib/sqfs/dir_reader.c b/lib/sqfs/dir_reader.c new file mode 100644 index 0000000..d4bfd27 --- /dev/null +++ b/lib/sqfs/dir_reader.c @@ -0,0 +1,266 @@ +/* SPDX-License-Identifier: LGPL-3.0-or-later */ +/* + * fs_reader.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#define SQFS_BUILDING_DLL +#include "config.h" + +#include "sqfs/meta_reader.h" +#include "sqfs/compress.h" +#include "sqfs/super.h" +#include "sqfs/inode.h" +#include "sqfs/error.h" +#include "sqfs/dir.h" +#include "util.h" + +#include <string.h> +#include <stdlib.h> + +struct sqfs_dir_reader_t { + sqfs_meta_reader_t *meta_dir; + sqfs_meta_reader_t *meta_inode; + const sqfs_super_t *super; + + sqfs_dir_header_t hdr; + uint64_t dir_block_start; + size_t entries; + size_t size; + + size_t start_size; + uint16_t dir_offset; + uint16_t inode_offset; +}; + +sqfs_dir_reader_t *sqfs_dir_reader_create(const sqfs_super_t *super, + sqfs_compressor_t *cmp, + sqfs_file_t *file) +{ + sqfs_dir_reader_t *rd = calloc(1, sizeof(*rd)); + uint64_t start, limit; + + if (rd == NULL) + return NULL; + + 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) { + free(rd); + return NULL; + } + + 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) { + sqfs_meta_reader_destroy(rd->meta_inode); + free(rd); + return NULL; + } + + rd->super = super; + return rd; +} + +void sqfs_dir_reader_destroy(sqfs_dir_reader_t *rd) +{ + sqfs_meta_reader_destroy(rd->meta_inode); + sqfs_meta_reader_destroy(rd->meta_dir); + free(rd); +} + +int sqfs_dir_reader_open_dir(sqfs_dir_reader_t *rd, + const sqfs_inode_generic_t *inode) +{ + uint64_t block_start; + size_t size, offset; + + if (inode->base.type == SQFS_INODE_DIR) { + size = inode->data.dir.size; + offset = inode->data.dir.offset; + block_start = inode->data.dir.start_block; + } else if (inode->base.type == SQFS_INODE_EXT_DIR) { + size = inode->data.dir_ext.size; + offset = inode->data.dir_ext.offset; + block_start = inode->data.dir_ext.start_block; + } else { + return SQFS_ERROR_NOT_DIR; + } + + memset(&rd->hdr, 0, sizeof(rd->hdr)); + rd->size = size; + rd->entries = 0; + + if (rd->size <= sizeof(rd->hdr)) + return 0; + + block_start += rd->super->directory_table_start; + + rd->dir_block_start = block_start; + rd->dir_offset = offset; + rd->start_size = size; + + return sqfs_meta_reader_seek(rd->meta_dir, block_start, offset); +} + +int sqfs_dir_reader_read(sqfs_dir_reader_t *rd, sqfs_dir_entry_t **out) +{ + sqfs_dir_entry_t *ent; + size_t count; + int err; + + if (!rd->entries) { + if (rd->size < sizeof(rd->hdr)) + return 1; + + err = sqfs_meta_reader_read_dir_header(rd->meta_dir, &rd->hdr); + if (err) + return err; + + rd->size -= sizeof(rd->hdr); + rd->entries = rd->hdr.count + 1; + } + + err = sqfs_meta_reader_read_dir_ent(rd->meta_dir, &ent); + if (err) + return err; + + count = sizeof(*ent) + strlen((const char *)ent->name); + + if (count > rd->size) { + rd->size = 0; + rd->entries = 0; + } else { + rd->size -= count; + rd->entries -= 1; + } + + rd->inode_offset = ent->offset; + *out = ent; + return 0; +} + +int sqfs_dir_reader_rewind(sqfs_dir_reader_t *rd) +{ + memset(&rd->hdr, 0, sizeof(rd->hdr)); + rd->size = rd->start_size; + rd->entries = 0; + + return sqfs_meta_reader_seek(rd->meta_dir, rd->dir_block_start, + rd->dir_offset); +} + +int sqfs_dir_reader_find(sqfs_dir_reader_t *rd, const char *name) +{ + sqfs_dir_entry_t *ent; + int ret; + + if (rd->size != rd->start_size) { + ret = sqfs_dir_reader_rewind(rd); + if (ret) + 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) +{ + uint64_t block_start; + + block_start = rd->hdr.start_block; + + return sqfs_meta_reader_read_inode(rd->meta_inode, rd->super, + block_start, rd->inode_offset, + inode); +} + +int sqfs_dir_reader_get_root_inode(sqfs_dir_reader_t *rd, + sqfs_inode_generic_t **inode) +{ + uint64_t block_start = rd->super->root_inode_ref >> 16; + uint16_t offset = rd->super->root_inode_ref & 0xFFFF; + + return sqfs_meta_reader_read_inode(rd->meta_inode, rd->super, + block_start, offset, inode); +} + +int sqfs_dir_reader_find_by_path(sqfs_dir_reader_t *rd, const char *path, + sqfs_inode_generic_t **out) +{ + sqfs_inode_generic_t *inode; + sqfs_dir_entry_t *ent; + const char *ptr; + int ret; + + ret = sqfs_dir_reader_get_root_inode(rd, &inode); + if (ret) + return ret; + + while (*path != '\0') { + if (*path == '/' || *path == '\\') { + while (*path == '/' || *path == '\\') + ++path; + continue; + } + + ret = sqfs_dir_reader_open_dir(rd, inode); + free(inode); + if (ret) + return ret; + + ptr = strchr(path, '/'); + if (ptr == NULL) + ptr = strchrnul(path, '\\'); + + do { + ret = sqfs_dir_reader_read(rd, &ent); + if (ret < 0) + return ret; + + if (ret == 0) { + ret = strncmp((const char *)ent->name, + path, ptr - path); + if (ret == 0) + ret = ent->name[ptr - path]; + free(ent); + } + } while (ret < 0); + + if (ret > 0) + return SQFS_ERROR_NO_ENTRY; + + ret = sqfs_dir_reader_get_inode(rd, &inode); + if (ret) + return ret; + + path = ptr; + } + + *out = inode; + return 0; +} diff --git a/lib/sqfshelper/deserialize_fstree.c b/lib/sqfshelper/deserialize_fstree.c index ef000d4..e0f53e7 100644 --- a/lib/sqfshelper/deserialize_fstree.c +++ b/lib/sqfshelper/deserialize_fstree.c @@ -93,102 +93,70 @@ fail: return false; } -static int fill_dir(sqfs_meta_reader_t *ir, sqfs_meta_reader_t *dr, - tree_node_t *root, sqfs_super_t *super, - sqfs_id_table_t *idtbl, +static int fill_dir(sqfs_dir_reader_t *dr, + tree_node_t *root, sqfs_id_table_t *idtbl, fstree_t *fs, sqfs_xattr_reader_t *xr, int flags) { sqfs_inode_generic_t *inode; - sqfs_dir_header_t hdr; sqfs_dir_entry_t *ent; tree_node_t *n, *prev; - uint64_t block_start; - size_t size, diff; - uint32_t i; int err; - size = root->data.dir->size; - if (size <= sizeof(hdr)) - return 0; - - block_start = root->data.dir->start_block; - block_start += super->directory_table_start; - - if (sqfs_meta_reader_seek(dr, block_start, - root->data.dir->block_offset)) { - return -1; - } - - while (size > sizeof(hdr)) { - if (sqfs_meta_reader_read_dir_header(dr, &hdr)) + for (;;) { + err = sqfs_dir_reader_read(dr, &ent); + if (err > 0) + break; + if (err < 0) return -1; - size -= sizeof(hdr); - - for (i = 0; i <= hdr.count && size > sizeof(*ent); ++i) { - if (sqfs_meta_reader_read_dir_ent(dr, &ent)) - return -1; - - diff = sizeof(*ent) + strlen((char *)ent->name); - if (diff > size) { - free(ent); - break; - } - size -= diff; - - if (should_skip(ent->type, flags)) { - free(ent); - continue; - } + if (should_skip(ent->type, flags)) { + free(ent); + continue; + } - if (!is_name_sane((const char *)ent->name)) { - free(ent); - continue; - } + if (!is_name_sane((const char *)ent->name)) { + free(ent); + continue; + } - err = sqfs_meta_reader_read_inode(ir, super, - hdr.start_block, - ent->offset, &inode); - if (err) { - free(ent); - return err; - } + err = sqfs_dir_reader_get_inode(dr, &inode); + if (err) { + free(ent); + return err; + } - n = tree_node_from_inode(inode, idtbl, - (char *)ent->name); + n = tree_node_from_inode(inode, idtbl, (char *)ent->name); + if (n == NULL) { + free(ent); + free(inode); + return -1; + } - if (n == NULL) { - free(ent); - free(inode); - return -1; - } + if (node_would_be_own_parent(root, n)) { + fputs("WARNING: Found a directory that " + "contains itself, skipping loop back " + "reference!\n", stderr); + free(n); + free(ent); + free(inode); + continue; + } - if (node_would_be_own_parent(root, n)) { - fputs("WARNING: Found a directory that " - "contains itself, skipping loop back " - "reference!\n", stderr); + if (flags & RDTREE_READ_XATTR) { + if (restore_xattr(xr, fs, n, inode)) { free(n); free(ent); free(inode); - continue; + return -1; } + } - if (flags & RDTREE_READ_XATTR) { - if (restore_xattr(xr, fs, n, inode)) { - free(n); - free(ent); - free(inode); - return -1; - } - } + free(ent); - free(ent); - free(inode); - - n->parent = root; - n->next = root->data.dir->children; - root->data.dir->children = n; - } + n->inode = inode; + n->parent = root; + n->next = root->data.dir->children; + root->data.dir->children = n; } n = root->data.dir->children; @@ -196,11 +164,16 @@ static int fill_dir(sqfs_meta_reader_t *ir, sqfs_meta_reader_t *dr, while (n != NULL) { if (S_ISDIR(n->mode)) { - if (fill_dir(ir, dr, n, super, idtbl, fs, xr, flags)) + err = sqfs_dir_reader_open_dir(dr, n->inode); + if (err) + return -1; + + if (fill_dir(dr, n, idtbl, fs, xr, flags)) return -1; if (n->data.dir->children == NULL && (flags & RDTREE_NO_EMPTY)) { + free(n->inode); if (prev == NULL) { root->data.dir->children = n->next; free(n); @@ -214,6 +187,9 @@ static int fill_dir(sqfs_meta_reader_t *ir, sqfs_meta_reader_t *dr, } } + free(n->inode); + n->inode = NULL; + prev = n; n = n->next; } @@ -224,29 +200,15 @@ static int fill_dir(sqfs_meta_reader_t *ir, sqfs_meta_reader_t *dr, int deserialize_fstree(fstree_t *out, sqfs_super_t *super, sqfs_compressor_t *cmp, sqfs_file_t *file, int flags) { - uint64_t block_start, limit; - sqfs_meta_reader_t *ir, *dr; sqfs_inode_generic_t *root; sqfs_xattr_reader_t *xr; sqfs_id_table_t *idtbl; + sqfs_dir_reader_t *dr; int status = -1; - size_t offset; - - ir = sqfs_meta_reader_create(file, cmp, super->inode_table_start, - super->directory_table_start); - if (ir == NULL) - return -1; - - limit = super->id_table_start; - if (super->export_table_start < limit) - limit = super->export_table_start; - if (super->fragment_table_start < limit) - limit = super->fragment_table_start; - dr = sqfs_meta_reader_create(file, cmp, super->directory_table_start, - limit); + dr = sqfs_dir_reader_create(super, cmp, file); if (dr == NULL) - goto out_ir; + return -1; idtbl = sqfs_id_table_create(); if (idtbl == NULL) @@ -262,9 +224,7 @@ int deserialize_fstree(fstree_t *out, sqfs_super_t *super, if (sqfs_xattr_reader_load_locations(xr)) goto out_xr; - block_start = super->root_inode_ref >> 16; - offset = super->root_inode_ref & 0xFFFF; - if (sqfs_meta_reader_read_inode(ir, super, block_start, offset, &root)) + if (sqfs_dir_reader_get_root_inode(dr, &root)) goto out_xr; if (root->base.type != SQFS_INODE_DIR && @@ -308,9 +268,14 @@ int deserialize_fstree(fstree_t *out, sqfs_super_t *super, } } + if (sqfs_dir_reader_open_dir(dr, root)) { + free(root); + goto fail_fs; + } + free(root); - if (fill_dir(ir, dr, out->root, super, idtbl, out, xr, flags)) + if (fill_dir(dr, out->root, idtbl, out, xr, flags)) goto fail_fs; tree_node_sort_recursive(out->root); @@ -321,9 +286,7 @@ out_xr: out_id: sqfs_id_table_destroy(idtbl); out_dr: - sqfs_meta_reader_destroy(dr); -out_ir: - sqfs_meta_reader_destroy(ir); + sqfs_dir_reader_destroy(dr); return status; fail_fs: fstree_cleanup(out); diff --git a/lib/sqfshelper/tree_node_from_inode.c b/lib/sqfshelper/tree_node_from_inode.c index f5b643c..3ada722 100644 --- a/lib/sqfshelper/tree_node_from_inode.c +++ b/lib/sqfshelper/tree_node_from_inode.c @@ -82,16 +82,12 @@ tree_node_t *tree_node_from_inode(sqfs_inode_generic_t *inode, out->name += sizeof(dir_info_t); out->data.dir->size = inode->data.dir.size; - out->data.dir->start_block = inode->data.dir.start_block; - out->data.dir->block_offset = inode->data.dir.offset; break; case SQFS_INODE_EXT_DIR: out->data.dir = (dir_info_t *)out->payload; out->name += sizeof(dir_info_t); out->data.dir->size = inode->data.dir_ext.size; - out->data.dir->start_block = inode->data.dir_ext.start_block; - out->data.dir->block_offset = inode->data.dir_ext.offset; break; case SQFS_INODE_FILE: out->data.file = (file_info_t *)out->payload; |