summaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bin')
-rw-r--r--bin/Makemodule.am42
-rw-r--r--bin/gensquashfs/dirscan.c321
-rw-r--r--bin/gensquashfs/mkfs.c216
-rw-r--r--bin/gensquashfs/mkfs.h72
-rw-r--r--bin/gensquashfs/options.c289
-rw-r--r--bin/gensquashfs/selinux.c78
-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
-rw-r--r--bin/sqfs2tar.c688
-rw-r--r--bin/sqfsdiff/compare_dir.c94
-rw-r--r--bin/sqfsdiff/compare_files.c72
-rw-r--r--bin/sqfsdiff/extract.c57
-rw-r--r--bin/sqfsdiff/node_compare.c203
-rw-r--r--bin/sqfsdiff/options.c131
-rw-r--r--bin/sqfsdiff/sqfsdiff.c168
-rw-r--r--bin/sqfsdiff/sqfsdiff.h69
-rw-r--r--bin/sqfsdiff/super.c125
-rw-r--r--bin/sqfsdiff/util.c25
-rw-r--r--bin/tar2sqfs.c544
25 files changed, 4567 insertions, 0 deletions
diff --git a/bin/Makemodule.am b/bin/Makemodule.am
new file mode 100644
index 0000000..4199ac5
--- /dev/null
+++ b/bin/Makemodule.am
@@ -0,0 +1,42 @@
+sqfs2tar_SOURCES = bin/sqfs2tar.c
+sqfs2tar_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS)
+sqfs2tar_LDADD = libcommon.a libutil.a libsquashfs.la libtar.a libcompat.a
+sqfs2tar_LDADD += libfstree.a $(LZO_LIBS) $(PTHREAD_LIBS)
+
+tar2sqfs_SOURCES = bin/tar2sqfs.c
+tar2sqfs_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS)
+tar2sqfs_LDADD = libcommon.a libsquashfs.la libtar.a
+tar2sqfs_LDADD += libfstree.a libcompat.a libfstree.a $(LZO_LIBS)
+tar2sqfs_LDADD += $(PTHREAD_LIBS)
+
+rdsquashfs_SOURCES = bin/rdsquashfs/rdsquashfs.c bin/rdsquashfs/rdsquashfs.h
+rdsquashfs_SOURCES += bin/rdsquashfs/list_files.c bin/rdsquashfs/options.c
+rdsquashfs_SOURCES += bin/rdsquashfs/restore_fstree.c bin/rdsquashfs/describe.c
+rdsquashfs_SOURCES += bin/rdsquashfs/fill_files.c bin/rdsquashfs/dump_xattrs.c
+rdsquashfs_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS)
+rdsquashfs_LDADD = libcommon.a libcompat.a libsquashfs.la
+rdsquashfs_LDADD += libfstree.a $(LZO_LIBS) $(PTHREAD_LIBS)
+
+sqfsdiff_SOURCES = bin/sqfsdiff/sqfsdiff.c bin/sqfsdiff/sqfsdiff.h
+sqfsdiff_SOURCES += bin/sqfsdiff/util.c bin/sqfsdiff/options.c
+sqfsdiff_SOURCES += bin/sqfsdiff/compare_dir.c bin/sqfsdiff/node_compare.c
+sqfsdiff_SOURCES += bin/sqfsdiff/compare_files.c bin/sqfsdiff/super.c
+sqfsdiff_SOURCES += bin/sqfsdiff/extract.c
+sqfsdiff_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS)
+sqfsdiff_LDADD = libcommon.a libsquashfs.la libcompat.a $(LZO_LIBS) libfstree.a
+sqfsdiff_LDADD += $(PTHREAD_LIBS)
+
+gensquashfs_SOURCES = bin/gensquashfs/mkfs.c bin/gensquashfs/mkfs.h
+gensquashfs_SOURCES += bin/gensquashfs/options.c bin/gensquashfs/selinux.c
+gensquashfs_SOURCES += bin/gensquashfs/dirscan.c
+gensquashfs_LDADD = libcommon.a libsquashfs.la libfstree.a
+gensquashfs_LDADD += libcompat.a $(LIBSELINUX_LIBS) $(LZO_LIBS)
+gensquashfs_LDADD += $(PTHREAD_LIBS)
+gensquashfs_CPPFLAGS = $(AM_CPPFLAGS)
+gensquashfs_CFLAGS = $(AM_CFLAGS) $(LIBSELINUX_CFLAGS) $(PTHREAD_CFLAGS)
+
+if WITH_SELINUX
+gensquashfs_CPPFLAGS += -DWITH_SELINUX
+endif
+
+bin_PROGRAMS += sqfs2tar tar2sqfs gensquashfs rdsquashfs sqfsdiff
diff --git a/bin/gensquashfs/dirscan.c b/bin/gensquashfs/dirscan.c
new file mode 100644
index 0000000..dbc862c
--- /dev/null
+++ b/bin/gensquashfs/dirscan.c
@@ -0,0 +1,321 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * fstree_from_dir.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "mkfs.h"
+
+#ifdef HAVE_SYS_XATTR_H
+static char *get_full_path(const char *prefix, tree_node_t *node)
+{
+ char *path = NULL, *new = NULL;
+ size_t path_len, prefix_len;
+ int ret;
+
+ path = fstree_get_path(node);
+ if (path == NULL)
+ goto fail;
+
+ ret = canonicalize_name(path);
+ assert(ret == 0);
+
+ path_len = strlen(path);
+ prefix_len = strlen(prefix);
+
+ while (prefix_len > 0 && prefix[prefix_len - 1] == '/')
+ --prefix_len;
+
+ if (prefix_len > 0) {
+ new = realloc(path, path_len + prefix_len + 2);
+ if (new == NULL)
+ goto fail;
+
+ path = new;
+
+ memmove(path + prefix_len + 1, path, path_len + 1);
+ memcpy(path, prefix, prefix_len);
+ path[prefix_len] = '/';
+ }
+
+ return path;
+fail:
+ perror("getting full path for xattr scan");
+ free(path);
+ return NULL;
+}
+
+static int xattr_from_path(sqfs_xattr_writer_t *xwr, const char *path)
+{
+ char *key, *value = NULL, *buffer = NULL;
+ ssize_t buflen, vallen, keylen;
+ int ret;
+
+ buflen = llistxattr(path, NULL, 0);
+ if (buflen < 0) {
+ fprintf(stderr, "llistxattr %s: %s", path, strerror(errno));
+ return -1;
+ }
+
+ if (buflen == 0)
+ return 0;
+
+ buffer = malloc(buflen);
+ if (buffer == NULL) {
+ perror("xattr name buffer");
+ return -1;
+ }
+
+ buflen = llistxattr(path, buffer, buflen);
+ if (buflen == -1) {
+ fprintf(stderr, "llistxattr %s: %s", path, strerror(errno));
+ goto fail;
+ }
+
+ key = buffer;
+ while (buflen > 0) {
+ vallen = lgetxattr(path, key, NULL, 0);
+ if (vallen == -1) {
+ fprintf(stderr, "lgetxattr %s: %s",
+ path, strerror(errno));
+ goto fail;
+ }
+
+ if (vallen > 0) {
+ value = calloc(1, vallen);
+ if (value == NULL) {
+ perror("allocating xattr value buffer");
+ goto fail;
+ }
+
+ vallen = lgetxattr(path, key, value, vallen);
+ if (vallen == -1) {
+ fprintf(stderr, "lgetxattr %s: %s\n",
+ path, strerror(errno));
+ goto fail;
+ }
+
+ ret = sqfs_xattr_writer_add(xwr, key, value, vallen);
+ if (ret) {
+ sqfs_perror(path,
+ "storing xattr key-value pairs",
+ ret);
+ goto fail;
+ }
+
+ free(value);
+ value = NULL;
+ }
+
+ keylen = strlen(key) + 1;
+ buflen -= keylen;
+ key += keylen;
+ }
+
+ free(buffer);
+ return 0;
+fail:
+ free(value);
+ free(buffer);
+ return -1;
+}
+#endif
+
+#ifdef _WIN32
+int fstree_from_dir(fstree_t *fs, const char *path, void *selinux_handle,
+ sqfs_xattr_writer_t *xwr, unsigned int flags)
+{
+ (void)fs; (void)path; (void)selinux_handle; (void)xwr; (void)flags;
+ fputs("Packing a directory is not supported on Windows.\n", stderr);
+ return -1;
+}
+#else
+static int xattr_xcan_dfs(const char *path_prefix, void *selinux_handle,
+ sqfs_xattr_writer_t *xwr, unsigned int flags,
+ tree_node_t *node)
+{
+ char *path;
+ int ret;
+
+ ret = sqfs_xattr_writer_begin(xwr);
+ if (ret) {
+ sqfs_perror(node->name, "recoding xattr key-value pairs\n",
+ ret);
+ return -1;
+ }
+
+#ifdef HAVE_SYS_XATTR_H
+ if (flags & DIR_SCAN_READ_XATTR) {
+ path = get_full_path(path_prefix, node);
+ if (path == NULL)
+ return -1;
+
+ ret = xattr_from_path(xwr, path);
+ free(path);
+
+ if (ret)
+ return -1;
+ }
+#else
+ (void)path_prefix;
+#endif
+
+ if (selinux_handle != NULL) {
+ path = fstree_get_path(node);
+ if (path == NULL) {
+ perror("reconstructing absolute path");
+ return -1;
+ }
+
+ ret = selinux_relable_node(selinux_handle, xwr, node, path);
+ free(path);
+
+ if (ret)
+ return -1;
+ }
+
+ if (sqfs_xattr_writer_end(xwr, &node->xattr_idx)) {
+ sqfs_perror(node->name, "completing xattr key-value pairs",
+ ret);
+ return -1;
+ }
+
+ if (S_ISDIR(node->mode)) {
+ node = node->data.dir.children;
+
+ while (node != NULL) {
+ if (xattr_xcan_dfs(path_prefix, selinux_handle, xwr,
+ flags, node)) {
+ return -1;
+ }
+
+ node = node->next;
+ }
+ }
+
+ return 0;
+}
+
+static int populate_dir(int dir_fd, fstree_t *fs, tree_node_t *root,
+ dev_t devstart, unsigned int flags)
+{
+ char *extra = NULL;
+ struct dirent *ent;
+ struct stat sb;
+ tree_node_t *n;
+ int childfd;
+ DIR *dir;
+
+ dir = fdopendir(dir_fd);
+ if (dir == NULL) {
+ perror("fdopendir");
+ close(dir_fd);
+ return -1;
+ }
+
+ /* XXX: fdopendir can dup and close dir_fd internally
+ and still be compliant with the spec. */
+ dir_fd = dirfd(dir);
+
+ for (;;) {
+ errno = 0;
+ ent = readdir(dir);
+
+ if (ent == NULL) {
+ if (errno) {
+ perror("readdir");
+ goto fail;
+ }
+ break;
+ }
+
+ if (!strcmp(ent->d_name, "..") || !strcmp(ent->d_name, "."))
+ continue;
+
+ if (fstatat(dir_fd, ent->d_name, &sb, AT_SYMLINK_NOFOLLOW)) {
+ perror(ent->d_name);
+ goto fail;
+ }
+
+ if ((flags & DIR_SCAN_ONE_FILESYSTEM) && sb.st_dev != devstart)
+ continue;
+
+ if (S_ISLNK(sb.st_mode)) {
+ extra = calloc(1, sb.st_size + 1);
+ if (extra == NULL)
+ goto fail_rdlink;
+
+ if (readlinkat(dir_fd, ent->d_name,
+ extra, sb.st_size) < 0) {
+ goto fail_rdlink;
+ }
+
+ extra[sb.st_size] = '\0';
+ }
+
+ if (!(flags & DIR_SCAN_KEEP_TIME))
+ sb.st_mtime = fs->defaults.st_mtime;
+
+ n = fstree_mknode(root, ent->d_name, strlen(ent->d_name),
+ extra, &sb);
+ if (n == NULL) {
+ perror("creating tree node");
+ goto fail;
+ }
+
+ free(extra);
+ extra = NULL;
+
+ if (S_ISDIR(n->mode)) {
+ childfd = openat(dir_fd, n->name, O_DIRECTORY |
+ O_RDONLY | O_CLOEXEC);
+ if (childfd < 0) {
+ perror(n->name);
+ goto fail;
+ }
+
+ if (populate_dir(childfd, fs, n, devstart, flags))
+ goto fail;
+ }
+ }
+
+ closedir(dir);
+ return 0;
+fail_rdlink:
+ perror("readlink");
+fail:
+ closedir(dir);
+ free(extra);
+ return -1;
+}
+
+int fstree_from_dir(fstree_t *fs, const char *path, void *selinux_handle,
+ sqfs_xattr_writer_t *xwr, unsigned int flags)
+{
+ struct stat sb;
+ int fd;
+
+ fd = open(path, O_DIRECTORY | O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ perror(path);
+ return -1;
+ }
+
+ if (fstat(fd, &sb)) {
+ perror(path);
+ close(fd);
+ return -1;
+ }
+
+ if (populate_dir(fd, fs, fs->root, sb.st_dev, flags))
+ return -1;
+
+ if (xwr != NULL && (selinux_handle != NULL ||
+ (flags & DIR_SCAN_READ_XATTR))) {
+ if (xattr_xcan_dfs(path, selinux_handle, xwr, flags, fs->root))
+ return -1;
+ }
+
+ return 0;
+}
+#endif
diff --git a/bin/gensquashfs/mkfs.c b/bin/gensquashfs/mkfs.c
new file mode 100644
index 0000000..9ffbb94
--- /dev/null
+++ b/bin/gensquashfs/mkfs.c
@@ -0,0 +1,216 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * mkfs.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "mkfs.h"
+
+static int set_working_dir(options_t *opt)
+{
+ const char *ptr;
+ char *path;
+
+ if (opt->packdir != NULL) {
+ if (chdir(opt->packdir)) {
+ perror(opt->packdir);
+ return -1;
+ }
+ return 0;
+ }
+
+ ptr = strrchr(opt->infile, '/');
+ if (ptr == NULL)
+ return 0;
+
+ path = strndup(opt->infile, ptr - opt->infile);
+ if (path == NULL) {
+ perror("constructing input directory path");
+ return -1;
+ }
+
+ if (chdir(path)) {
+ perror(path);
+ free(path);
+ return -1;
+ }
+
+ free(path);
+ return 0;
+}
+
+static int pack_files(sqfs_block_processor_t *data, fstree_t *fs,
+ options_t *opt)
+{
+ sqfs_inode_generic_t **inode_ptr;
+ sqfs_u64 filesize;
+ sqfs_file_t *file;
+ tree_node_t *node;
+ const char *path;
+ char *node_path;
+ file_info_t *fi;
+ int flags;
+ int ret;
+
+ if (set_working_dir(opt))
+ return -1;
+
+ for (fi = fs->files; fi != NULL; fi = fi->next) {
+ if (fi->input_file == NULL) {
+ node = container_of(fi, tree_node_t, data.file);
+
+ node_path = fstree_get_path(node);
+ if (node_path == NULL) {
+ perror("reconstructing file path");
+ return -1;
+ }
+
+ ret = canonicalize_name(node_path);
+ assert(ret == 0);
+
+ path = node_path;
+ } else {
+ node_path = NULL;
+ path = fi->input_file;
+ }
+
+ if (!opt->cfg.quiet)
+ printf("packing %s\n", path);
+
+ file = sqfs_open_file(path, SQFS_FILE_OPEN_READ_ONLY);
+ if (file == NULL) {
+ perror(path);
+ free(node_path);
+ return -1;
+ }
+
+ flags = 0;
+ filesize = file->get_size(file);
+
+ if (opt->no_tail_packing && filesize > opt->cfg.block_size)
+ flags |= SQFS_BLK_DONT_FRAGMENT;
+
+ inode_ptr = (sqfs_inode_generic_t **)&fi->user_ptr;
+
+ ret = write_data_from_file(path, data, inode_ptr, file, flags);
+ sqfs_destroy(file);
+ free(node_path);
+
+ if (ret)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int relabel_tree_dfs(const char *filename, sqfs_xattr_writer_t *xwr,
+ tree_node_t *n, void *selinux_handle)
+{
+ char *path = fstree_get_path(n);
+ int ret;
+
+ if (path == NULL) {
+ perror("getting absolute node path for SELinux relabeling");
+ return -1;
+ }
+
+ ret = sqfs_xattr_writer_begin(xwr);
+ if (ret) {
+ sqfs_perror(filename, "recording xattr key-value pairs", ret);
+ return -1;
+ }
+
+ if (selinux_relable_node(selinux_handle, xwr, n, path)) {
+ free(path);
+ return -1;
+ }
+
+ ret = sqfs_xattr_writer_end(xwr, &n->xattr_idx);
+ if (ret) {
+ sqfs_perror(filename, "flushing completed key-value pairs",
+ ret);
+ return -1;
+ }
+
+ free(path);
+
+ if (S_ISDIR(n->mode)) {
+ for (n = n->data.dir.children; n != NULL; n = n->next) {
+ if (relabel_tree_dfs(filename, xwr, n, selinux_handle))
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int read_fstree(fstree_t *fs, options_t *opt, sqfs_xattr_writer_t *xwr,
+ void *selinux_handle)
+{
+ FILE *fp;
+ int ret;
+
+ if (opt->infile == NULL) {
+ return fstree_from_dir(fs, opt->packdir, selinux_handle,
+ xwr, opt->dirscan_flags);
+ }
+
+ fp = fopen(opt->infile, "rb");
+ if (fp == NULL) {
+ perror(opt->infile);
+ return -1;
+ }
+
+ ret = fstree_from_file(fs, opt->infile, fp);
+ fclose(fp);
+
+ if (ret == 0 && selinux_handle != NULL)
+ ret = relabel_tree_dfs(opt->cfg.filename, xwr,
+ fs->root, selinux_handle);
+
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+ int status = EXIT_FAILURE;
+ void *sehnd = NULL;
+ sqfs_writer_t sqfs;
+ options_t opt;
+
+ process_command_line(&opt, argc, argv);
+
+ if (sqfs_writer_init(&sqfs, &opt.cfg))
+ return EXIT_FAILURE;
+
+ if (opt.selinux != NULL) {
+ sehnd = selinux_open_context_file(opt.selinux);
+ if (sehnd == NULL)
+ goto out;
+ }
+
+ if (read_fstree(&sqfs.fs, &opt, sqfs.xwr, sehnd)) {
+ if (sehnd != NULL)
+ selinux_close_context_file(sehnd);
+ goto out;
+ }
+
+ if (sehnd != NULL) {
+ selinux_close_context_file(sehnd);
+ sehnd = NULL;
+ }
+
+ if (fstree_post_process(&sqfs.fs))
+ goto out;
+
+ if (pack_files(sqfs.data, &sqfs.fs, &opt))
+ goto out;
+
+ if (sqfs_writer_finish(&sqfs, &opt.cfg))
+ goto out;
+
+ status = EXIT_SUCCESS;
+out:
+ sqfs_writer_cleanup(&sqfs, status);
+ return status;
+}
diff --git a/bin/gensquashfs/mkfs.h b/bin/gensquashfs/mkfs.h
new file mode 100644
index 0000000..1b767aa
--- /dev/null
+++ b/bin/gensquashfs/mkfs.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * mkfs.h
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#ifndef MKFS_H
+#define MKFS_H
+
+#include "config.h"
+
+#include "common.h"
+#include "fstree.h"
+
+#ifdef HAVE_SYS_XATTR_H
+#include <sys/xattr.h>
+
+#if defined(__APPLE__) && defined(__MACH__)
+#define llistxattr(path, list, size) \
+ listxattr(path, list, size, XATTR_NOFOLLOW)
+
+#define lgetxattr(path, name, value, size) \
+ getxattr(path, name, value, size, 0, XATTR_NOFOLLOW)
+#endif
+#endif
+
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
+#include <selinux/label.h>
+#endif
+
+#include <getopt.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+
+typedef struct {
+ sqfs_writer_cfg_t cfg;
+ unsigned int dirscan_flags;
+ const char *infile;
+ const char *packdir;
+ const char *selinux;
+ bool no_tail_packing;
+} options_t;
+
+enum {
+ DIR_SCAN_KEEP_TIME = 0x01,
+
+ DIR_SCAN_ONE_FILESYSTEM = 0x02,
+
+ DIR_SCAN_READ_XATTR = 0x04,
+};
+
+void process_command_line(options_t *opt, int argc, char **argv);
+
+int fstree_from_dir(fstree_t *fs, const char *path, void *selinux_handle,
+ sqfs_xattr_writer_t *xwr, unsigned int flags);
+
+
+void *selinux_open_context_file(const char *filename);
+
+int selinux_relable_node(void *sehnd, sqfs_xattr_writer_t *xwr,
+ tree_node_t *node, const char *path);
+
+void selinux_close_context_file(void *sehnd);
+
+#endif /* MKFS_H */
diff --git a/bin/gensquashfs/options.c b/bin/gensquashfs/options.c
new file mode 100644
index 0000000..2369787
--- /dev/null
+++ b/bin/gensquashfs/options.c
@@ -0,0 +1,289 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * options.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "mkfs.h"
+
+static struct option long_opts[] = {
+ { "compressor", required_argument, NULL, 'c' },
+ { "block-size", required_argument, NULL, 'b' },
+ { "dev-block-size", required_argument, NULL, 'B' },
+ { "defaults", required_argument, NULL, 'd' },
+ { "comp-extra", required_argument, NULL, 'X' },
+ { "pack-file", required_argument, NULL, 'F' },
+ { "pack-dir", required_argument, NULL, 'D' },
+ { "num-jobs", required_argument, NULL, 'j' },
+ { "queue-backlog", required_argument, NULL, 'Q' },
+ { "keep-time", no_argument, NULL, 'k' },
+#ifdef HAVE_SYS_XATTR_H
+ { "keep-xattr", no_argument, NULL, 'x' },
+#endif
+ { "one-file-system", no_argument, NULL, 'o' },
+ { "exportable", no_argument, NULL, 'e' },
+ { "no-tail-packing", no_argument, NULL, 'T' },
+ { "force", no_argument, NULL, 'f' },
+ { "quiet", no_argument, NULL, 'q' },
+#ifdef WITH_SELINUX
+ { "selinux", required_argument, NULL, 's' },
+#endif
+ { "version", no_argument, NULL, 'V' },
+ { "help", no_argument, NULL, 'h' },
+ { NULL, 0, NULL, 0 },
+};
+
+static const char *short_opts = "F:D:X:c:b:B:d:j:Q:kxoefqThV"
+#ifdef WITH_SELINUX
+"s:"
+#endif
+#ifdef HAVE_SYS_XATTR_H
+"x"
+#endif
+;
+
+static const char *help_string =
+"Usage: gensquashfs [OPTIONS...] <squashfs-file>\n"
+"\n"
+"Possible options:\n"
+"\n"
+" --pack-file, -F <file> Use a `gen_init_cpio` style description file.\n"
+" The file format is specified below.\n"
+" If --pack-dir is used, input file paths are\n"
+" relative to the pack directory, otherwise\n"
+" they are relative to the directory the pack\n"
+" file is in.\n"
+" --pack-dir, -D <directory> If --pack-file is used, this is the root path\n"
+" relative to which to read files. If no pack\n"
+" file is specified, pack the contents of the\n"
+" given directory into a SquashFS image. The\n"
+" directory becomes the root of the file\n"
+" system.\n"
+"\n"
+" --compressor, -c <name> Select the compressor to use.\n"
+" A list of available compressors is below.\n"
+" --comp-extra, -X <options> A comma separated list of extra options for\n"
+" the selected compressor. Specify 'help' to\n"
+" get a list of available options.\n"
+" --num-jobs, -j <count> Number of compressor jobs to create.\n"
+" --queue-backlog, -Q <count> Maximum number of data blocks in the thread\n"
+" worker queue before the packer starts waiting\n"
+" for the block processors to catch up.\n"
+" Defaults to 10 times the number of jobs.\n"
+" --block-size, -b <size> Block size to use for Squashfs image.\n"
+" Defaults to %u.\n"
+" --dev-block-size, -B <size> Device block size to padd the image to.\n"
+" Defaults to %u.\n"
+" --defaults, -d <options> A comma separated list of default values for\n"
+" implicitly created directories.\n"
+"\n"
+" Possible options:\n"
+" uid=<value> 0 if not set.\n"
+" gid=<value> 0 if not set.\n"
+" mode=<value> 0755 if not set.\n"
+" mtime=<value> 0 if not set.\n"
+"\n"
+#ifdef WITH_SELINUX
+" --selinux, -s <file> Specify an SELinux label file to get context\n"
+" attributes from.\n"
+#endif
+" --keep-time, -k When using --pack-dir only, use the timestamps\n"
+" from the input files instead of setting\n"
+" defaults on all input paths.\n"
+" --keep-xattr, -x When using --pack-dir only, read and pack the\n"
+" extended attributes from the input files.\n"
+" --one-file-system, -o When using --pack-dir only, stay in local file\n"
+" system and do not cross mount points.\n"
+" --exportable, -e Generate an export table for NFS support.\n"
+" --no-tail-packing, -T Do not perform tail end packing on files that\n"
+" are larger than block size.\n"
+" --force, -f Overwrite the output file if it exists.\n"
+" --quiet, -q Do not print out progress reports.\n"
+" --help, -h Print help text and exit.\n"
+" --version, -V Print version information and exit.\n"
+"\n";
+
+const char *help_details =
+"When using the pack file option, the given file is expected to contain\n"
+"newline separated entries that describe the files to be included in the\n"
+"SquashFS image. The following entry types can be specified:\n"
+"\n"
+"# a comment\n"
+"file <path> <mode> <uid> <gid> [<location>]\n"
+"dir <path> <mode> <uid> <gid>\n"
+"nod <path> <mode> <uid> <gid> <dev_type> <maj> <min>\n"
+"slink <path> <mode> <uid> <gid> <target>\n"
+"link <path> <dummy> <dummy> <dummy> <target>\n"
+"pipe <path> <mode> <uid> <gid>\n"
+"sock <path> <mode> <uid> <gid>\n"
+"\n"
+"<path> Absolute path of the entry in the image. Can be put in quotes\n"
+" if some components contain spaces.\n"
+"<location> If given, location of the input file. Either absolute or relative\n"
+" to the description file. If omitted, the image path is used,\n"
+" relative to the description file.\n"
+"<target> Symlink or hardlink target.\n"
+"<mode> Mode/permissions of the entry.\n"
+"<uid> Numeric user id.\n"
+"<gid> Numeric group id.\n"
+"<dev_type> Device type (b=block, c=character).\n"
+"<maj> Major number of a device special file.\n"
+"<min> Minor number of a device special file.\n"
+"\n"
+"Example:\n"
+" # A simple squashfs image\n"
+" dir /dev 0755 0 0\n"
+" nod /dev/console 0600 0 0 c 5 1\n"
+" dir /root 0700 0 0\n"
+" dir /sbin 0755 0 0\n"
+" \n"
+" # Add a file. Input is relative to listing or pack dir.\n"
+" file /sbin/init 0755 0 0 ../init/sbin/init\n"
+" \n"
+" # Read bin/bash, relative to listing or pack dir.\n"
+" # Implicitly create /bin.\n"
+" file /bin/bash 0755 0 0\n"
+" \n"
+" # file name with a space in it.\n"
+" file \"/opt/my app/\\\"special\\\"/data\" 0600 0 0\n"
+"\n\n";
+
+void process_command_line(options_t *opt, int argc, char **argv)
+{
+ bool have_compressor;
+ int i, ret;
+
+ memset(opt, 0, sizeof(*opt));
+ sqfs_writer_cfg_init(&opt->cfg);
+
+ for (;;) {
+ i = getopt_long(argc, argv, short_opts, long_opts, NULL);
+ if (i == -1)
+ break;
+
+ switch (i) {
+ case 'T':
+ opt->no_tail_packing = true;
+ break;
+ case 'c':
+ have_compressor = true;
+ ret = sqfs_compressor_id_from_name(optarg);
+
+ if (ret < 0) {
+ have_compressor = false;
+#ifdef WITH_LZO
+ if (opt->cfg.comp_id == SQFS_COMP_LZO)
+ have_compressor = true;
+#endif
+ }
+
+ if (!have_compressor) {
+ fprintf(stderr, "Unsupported compressor '%s'\n",
+ optarg);
+ exit(EXIT_FAILURE);
+ }
+
+ opt->cfg.comp_id = ret;
+ break;
+ case 'b':
+ if (parse_size("Block size", &opt->cfg.block_size,
+ optarg, 0)) {
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'j':
+ opt->cfg.num_jobs = strtol(optarg, NULL, 0);
+ break;
+ case 'Q':
+ opt->cfg.max_backlog = strtol(optarg, NULL, 0);
+ break;
+ case 'B':
+ if (parse_size("Device block size",
+ &opt->cfg.devblksize, optarg, 0)) {
+ exit(EXIT_FAILURE);
+ }
+ if (opt->cfg.devblksize < 1024) {
+ fputs("Device block size must be at "
+ "least 1024\n", stderr);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'd':
+ opt->cfg.fs_defaults = optarg;
+ break;
+ case 'k':
+ opt->dirscan_flags |= DIR_SCAN_KEEP_TIME;
+ break;
+#ifdef HAVE_SYS_XATTR_H
+ case 'x':
+ opt->dirscan_flags |= DIR_SCAN_READ_XATTR;
+ break;
+#endif
+ case 'o':
+ opt->dirscan_flags |= DIR_SCAN_ONE_FILESYSTEM;
+ break;
+ case 'e':
+ opt->cfg.exportable = true;
+ break;
+ case 'f':
+ opt->cfg.outmode |= SQFS_FILE_OPEN_OVERWRITE;
+ break;
+ case 'q':
+ opt->cfg.quiet = true;
+ break;
+ case 'X':
+ opt->cfg.comp_extra = optarg;
+ break;
+ case 'F':
+ opt->infile = optarg;
+ break;
+ case 'D':
+ opt->packdir = optarg;
+ break;
+#ifdef WITH_SELINUX
+ case 's':
+ opt->selinux = optarg;
+ break;
+#endif
+ case 'h':
+ printf(help_string,
+ SQFS_DEFAULT_BLOCK_SIZE, SQFS_DEVBLK_SIZE);
+ fputs(help_details, stdout);
+ compressor_print_available();
+ exit(EXIT_SUCCESS);
+ case 'V':
+ print_version("gensquashfs");
+ exit(EXIT_SUCCESS);
+ default:
+ goto fail_arg;
+ }
+ }
+
+ if (opt->cfg.num_jobs < 1)
+ opt->cfg.num_jobs = 1;
+
+ if (opt->cfg.max_backlog < 1)
+ opt->cfg.max_backlog = 10 * opt->cfg.num_jobs;
+
+ if (opt->cfg.comp_extra != NULL &&
+ strcmp(opt->cfg.comp_extra, "help") == 0) {
+ compressor_print_help(opt->cfg.comp_id);
+ exit(EXIT_SUCCESS);
+ }
+
+ if (opt->infile == NULL && opt->packdir == NULL) {
+ fputs("No input file or directory specified.\n", stderr);
+ goto fail_arg;
+ }
+
+ if (optind >= argc) {
+ fputs("No output file specified.\n", stderr);
+ goto fail_arg;
+ }
+
+ opt->cfg.filename = argv[optind++];
+ return;
+fail_arg:
+ fputs("Try `gensquashfs --help' for more information.\n", stderr);
+ exit(EXIT_FAILURE);
+}
diff --git a/bin/gensquashfs/selinux.c b/bin/gensquashfs/selinux.c
new file mode 100644
index 0000000..678723b
--- /dev/null
+++ b/bin/gensquashfs/selinux.c
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * selinux.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "mkfs.h"
+
+#define XATTR_NAME_SELINUX "security.selinux"
+#define XATTR_VALUE_SELINUX "system_u:object_r:unlabeled_t:s0"
+
+#ifdef WITH_SELINUX
+int selinux_relable_node(void *sehnd, sqfs_xattr_writer_t *xwr,
+ tree_node_t *node, const char *path)
+{
+ char *context = NULL;
+ int ret;
+
+ if (selabel_lookup(sehnd, &context, path, node->mode) < 0) {
+ context = strdup(XATTR_VALUE_SELINUX);
+ if (context == NULL)
+ goto fail;
+ }
+
+ ret = sqfs_xattr_writer_add(xwr, XATTR_NAME_SELINUX,
+ context, strlen(context));
+ free(context);
+
+ if (ret)
+ sqfs_perror(node->name, "storing SELinux xattr", ret);
+
+ return ret;
+fail:
+ perror("relabeling files");
+ return -1;
+}
+
+void *selinux_open_context_file(const char *filename)
+{
+ struct selabel_handle *sehnd;
+ struct selinux_opt seopts[] = {
+ { SELABEL_OPT_PATH, filename },
+ };
+
+ sehnd = selabel_open(SELABEL_CTX_FILE, seopts, 1);
+ if (sehnd == NULL)
+ perror(filename);
+
+ return sehnd;
+}
+
+void selinux_close_context_file(void *sehnd)
+{
+ selabel_close(sehnd);
+}
+#else
+int selinux_relable_node(void *sehnd, sqfs_xattr_writer_t *xwr,
+ tree_node_t *node, const char *path)
+{
+ (void)sehnd; (void)xwr; (void)node; (void)path;
+ fputs("Built without SELinux support, cannot add SELinux labels\n",
+ stderr);
+ return -1;
+}
+
+void *selinux_open_context_file(const char *filename)
+{
+ (void)filename;
+ fputs("Built without SELinux support, cannot open contexts file\n",
+ stderr);
+ return NULL;
+}
+
+void selinux_close_context_file(void *sehnd)
+{
+ (void)sehnd;
+}
+#endif
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;
+}
diff --git a/bin/sqfs2tar.c b/bin/sqfs2tar.c
new file mode 100644
index 0000000..6d2a51a
--- /dev/null
+++ b/bin/sqfs2tar.c
@@ -0,0 +1,688 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * sqfs2tar.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "config.h"
+#include "common.h"
+#include "tar.h"
+
+#include <getopt.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+
+static struct option long_opts[] = {
+ { "subdir", required_argument, NULL, 'd' },
+ { "keep-as-dir", no_argument, NULL, 'k' },
+ { "root-becomes", required_argument, NULL, 'r' },
+ { "no-skip", no_argument, NULL, 's' },
+ { "no-xattr", no_argument, NULL, 'X' },
+ { "no-hard-links", no_argument, NULL, 'L' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { NULL, 0, NULL, 0 },
+};
+
+static const char *short_opts = "d:kr:sXLhV";
+
+static const char *usagestr =
+"Usage: sqfs2tar [OPTIONS...] <sqfsfile>\n"
+"\n"
+"Read an input squashfs archive and turn it into a tar archive, written\n"
+"to stdout.\n"
+"\n"
+"Possible options:\n"
+"\n"
+" --subdir, -d <dir> Unpack the given sub directory instead of the\n"
+" filesystem root. Can be specified more than\n"
+" once to select multiple directories. If only\n"
+" one is specified, it becomes the new root of\n"
+" node of the archive file system tree.\n"
+"\n"
+" --root-becomes, -r <dir> Turn the root inode into a directory with the\n"
+" specified name. Everything else will be stored\n"
+" inside this directory. The special value '.' is\n"
+" allowed to prefix all tar paths with './' and\n"
+" add an entry named '.' for the root inode.\n"
+" If this option isn't used, all meta data stored\n"
+" in the root inode IS LOST!\n"
+"\n"
+" --keep-as-dir, -k If --subdir is used only once, don't make the\n"
+" subdir the archive root, instead keep it as\n"
+" prefix for all unpacked files.\n"
+" Using --subdir more than once implies\n"
+" --keep-as-dir.\n"
+" --no-xattr, -X Do not copy extended attributes.\n"
+" --no-hard-links, -L Do not generate hard links. Produce duplicate\n"
+" entries instead.\n"
+"\n"
+" --no-skip, -s Abort if a file cannot be stored in a tar\n"
+" archive. By default, it is simply skipped\n"
+" and a warning is written to stderr.\n"
+"\n"
+" --help, -h Print help text and exit.\n"
+" --version, -V Print version information and exit.\n"
+"\n"
+"Examples:\n"
+"\n"
+"\tsqfs2tar rootfs.sqfs > rootfs.tar\n"
+"\tsqfs2tar rootfs.sqfs | gzip > rootfs.tar.gz\n"
+"\tsqfs2tar rootfs.sqfs | xz > rootfs.tar.xz\n"
+"\n";
+
+static const char *filename;
+static unsigned int record_counter;
+static bool dont_skip = false;
+static bool keep_as_dir = false;
+static bool no_xattr = false;
+static bool no_links = false;
+
+static char *root_becomes = NULL;
+static char **subdirs = NULL;
+static size_t num_subdirs = 0;
+static size_t max_subdirs = 0;
+
+static sqfs_xattr_reader_t *xr;
+static sqfs_data_reader_t *data;
+static sqfs_file_t *file;
+static sqfs_super_t super;
+static sqfs_hard_link_t *links = NULL;
+
+static FILE *out_file = NULL;
+
+static void process_args(int argc, char **argv)
+{
+ size_t idx, new_count;
+ int i, ret;
+ void *new;
+
+ for (;;) {
+ i = getopt_long(argc, argv, short_opts, long_opts, NULL);
+ if (i == -1)
+ break;
+
+ switch (i) {
+ case 'd':
+ if (num_subdirs == max_subdirs) {
+ new_count = max_subdirs ? max_subdirs * 2 : 16;
+ new = realloc(subdirs,
+ new_count * sizeof(subdirs[0]));
+ if (new == NULL)
+ goto fail_errno;
+
+ max_subdirs = new_count;
+ subdirs = new;
+ }
+
+ subdirs[num_subdirs] = strdup(optarg);
+ if (subdirs[num_subdirs] == NULL)
+ goto fail_errno;
+
+ if (canonicalize_name(subdirs[num_subdirs])) {
+ perror(optarg);
+ goto fail;
+ }
+
+ ++num_subdirs;
+ break;
+ case 'r':
+ free(root_becomes);
+ root_becomes = strdup(optarg);
+ if (root_becomes == NULL)
+ goto fail_errno;
+
+ if (strcmp(root_becomes, "./") == 0)
+ root_becomes[1] = '\0';
+
+ if (strcmp(root_becomes, ".") == 0)
+ break;
+
+ if (canonicalize_name(root_becomes) != 0 ||
+ strlen(root_becomes) == 0) {
+ fprintf(stderr,
+ "Invalid root directory '%s'.\n",
+ optarg);
+ goto fail_arg;
+ }
+ break;
+ case 'k':
+ keep_as_dir = true;
+ break;
+ case 's':
+ dont_skip = true;
+ break;
+ case 'X':
+ no_xattr = true;
+ break;
+ case 'L':
+ no_links = true;
+ break;
+ case 'h':
+ fputs(usagestr, stdout);
+ goto out_success;
+ case 'V':
+ print_version("sqfs2tar");
+ goto out_success;
+ default:
+ goto fail_arg;
+ }
+ }
+
+ if (optind >= argc) {
+ fputs("Missing argument: squashfs image\n", stderr);
+ goto fail_arg;
+ }
+
+ filename = argv[optind++];
+
+ if (optind < argc) {
+ fputs("Unknown extra arguments\n", stderr);
+ goto fail_arg;
+ }
+
+ if (num_subdirs > 1)
+ keep_as_dir = true;
+
+ return;
+fail_errno:
+ perror("parsing options");
+ goto fail;
+fail_arg:
+ fputs("Try `sqfs2tar --help' for more information.\n", stderr);
+ goto fail;
+fail:
+ ret = EXIT_FAILURE;
+ goto out_exit;
+out_success:
+ ret = EXIT_SUCCESS;
+ goto out_exit;
+out_exit:
+ for (idx = 0; idx < num_subdirs; ++idx)
+ free(subdirs[idx]);
+ free(root_becomes);
+ free(subdirs);
+ exit(ret);
+}
+
+static int terminate_archive(void)
+{
+ char buffer[1024];
+
+ memset(buffer, '\0', sizeof(buffer));
+
+ return write_retry("adding archive terminator", out_file,
+ buffer, sizeof(buffer));
+}
+
+static int get_xattrs(const char *name, const sqfs_inode_generic_t *inode,
+ tar_xattr_t **out)
+{
+ tar_xattr_t *list = NULL, *ent;
+ sqfs_xattr_value_t *value;
+ sqfs_xattr_entry_t *key;
+ sqfs_xattr_id_t desc;
+ sqfs_u32 index;
+ size_t i;
+ int ret;
+
+ if (xr == NULL)
+ return 0;
+
+ sqfs_inode_get_xattr_index(inode, &index);
+
+ if (index == 0xFFFFFFFF)
+ return 0;
+
+ ret = sqfs_xattr_reader_get_desc(xr, index, &desc);
+ if (ret) {
+ sqfs_perror(name, "resolving xattr index", ret);
+ return -1;
+ }
+
+ ret = sqfs_xattr_reader_seek_kv(xr, &desc);
+ if (ret) {
+ sqfs_perror(name, "locating xattr key-value pairs", ret);
+ return -1;
+ }
+
+ for (i = 0; i < desc.count; ++i) {
+ ret = sqfs_xattr_reader_read_key(xr, &key);
+ if (ret) {
+ sqfs_perror(name, "reading xattr key", ret);
+ goto fail;
+ }
+
+ ret = sqfs_xattr_reader_read_value(xr, key, &value);
+ if (ret) {
+ sqfs_perror(name, "reading xattr value", ret);
+ free(key);
+ goto fail;
+ }
+
+ ent = calloc(1, sizeof(*ent) + strlen((const char *)key->key) +
+ value->size + 2);
+ if (ent == NULL) {
+ perror("creating xattr entry");
+ free(key);
+ free(value);
+ goto fail;
+ }
+
+ ent->key = ent->data;
+ strcpy(ent->key, (const char *)key->key);
+
+ ent->value = (sqfs_u8 *)ent->key + strlen(ent->key) + 1;
+ memcpy(ent->value, value->value, value->size + 1);
+
+ ent->value_len = value->size;
+ ent->next = list;
+ list = ent;
+
+ free(key);
+ free(value);
+ }
+
+ *out = list;
+ return 0;
+fail:
+ while (list != NULL) {
+ ent = list;
+ list = list->next;
+ free(ent);
+ }
+ return -1;
+}
+
+static char *assemble_tar_path(char *name, bool is_dir)
+{
+ size_t len, new_len;
+ char *temp;
+ (void)is_dir;
+
+ if (root_becomes == NULL && !is_dir)
+ return name;
+
+ new_len = strlen(name);
+ if (root_becomes != NULL)
+ new_len += strlen(root_becomes) + 1;
+ if (is_dir)
+ new_len += 1;
+
+ temp = realloc(name, new_len + 1);
+ if (temp == NULL) {
+ perror("assembling tar entry filename");
+ free(name);
+ return NULL;
+ }
+
+ name = temp;
+
+ if (root_becomes != NULL) {
+ len = strlen(root_becomes);
+
+ memmove(name + len + 1, name, strlen(name) + 1);
+ memcpy(name, root_becomes, len);
+ name[len] = '/';
+ }
+
+ if (is_dir) {
+ len = strlen(name);
+
+ if (len == 0 || name[len - 1] != '/') {
+ name[len++] = '/';
+ name[len] = '\0';
+ }
+ }
+
+ return name;
+}
+
+static int write_tree_dfs(const sqfs_tree_node_t *n)
+{
+ tar_xattr_t *xattr = NULL, *xit;
+ sqfs_hard_link_t *lnk = NULL;
+ char *name, *target;
+ struct stat sb;
+ size_t len;
+ int ret;
+
+ inode_stat(n, &sb);
+
+ if (n->parent == NULL) {
+ if (root_becomes == NULL)
+ goto skip_hdr;
+
+ len = strlen(root_becomes);
+ name = malloc(len + 2);
+ if (name == NULL) {
+ perror("creating root directory");
+ return -1;
+ }
+
+ memcpy(name, root_becomes, len);
+ name[len] = '/';
+ name[len + 1] = '\0';
+ } else {
+ if (!is_filename_sane((const char *)n->name, false)) {
+ fprintf(stderr, "Found a file named '%s', skipping.\n",
+ n->name);
+ if (dont_skip) {
+ fputs("Not allowed to skip files, aborting!\n",
+ stderr);
+ return -1;
+ }
+ return 0;
+ }
+
+ name = sqfs_tree_node_get_path(n);
+ if (name == NULL) {
+ perror("resolving tree node path");
+ return -1;
+ }
+
+ if (canonicalize_name(name))
+ goto out_skip;
+
+ for (lnk = links; lnk != NULL; lnk = lnk->next) {
+ if (lnk->inode_number == n->inode->base.inode_number) {
+ if (strcmp(name, lnk->target) == 0)
+ lnk = NULL;
+ break;
+ }
+ }
+
+ name = assemble_tar_path(name, S_ISDIR(sb.st_mode));
+ if (name == NULL)
+ return -1;
+ }
+
+ if (lnk != NULL) {
+ ret = write_hard_link(out_file, &sb, name, lnk->target,
+ record_counter++);
+ free(name);
+ return ret;
+ }
+
+ if (!no_xattr) {
+ if (get_xattrs(name, n->inode, &xattr)) {
+ free(name);
+ return -1;
+ }
+ }
+
+ target = S_ISLNK(sb.st_mode) ? (char *)n->inode->extra : NULL;
+ ret = write_tar_header(out_file, &sb, name, target, xattr,
+ record_counter++);
+
+ while (xattr != NULL) {
+ xit = xattr;
+ xattr = xattr->next;
+ free(xit);
+ }
+
+ if (ret > 0)
+ goto out_skip;
+
+ if (ret < 0) {
+ free(name);
+ return -1;
+ }
+
+ if (S_ISREG(sb.st_mode)) {
+ if (sqfs_data_reader_dump(name, data, n->inode, out_file,
+ super.block_size, false)) {
+ free(name);
+ return -1;
+ }
+
+ if (padd_file(out_file, sb.st_size)) {
+ free(name);
+ return -1;
+ }
+ }
+
+ free(name);
+skip_hdr:
+ for (n = n->children; n != NULL; n = n->next) {
+ if (write_tree_dfs(n))
+ return -1;
+ }
+ return 0;
+out_skip:
+ if (dont_skip) {
+ fputs("Not allowed to skip files, aborting!\n", stderr);
+ ret = -1;
+ } else {
+ fprintf(stderr, "Skipping %s\n", name);
+ ret = 0;
+ }
+ free(name);
+ return ret;
+}
+
+static sqfs_tree_node_t *tree_merge(sqfs_tree_node_t *lhs,
+ sqfs_tree_node_t *rhs)
+{
+ sqfs_tree_node_t *head = NULL, **next_ptr = &head;
+ sqfs_tree_node_t *it, *l, *r;
+ int diff;
+
+ while (lhs->children != NULL && rhs->children != NULL) {
+ diff = strcmp((const char *)lhs->children->name,
+ (const char *)rhs->children->name);
+
+ if (diff < 0) {
+ it = lhs->children;
+ lhs->children = lhs->children->next;
+ } else if (diff > 0) {
+ it = rhs->children;
+ rhs->children = rhs->children->next;
+ } else {
+ l = lhs->children;
+ lhs->children = lhs->children->next;
+
+ r = rhs->children;
+ rhs->children = rhs->children->next;
+
+ it = tree_merge(l, r);
+ }
+
+ *next_ptr = it;
+ next_ptr = &it->next;
+ }
+
+ it = (lhs->children != NULL ? lhs->children : rhs->children);
+ *next_ptr = it;
+
+ sqfs_dir_tree_destroy(rhs);
+ lhs->children = head;
+ return lhs;
+}
+
+int main(int argc, char **argv)
+{
+ sqfs_tree_node_t *root = NULL, *subtree;
+ int flags, ret, status = EXIT_FAILURE;
+ sqfs_compressor_config_t cfg;
+ sqfs_compressor_t *cmp;
+ sqfs_id_table_t *idtbl;
+ sqfs_dir_reader_t *dr;
+ sqfs_hard_link_t *lnk;
+ size_t i;
+
+ process_args(argc, argv);
+
+#ifdef _WIN32
+ _setmode(_fileno(stdout), _O_BINARY);
+ out_file = stdout;
+#else
+ out_file = freopen(NULL, "wb", stdout);
+#endif
+
+ if (out_file == NULL) {
+ perror("changing stdout to binary mode");
+ goto out_dirs;
+ }
+
+ file = sqfs_open_file(filename, SQFS_FILE_OPEN_READ_ONLY);
+ if (file == NULL) {
+ perror(filename);
+ goto out_dirs;
+ }
+
+ ret = sqfs_super_read(&super, file);
+ if (ret) {
+ sqfs_perror(filename, "reading super block", ret);
+ goto out_fd;
+ }
+
+ 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(filename, "creating compressor", ret);
+ goto out_fd;
+ }
+
+ idtbl = sqfs_id_table_create(0);
+
+ if (idtbl == NULL) {
+ perror("creating ID table");
+ goto out_cmp;
+ }
+
+ ret = sqfs_id_table_read(idtbl, file, &super, cmp);
+ if (ret) {
+ sqfs_perror(filename, "loading ID table", ret);
+ goto out_id;
+ }
+
+ data = sqfs_data_reader_create(file, super.block_size, cmp);
+ if (data == NULL) {
+ sqfs_perror(filename, "creating data reader",
+ SQFS_ERROR_ALLOC);
+ goto out_id;
+ }
+
+ ret = sqfs_data_reader_load_fragment_table(data, &super);
+ if (ret) {
+ sqfs_perror(filename, "loading fragment table", ret);
+ goto out_data;
+ }
+
+ dr = sqfs_dir_reader_create(&super, cmp, file);
+ if (dr == NULL) {
+ sqfs_perror(filename, "creating dir reader",
+ SQFS_ERROR_ALLOC);
+ goto out_data;
+ }
+
+ if (!no_xattr && !(super.flags & SQFS_FLAG_NO_XATTRS)) {
+ xr = sqfs_xattr_reader_create(0);
+ if (xr == NULL) {
+ sqfs_perror(filename, "creating xattr reader",
+ SQFS_ERROR_ALLOC);
+ goto out_dr;
+ }
+
+ ret = sqfs_xattr_reader_load(xr, &super, file, cmp);
+ if (ret) {
+ sqfs_perror(filename, "loading xattr table", ret);
+ goto out_xr;
+ }
+ }
+
+ if (num_subdirs == 0) {
+ ret = sqfs_dir_reader_get_full_hierarchy(dr, idtbl, NULL,
+ 0, &root);
+ if (ret) {
+ sqfs_perror(filename, "loading filesystem tree", ret);
+ goto out;
+ }
+ } else {
+ flags = 0;
+
+ if (keep_as_dir || num_subdirs > 1)
+ flags = SQFS_TREE_STORE_PARENTS;
+
+ for (i = 0; i < num_subdirs; ++i) {
+ ret = sqfs_dir_reader_get_full_hierarchy(dr, idtbl,
+ subdirs[i],
+ flags,
+ &subtree);
+ if (ret) {
+ sqfs_perror(subdirs[i], "loading filesystem "
+ "tree", ret);
+ goto out;
+ }
+
+ if (root == NULL) {
+ root = subtree;
+ } else {
+ root = tree_merge(root, subtree);
+ }
+ }
+ }
+
+ if (!no_links) {
+ if (sqfs_tree_find_hard_links(root, &links))
+ goto out_tree;
+
+ for (lnk = links; lnk != NULL; lnk = lnk->next) {
+ lnk->target = assemble_tar_path(lnk->target, false);
+ if (lnk->target == NULL)
+ goto out;
+ }
+ }
+
+ if (write_tree_dfs(root))
+ goto out;
+
+ if (terminate_archive())
+ goto out;
+
+ status = EXIT_SUCCESS;
+ fflush(out_file);
+out:
+ while (links != NULL) {
+ lnk = links;
+ links = links->next;
+ free(lnk->target);
+ free(lnk);
+ }
+out_tree:
+ if (root != NULL)
+ sqfs_dir_tree_destroy(root);
+out_xr:
+ if (xr != NULL)
+ sqfs_destroy(xr);
+out_dr:
+ sqfs_destroy(dr);
+out_data:
+ sqfs_destroy(data);
+out_id:
+ sqfs_destroy(idtbl);
+out_cmp:
+ sqfs_destroy(cmp);
+out_fd:
+ sqfs_destroy(file);
+out_dirs:
+ for (i = 0; i < num_subdirs; ++i)
+ free(subdirs[i]);
+ free(subdirs);
+ free(root_becomes);
+ return status;
+}
diff --git a/bin/sqfsdiff/compare_dir.c b/bin/sqfsdiff/compare_dir.c
new file mode 100644
index 0000000..1a4c800
--- /dev/null
+++ b/bin/sqfsdiff/compare_dir.c
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * compare_dir.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "sqfsdiff.h"
+
+static int print_omitted(sqfsdiff_t *sd, bool is_old, sqfs_tree_node_t *n)
+{
+ char *path = node_path(n);
+
+ if (path == NULL)
+ return -1;
+
+ fprintf(stdout, "%c %s\n", is_old ? '<' : '>', path);
+
+ if ((sd->compare_flags & COMPARE_EXTRACT_FILES) &&
+ S_ISREG(n->inode->base.mode)) {
+ if (extract_files(sd, is_old ? n->inode : NULL,
+ is_old ? NULL : n->inode, path)) {
+ free(path);
+ return -1;
+ }
+ }
+
+ free(path);
+
+ for (n = n->children; n != NULL; n = n->next) {
+ if (print_omitted(sd, is_old, n))
+ return -1;
+ }
+
+ return 0;
+}
+
+int compare_dir_entries(sqfsdiff_t *sd, sqfs_tree_node_t *old,
+ sqfs_tree_node_t *new)
+{
+ sqfs_tree_node_t *old_it = old->children, *old_prev = NULL;
+ sqfs_tree_node_t *new_it = new->children, *new_prev = NULL;
+ int ret, result = 0;
+
+ while (old_it != NULL || new_it != NULL) {
+ if (old_it != NULL && new_it != NULL) {
+ ret = strcmp((const char *)old_it->name,
+ (const char *)new_it->name);
+ } else if (old_it == NULL) {
+ ret = 1;
+ } else {
+ ret = -1;
+ }
+
+ if (ret < 0) {
+ result = 1;
+
+ if (print_omitted(sd, true, old_it))
+ return -1;
+
+ if (old_prev == NULL) {
+ old->children = old_it->next;
+ sqfs_dir_tree_destroy(old_it);
+ old_it = old->children;
+ } else {
+ old_prev->next = old_it->next;
+ sqfs_dir_tree_destroy(old_it);
+ old_it = old_prev->next;
+ }
+ } else if (ret > 0) {
+ result = 1;
+
+ if (print_omitted(sd, false, new_it))
+ return -1;
+
+ if (new_prev == NULL) {
+ new->children = new_it->next;
+ sqfs_dir_tree_destroy(new_it);
+ new_it = new->children;
+ } else {
+ new_prev->next = new_it->next;
+ sqfs_dir_tree_destroy(new_it);
+ new_it = new_prev->next;
+ }
+ } else {
+ old_prev = old_it;
+ old_it = old_it->next;
+
+ new_prev = new_it;
+ new_it = new_it->next;
+ }
+ }
+
+ return result;
+}
diff --git a/bin/sqfsdiff/compare_files.c b/bin/sqfsdiff/compare_files.c
new file mode 100644
index 0000000..51b66bb
--- /dev/null
+++ b/bin/sqfsdiff/compare_files.c
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * compare_files.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "sqfsdiff.h"
+
+static unsigned char old_buf[MAX_WINDOW_SIZE];
+static unsigned char new_buf[MAX_WINDOW_SIZE];
+
+static int read_blob(const char *prefix, const char *path,
+ sqfs_data_reader_t *rd, const sqfs_inode_generic_t *inode,
+ void *buffer, sqfs_u64 offset, size_t size)
+{
+ ssize_t ret;
+
+ ret = sqfs_data_reader_read(rd, inode, offset, buffer, size);
+ ret = (ret < 0 || (size_t)ret < size) ? -1 : 0;
+
+ if (ret) {
+ fprintf(stderr, "Failed to read %s from %s\n",
+ path, prefix);
+ return -1;
+ }
+
+ return 0;
+}
+
+int compare_files(sqfsdiff_t *sd, const sqfs_inode_generic_t *old,
+ const sqfs_inode_generic_t *new, const char *path)
+{
+ sqfs_u64 offset, diff, oldsz, newsz;
+ int status = 0, ret;
+
+ sqfs_inode_get_file_size(old, &oldsz);
+ sqfs_inode_get_file_size(new, &newsz);
+
+ if (oldsz != newsz)
+ goto out_different;
+
+ if (sd->compare_flags & COMPARE_NO_CONTENTS)
+ return 0;
+
+ for (offset = 0; offset < oldsz; offset += diff) {
+ diff = oldsz - offset;
+
+ if (diff > MAX_WINDOW_SIZE)
+ diff = MAX_WINDOW_SIZE;
+
+ ret = read_blob(sd->old_path, path,
+ sd->sqfs_old.data, old, old_buf, offset, diff);
+ if (ret)
+ return -1;
+
+ ret = read_blob(sd->new_path, path,
+ sd->sqfs_new.data, new, new_buf, offset, diff);
+ if (ret)
+ return -1;
+
+ if (memcmp(old_buf, new_buf, diff) != 0)
+ goto out_different;
+ }
+
+ return status;
+out_different:
+ if (sd->compare_flags & COMPARE_EXTRACT_FILES) {
+ if (extract_files(sd, old, new, path))
+ return -1;
+ }
+ return 1;
+}
diff --git a/bin/sqfsdiff/extract.c b/bin/sqfsdiff/extract.c
new file mode 100644
index 0000000..979572a
--- /dev/null
+++ b/bin/sqfsdiff/extract.c
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * extract.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "sqfsdiff.h"
+
+static int extract(sqfs_data_reader_t *data, const sqfs_inode_generic_t *inode,
+ const char *prefix, const char *path, size_t block_size)
+{
+ char *ptr, *temp;
+ FILE *fp;
+
+ temp = alloca(strlen(prefix) + strlen(path) + 2);
+ sprintf(temp, "%s/%s", prefix, path);
+
+ ptr = strrchr(temp, '/');
+ *ptr = '\0';
+ if (mkdir_p(temp))
+ return -1;
+ *ptr = '/';
+
+ fp = fopen(temp, "wb");
+ if (fp == NULL) {
+ perror(temp);
+ return -1;
+ }
+
+ if (sqfs_data_reader_dump(path, data, inode, fp, block_size, true)) {
+ fclose(fp);
+ return -1;
+ }
+
+ fflush(fp);
+ fclose(fp);
+ return 0;
+}
+
+int extract_files(sqfsdiff_t *sd, const sqfs_inode_generic_t *old,
+ const sqfs_inode_generic_t *new,
+ const char *path)
+{
+ if (old != NULL) {
+ if (extract(sd->sqfs_old.data, old, "old",
+ path, sd->sqfs_old.super.block_size))
+ return -1;
+ }
+
+ if (new != NULL) {
+ if (extract(sd->sqfs_new.data, new, "new",
+ path, sd->sqfs_new.super.block_size))
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/bin/sqfsdiff/node_compare.c b/bin/sqfsdiff/node_compare.c
new file mode 100644
index 0000000..59d1831
--- /dev/null
+++ b/bin/sqfsdiff/node_compare.c
@@ -0,0 +1,203 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * node_compare.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "sqfsdiff.h"
+
+int node_compare(sqfsdiff_t *sd, sqfs_tree_node_t *a, sqfs_tree_node_t *b)
+{
+ char *path = sqfs_tree_node_get_path(a);
+ sqfs_tree_node_t *ait, *bit;
+ bool promoted, demoted;
+ int ret, status = 0;
+
+ if (path == NULL) {
+ perror("constructing absolute file path");
+ return -1;
+ }
+
+ if (a->inode->base.type != b->inode->base.type) {
+ promoted = demoted = false;
+
+ switch (a->inode->base.type) {
+ case SQFS_INODE_DIR:
+ if (b->inode->base.type == SQFS_INODE_EXT_DIR)
+ promoted = true;
+ break;
+ case SQFS_INODE_FILE:
+ if (b->inode->base.type == SQFS_INODE_EXT_FILE)
+ promoted = true;
+ break;
+ case SQFS_INODE_SLINK:
+ if (b->inode->base.type == SQFS_INODE_EXT_SLINK)
+ promoted = true;
+ break;
+ case SQFS_INODE_BDEV:
+ if (b->inode->base.type == SQFS_INODE_EXT_BDEV)
+ promoted = true;
+ break;
+ case SQFS_INODE_CDEV:
+ if (b->inode->base.type == SQFS_INODE_EXT_CDEV)
+ promoted = true;
+ break;
+ case SQFS_INODE_FIFO:
+ if (b->inode->base.type == SQFS_INODE_EXT_FIFO)
+ promoted = true;
+ break;
+ case SQFS_INODE_SOCKET:
+ if (b->inode->base.type == SQFS_INODE_EXT_SOCKET)
+ promoted = true;
+ break;
+ case SQFS_INODE_EXT_DIR:
+ if (b->inode->base.type == SQFS_INODE_DIR)
+ demoted = true;
+ break;
+ case SQFS_INODE_EXT_FILE:
+ if (b->inode->base.type == SQFS_INODE_FILE)
+ demoted = true;
+ break;
+ case SQFS_INODE_EXT_SLINK:
+ if (b->inode->base.type == SQFS_INODE_SLINK)
+ demoted = true;
+ break;
+ case SQFS_INODE_EXT_BDEV:
+ if (b->inode->base.type == SQFS_INODE_BDEV)
+ demoted = true;
+ break;
+ case SQFS_INODE_EXT_CDEV:
+ if (b->inode->base.type == SQFS_INODE_CDEV)
+ demoted = true;
+ break;
+ case SQFS_INODE_EXT_FIFO:
+ if (b->inode->base.type == SQFS_INODE_FIFO)
+ demoted = true;
+ break;
+ case SQFS_INODE_EXT_SOCKET:
+ if (b->inode->base.type == SQFS_INODE_SOCKET)
+ demoted = true;
+ break;
+ }
+
+ if (promoted) {
+ fprintf(stdout, "%s has an extended type\n", path);
+ status = 1;
+ } else if (demoted) {
+ fprintf(stdout, "%s has a basic type\n", path);
+ status = 1;
+ } else {
+ fprintf(stdout, "%s has a different type\n", path);
+ free(path);
+ return 1;
+ }
+ }
+
+ if (!(sd->compare_flags & COMPARE_NO_PERM)) {
+ if ((a->inode->base.mode & ~S_IFMT) !=
+ (b->inode->base.mode & ~S_IFMT)) {
+ fprintf(stdout, "%s has different permissions\n",
+ path);
+ status = 1;
+ }
+ }
+
+ if (!(sd->compare_flags & COMPARE_NO_OWNER)) {
+ if (a->uid != b->uid || a->gid != b->gid) {
+ fprintf(stdout, "%s has different ownership\n", path);
+ status = 1;
+ }
+ }
+
+ if (sd->compare_flags & COMPARE_TIMESTAMP) {
+ if (a->inode->base.mod_time != b->inode->base.mod_time) {
+ fprintf(stdout, "%s has a different timestamp\n", path);
+ status = 1;
+ }
+ }
+
+ if (sd->compare_flags & COMPARE_INODE_NUM) {
+ if (a->inode->base.inode_number !=
+ b->inode->base.inode_number) {
+ fprintf(stdout, "%s has a different inode number\n",
+ path);
+ status = 1;
+ }
+ }
+
+ switch (a->inode->base.type) {
+ case SQFS_INODE_SOCKET:
+ case SQFS_INODE_EXT_SOCKET:
+ case SQFS_INODE_FIFO:
+ case SQFS_INODE_EXT_FIFO:
+ break;
+ case SQFS_INODE_BDEV:
+ case SQFS_INODE_CDEV:
+ if (a->inode->data.dev.devno != b->inode->data.dev.devno) {
+ fprintf(stdout, "%s has different device number\n",
+ path);
+ status = 1;
+ }
+ break;
+ case SQFS_INODE_EXT_BDEV:
+ case SQFS_INODE_EXT_CDEV:
+ if (a->inode->data.dev_ext.devno !=
+ b->inode->data.dev_ext.devno) {
+ fprintf(stdout, "%s has different device number\n",
+ path);
+ status = 1;
+ }
+ break;
+ case SQFS_INODE_SLINK:
+ case SQFS_INODE_EXT_SLINK:
+ if (strcmp((const char *)a->inode->extra,
+ (const char *)b->inode->extra)) {
+ fprintf(stdout, "%s has a different link target\n",
+ path);
+ }
+ break;
+ case SQFS_INODE_DIR:
+ case SQFS_INODE_EXT_DIR:
+ ret = compare_dir_entries(sd, a, b);
+ if (ret < 0) {
+ status = -1;
+ break;
+ }
+ if (ret > 0)
+ status = 1;
+
+ free(path);
+ path = NULL;
+
+ ait = a->children;
+ bit = b->children;
+
+ while (ait != NULL && bit != NULL) {
+ ret = node_compare(sd, ait, bit);
+ if (ret < 0)
+ return -1;
+ if (ret > 0)
+ status = 1;
+
+ ait = ait->next;
+ bit = bit->next;
+ }
+ break;
+ case SQFS_INODE_FILE:
+ case SQFS_INODE_EXT_FILE:
+ ret = compare_files(sd, a->inode, b->inode, path);
+ if (ret < 0) {
+ status = -1;
+ } else if (ret > 0) {
+ fprintf(stdout, "regular file %s differs\n", path);
+ status = 1;
+ }
+ break;
+ default:
+ fprintf(stdout, "%s has unknown type, ignoring\n", path);
+ break;
+ }
+
+ free(path);
+ return status;
+}
diff --git a/bin/sqfsdiff/options.c b/bin/sqfsdiff/options.c
new file mode 100644
index 0000000..b8ce7f0
--- /dev/null
+++ b/bin/sqfsdiff/options.c
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * sqfsdiff.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "sqfsdiff.h"
+
+static struct option long_opts[] = {
+ { "old", required_argument, NULL, 'a' },
+ { "new", required_argument, NULL, 'b' },
+ { "no-owner", no_argument, NULL, 'O' },
+ { "no-permissions", no_argument, NULL, 'P' },
+ { "no-contents", no_argument, NULL, 'C' },
+ { "timestamps", no_argument, NULL, 'T' },
+ { "inode-num", no_argument, NULL, 'I' },
+ { "super", no_argument, NULL, 'S' },
+ { "extract", required_argument, NULL, 'e' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { NULL, 0, NULL, 0 },
+};
+
+static const char *short_opts = "a:b:OPCTISe:hV";
+
+static const char *usagestr =
+"Usage: sqfsdiff [OPTIONS...] --old,-a <first> --new,-b <second>\n"
+"\n"
+"Compare two squashfs images. In contrast to doing a direct diff of the\n"
+"images, this actually parses the filesystems and generates a more\n"
+"meaningful difference report.\n"
+"\n"
+"If only contents are compared, any differences in packed file layout,\n"
+"ordering, compression, inode meta data and so on is ignored and the two\n"
+"images are considered equal if each directory contains the same entries,\n"
+"symlink with the same paths have the same targets, device nodes the same\n"
+"device number and files the same size and contents.\n"
+"\n"
+"A report of any difference is printed to stdout. The exit status is similar\n"
+"that of diff(1): 0 means equal, 1 means different, 2 means problem.\n"
+"\n"
+"Possible options:\n"
+"\n"
+" --old, -a <first> The first of the two filesystems to compare.\n"
+" --new, -b <second> The second of the two filesystems to compare.\n"
+"\n"
+" --no-contents, -C Do not compare file contents.\n"
+" --no-owner, -O Do not compare file owners.\n"
+" --no-permissions, -P Do not compare permission bits.\n"
+"\n"
+" --timestamps, -T Compare file timestamps.\n"
+" --inode-num, -I Compare inode numbers of all files.\n"
+" --super, -S Also compare meta data in super blocks.\n"
+"\n"
+" --extract, -e <path> Extract files that differ to the specified\n"
+" directory. Contents of the first filesystem\n"
+" end up in a subdirectory 'old' and of the\n"
+" second filesystem in a subdirectory 'new'.\n"
+"\n"
+" --help, -h Print help text and exit.\n"
+" --version, -V Print version information and exit.\n"
+"\n";
+
+void process_options(sqfsdiff_t *sd, int argc, char **argv)
+{
+ int i;
+
+ for (;;) {
+ i = getopt_long(argc, argv, short_opts, long_opts, NULL);
+ if (i == -1)
+ break;
+
+ switch (i) {
+ case 'a':
+ sd->old_path = optarg;
+ break;
+ case 'b':
+ sd->new_path = optarg;
+ break;
+ case 'O':
+ sd->compare_flags |= COMPARE_NO_OWNER;
+ break;
+ case 'P':
+ sd->compare_flags |= COMPARE_NO_PERM;
+ break;
+ case 'C':
+ sd->compare_flags |= COMPARE_NO_CONTENTS;
+ break;
+ case 'T':
+ sd->compare_flags |= COMPARE_TIMESTAMP;
+ break;
+ case 'I':
+ sd->compare_flags |= COMPARE_INODE_NUM;
+ break;
+ case 'S':
+ sd->compare_super = true;
+ break;
+ case 'e':
+ sd->compare_flags |= COMPARE_EXTRACT_FILES;
+ sd->extract_dir = optarg;
+ break;
+ case 'h':
+ fputs(usagestr, stdout);
+ exit(0);
+ case 'V':
+ print_version("sqfsdiff");
+ exit(0);
+ default:
+ goto fail_arg;
+ }
+ }
+
+ if (sd->old_path == NULL) {
+ fputs("Missing arguments: first filesystem\n", stderr);
+ goto fail_arg;
+ }
+
+ if (sd->new_path == NULL) {
+ fputs("Missing arguments: second filesystem\n", stderr);
+ goto fail_arg;
+ }
+
+ if (optind < argc) {
+ fputs("Unknown extra arguments\n", stderr);
+ goto fail_arg;
+ }
+ return;
+fail_arg:
+ fprintf(stderr, "Try `sqfsdiff --help' for more information.\n");
+ exit(2);
+}
diff --git a/bin/sqfsdiff/sqfsdiff.c b/bin/sqfsdiff/sqfsdiff.c
new file mode 100644
index 0000000..2871322
--- /dev/null
+++ b/bin/sqfsdiff/sqfsdiff.c
@@ -0,0 +1,168 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * sqfsdiff.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "sqfsdiff.h"
+
+static int open_sfqs(sqfs_state_t *state, const char *path)
+{
+ int ret;
+
+ state->file = sqfs_open_file(path, SQFS_FILE_OPEN_READ_ONLY);
+ if (state->file == NULL) {
+ perror(path);
+ return -1;
+ }
+
+ ret = sqfs_super_read(&state->super, state->file);
+ if (ret) {
+ sqfs_perror(path, "reading super block", ret);
+ goto fail_file;
+ }
+
+ sqfs_compressor_config_init(&state->cfg, state->super.compression_id,
+ state->super.block_size,
+ SQFS_COMP_FLAG_UNCOMPRESS);
+
+ ret = sqfs_compressor_create(&state->cfg, &state->cmp);
+
+#ifdef WITH_LZO
+ if (state->super.compression_id == SQFS_COMP_LZO && ret != 0)
+ ret = lzo_compressor_create(&state->cfg, &state->cmp);
+#endif
+
+ if (ret != 0) {
+ sqfs_perror(path, "creating compressor", ret);
+ goto fail_file;
+ }
+
+ if (state->super.flags & SQFS_FLAG_COMPRESSOR_OPTIONS) {
+ ret = state->cmp->read_options(state->cmp, state->file);
+ if (ret) {
+ sqfs_perror(path, "reading compressor options", ret);
+ goto fail_cmp;
+ }
+ }
+
+ state->idtbl = sqfs_id_table_create(0);
+ if (state->idtbl == NULL) {
+ sqfs_perror(path, "creating ID table", SQFS_ERROR_ALLOC);
+ goto fail_cmp;
+ }
+
+ ret = sqfs_id_table_read(state->idtbl, state->file,
+ &state->super, state->cmp);
+ if (ret) {
+ sqfs_perror(path, "loading ID table", ret);
+ goto fail_id;
+ }
+
+ state->dr = sqfs_dir_reader_create(&state->super, state->cmp,
+ state->file);
+ if (state->dr == NULL) {
+ sqfs_perror(path, "creating directory reader",
+ SQFS_ERROR_ALLOC);
+ goto fail_id;
+ }
+
+ ret = sqfs_dir_reader_get_full_hierarchy(state->dr, state->idtbl,
+ NULL, 0, &state->root);
+ if (ret) {
+ sqfs_perror(path, "loading filesystem tree", ret);
+ goto fail_dr;
+ }
+
+ state->data = sqfs_data_reader_create(state->file,
+ state->super.block_size,
+ state->cmp);
+ if (state->data == NULL) {
+ sqfs_perror(path, "creating data reader", SQFS_ERROR_ALLOC);
+ goto fail_tree;
+ }
+
+ ret = sqfs_data_reader_load_fragment_table(state->data, &state->super);
+ if (ret) {
+ sqfs_perror(path, "loading fragment table", ret);
+ goto fail_data;
+ }
+
+ return 0;
+fail_data:
+ sqfs_destroy(state->data);
+fail_tree:
+ sqfs_dir_tree_destroy(state->root);
+fail_dr:
+ sqfs_destroy(state->dr);
+fail_id:
+ sqfs_destroy(state->idtbl);
+fail_cmp:
+ sqfs_destroy(state->cmp);
+fail_file:
+ sqfs_destroy(state->file);
+ return -1;
+}
+
+static void close_sfqs(sqfs_state_t *state)
+{
+ sqfs_destroy(state->data);
+ sqfs_dir_tree_destroy(state->root);
+ sqfs_destroy(state->dr);
+ sqfs_destroy(state->idtbl);
+ sqfs_destroy(state->cmp);
+ sqfs_destroy(state->file);
+}
+
+int main(int argc, char **argv)
+{
+ int status, ret = 0;
+ sqfsdiff_t sd;
+
+ memset(&sd, 0, sizeof(sd));
+ process_options(&sd, argc, argv);
+
+ if (sd.extract_dir != NULL) {
+ if (mkdir_p(sd.extract_dir))
+ return 2;
+ }
+
+ if (open_sfqs(&sd.sqfs_old, sd.old_path))
+ return 2;
+
+ if (open_sfqs(&sd.sqfs_new, sd.new_path)) {
+ status = 2;
+ goto out_sqfs_old;
+ }
+
+ if (sd.extract_dir != NULL) {
+ if (chdir(sd.extract_dir)) {
+ perror(sd.extract_dir);
+ ret = -1;
+ goto out;
+ }
+ }
+
+ ret = node_compare(&sd, sd.sqfs_old.root, sd.sqfs_new.root);
+ if (ret != 0)
+ goto out;
+
+ if (sd.compare_super) {
+ ret = compare_super_blocks(&sd.sqfs_old.super,
+ &sd.sqfs_new.super);
+ if (ret != 0)
+ goto out;
+ }
+out:
+ if (ret < 0) {
+ status = 2;
+ } else if (ret > 0) {
+ status = 1;
+ } else {
+ status = 0;
+ }
+ close_sfqs(&sd.sqfs_new);
+out_sqfs_old:
+ close_sfqs(&sd.sqfs_old);
+ return status;
+}
diff --git a/bin/sqfsdiff/sqfsdiff.h b/bin/sqfsdiff/sqfsdiff.h
new file mode 100644
index 0000000..94fce93
--- /dev/null
+++ b/bin/sqfsdiff/sqfsdiff.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * sqfsdiff.h
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#ifndef DIFFTOOL_H
+#define DIFFTOOL_H
+
+#include "config.h"
+#include "common.h"
+#include "fstree.h"
+
+#include <stdlib.h>
+#include <getopt.h>
+#include <string.h>
+#include <errno.h>
+
+#define MAX_WINDOW_SIZE (1024 * 1024 * 4)
+
+typedef struct {
+ sqfs_compressor_config_t cfg;
+ sqfs_compressor_t *cmp;
+ sqfs_super_t super;
+ sqfs_file_t *file;
+ sqfs_id_table_t *idtbl;
+ sqfs_dir_reader_t *dr;
+ sqfs_tree_node_t *root;
+ sqfs_data_reader_t *data;
+} sqfs_state_t;
+
+typedef struct {
+ const char *old_path;
+ const char *new_path;
+ int compare_flags;
+ sqfs_state_t sqfs_old;
+ sqfs_state_t sqfs_new;
+ bool compare_super;
+ const char *extract_dir;
+} sqfsdiff_t;
+
+enum {
+ COMPARE_NO_PERM = 0x01,
+ COMPARE_NO_OWNER = 0x02,
+ COMPARE_NO_CONTENTS = 0x04,
+ COMPARE_TIMESTAMP = 0x08,
+ COMPARE_INODE_NUM = 0x10,
+ COMPARE_EXTRACT_FILES = 0x20,
+};
+
+int compare_dir_entries(sqfsdiff_t *sd, sqfs_tree_node_t *old,
+ sqfs_tree_node_t *new);
+
+char *node_path(const sqfs_tree_node_t *n);
+
+int compare_files(sqfsdiff_t *sd, const sqfs_inode_generic_t *old,
+ const sqfs_inode_generic_t *new, const char *path);
+
+int node_compare(sqfsdiff_t *sd, sqfs_tree_node_t *a, sqfs_tree_node_t *b);
+
+int compare_super_blocks(const sqfs_super_t *a, const sqfs_super_t *b);
+
+int extract_files(sqfsdiff_t *sd, const sqfs_inode_generic_t *old,
+ const sqfs_inode_generic_t *new,
+ const char *path);
+
+void process_options(sqfsdiff_t *sd, int argc, char **argv);
+
+#endif /* DIFFTOOL_H */
diff --git a/bin/sqfsdiff/super.c b/bin/sqfsdiff/super.c
new file mode 100644
index 0000000..111412a
--- /dev/null
+++ b/bin/sqfsdiff/super.c
@@ -0,0 +1,125 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * super.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "sqfsdiff.h"
+
+static const struct {
+ sqfs_u16 mask;
+ const char *name;
+} sqfs_flags[] = {
+ { SQFS_FLAG_UNCOMPRESSED_INODES, "uncompressed inodes" },
+ { SQFS_FLAG_UNCOMPRESSED_DATA, "uncompressed data" },
+ { SQFS_FLAG_UNCOMPRESSED_FRAGMENTS, "uncompressed fragments" },
+ { SQFS_FLAG_NO_FRAGMENTS, "no fragments" },
+ { SQFS_FLAG_ALWAYS_FRAGMENTS, "always fragments" },
+ { SQFS_FLAG_DUPLICATES, "duplicates" },
+ { SQFS_FLAG_EXPORTABLE, "exportable" },
+ { SQFS_FLAG_UNCOMPRESSED_XATTRS, "uncompressed xattrs" },
+ { SQFS_FLAG_NO_XATTRS, "no xattrs" },
+ { SQFS_FLAG_COMPRESSOR_OPTIONS, "compressor options" },
+ { SQFS_FLAG_UNCOMPRESSED_IDS, "uncompressed ids" },
+};
+
+static void print_value_difference(const char *name, sqfs_u64 a, sqfs_u64 b)
+{
+ sqfs_u64 diff;
+ char c;
+
+ if (a != b) {
+ if (a < b) {
+ c = '+';
+ diff = b - a;
+ } else {
+ c = '-';
+ diff = a - b;
+ }
+ fprintf(stdout, "%s: %c%lu\n", name, c,
+ (unsigned long)diff);
+ }
+}
+
+static void print_offset_diff(const char *name, sqfs_u64 a, sqfs_u64 b)
+{
+ if (a != b)
+ fprintf(stdout, "Location of %s differs\n", name);
+}
+
+static void print_flag_diff(sqfs_u16 a, sqfs_u16 b)
+{
+ sqfs_u16 diff = a ^ b, mask;
+ size_t i;
+ char c;
+
+ if (diff == 0)
+ return;
+
+ fputs("flags:\n", stdout);
+
+ for (i = 0; i < sizeof(sqfs_flags) / sizeof(sqfs_flags[0]); ++i) {
+ if (diff & sqfs_flags[i].mask) {
+ c = a & sqfs_flags[i].mask ? '<' : '>';
+
+ fprintf(stdout, "\t%c%s\n", c, sqfs_flags[i].name);
+ }
+
+ a &= ~sqfs_flags[i].mask;
+ b &= ~sqfs_flags[i].mask;
+ diff &= ~sqfs_flags[i].mask;
+ }
+
+ for (i = 0, mask = 0x01; i < 16; ++i, mask <<= 1) {
+ if (diff & mask) {
+ fprintf(stdout, "\t%c additional unknown\n",
+ a & mask ? '<' : '>');
+ }
+ }
+}
+
+int compare_super_blocks(const sqfs_super_t *a, const sqfs_super_t *b)
+{
+ if (memcmp(a, b, sizeof(*a)) == 0)
+ return 0;
+
+ fputs("======== super blocks are different ========\n", stdout);
+
+ /* TODO: if a new magic number or squashfs version is introduced,
+ compare them. */
+
+ print_value_difference("inode count", a->inode_count, b->inode_count);
+ print_value_difference("modification time", a->modification_time,
+ b->modification_time);
+ print_value_difference("block size", a->block_size, b->block_size);
+ print_value_difference("block log", a->block_log, b->block_log);
+ print_value_difference("fragment table entries",
+ a->fragment_entry_count,
+ b->fragment_entry_count);
+ print_value_difference("ID table entries", a->id_count, b->id_count);
+
+ if (a->compression_id != b->compression_id) {
+ fprintf(stdout, "compressor: %s vs %s\n",
+ sqfs_compressor_name_from_id(a->compression_id),
+ sqfs_compressor_name_from_id(b->compression_id));
+ }
+
+ print_flag_diff(a->flags, b->flags);
+
+ print_value_difference("total bytes used", a->bytes_used,
+ b->bytes_used);
+
+ print_offset_diff("root inode", a->root_inode_ref, b->root_inode_ref);
+ print_offset_diff("ID table", a->id_table_start, b->id_table_start);
+ print_offset_diff("xattr ID table", a->xattr_id_table_start,
+ b->xattr_id_table_start);
+ print_offset_diff("inode table", a->inode_table_start,
+ b->inode_table_start);
+ print_offset_diff("directory table", a->directory_table_start,
+ b->directory_table_start);
+ print_offset_diff("fragment table", a->fragment_table_start,
+ b->fragment_table_start);
+ print_offset_diff("export table", a->export_table_start,
+ b->export_table_start);
+ return 1;
+}
diff --git a/bin/sqfsdiff/util.c b/bin/sqfsdiff/util.c
new file mode 100644
index 0000000..5e9161a
--- /dev/null
+++ b/bin/sqfsdiff/util.c
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * util.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "sqfsdiff.h"
+
+char *node_path(const sqfs_tree_node_t *n)
+{
+ char *path = sqfs_tree_node_get_path(n);
+
+ if (path == NULL) {
+ perror("get path");
+ return NULL;
+ }
+
+ if (canonicalize_name(path)) {
+ fprintf(stderr, "failed to canonicalization '%s'\n", path);
+ free(path);
+ return NULL;
+ }
+
+ return path;
+}
diff --git a/bin/tar2sqfs.c b/bin/tar2sqfs.c
new file mode 100644
index 0000000..6025dc9
--- /dev/null
+++ b/bin/tar2sqfs.c
@@ -0,0 +1,544 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * tar2sqfs.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "config.h"
+#include "common.h"
+#include "compat.h"
+#include "tar.h"
+
+#include <stdlib.h>
+#include <getopt.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+#ifdef _WIN32
+#include <io.h>
+#endif
+
+static struct option long_opts[] = {
+ { "root-becomes", required_argument, NULL, 'r' },
+ { "compressor", required_argument, NULL, 'c' },
+ { "block-size", required_argument, NULL, 'b' },
+ { "dev-block-size", required_argument, NULL, 'B' },
+ { "defaults", required_argument, NULL, 'd' },
+ { "num-jobs", required_argument, NULL, 'j' },
+ { "queue-backlog", required_argument, NULL, 'Q' },
+ { "comp-extra", required_argument, NULL, 'X' },
+ { "no-skip", no_argument, NULL, 's' },
+ { "no-xattr", no_argument, NULL, 'x' },
+ { "no-keep-time", no_argument, NULL, 'k' },
+ { "exportable", no_argument, NULL, 'e' },
+ { "no-tail-packing", no_argument, NULL, 'T' },
+ { "force", no_argument, NULL, 'f' },
+ { "quiet", no_argument, NULL, 'q' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { NULL, 0, NULL, 0 },
+};
+
+static const char *short_opts = "r:c:b:B:d:X:j:Q:sxekfqThV";
+
+static const char *usagestr =
+"Usage: tar2sqfs [OPTIONS...] <sqfsfile>\n"
+"\n"
+"Read an uncompressed tar archive from stdin and turn it into a squashfs\n"
+"filesystem image.\n"
+"\n"
+"Possible options:\n"
+"\n"
+" --root-becomes, -r <dir> The specified directory becomes the root.\n"
+" Only its children are packed into the image\n"
+" and its attributes (ownership, permissions,\n"
+" xattrs, ...) are stored in the root inode.\n"
+" If not set and a tarbal has an entry for './'\n"
+" or '/', it becomes the root instead.\n"
+"\n"
+" --compressor, -c <name> Select the compressor to use.\n"
+" A list of available compressors is below.\n"
+" --comp-extra, -X <options> A comma separated list of extra options for\n"
+" the selected compressor. Specify 'help' to\n"
+" get a list of available options.\n"
+" --num-jobs, -j <count> Number of compressor jobs to create.\n"
+" --queue-backlog, -Q <count> Maximum number of data blocks in the thread\n"
+" worker queue before the packer starts waiting\n"
+" for the block processors to catch up.\n"
+" Defaults to 10 times the number of jobs.\n"
+" --block-size, -b <size> Block size to use for Squashfs image.\n"
+" Defaults to %u.\n"
+" --dev-block-size, -B <size> Device block size to padd the image to.\n"
+" Defaults to %u.\n"
+" --defaults, -d <options> A comma separated list of default values for\n"
+" implicitly created directories.\n"
+"\n"
+" Possible options:\n"
+" uid=<value> 0 if not set.\n"
+" gid=<value> 0 if not set.\n"
+" mode=<value> 0755 if not set.\n"
+" mtime=<value> 0 if not set.\n"
+"\n"
+" --no-skip, -s Abort if a tar record cannot be read instead\n"
+" of skipping it.\n"
+" --no-xattr, -x Do not copy extended attributes from archive.\n"
+" --no-keep-time, -k Do not keep the time stamps stored in the\n"
+" archive. Instead, set defaults on all files.\n"
+" --exportable, -e Generate an export table for NFS support.\n"
+" --no-tail-packing, -T Do not perform tail end packing on files that\n"
+" are larger than block size.\n"
+" --force, -f Overwrite the output file if it exists.\n"
+" --quiet, -q Do not print out progress reports.\n"
+" --help, -h Print help text and exit.\n"
+" --version, -V Print version information and exit.\n"
+"\n"
+"Examples:\n"
+"\n"
+"\ttar2sqfs rootfs.sqfs < rootfs.tar\n"
+"\tzcat rootfs.tar.gz | tar2sqfs rootfs.sqfs\n"
+"\txzcat rootfs.tar.xz | tar2sqfs rootfs.sqfs\n"
+"\n";
+
+static bool dont_skip = false;
+static bool keep_time = true;
+static bool no_tail_pack = false;
+static sqfs_writer_cfg_t cfg;
+static sqfs_writer_t sqfs;
+static FILE *input_file = NULL;
+static char *root_becomes = NULL;
+
+static void process_args(int argc, char **argv)
+{
+ bool have_compressor;
+ int i, ret;
+
+ sqfs_writer_cfg_init(&cfg);
+
+ for (;;) {
+ i = getopt_long(argc, argv, short_opts, long_opts, NULL);
+ if (i == -1)
+ break;
+
+ switch (i) {
+ case 'T':
+ no_tail_pack = true;
+ break;
+ case 'b':
+ if (parse_size("Block size", &cfg.block_size,
+ optarg, 0)) {
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'B':
+ if (parse_size("Device block size", &cfg.devblksize,
+ optarg, 0)) {
+ exit(EXIT_FAILURE);
+ }
+ if (cfg.devblksize < 1024) {
+ fputs("Device block size must be at "
+ "least 1024\n", stderr);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'c':
+ have_compressor = true;
+ ret = sqfs_compressor_id_from_name(optarg);
+
+ if (ret < 0) {
+ have_compressor = false;
+#ifdef WITH_LZO
+ if (cfg.comp_id == SQFS_COMP_LZO)
+ have_compressor = true;
+#endif
+ }
+
+ if (!have_compressor) {
+ fprintf(stderr, "Unsupported compressor '%s'\n",
+ optarg);
+ exit(EXIT_FAILURE);
+ }
+
+ cfg.comp_id = ret;
+ break;
+ case 'j':
+ cfg.num_jobs = strtol(optarg, NULL, 0);
+ break;
+ case 'Q':
+ cfg.max_backlog = strtol(optarg, NULL, 0);
+ break;
+ case 'X':
+ cfg.comp_extra = optarg;
+ break;
+ case 'd':
+ cfg.fs_defaults = optarg;
+ break;
+ case 'x':
+ cfg.no_xattr = true;
+ break;
+ case 'k':
+ keep_time = false;
+ break;
+ case 'r':
+ free(root_becomes);
+ root_becomes = strdup(optarg);
+ if (root_becomes == NULL) {
+ perror("copying root directory name");
+ exit(EXIT_FAILURE);
+ }
+
+ if (canonicalize_name(root_becomes) != 0 ||
+ strlen(root_becomes) == 0) {
+ fprintf(stderr,
+ "Invalid root directory '%s'.\n",
+ optarg);
+ goto fail_arg;
+ }
+ break;
+ case 's':
+ dont_skip = true;
+ break;
+ case 'e':
+ cfg.exportable = true;
+ break;
+ case 'f':
+ cfg.outmode |= SQFS_FILE_OPEN_OVERWRITE;
+ break;
+ case 'q':
+ cfg.quiet = true;
+ break;
+ case 'h':
+ printf(usagestr, SQFS_DEFAULT_BLOCK_SIZE,
+ SQFS_DEVBLK_SIZE);
+ compressor_print_available();
+ exit(EXIT_SUCCESS);
+ case 'V':
+ print_version("tar2sqfs");
+ exit(EXIT_SUCCESS);
+ default:
+ goto fail_arg;
+ }
+ }
+
+ if (cfg.num_jobs < 1)
+ cfg.num_jobs = 1;
+
+ if (cfg.max_backlog < 1)
+ cfg.max_backlog = 10 * cfg.num_jobs;
+
+ if (cfg.comp_extra != NULL && strcmp(cfg.comp_extra, "help") == 0) {
+ compressor_print_help(cfg.comp_id);
+ exit(EXIT_SUCCESS);
+ }
+
+ if (optind >= argc) {
+ fputs("Missing argument: squashfs image\n", stderr);
+ goto fail_arg;
+ }
+
+ cfg.filename = argv[optind++];
+
+ if (optind < argc) {
+ fputs("Unknown extra arguments\n", stderr);
+ goto fail_arg;
+ }
+ return;
+fail_arg:
+ fputs("Try `tar2sqfs --help' for more information.\n", stderr);
+ exit(EXIT_FAILURE);
+}
+
+static int write_file(tar_header_decoded_t *hdr, file_info_t *fi,
+ sqfs_u64 filesize)
+{
+ sqfs_file_t *file;
+ int flags;
+ int ret;
+
+ file = sqfs_get_stdin_file(input_file, hdr->sparse, filesize);
+ if (file == NULL) {
+ perror("packing files");
+ return -1;
+ }
+
+ flags = 0;
+ if (no_tail_pack && filesize > cfg.block_size)
+ flags |= SQFS_BLK_DONT_FRAGMENT;
+
+ ret = write_data_from_file(hdr->name, sqfs.data,
+ (sqfs_inode_generic_t **)&fi->user_ptr,
+ file, flags);
+ sqfs_destroy(file);
+
+ if (ret)
+ return -1;
+
+ return skip_padding(input_file, hdr->sparse == NULL ?
+ filesize : hdr->record_size);
+}
+
+static int copy_xattr(tree_node_t *node, const tar_header_decoded_t *hdr)
+{
+ tar_xattr_t *xattr;
+ int ret;
+
+ ret = sqfs_xattr_writer_begin(sqfs.xwr);
+ if (ret) {
+ sqfs_perror(hdr->name, "beginning xattr block", ret);
+ return -1;
+ }
+
+ for (xattr = hdr->xattr; xattr != NULL; xattr = xattr->next) {
+ if (sqfs_get_xattr_prefix_id(xattr->key) < 0) {
+ fprintf(stderr, "%s: squashfs does not "
+ "support xattr prefix of %s\n",
+ dont_skip ? "ERROR" : "WARNING",
+ xattr->key);
+
+ if (dont_skip)
+ return -1;
+ continue;
+ }
+
+ ret = sqfs_xattr_writer_add(sqfs.xwr, xattr->key, xattr->value,
+ xattr->value_len);
+ if (ret) {
+ sqfs_perror(hdr->name, "storing xattr key-value pair",
+ ret);
+ return -1;
+ }
+ }
+
+ ret = sqfs_xattr_writer_end(sqfs.xwr, &node->xattr_idx);
+ if (ret) {
+ sqfs_perror(hdr->name, "completing xattr block", ret);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int create_node_and_repack_data(tar_header_decoded_t *hdr)
+{
+ tree_node_t *node;
+
+ if (hdr->is_hard_link) {
+ node = fstree_add_hard_link(&sqfs.fs, hdr->name,
+ hdr->link_target);
+ if (node == NULL)
+ goto fail_errno;
+
+ if (!cfg.quiet) {
+ printf("Hard link %s -> %s\n", hdr->name,
+ hdr->link_target);
+ }
+ return 0;
+ }
+
+ if (!keep_time) {
+ hdr->sb.st_mtime = sqfs.fs.defaults.st_mtime;
+ }
+
+ node = fstree_add_generic(&sqfs.fs, hdr->name,
+ &hdr->sb, hdr->link_target);
+ if (node == NULL)
+ goto fail_errno;
+
+ if (!cfg.quiet)
+ printf("Packing %s\n", hdr->name);
+
+ if (!cfg.no_xattr) {
+ if (copy_xattr(node, hdr))
+ return -1;
+ }
+
+ if (S_ISREG(hdr->sb.st_mode)) {
+ if (write_file(hdr, &node->data.file, hdr->sb.st_size))
+ return -1;
+ }
+
+ return 0;
+fail_errno:
+ perror(hdr->name);
+ return -1;
+}
+
+static int set_root_attribs(const tar_header_decoded_t *hdr)
+{
+ if (hdr->is_hard_link || !S_ISDIR(hdr->sb.st_mode)) {
+ fprintf(stderr, "'%s' is not a directory!\n", hdr->name);
+ return -1;
+ }
+
+ sqfs.fs.root->uid = hdr->sb.st_uid;
+ sqfs.fs.root->gid = hdr->sb.st_gid;
+ sqfs.fs.root->mode = hdr->sb.st_mode;
+
+ if (keep_time)
+ sqfs.fs.root->mod_time = hdr->sb.st_mtime;
+
+ if (!cfg.no_xattr) {
+ if (copy_xattr(sqfs.fs.root, hdr))
+ return -1;
+ }
+
+ return 0;
+}
+
+static int process_tar_ball(void)
+{
+ bool skip, is_root, is_prefixed;
+ tar_header_decoded_t hdr;
+ sqfs_u64 offset, count;
+ sparse_map_t *m;
+ size_t rootlen;
+ int ret;
+
+ rootlen = root_becomes == NULL ? 0 : strlen(root_becomes);
+
+ for (;;) {
+ ret = read_header(input_file, &hdr);
+ if (ret > 0)
+ break;
+ if (ret < 0)
+ return -1;
+
+ if (hdr.mtime < 0)
+ hdr.mtime = 0;
+
+ if ((sqfs_u64)hdr.mtime > 0x0FFFFFFFFUL)
+ hdr.mtime = 0x0FFFFFFFFUL;
+
+ hdr.sb.st_mtime = hdr.mtime;
+
+ skip = false;
+ is_root = false;
+ is_prefixed = true;
+
+ if (hdr.name == NULL || canonicalize_name(hdr.name) != 0) {
+ fprintf(stderr, "skipping '%s' (invalid name)\n",
+ hdr.name);
+ skip = true;
+ }
+
+ if (root_becomes != NULL) {
+ if (strncmp(hdr.name, root_becomes, rootlen) == 0) {
+ if (hdr.name[rootlen] == '\0') {
+ is_root = true;
+ } else if (hdr.name[rootlen] != '/') {
+ is_prefixed = false;
+ }
+ } else {
+ is_prefixed = false;
+ }
+
+ if (is_prefixed && !is_root) {
+ memmove(hdr.name, hdr.name + rootlen + 1,
+ strlen(hdr.name + rootlen + 1) + 1);
+ }
+
+ if (is_prefixed && hdr.name[0] == '\0') {
+ fputs("skipping entry with empty name\n",
+ stderr);
+ skip = true;
+ }
+ } else if (hdr.name[0] == '\0') {
+ is_root = true;
+ }
+
+ if (!is_prefixed) {
+ clear_header(&hdr);
+ continue;
+ }
+
+ if (is_root) {
+ if (set_root_attribs(&hdr))
+ goto fail;
+ clear_header(&hdr);
+ continue;
+ }
+
+ if (!skip && hdr.unknown_record) {
+ fprintf(stderr, "%s: unknown entry type\n", hdr.name);
+ skip = true;
+ }
+
+ if (!skip && hdr.sparse != NULL) {
+ offset = hdr.sparse->offset;
+ count = 0;
+
+ for (m = hdr.sparse; m != NULL; m = m->next) {
+ if (m->offset < offset) {
+ skip = true;
+ break;
+ }
+ offset = m->offset + m->count;
+ count += m->count;
+ }
+
+ if (count != hdr.record_size)
+ skip = true;
+
+ if (skip) {
+ fprintf(stderr, "%s: broken sparse "
+ "file layout\n", hdr.name);
+ }
+ }
+
+ if (skip) {
+ if (dont_skip)
+ goto fail;
+ if (skip_entry(input_file, hdr.sb.st_size))
+ goto fail;
+
+ clear_header(&hdr);
+ continue;
+ }
+
+ if (create_node_and_repack_data(&hdr))
+ goto fail;
+
+ clear_header(&hdr);
+ }
+
+ return 0;
+fail:
+ clear_header(&hdr);
+ return -1;
+}
+
+int main(int argc, char **argv)
+{
+ int status = EXIT_FAILURE;
+
+ process_args(argc, argv);
+
+#ifdef _WIN32
+ _setmode(_fileno(stdin), _O_BINARY);
+ input_file = stdin;
+#else
+ input_file = freopen(NULL, "rb", stdin);
+#endif
+
+ if (input_file == NULL) {
+ perror("changing stdin to binary mode");
+ return EXIT_FAILURE;
+ }
+
+ if (sqfs_writer_init(&sqfs, &cfg))
+ return EXIT_FAILURE;
+
+ if (process_tar_ball())
+ goto out;
+
+ if (fstree_post_process(&sqfs.fs))
+ goto out;
+
+ if (sqfs_writer_finish(&sqfs, &cfg))
+ goto out;
+
+ status = EXIT_SUCCESS;
+out:
+ sqfs_writer_cleanup(&sqfs, status);
+ return status;
+}