summaryrefslogtreecommitdiff
path: root/bin/rdsquashfs
diff options
context:
space:
mode:
Diffstat (limited to 'bin/rdsquashfs')
-rw-r--r--bin/rdsquashfs/describe.c125
-rw-r--r--bin/rdsquashfs/dump_xattrs.c120
-rw-r--r--bin/rdsquashfs/fill_files.c183
-rw-r--r--bin/rdsquashfs/list_files.c156
-rw-r--r--bin/rdsquashfs/options.c217
-rw-r--r--bin/rdsquashfs/rdsquashfs.c175
-rw-r--r--bin/rdsquashfs/rdsquashfs.h77
-rw-r--r--bin/rdsquashfs/restore_fstree.c320
8 files changed, 1373 insertions, 0 deletions
diff --git a/bin/rdsquashfs/describe.c b/bin/rdsquashfs/describe.c
new file mode 100644
index 0000000..d30f844
--- /dev/null
+++ b/bin/rdsquashfs/describe.c
@@ -0,0 +1,125 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * describe.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "rdsquashfs.h"
+
+static int print_name(const sqfs_tree_node_t *n)
+{
+ char *start, *ptr, *name = sqfs_tree_node_get_path(n);
+ int ret;
+
+ if (name == NULL) {
+ perror("Recovering file path of tree node");
+ return -1;
+ }
+
+ ret = canonicalize_name(name);
+ assert(ret == 0);
+
+ if (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);
+ }
+
+ free(name);
+ return 0;
+}
+
+static void print_perm(const sqfs_tree_node_t *n)
+{
+ printf(" 0%o %d %d", 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))
+ 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;
+
+ 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))
+ return -1;
+ print_perm(root);
+ printf(" %s/", unpack_root);
+ if (print_name(root))
+ 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 %d %d",
+ 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;
+ }
+
+ return 0;
+}
diff --git a/bin/rdsquashfs/dump_xattrs.c b/bin/rdsquashfs/dump_xattrs.c
new file mode 100644
index 0000000..93b0b01
--- /dev/null
+++ b/bin/rdsquashfs/dump_xattrs.c
@@ -0,0 +1,120 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * dump_xattrs.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#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);
+ 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");
+ }
+
+ free(key);
+ free(value);
+ }
+
+ return 0;
+}
diff --git a/bin/rdsquashfs/fill_files.c b/bin/rdsquashfs/fill_files.c
new file mode 100644
index 0000000..b75afbf
--- /dev/null
+++ b/bin/rdsquashfs/fill_files.c
@@ -0,0 +1,183 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * fill_files.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#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)
+{
+ size_t new_sz;
+ char *path;
+ void *new;
+
+ 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;
+ }
+
+ path = sqfs_tree_node_get_path(node);
+ if (path == NULL) {
+ perror("assembling file path");
+ return -1;
+ }
+
+ if (canonicalize_name(path)) {
+ fprintf(stderr, "Invalid file path '%s'\n", path);
+ 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)
+ 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)
+{
+ size_t i;
+ FILE *fp;
+
+ for (i = 0; i < num_files; ++i) {
+ fp = fopen(files[i].path, "wb");
+ if (fp == NULL) {
+ fprintf(stderr, "unpacking %s: %s\n",
+ files[i].path, strerror(errno));
+ return -1;
+ }
+
+ if (!(flags & UNPACK_QUIET))
+ printf("unpacking %s\n", files[i].path);
+
+ if (sqfs_data_reader_dump(files[i].path, data, files[i].inode,
+ fp, block_size,
+ (flags & UNPACK_NO_SPARSE) == 0)) {
+ fclose(fp);
+ return -1;
+ }
+
+ fflush(fp);
+ fclose(fp);
+ }
+
+ 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/list_files.c b/bin/rdsquashfs/list_files.c
new file mode 100644
index 0000000..238ffec
--- /dev/null
+++ b/bin/rdsquashfs/list_files.c
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * list_files.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#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;
+ }
+
+ *(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;
+ }
+
+ *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/options.c b/bin/rdsquashfs/options.c
new file mode 100644
index 0000000..cdd19e1
--- /dev/null
+++ b/bin/rdsquashfs/options.c
@@ -0,0 +1,217 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * options.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "rdsquashfs.h"
+
+static struct option long_opts[] = {
+ { "list", required_argument, NULL, 'l' },
+ { "cat", required_argument, NULL, 'c' },
+ { "xattr", required_argument, NULL, 'x' },
+ { "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:DSFLCOEZTj:dqhV"
+#ifdef HAVE_SYS_XATTR_H
+ "X"
+#endif
+ ;
+
+static const char *help_string =
+"Usage: rdsquashfs [OPTIONS] <squashfs-file>\n"
+"\n"
+"View or extract the contents of a squashfs image.\n"
+"\n"
+"Possible options:\n"
+"\n"
+" --list, -l <path> Produce a directory listing for a given path in\n"
+" the squashfs image.\n"
+" --cat, -c <path> If the specified path is a regular file in the,\n"
+" image, dump its contents to stdout.\n"
+" --xattr, -x <path> Enumerate extended attributes associated with\n"
+" an inode that the given path resolves to.\n"
+" --unpack-path, -u <path> Unpack this sub directory from the image. To\n"
+" unpack everything, simply specify /.\n"
+" --describe, -d Produce a file listing from the image.\n"
+"\n"
+" --unpack-root, -p <path> 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 '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->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/rdsquashfs.c b/bin/rdsquashfs/rdsquashfs.c
new file mode 100644
index 0000000..fa2bbb4
--- /dev/null
+++ b/bin/rdsquashfs/rdsquashfs.c
@@ -0,0 +1,175 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * rdsquashfs.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "rdsquashfs.h"
+
+int main(int argc, char **argv)
+{
+ sqfs_xattr_reader_t *xattr = NULL;
+ sqfs_compressor_config_t cfg;
+ int status = EXIT_FAILURE;
+ sqfs_data_reader_t *data;
+ sqfs_dir_reader_t *dirrd;
+ sqfs_compressor_t *cmp;
+ sqfs_id_table_t *idtbl;
+ sqfs_tree_node_t *n;
+ sqfs_super_t super;
+ sqfs_file_t *file;
+ 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_cmd;
+ }
+
+ ret = sqfs_super_read(&super, file);
+ if (ret) {
+ sqfs_perror(opt.image_name, "reading super block", ret);
+ goto out_file;
+ }
+
+ 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_file;
+ }
+
+ 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_cmp;
+ }
+
+ ret = sqfs_xattr_reader_load(xattr, &super, file, cmp);
+ if (ret) {
+ sqfs_perror(opt.image_name, "loading xattr table",
+ ret);
+ goto out_xr;
+ }
+ }
+
+ idtbl = sqfs_id_table_create(0);
+ if (idtbl == NULL) {
+ sqfs_perror(opt.image_name, "creating ID table",
+ SQFS_ERROR_ALLOC);
+ goto out_xr;
+ }
+
+ ret = sqfs_id_table_read(idtbl, file, &super, cmp);
+ if (ret) {
+ sqfs_perror(opt.image_name, "loading ID table", ret);
+ goto out_id;
+ }
+
+ dirrd = sqfs_dir_reader_create(&super, cmp, file);
+ if (dirrd == NULL) {
+ sqfs_perror(opt.image_name, "creating dir reader",
+ SQFS_ERROR_ALLOC);
+ goto out_id;
+ }
+
+ data = sqfs_data_reader_create(file, super.block_size, cmp);
+ if (data == NULL) {
+ sqfs_perror(opt.image_name, "creating data reader",
+ SQFS_ERROR_ALLOC);
+ goto out_dr;
+ }
+
+ ret = sqfs_data_reader_load_fragment_table(data, &super);
+ if (ret) {
+ sqfs_perror(opt.image_name, "loading fragment table", ret);
+ goto out_data;
+ }
+
+ 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_data;
+ }
+
+ switch (opt.op) {
+ case OP_LS:
+ list_files(n);
+ break;
+ case OP_CAT:
+ if (!S_ISREG(n->inode->base.mode)) {
+ fprintf(stderr, "/%s: not a regular file\n",
+ opt.cmdpath);
+ goto out;
+ }
+
+ if (sqfs_data_reader_dump(opt.cmdpath, data, n->inode,
+ stdout, super.block_size, false)) {
+ goto out;
+ }
+ break;
+ case OP_UNPACK:
+ 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;
+ }
+
+ status = EXIT_SUCCESS;
+out:
+ sqfs_dir_tree_destroy(n);
+out_data:
+ sqfs_destroy(data);
+out_dr:
+ sqfs_destroy(dirrd);
+out_id:
+ sqfs_destroy(idtbl);
+out_xr:
+ if (xattr != NULL)
+ sqfs_destroy(xattr);
+out_cmp:
+ sqfs_destroy(cmp);
+out_file:
+ sqfs_destroy(file);
+out_cmd:
+ free(opt.cmdpath);
+ return status;
+}
diff --git a/bin/rdsquashfs/rdsquashfs.h b/bin/rdsquashfs/rdsquashfs.h
new file mode 100644
index 0000000..17c0a85
--- /dev/null
+++ b/bin/rdsquashfs/rdsquashfs.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * rdsquashfs.h
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#ifndef RDSQUASHFS_H
+#define RDSQUASHFS_H
+
+#include "config.h"
+#include "common.h"
+#include "fstree.h"
+
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif
+#ifdef HAVE_SYS_XATTR_H
+#include <sys/xattr.h>
+
+#if defined(__APPLE__) && defined(__MACH__)
+#define lsetxattr(path, name, value, size, flags) \
+ setxattr(path, name, value, size, 0, flags | XATTR_NOFOLLOW)
+#endif
+#endif
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+
+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,
+};
+
+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 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/restore_fstree.c b/bin/rdsquashfs/restore_fstree.c
new file mode 100644
index 0000000..8f99439
--- /dev/null
+++ b/bin/rdsquashfs/restore_fstree.c
@@ -0,0 +1,320 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * restore_fstree.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "rdsquashfs.h"
+
+#ifdef _WIN32
+static int create_node(const sqfs_tree_node_t *n, const char *name)
+{
+ WCHAR *wpath;
+ HANDLE fh;
+
+ wpath = path_to_windows(name);
+ if (wpath == NULL)
+ return -1;
+
+ switch (n->inode->base.mode & S_IFMT) {
+ case S_IFDIR:
+ if (!CreateDirectoryW(wpath, NULL))
+ 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:
+ fprintf(stderr, "Creating %s: %ld\n", name, GetLastError());
+ free(wpath);
+ return -1;
+}
+#else
+static int create_node(const sqfs_tree_node_t *n, const char *name)
+{
+ sqfs_u32 devno;
+ int fd;
+
+ 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:
+ fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0600);
+
+ 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;
+ }
+
+ name = sqfs_tree_node_get_path(n);
+ if (name == NULL) {
+ fprintf(stderr, "Constructing full path for '%s': %s\n",
+ (const char *)n->name, strerror(errno));
+ return -1;
+ }
+
+ ret = canonicalize_name(name);
+ assert(ret == 0);
+
+ if (!(flags & UNPACK_QUIET))
+ printf("creating %s\n", name);
+
+ ret = create_node(n, name);
+ 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);
+ 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));
+ }
+
+ free(key);
+ 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;
+ }
+ }
+
+ path = sqfs_tree_node_get_path(n);
+ if (path == NULL) {
+ fprintf(stderr, "Reconstructing full path: %s\n",
+ strerror(errno));
+ 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;
+ }
+ }
+
+ free(path);
+ return 0;
+fail:
+ 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;
+}