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; +} | 
