From cdccc69c62579b0c13b35fad0728079652b8f3c9 Mon Sep 17 00:00:00 2001 From: David Oberhollenzer Date: Tue, 31 Jan 2023 11:21:30 +0100 Subject: Move library source into src sub-directory Signed-off-by: David Oberhollenzer --- bin/rdsquashfs/src/describe.c | 139 +++++++++++++++ bin/rdsquashfs/src/dump_xattrs.c | 120 +++++++++++++ bin/rdsquashfs/src/fill_files.c | 186 ++++++++++++++++++++ bin/rdsquashfs/src/list_files.c | 158 +++++++++++++++++ bin/rdsquashfs/src/options.c | 226 ++++++++++++++++++++++++ bin/rdsquashfs/src/rdsquashfs.c | 275 +++++++++++++++++++++++++++++ bin/rdsquashfs/src/rdsquashfs.h | 82 +++++++++ bin/rdsquashfs/src/restore_fstree.c | 336 ++++++++++++++++++++++++++++++++++++ bin/rdsquashfs/src/stat.c | 187 ++++++++++++++++++++ 9 files changed, 1709 insertions(+) create mode 100644 bin/rdsquashfs/src/describe.c create mode 100644 bin/rdsquashfs/src/dump_xattrs.c create mode 100644 bin/rdsquashfs/src/fill_files.c create mode 100644 bin/rdsquashfs/src/list_files.c create mode 100644 bin/rdsquashfs/src/options.c create mode 100644 bin/rdsquashfs/src/rdsquashfs.c create mode 100644 bin/rdsquashfs/src/rdsquashfs.h create mode 100644 bin/rdsquashfs/src/restore_fstree.c create mode 100644 bin/rdsquashfs/src/stat.c (limited to 'bin/rdsquashfs/src') diff --git a/bin/rdsquashfs/src/describe.c b/bin/rdsquashfs/src/describe.c new file mode 100644 index 0000000..540b126 --- /dev/null +++ b/bin/rdsquashfs/src/describe.c @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * describe.c + * + * Copyright (C) 2019 David Oberhollenzer + */ +#include "rdsquashfs.h" + +static int print_name(const sqfs_tree_node_t *n, bool dont_escape) +{ + char *start, *ptr, *name; + int ret; + + ret = sqfs_tree_node_get_path(n, &name); + if (ret != 0) { + sqfs_perror(NULL, "Recovering file path of tree node", ret); + return -1; + } + + if (canonicalize_name(name) != 0) { + fprintf(stderr, "Error sanitizing file path '%s'\n", name); + sqfs_free(name); + return -1; + } + + if (dont_escape || (strchr(name, ' ') == NULL && + strchr(name, '"') == NULL)) { + fputs(name, stdout); + } else { + fputc('"', stdout); + + ptr = strchr(name, '"'); + + if (ptr != NULL) { + start = name; + + do { + fwrite(start, 1, ptr - start, stdout); + fputs("\\\"", stdout); + start = ptr + 1; + ptr = strchr(start, '"'); + } while (ptr != NULL); + + fputs(start, stdout); + } else { + fputs(name, stdout); + } + + fputc('"', stdout); + } + + sqfs_free(name); + return 0; +} + +static void print_perm(const sqfs_tree_node_t *n) +{ + printf(" 0%o %u %u", (unsigned int)n->inode->base.mode & (~S_IFMT), + n->uid, n->gid); +} + +static int print_simple(const char *type, const sqfs_tree_node_t *n, + const char *extra) +{ + printf("%s ", type); + if (print_name(n, false)) + return -1; + print_perm(n); + if (extra != NULL) + printf(" %s", extra); + fputc('\n', stdout); + return 0; +} + +int describe_tree(const sqfs_tree_node_t *root, const char *unpack_root) +{ + const sqfs_tree_node_t *n; + + if (!is_filename_sane((const char *)root->name, false)) { + fprintf(stderr, "Encountered illegal file name '%s'\n", + root->name); + return -1; + } + + switch (root->inode->base.mode & S_IFMT) { + case S_IFSOCK: + return print_simple("sock", root, NULL); + case S_IFLNK: + return print_simple("slink", root, + (const char *)root->inode->extra); + case S_IFIFO: + return print_simple("pipe", root, NULL); + case S_IFREG: + if (unpack_root == NULL) + return print_simple("file", root, NULL); + + fputs("file ", stdout); + if (print_name(root, false)) + return -1; + print_perm(root); + printf(" %s/", unpack_root); + if (print_name(root, true)) + return -1; + fputc('\n', stdout); + break; + case S_IFCHR: + case S_IFBLK: { + char buffer[32]; + sqfs_u32 devno; + + if (root->inode->base.type == SQFS_INODE_EXT_BDEV || + root->inode->base.type == SQFS_INODE_EXT_CDEV) { + devno = root->inode->data.dev_ext.devno; + } else { + devno = root->inode->data.dev.devno; + } + + sprintf(buffer, "%c %u %u", + S_ISCHR(root->inode->base.mode) ? 'c' : 'b', + major(devno), minor(devno)); + return print_simple("nod", root, buffer); + } + case S_IFDIR: + if (root->name[0] != '\0') { + if (print_simple("dir", root, NULL)) + return -1; + } + + for (n = root->children; n != NULL; n = n->next) { + if (describe_tree(n, unpack_root)) + return -1; + } + break; + default: + break; + } + + return 0; +} diff --git a/bin/rdsquashfs/src/dump_xattrs.c b/bin/rdsquashfs/src/dump_xattrs.c new file mode 100644 index 0000000..9dbe437 --- /dev/null +++ b/bin/rdsquashfs/src/dump_xattrs.c @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * dump_xattrs.c + * + * Copyright (C) 2019 David Oberhollenzer + */ +#include "rdsquashfs.h" + +static void print_hex(const sqfs_u8 *value, size_t len) +{ + printf("0x"); + + while (len--) + printf("%02X", *(value++)); +} + +static bool is_printable(const sqfs_u8 *value, size_t len) +{ + size_t utf8_cont = 0; + sqfs_u8 x; + + while (len--) { + x = *(value++); + + if (utf8_cont > 0) { + if ((x & 0xC0) != 0x80) + return false; + + --utf8_cont; + } else { + if (x < 0x80) { + if (x < 0x20) { + if (x >= 0x07 && x <= 0x0D) + continue; + if (x == 0x00) + continue; + return false; + } + + if (x == 0x7F) + return false; + } + + if ((x & 0xE0) == 0xC0) { + utf8_cont = 1; + } else if ((x & 0xF0) == 0xE0) { + utf8_cont = 2; + } else if ((x & 0xF8) == 0xF0) { + utf8_cont = 3; + } else if ((x & 0xFC) == 0xF8) { + utf8_cont = 4; + } else if ((x & 0xFE) == 0xFC) { + utf8_cont = 5; + } + + if (utf8_cont > 0 && len < utf8_cont) + return false; + } + } + + return true; +} + +int dump_xattrs(sqfs_xattr_reader_t *xattr, const sqfs_inode_generic_t *inode) +{ + sqfs_xattr_value_t *value; + sqfs_xattr_entry_t *key; + sqfs_xattr_id_t desc; + sqfs_u32 index; + size_t i; + + if (xattr == NULL) + return 0; + + sqfs_inode_get_xattr_index(inode, &index); + + if (index == 0xFFFFFFFF) + return 0; + + if (sqfs_xattr_reader_get_desc(xattr, index, &desc)) { + fputs("Error resolving xattr index\n", stderr); + return -1; + } + + if (sqfs_xattr_reader_seek_kv(xattr, &desc)) { + fputs("Error locating xattr key-value pairs\n", stderr); + return -1; + } + + for (i = 0; i < desc.count; ++i) { + if (sqfs_xattr_reader_read_key(xattr, &key)) { + fputs("Error reading xattr key\n", stderr); + return -1; + } + + if (sqfs_xattr_reader_read_value(xattr, key, &value)) { + fputs("Error reading xattr value\n", stderr); + sqfs_free(key); + return -1; + } + + if (is_printable(key->key, key->size)) { + printf("%s=", key->key); + } else { + print_hex(key->key, key->size); + } + + if (is_printable(value->value, value->size)) { + printf("%s\n", value->value); + } else { + print_hex(value->value, value->size); + printf("\n"); + } + + sqfs_free(key); + sqfs_free(value); + } + + return 0; +} diff --git a/bin/rdsquashfs/src/fill_files.c b/bin/rdsquashfs/src/fill_files.c new file mode 100644 index 0000000..3104146 --- /dev/null +++ b/bin/rdsquashfs/src/fill_files.c @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * fill_files.c + * + * Copyright (C) 2019 David Oberhollenzer + */ +#include "config.h" +#include "rdsquashfs.h" + +static struct file_ent { + char *path; + const sqfs_inode_generic_t *inode; +} *files = NULL; + +static size_t num_files = 0, max_files = 0; +static size_t block_size = 0; + +static int compare_files(const void *l, const void *r) +{ + sqfs_u32 lhs_frag_idx, lhs_frag_off, rhs_frag_idx, rhs_frag_off; + sqfs_u64 lhs_size, rhs_size, lhs_start, rhs_start; + const struct file_ent *lhs = l, *rhs = r; + + sqfs_inode_get_frag_location(lhs->inode, &lhs_frag_idx, &lhs_frag_off); + sqfs_inode_get_file_block_start(lhs->inode, &lhs_start); + sqfs_inode_get_file_size(lhs->inode, &lhs_size); + + sqfs_inode_get_frag_location(rhs->inode, &rhs_frag_idx, &rhs_frag_off); + sqfs_inode_get_file_block_start(rhs->inode, &rhs_start); + sqfs_inode_get_file_size(rhs->inode, &rhs_size); + + /* Files with fragments come first, ordered by ID. + In case of tie, files without data blocks come first, + and the others are ordered by start block. */ + if ((lhs_size % block_size) && (lhs_frag_off < block_size) && + (lhs_frag_idx != 0xFFFFFFFF)) { + if ((rhs_size % block_size) && (rhs_frag_off < block_size) && + (rhs_frag_idx != 0xFFFFFFFF)) + return -1; + + if (lhs_frag_idx < rhs_frag_idx) + return -1; + + if (lhs_frag_idx > rhs_frag_idx) + return 1; + + if (lhs_size < block_size) + return (rhs_size < block_size) ? 0 : -1; + + if (rhs_size < block_size) + return 1; + + goto order_by_start; + } + + if ((rhs_size % block_size) && (rhs_frag_off < block_size) && + (rhs_frag_idx != 0xFFFFFFFF)) + return 1; + + /* order the rest by start block */ +order_by_start: + return lhs_start < rhs_start ? -1 : lhs_start > rhs_start ? 1 : 0; +} + +static int add_file(const sqfs_tree_node_t *node) +{ + struct file_ent *new; + size_t new_sz; + char *path; + int ret; + + if (num_files == max_files) { + new_sz = max_files ? max_files * 2 : 256; + new = realloc(files, sizeof(files[0]) * new_sz); + + if (new == NULL) { + perror("expanding file list"); + return -1; + } + + files = new; + max_files = new_sz; + } + + ret = sqfs_tree_node_get_path(node, &path); + if (ret != 0) { + sqfs_perror(NULL, "assembling file path", ret); + return -1; + } + + if (canonicalize_name(path)) { + fprintf(stderr, "Invalid file path '%s'\n", path); + sqfs_free(path); + return -1; + } + + files[num_files].path = path; + files[num_files].inode = node->inode; + num_files++; + return 0; +} + +static void clear_file_list(void) +{ + size_t i; + + for (i = 0; i < num_files; ++i) + sqfs_free(files[i].path); + + free(files); + files = NULL; + num_files = 0; + max_files = 0; +} + +static int gen_file_list_dfs(const sqfs_tree_node_t *n) +{ + if (!is_filename_sane((const char *)n->name, true)) { + fprintf(stderr, "Found an entry named '%s', skipping.\n", + n->name); + return 0; + } + + if (S_ISREG(n->inode->base.mode)) + return add_file(n); + + if (S_ISDIR(n->inode->base.mode)) { + for (n = n->children; n != NULL; n = n->next) { + if (gen_file_list_dfs(n)) + return -1; + } + } + + return 0; +} + +static int fill_files(sqfs_data_reader_t *data, int flags) +{ + int ret, openflags; + ostream_t *fp; + size_t i; + + openflags = OSTREAM_OPEN_OVERWRITE; + + if (flags & UNPACK_NO_SPARSE) + openflags |= OSTREAM_OPEN_SPARSE; + + for (i = 0; i < num_files; ++i) { + fp = ostream_open_file(files[i].path, openflags); + if (fp == NULL) + return -1; + + if (!(flags & UNPACK_QUIET)) + printf("unpacking %s\n", files[i].path); + + ret = sqfs_data_reader_dump(files[i].path, data, files[i].inode, + fp, block_size); + if (ret == 0) + ret = ostream_flush(fp); + + sqfs_drop(fp); + if (ret) + return -1; + } + + return 0; +} + +int fill_unpacked_files(size_t blk_sz, const sqfs_tree_node_t *root, + sqfs_data_reader_t *data, int flags) +{ + int status; + + block_size = blk_sz; + + if (gen_file_list_dfs(root)) { + clear_file_list(); + return -1; + } + + qsort(files, num_files, sizeof(files[0]), compare_files); + + status = fill_files(data, flags); + clear_file_list(); + return status; +} diff --git a/bin/rdsquashfs/src/list_files.c b/bin/rdsquashfs/src/list_files.c new file mode 100644 index 0000000..b1a0102 --- /dev/null +++ b/bin/rdsquashfs/src/list_files.c @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * list_files.c + * + * Copyright (C) 2019 David Oberhollenzer + */ +#include "rdsquashfs.h" + +static void mode_to_str(sqfs_u16 mode, char *p) +{ + switch (mode & S_IFMT) { + case S_IFDIR: *(p++) = 'd'; break; + case S_IFCHR: *(p++) = 'c'; break; + case S_IFBLK: *(p++) = 'b'; break; + case S_IFREG: *(p++) = '-'; break; + case S_IFLNK: *(p++) = 'l'; break; + case S_IFSOCK: *(p++) = 's'; break; + case S_IFIFO: *(p++) = 'p'; break; + default: *(p++) = '?'; break; + } + + *(p++) = (mode & S_IRUSR) ? 'r' : '-'; + *(p++) = (mode & S_IWUSR) ? 'w' : '-'; + + switch (mode & (S_IXUSR | S_ISUID)) { + case S_IXUSR | S_ISUID: *(p++) = 's'; break; + case S_IXUSR: *(p++) = 'x'; break; + case S_ISUID: *(p++) = 'S'; break; + default: *(p++) = '-'; break; + } + + *(p++) = (mode & S_IRGRP) ? 'r' : '-'; + *(p++) = (mode & S_IWGRP) ? 'w' : '-'; + + switch (mode & (S_IXGRP | S_ISGID)) { + case S_IXGRP | S_ISGID: *(p++) = 's'; break; + case S_IXGRP: *(p++) = 'x'; break; + case S_ISGID: *(p++) = 'S'; break; + case 0: *(p++) = '-'; break; + default: break; + } + + *(p++) = (mode & S_IROTH) ? 'r' : '-'; + *(p++) = (mode & S_IWOTH) ? 'w' : '-'; + + switch (mode & (S_IXOTH | S_ISVTX)) { + case S_IXOTH | S_ISVTX: *(p++) = 't'; break; + case S_IXOTH: *(p++) = 'x'; break; + case S_ISVTX: *(p++) = 'T'; break; + case 0: *(p++) = '-'; break; + default: break; + } + + *p = '\0'; +} + +static int count_int_chars(unsigned int i) +{ + int count = 1; + + while (i > 10) { + ++count; + i /= 10; + } + + return count; +} + +static void print_node_size(const sqfs_tree_node_t *n, char *buffer) +{ + switch (n->inode->base.mode & S_IFMT) { + case S_IFLNK: + print_size(strlen((const char *)n->inode->extra), buffer, true); + break; + case S_IFREG: { + sqfs_u64 size; + sqfs_inode_get_file_size(n->inode, &size); + print_size(size, buffer, true); + break; + } + case S_IFDIR: + if (n->inode->base.type == SQFS_INODE_EXT_DIR) { + print_size(n->inode->data.dir_ext.size, buffer, true); + } else { + print_size(n->inode->data.dir.size, buffer, true); + } + break; + case S_IFBLK: + case S_IFCHR: { + sqfs_u32 devno; + + if (n->inode->base.type == SQFS_INODE_EXT_BDEV || + n->inode->base.type == SQFS_INODE_EXT_CDEV) { + devno = n->inode->data.dev_ext.devno; + } else { + devno = n->inode->data.dev.devno; + } + + sprintf(buffer, "%u:%u", major(devno), minor(devno)); + break; + } + default: + buffer[0] = '0'; + buffer[1] = '\0'; + break; + } +} + +void list_files(const sqfs_tree_node_t *node) +{ + int i, max_uid_chars = 0, max_gid_chars = 0, max_sz_chars = 0; + char modestr[12], sizestr[32]; + const sqfs_tree_node_t *n; + + if (S_ISDIR(node->inode->base.mode)) { + for (n = node->children; n != NULL; n = n->next) { + i = count_int_chars(n->uid); + max_uid_chars = i > max_uid_chars ? i : max_uid_chars; + + i = count_int_chars(n->gid); + max_gid_chars = i > max_gid_chars ? i : max_gid_chars; + + print_node_size(n, sizestr); + i = strlen(sizestr); + max_sz_chars = i > max_sz_chars ? i : max_sz_chars; + } + + for (n = node->children; n != NULL; n = n->next) { + mode_to_str(n->inode->base.mode, modestr); + print_node_size(n, sizestr); + + printf("%s %*u/%-*u %*s %s", modestr, + max_uid_chars, n->uid, + max_gid_chars, n->gid, + max_sz_chars, sizestr, + n->name); + + if (S_ISLNK(n->inode->base.mode)) { + printf(" -> %s\n", + (const char *)n->inode->extra); + } else { + fputc('\n', stdout); + } + } + } else { + mode_to_str(node->inode->base.mode, modestr); + print_node_size(node, sizestr); + + printf("%s %u/%u %s %s", modestr, + node->uid, node->gid, sizestr, node->name); + + if (S_ISLNK(node->inode->base.mode)) { + printf(" -> %s\n", (const char *)node->inode->extra); + } else { + fputc('\n', stdout); + } + } +} diff --git a/bin/rdsquashfs/src/options.c b/bin/rdsquashfs/src/options.c new file mode 100644 index 0000000..dbb5e40 --- /dev/null +++ b/bin/rdsquashfs/src/options.c @@ -0,0 +1,226 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * options.c + * + * Copyright (C) 2019 David Oberhollenzer + */ +#include "rdsquashfs.h" + +static struct option long_opts[] = { + { "list", required_argument, NULL, 'l' }, + { "cat", required_argument, NULL, 'c' }, + { "xattr", required_argument, NULL, 'x' }, + { "stat", required_argument, NULL, 's' }, + { "unpack-root", required_argument, NULL, 'p' }, + { "unpack-path", required_argument, NULL, 'u' }, + { "no-dev", no_argument, NULL, 'D' }, + { "no-sock", no_argument, NULL, 'S' }, + { "no-fifo", no_argument, NULL, 'F' }, + { "no-slink", no_argument, NULL, 'L' }, + { "no-empty-dir", no_argument, NULL, 'E' }, + { "no-sparse", no_argument, NULL, 'Z' }, +#ifdef HAVE_SYS_XATTR_H + { "set-xattr", no_argument, NULL, 'X' }, +#endif + { "set-times", no_argument, NULL, 'T' }, + { "describe", no_argument, NULL, 'd' }, + { "chmod", no_argument, NULL, 'C' }, + { "chown", no_argument, NULL, 'O' }, + { "quiet", no_argument, NULL, 'q' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 }, +}; + +static const char *short_opts = + "l:c:u:p:x:s:DSFLCOEZTj:dqhV" +#ifdef HAVE_SYS_XATTR_H + "X" +#endif + ; + +static const char *help_string = +"Usage: rdsquashfs [OPTIONS] \n" +"\n" +"View or extract the contents of a squashfs image.\n" +"\n" +"Possible options:\n" +"\n" +" --list, -l Produce a directory listing for a given path in\n" +" the squashfs image.\n" +" --cat, -c If the specified path is a regular file in the,\n" +" image, dump its contents to stdout.\n" +" --xattr, -x Enumerate extended attributes associated with\n" +" an inode that the given path resolves to.\n" +" --unpack-path, -u Unpack this sub directory from the image. To\n" +" unpack everything, simply specify /.\n" +" --stat, -s Dump all information that can be extracted from\n" +" the inode coresponding to a path, including\n" +" SquashFS specific internals.\n" +" --describe, -d Produce a file listing from the image.\n" +"\n" +" --unpack-root, -p If used with --unpack-path, this is where the\n" +" data unpacked to. If used with --describe, this\n" +" is used as a prefix for the input path of\n" +" regular files.\n" +"\n" +" --no-dev, -D Do not unpack device special files.\n" +" --no-sock, -S Do not unpack socket files.\n" +" --no-fifo, -F Do not unpack named pipes.\n" +" --no-slink, -L Do not unpack symbolic links.\n" +" --no-empty-dir, -E Do not unpack directories that would end up\n" +" empty after applying the above rules.\n" +" --no-sparse, -Z Do not create sparse files, always write zero\n" +" blocks to disk.\n" +#ifdef HAVE_SYS_XATTR_H +" --set-xattr, -X When unpacking files to disk, set the extended\n" +" attributes from the squashfs image.\n" +#endif +" --set-times, -T When unpacking files to disk, set the create\n" +" and modify timestamps from the squashfs image.\n" +" --chmod, -C Change permission flags of unpacked files to\n" +" those store in the squashfs image.\n" +" --chown, -O Change ownership of unpacked files to the\n" +" UID/GID set in the squashfs image.\n" +" --quiet, -q Do not print out progress while unpacking.\n" +"\n" +" --help, -h Print help text and exit.\n" +" --version, -V Print version information and exit.\n" +"\n"; + +static char *get_path(char *old, const char *arg) +{ + char *path; + + free(old); + + path = strdup(arg); + if (path == NULL) { + perror("processing arguments"); + exit(EXIT_FAILURE); + } + + if (canonicalize_name(path)) { + fprintf(stderr, "Invalid path: %s\n", arg); + free(path); + exit(EXIT_FAILURE); + } + + return path; +} + +void process_command_line(options_t *opt, int argc, char **argv) +{ + int i; + + opt->op = OP_NONE; + opt->rdtree_flags = 0; + opt->flags = 0; + opt->cmdpath = NULL; + opt->unpack_root = NULL; + opt->image_name = NULL; + + for (;;) { + i = getopt_long(argc, argv, short_opts, long_opts, NULL); + if (i == -1) + break; + + switch (i) { + case 'D': + opt->rdtree_flags |= SQFS_TREE_NO_DEVICES; + break; + case 'S': + opt->rdtree_flags |= SQFS_TREE_NO_SOCKETS; + break; + case 'F': + opt->rdtree_flags |= SQFS_TREE_NO_FIFO; + break; + case 'L': + opt->rdtree_flags |= SQFS_TREE_NO_SLINKS; + break; + case 'E': + opt->rdtree_flags |= SQFS_TREE_NO_EMPTY; + break; + case 'C': + opt->flags |= UNPACK_CHMOD; + break; + case 'O': + opt->flags |= UNPACK_CHOWN; + break; + case 'Z': + opt->flags |= UNPACK_NO_SPARSE; + break; +#ifdef HAVE_SYS_XATTR_H + case 'X': + opt->flags |= UNPACK_SET_XATTR; + break; +#endif + case 'T': + opt->flags |= UNPACK_SET_TIMES; + break; + case 'c': + opt->op = OP_CAT; + opt->cmdpath = get_path(opt->cmdpath, optarg); + break; + case 'd': + opt->op = OP_DESCRIBE; + free(opt->cmdpath); + opt->cmdpath = NULL; + break; + case 'x': + opt->op = OP_RDATTR; + opt->cmdpath = get_path(opt->cmdpath, optarg); + break; + case 's': + opt->op = OP_STAT; + opt->cmdpath = get_path(opt->cmdpath, optarg); + break; + case 'l': + opt->op = OP_LS; + opt->cmdpath = get_path(opt->cmdpath, optarg); + break; + case 'p': + opt->unpack_root = optarg; + break; + case 'u': + opt->op = OP_UNPACK; + opt->cmdpath = get_path(opt->cmdpath, optarg); + break; + case 'q': + opt->flags |= UNPACK_QUIET; + break; + case 'h': + fputs(help_string, stdout); + free(opt->cmdpath); + exit(EXIT_SUCCESS); + case 'V': + print_version("rdsquashfs"); + free(opt->cmdpath); + exit(EXIT_SUCCESS); + default: + goto fail_arg; + } + } + + if (opt->op == OP_NONE) { + fputs("No operation specified\n", stderr); + goto fail_arg; + } + + if (opt->op == OP_LS || opt->op == OP_CAT || opt->op == OP_RDATTR || + opt->op == OP_STAT) { + opt->rdtree_flags |= SQFS_TREE_NO_RECURSE; + } + + if (optind >= argc) { + fputs("Missing image argument\n", stderr); + goto fail_arg; + } + + opt->image_name = argv[optind++]; + return; +fail_arg: + fputs("Try `rdsquashfs --help' for more information.\n", stderr); + free(opt->cmdpath); + exit(EXIT_FAILURE); +} diff --git a/bin/rdsquashfs/src/rdsquashfs.c b/bin/rdsquashfs/src/rdsquashfs.c new file mode 100644 index 0000000..bdcc5a0 --- /dev/null +++ b/bin/rdsquashfs/src/rdsquashfs.c @@ -0,0 +1,275 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * rdsquashfs.c + * + * Copyright (C) 2019 David Oberhollenzer + */ +#include "rdsquashfs.h" + +static sqfs_tree_node_t *list_merge(sqfs_tree_node_t *lhs, + sqfs_tree_node_t *rhs) +{ + sqfs_tree_node_t *it, *head = NULL, **next_ptr = &head; + + while (lhs != NULL && rhs != NULL) { + if (strcmp((const char *)lhs->name, + (const char *)rhs->name) <= 0) { + it = lhs; + lhs = lhs->next; + } else { + it = rhs; + rhs = rhs->next; + } + + *next_ptr = it; + next_ptr = &it->next; + } + + it = (lhs != NULL ? lhs : rhs); + *next_ptr = it; + return head; +} + +static sqfs_tree_node_t *list_sort(sqfs_tree_node_t *head) +{ + sqfs_tree_node_t *it, *half, *prev; + + it = half = prev = head; + + while (it != NULL) { + prev = half; + half = half->next; + it = it->next; + + if (it != NULL) + it = it->next; + } + + if (half == NULL) + return head; + + prev->next = NULL; + + return list_merge(list_sort(head), list_sort(half)); +} + +static int tree_sort(sqfs_tree_node_t *root) +{ + sqfs_tree_node_t *it; + + if (root->children == NULL) + return 0; + + root->children = list_sort(root->children); + + /* + XXX: not only an inconvenience but a security issue: e.g. we unpack a + SquashFS image that has a symlink pointing somewhere, and then a + sub-directory or file with the same name, the unpacker can be tricked + to follow the symlink and write anything, anywhere on the filesystem. + */ + for (it = root->children; it->next != NULL; it = it->next) { + if (strcmp((const char *)it->name, + (const char *)it->next->name) == 0) { + char *path; + int ret; + + ret = sqfs_tree_node_get_path(it, &path); + + if (ret == 0) { + fprintf(stderr, + "Entry '%s' found more than once!\n", + path); + } else { + fputs("Entry found more than once!\n", stderr); + } + + sqfs_free(path); + return -1; + } + } + + for (it = root->children; it != NULL; it = it->next) { + if (tree_sort(it)) + return -1; + } + + return 0; +} + +int main(int argc, char **argv) +{ + sqfs_xattr_reader_t *xattr = NULL; + sqfs_data_reader_t *data = NULL; + sqfs_dir_reader_t *dirrd = NULL; + sqfs_compressor_t *cmp = NULL; + sqfs_id_table_t *idtbl = NULL; + sqfs_compressor_config_t cfg; + sqfs_tree_node_t *n = NULL; + int status = EXIT_FAILURE; + sqfs_file_t *file = NULL; + sqfs_super_t super; + options_t opt; + int ret; + + process_command_line(&opt, argc, argv); + + file = sqfs_open_file(opt.image_name, SQFS_FILE_OPEN_READ_ONLY); + if (file == NULL) { + perror(opt.image_name); + goto out; + } + + ret = sqfs_super_read(&super, file); + if (ret) { + sqfs_perror(opt.image_name, "reading super block", ret); + goto out; + } + + sqfs_compressor_config_init(&cfg, super.compression_id, + super.block_size, + SQFS_COMP_FLAG_UNCOMPRESS); + + ret = sqfs_compressor_create(&cfg, &cmp); + +#ifdef WITH_LZO + if (super.compression_id == SQFS_COMP_LZO && ret != 0) + ret = lzo_compressor_create(&cfg, &cmp); +#endif + + if (ret != 0) { + sqfs_perror(opt.image_name, "creating compressor", ret); + goto out; + } + + if (!(super.flags & SQFS_FLAG_NO_XATTRS)) { + xattr = sqfs_xattr_reader_create(0); + if (xattr == NULL) { + sqfs_perror(opt.image_name, "creating xattr reader", + SQFS_ERROR_ALLOC); + goto out; + } + + ret = sqfs_xattr_reader_load(xattr, &super, file, cmp); + if (ret) { + sqfs_perror(opt.image_name, "loading xattr table", + ret); + goto out; + } + } + + idtbl = sqfs_id_table_create(0); + if (idtbl == NULL) { + sqfs_perror(opt.image_name, "creating ID table", + SQFS_ERROR_ALLOC); + goto out; + } + + ret = sqfs_id_table_read(idtbl, file, &super, cmp); + if (ret) { + sqfs_perror(opt.image_name, "loading ID table", ret); + goto out; + } + + dirrd = sqfs_dir_reader_create(&super, cmp, file, 0); + if (dirrd == NULL) { + sqfs_perror(opt.image_name, "creating dir reader", + SQFS_ERROR_ALLOC); + goto out; + } + + data = sqfs_data_reader_create(file, super.block_size, cmp, 0); + if (data == NULL) { + sqfs_perror(opt.image_name, "creating data reader", + SQFS_ERROR_ALLOC); + goto out; + } + + ret = sqfs_data_reader_load_fragment_table(data, &super); + if (ret) { + sqfs_perror(opt.image_name, "loading fragment table", ret); + goto out; + } + + ret = sqfs_dir_reader_get_full_hierarchy(dirrd, idtbl, opt.cmdpath, + opt.rdtree_flags, &n); + if (ret) { + sqfs_perror(opt.image_name, "reading filesystem tree", ret); + goto out; + } + + switch (opt.op) { + case OP_LS: + list_files(n); + break; + case OP_STAT: + if (stat_file(n)) + goto out; + break; + case OP_CAT: { + ostream_t *fp; + + if (!S_ISREG(n->inode->base.mode)) { + fprintf(stderr, "/%s: not a regular file\n", + opt.cmdpath); + goto out; + } + + fp = ostream_open_stdout(); + if (fp == NULL) + goto out; + + ret = sqfs_data_reader_dump(opt.cmdpath, data, n->inode, + fp, super.block_size); + sqfs_drop(fp); + if (ret) + goto out; + break; + } + case OP_UNPACK: + if (tree_sort(n)) + goto out; + + if (opt.unpack_root != NULL) { + if (mkdir_p(opt.unpack_root)) + goto out; + + if (chdir(opt.unpack_root)) { + perror(opt.unpack_root); + goto out; + } + } + + if (restore_fstree(n, opt.flags)) + goto out; + + if (fill_unpacked_files(super.block_size, n, data, opt.flags)) + goto out; + + if (update_tree_attribs(xattr, n, opt.flags)) + goto out; + break; + case OP_DESCRIBE: + if (describe_tree(n, opt.unpack_root)) + goto out; + break; + case OP_RDATTR: + if (dump_xattrs(xattr, n->inode)) + goto out; + break; + default: + break; + } + + status = EXIT_SUCCESS; +out: + sqfs_dir_tree_destroy(n); + sqfs_drop(data); + sqfs_drop(dirrd); + sqfs_drop(idtbl); + sqfs_drop(xattr); + sqfs_drop(cmp); + sqfs_drop(file); + free(opt.cmdpath); + return status; +} diff --git a/bin/rdsquashfs/src/rdsquashfs.h b/bin/rdsquashfs/src/rdsquashfs.h new file mode 100644 index 0000000..56bb836 --- /dev/null +++ b/bin/rdsquashfs/src/rdsquashfs.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * rdsquashfs.h + * + * Copyright (C) 2019 David Oberhollenzer + */ +#ifndef RDSQUASHFS_H +#define RDSQUASHFS_H + +#include "config.h" +#include "common.h" +#include "fstree.h" +#include "util/util.h" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif +#ifdef HAVE_SYS_XATTR_H +#include + +#if defined(__APPLE__) && defined(__MACH__) +#define lsetxattr(path, name, value, size, flags) \ + setxattr(path, name, value, size, 0, flags | XATTR_NOFOLLOW) +#endif +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +enum UNPACK_FLAGS { + UNPACK_CHMOD = 0x01, + UNPACK_CHOWN = 0x02, + UNPACK_QUIET = 0x04, + UNPACK_NO_SPARSE = 0x08, + UNPACK_SET_XATTR = 0x10, + UNPACK_SET_TIMES = 0x20, +}; + +enum { + OP_NONE = 0, + OP_LS, + OP_CAT, + OP_UNPACK, + OP_DESCRIBE, + OP_RDATTR, + OP_STAT, +}; + +typedef struct { + int op; + int rdtree_flags; + int flags; + char *cmdpath; + const char *unpack_root; + const char *image_name; +} options_t; + +void list_files(const sqfs_tree_node_t *node); + +int stat_file(const sqfs_tree_node_t *node); + +int restore_fstree(sqfs_tree_node_t *root, int flags); + +int update_tree_attribs(sqfs_xattr_reader_t *xattr, + const sqfs_tree_node_t *root, int flags); + +int fill_unpacked_files(size_t blk_sz, const sqfs_tree_node_t *root, + sqfs_data_reader_t *data, int flags); + +int describe_tree(const sqfs_tree_node_t *root, const char *unpack_root); + +int dump_xattrs(sqfs_xattr_reader_t *xattr, const sqfs_inode_generic_t *inode); + +void process_command_line(options_t *opt, int argc, char **argv); + +#endif /* RDSQUASHFS_H */ diff --git a/bin/rdsquashfs/src/restore_fstree.c b/bin/rdsquashfs/src/restore_fstree.c new file mode 100644 index 0000000..ea9d4f1 --- /dev/null +++ b/bin/rdsquashfs/src/restore_fstree.c @@ -0,0 +1,336 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * restore_fstree.c + * + * Copyright (C) 2019 David Oberhollenzer + */ +#include "rdsquashfs.h" + +#ifdef _WIN32 +static int create_node(const sqfs_tree_node_t *n, const char *name, int flags) +{ + WCHAR *wpath; + HANDLE fh; + (void)flags; + + wpath = path_to_windows(name); + if (wpath == NULL) + return -1; + + switch (n->inode->base.mode & S_IFMT) { + case S_IFDIR: + if (!CreateDirectoryW(wpath, NULL)) { + if (GetLastError() != ERROR_ALREADY_EXISTS) + goto fail; + } + break; + case S_IFREG: + fh = CreateFileW(wpath, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, CREATE_NEW, 0, NULL); + + if (fh == INVALID_HANDLE_VALUE) + goto fail; + + CloseHandle(fh); + break; + default: + break; + } + + free(wpath); + return 0; +fail: { + DWORD err = GetLastError(); + free(wpath); + SetLastError(err); + w32_perror(name); + + if (err == ERROR_FILE_EXISTS) { + fputs("\nHINT: this could be caused by case " + "sensitivity on Windows.\n", stderr); + } + return -1; +} +} +#else +static int create_node(const sqfs_tree_node_t *n, const char *name, int flags) +{ + sqfs_u32 devno; + int fd, mode; + + switch (n->inode->base.mode & S_IFMT) { + case S_IFDIR: + if (mkdir(name, 0755) && errno != EEXIST) { + fprintf(stderr, "mkdir %s: %s\n", + name, strerror(errno)); + return -1; + } + break; + case S_IFLNK: + if (symlink((const char *)n->inode->extra, name)) { + fprintf(stderr, "ln -s %s %s: %s\n", + (const char *)n->inode->extra, name, + strerror(errno)); + return -1; + } + break; + case S_IFSOCK: + case S_IFIFO: + if (mknod(name, (n->inode->base.mode & S_IFMT) | 0700, 0)) { + fprintf(stderr, "creating %s: %s\n", + name, strerror(errno)); + return -1; + } + break; + case S_IFBLK: + case S_IFCHR: + if (n->inode->base.type == SQFS_INODE_EXT_BDEV || + n->inode->base.type == SQFS_INODE_EXT_CDEV) { + devno = n->inode->data.dev_ext.devno; + } else { + devno = n->inode->data.dev.devno; + } + + if (mknod(name, n->inode->base.mode & S_IFMT, devno)) { + fprintf(stderr, "creating device %s: %s\n", + name, strerror(errno)); + return -1; + } + break; + case S_IFREG: + if (flags & UNPACK_CHMOD) { + mode = (n->inode->base.mode & ~S_IFMT) | 0200; + } else { + mode = 0644; + } + + fd = open(name, O_WRONLY | O_CREAT | O_EXCL, mode); + + if (fd < 0) { + fprintf(stderr, "creating %s: %s\n", + name, strerror(errno)); + return -1; + } + + close(fd); + break; + default: + break; + } + + return 0; +} +#endif + +static int create_node_dfs(const sqfs_tree_node_t *n, int flags) +{ + const sqfs_tree_node_t *c; + char *name; + int ret; + + if (!is_filename_sane((const char *)n->name, true)) { + fprintf(stderr, "Found an entry named '%s', skipping.\n", + n->name); + return 0; + } + + ret = sqfs_tree_node_get_path(n, &name); + if (ret != 0) { + sqfs_perror((const char *)n->name, + "constructing full path", ret); + return -1; + } + + ret = canonicalize_name(name); + assert(ret == 0); + + if (!(flags & UNPACK_QUIET)) + printf("creating %s\n", name); + + ret = create_node(n, name, flags); + sqfs_free(name); + if (ret) + return -1; + + if (S_ISDIR(n->inode->base.mode)) { + for (c = n->children; c != NULL; c = c->next) { + if (create_node_dfs(c, flags)) + return -1; + } + } + return 0; +} + +#ifdef HAVE_SYS_XATTR_H +static int set_xattr(const char *path, sqfs_xattr_reader_t *xattr, + const sqfs_tree_node_t *n) +{ + sqfs_xattr_value_t *value; + sqfs_xattr_entry_t *key; + sqfs_xattr_id_t desc; + sqfs_u32 index; + size_t i; + int ret; + + sqfs_inode_get_xattr_index(n->inode, &index); + + if (index == 0xFFFFFFFF) + return 0; + + if (sqfs_xattr_reader_get_desc(xattr, index, &desc)) { + fputs("Error resolving xattr index\n", stderr); + return -1; + } + + if (sqfs_xattr_reader_seek_kv(xattr, &desc)) { + fputs("Error locating xattr key-value pairs\n", stderr); + return -1; + } + + for (i = 0; i < desc.count; ++i) { + if (sqfs_xattr_reader_read_key(xattr, &key)) { + fputs("Error reading xattr key\n", stderr); + return -1; + } + + if (sqfs_xattr_reader_read_value(xattr, key, &value)) { + fputs("Error reading xattr value\n", stderr); + sqfs_free(key); + return -1; + } + + ret = lsetxattr(path, (const char *)key->key, + value->value, value->size, 0); + if (ret) { + fprintf(stderr, "setting xattr '%s' on %s: %s\n", + key->key, path, strerror(errno)); + } + + sqfs_free(key); + sqfs_free(value); + if (ret) + return -1; + } + + return 0; +} +#endif + +static int set_attribs(sqfs_xattr_reader_t *xattr, + const sqfs_tree_node_t *n, int flags) +{ + const sqfs_tree_node_t *c; + char *path; + int ret; + + if (!is_filename_sane((const char *)n->name, true)) + return 0; + + if (S_ISDIR(n->inode->base.mode)) { + for (c = n->children; c != NULL; c = c->next) { + if (set_attribs(xattr, c, flags)) + return -1; + } + } + + ret = sqfs_tree_node_get_path(n, &path); + if (ret != 0) { + sqfs_perror(NULL, "reconstructing full path", ret); + return -1; + } + + ret = canonicalize_name(path); + assert(ret == 0); + +#ifdef HAVE_SYS_XATTR_H + if ((flags & UNPACK_SET_XATTR) && xattr != NULL) { + if (set_xattr(path, xattr, n)) + goto fail; + } +#endif + +#ifndef _WIN32 + if (flags & UNPACK_SET_TIMES) { + struct timespec times[2]; + + memset(times, 0, sizeof(times)); + times[0].tv_sec = n->inode->base.mod_time; + times[1].tv_sec = n->inode->base.mod_time; + + if (utimensat(AT_FDCWD, path, times, AT_SYMLINK_NOFOLLOW)) { + fprintf(stderr, "setting timestamp on %s: %s\n", + path, strerror(errno)); + goto fail; + } + } +#endif + if (flags & UNPACK_CHOWN) { + if (fchownat(AT_FDCWD, path, n->uid, n->gid, + AT_SYMLINK_NOFOLLOW)) { + fprintf(stderr, "chown %s: %s\n", + path, strerror(errno)); + goto fail; + } + } + + if (flags & UNPACK_CHMOD && !S_ISLNK(n->inode->base.mode)) { + if (fchmodat(AT_FDCWD, path, + n->inode->base.mode & ~S_IFMT, 0)) { + fprintf(stderr, "chmod %s: %s\n", + path, strerror(errno)); + goto fail; + } + } + + sqfs_free(path); + return 0; +fail: + sqfs_free(path); + return -1; +} + +int restore_fstree(sqfs_tree_node_t *root, int flags) +{ + sqfs_tree_node_t *n, *old_parent; + + /* make sure fstree_get_path() stops at this node */ + old_parent = root->parent; + root->parent = NULL; + + if (S_ISDIR(root->inode->base.mode)) { + for (n = root->children; n != NULL; n = n->next) { + if (create_node_dfs(n, flags)) + return -1; + } + } else { + if (create_node_dfs(root, flags)) + return -1; + } + + root->parent = old_parent; + return 0; +} + +int update_tree_attribs(sqfs_xattr_reader_t *xattr, + const sqfs_tree_node_t *root, int flags) +{ + const sqfs_tree_node_t *n; + + if ((flags & (UNPACK_CHOWN | UNPACK_CHMOD | + UNPACK_SET_TIMES | UNPACK_SET_XATTR)) == 0) { + return 0; + } + + if (S_ISDIR(root->inode->base.mode)) { + for (n = root->children; n != NULL; n = n->next) { + if (set_attribs(xattr, n, flags)) + return -1; + } + } else { + if (set_attribs(xattr, root, flags)) + return -1; + } + + return 0; +} diff --git a/bin/rdsquashfs/src/stat.c b/bin/rdsquashfs/src/stat.c new file mode 100644 index 0000000..8b4581f --- /dev/null +++ b/bin/rdsquashfs/src/stat.c @@ -0,0 +1,187 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * stat.c + * + * Copyright (C) 2020 David Oberhollenzer + */ +#include "rdsquashfs.h" + +static const char *inode_types[] = { + [SQFS_INODE_DIR] = "directory", + [SQFS_INODE_FILE] = "file", + [SQFS_INODE_SLINK] = "symbolic link", + [SQFS_INODE_BDEV] = "block device", + [SQFS_INODE_CDEV] = "character device", + [SQFS_INODE_FIFO] = "named pipe", + [SQFS_INODE_SOCKET] = "socket", + [SQFS_INODE_EXT_DIR] = "extended directory", + [SQFS_INODE_EXT_FILE] = "extended file", + [SQFS_INODE_EXT_SLINK] = "extended symbolic link", + [SQFS_INODE_EXT_BDEV] = "extended block device", + [SQFS_INODE_EXT_CDEV] = "extended character device", + [SQFS_INODE_EXT_FIFO] = "extended named pipe", + [SQFS_INODE_EXT_SOCKET] = "extended socket", +}; + +int stat_file(const sqfs_tree_node_t *node) +{ + sqfs_u32 xattr_idx = 0xFFFFFFFF, devno = 0, link_size = 0; + const sqfs_inode_generic_t *inode = node->inode; + const char *type = NULL, *link_target = NULL; + sqfs_u32 frag_idx, frag_offset; + bool have_devno = false; + sqfs_u64 location, size; + unsigned int nlinks = 0; + sqfs_dir_index_t *idx; + char buffer[64]; + time_t timeval; + struct tm *tm; + size_t i; + int ret; + + /* decode */ + if ((size_t)inode->base.type < + sizeof(inode_types) / sizeof(inode_types[0])) { + type = inode_types[inode->base.type]; + } + + sqfs_inode_get_xattr_index(inode, &xattr_idx); + + switch (inode->base.type) { + case SQFS_INODE_DIR: + nlinks = inode->data.dir.nlink; + break; + case SQFS_INODE_SLINK: + nlinks = inode->data.slink.nlink; + link_target = (const char *)inode->extra; + link_size = inode->data.slink.target_size; + break; + case SQFS_INODE_BDEV: + case SQFS_INODE_CDEV: + nlinks = inode->data.dev.nlink; + devno = inode->data.dev.devno; + have_devno = true; + break; + case SQFS_INODE_FIFO: + case SQFS_INODE_SOCKET: + nlinks = inode->data.ipc.nlink; + break; + case SQFS_INODE_EXT_DIR: + nlinks = inode->data.dir_ext.nlink; + break; + case SQFS_INODE_EXT_FILE: + nlinks = inode->data.file_ext.nlink; + break; + case SQFS_INODE_EXT_SLINK: + nlinks = inode->data.slink_ext.nlink; + link_target = (const char *)inode->extra; + link_size = inode->data.slink_ext.target_size; + break; + case SQFS_INODE_EXT_BDEV: + case SQFS_INODE_EXT_CDEV: + nlinks = inode->data.dev_ext.nlink; + devno = inode->data.dev_ext.devno; + have_devno = true; + break; + case SQFS_INODE_EXT_FIFO: + case SQFS_INODE_EXT_SOCKET: + nlinks = inode->data.ipc_ext.nlink; + break; + default: + break; + } + + timeval = inode->base.mod_time; + tm = gmtime(&timeval); + strftime(buffer, sizeof(buffer), "%a, %d %b %Y %T %z", tm); + + /* info dump */ + printf("Name: %s\n", (const char *)node->name); + printf("Inode type: %s\n", type == NULL ? "UNKNOWN" : type); + printf("Inode number: %u\n", inode->base.inode_number); + printf("Access: 0%o\n", + (unsigned int)inode->base.mode & ~SQFS_INODE_MODE_MASK); + printf("UID: %u (index = %u)\n", node->uid, inode->base.uid_idx); + printf("GID: %u (index = %u)\n", node->gid, inode->base.gid_idx); + printf("Last modified: %s (%u)\n", buffer, inode->base.mod_time); + + if (type != NULL && inode->base.type != SQFS_INODE_FILE) + printf("Hard link count: %u\n", nlinks); + + if (type != NULL && inode->base.type >= SQFS_INODE_EXT_DIR) + printf("Xattr index: 0x%X\n", xattr_idx); + + if (link_target != NULL) + printf("Link target: %.*s\n", (int)link_size, link_target); + + if (have_devno) { + printf("Device number: %u:%u (%u)\n", + major(devno), minor(devno), devno); + } + + switch (inode->base.type) { + case SQFS_INODE_FILE: + case SQFS_INODE_EXT_FILE: + sqfs_inode_get_file_block_start(inode, &location); + sqfs_inode_get_file_size(inode, &size); + sqfs_inode_get_frag_location(inode, &frag_idx, &frag_offset); + + printf("Fragment index: 0x%X\n", frag_idx); + printf("Fragment offset: %u\n", frag_offset); + printf("File size: %lu\n", (unsigned long)size); + + if (inode->base.type == SQFS_INODE_EXT_FILE) { + printf("Sparse: " PRI_U64 "\n", + inode->data.file_ext.sparse); + } + + printf("Blocks start: %lu\n", (unsigned long)location); + printf("Block count: %lu\n", + (unsigned long)sqfs_inode_get_file_block_count(inode)); + + for (i = 0; i < sqfs_inode_get_file_block_count(inode); ++i) { + printf("\tBlock #%lu size: %u (%s)\n", (unsigned long)i, + SQFS_ON_DISK_BLOCK_SIZE(inode->extra[i]), + SQFS_IS_BLOCK_COMPRESSED(inode->extra[i]) ? + "compressed" : "uncompressed"); + } + break; + case SQFS_INODE_DIR: + printf("Start block: %u\n", inode->data.dir.start_block); + printf("Offset: %u\n", inode->data.dir.offset); + printf("Listing size: %u\n", inode->data.dir.size); + printf("Parent inode: %u\n", inode->data.dir.parent_inode); + break; + case SQFS_INODE_EXT_DIR: + printf("Start block: %u\n", inode->data.dir_ext.start_block); + printf("Offset: %u\n", inode->data.dir_ext.offset); + printf("Listing size: %u\n", inode->data.dir_ext.size); + printf("Parent inode: %u\n", inode->data.dir_ext.parent_inode); + printf("Directory index entries: %u\n", + inode->data.dir_ext.inodex_count); + + if (inode->data.dir_ext.size == 0) + break; + + for (i = 0; ; ++i) { + ret = sqfs_inode_unpack_dir_index_entry(inode, &idx, i); + if (ret == SQFS_ERROR_OUT_OF_BOUNDS) + break; + if (ret < 0) { + sqfs_perror(NULL, "reading directory index", + ret); + return -1; + } + + printf("\t'%.*s' -> block %u, header offset %u\n", + (int)(idx->size + 1), idx->name, + idx->start_block, idx->index); + + sqfs_free(idx); + } + break; + default: + break; + } + return 0; +} -- cgit v1.2.3