diff options
author | David Oberhollenzer <david.oberhollenzer@sigma-star.at> | 2020-04-27 11:59:02 +0200 |
---|---|---|
committer | David Oberhollenzer <david.oberhollenzer@sigma-star.at> | 2020-04-27 11:59:02 +0200 |
commit | 20b0d509f67dea802706cd6b80b5e20d14988931 (patch) | |
tree | 3a87ea358b1206f6823777693d109896d6908283 /bin | |
parent | 9e332a2d3eddcc262476ac263e03df021b3c44b4 (diff) |
Cleanup directory structure of the binary programs
Instead of having the binary programs in randomly named subdirectories,
move all of them to a "bin" subdirectory, similar to the utility
libraries that have subdirectories within "lib" and give the
subdirectories the propper names (e.g. have gensquashfs source in a
directory *actually* named "gensquashfs").
Signed-off-by: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
Diffstat (limited to 'bin')
-rw-r--r-- | bin/Makemodule.am | 42 | ||||
-rw-r--r-- | bin/gensquashfs/dirscan.c | 321 | ||||
-rw-r--r-- | bin/gensquashfs/mkfs.c | 216 | ||||
-rw-r--r-- | bin/gensquashfs/mkfs.h | 72 | ||||
-rw-r--r-- | bin/gensquashfs/options.c | 289 | ||||
-rw-r--r-- | bin/gensquashfs/selinux.c | 78 | ||||
-rw-r--r-- | bin/rdsquashfs/describe.c | 125 | ||||
-rw-r--r-- | bin/rdsquashfs/dump_xattrs.c | 120 | ||||
-rw-r--r-- | bin/rdsquashfs/fill_files.c | 183 | ||||
-rw-r--r-- | bin/rdsquashfs/list_files.c | 156 | ||||
-rw-r--r-- | bin/rdsquashfs/options.c | 217 | ||||
-rw-r--r-- | bin/rdsquashfs/rdsquashfs.c | 175 | ||||
-rw-r--r-- | bin/rdsquashfs/rdsquashfs.h | 77 | ||||
-rw-r--r-- | bin/rdsquashfs/restore_fstree.c | 320 | ||||
-rw-r--r-- | bin/sqfs2tar.c | 688 | ||||
-rw-r--r-- | bin/sqfsdiff/compare_dir.c | 94 | ||||
-rw-r--r-- | bin/sqfsdiff/compare_files.c | 72 | ||||
-rw-r--r-- | bin/sqfsdiff/extract.c | 57 | ||||
-rw-r--r-- | bin/sqfsdiff/node_compare.c | 203 | ||||
-rw-r--r-- | bin/sqfsdiff/options.c | 131 | ||||
-rw-r--r-- | bin/sqfsdiff/sqfsdiff.c | 168 | ||||
-rw-r--r-- | bin/sqfsdiff/sqfsdiff.h | 69 | ||||
-rw-r--r-- | bin/sqfsdiff/super.c | 125 | ||||
-rw-r--r-- | bin/sqfsdiff/util.c | 25 | ||||
-rw-r--r-- | bin/tar2sqfs.c | 544 |
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; +} |