diff options
| author | David Oberhollenzer <david.oberhollenzer@sigma-star.at> | 2019-06-12 17:11:08 +0200 | 
|---|---|---|
| committer | David Oberhollenzer <david.oberhollenzer@sigma-star.at> | 2019-06-15 15:44:30 +0200 | 
| commit | e2f9334cd84ff8dd4c65c850825e089a6c747601 (patch) | |
| tree | 567e17e2bae6d02f6828f47daf2764fc4bc2bd1a /tar | |
| parent | 5d609f7d539d8e277972c0337630b375f55d81c4 (diff) | |
Add utility to turn a squashfs image into a POSIX tar archvie
Signed-off-by: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
Diffstat (limited to 'tar')
| -rw-r--r-- | tar/Makemodule.am | 5 | ||||
| -rw-r--r-- | tar/sqfs2tar.c | 208 | ||||
| -rw-r--r-- | tar/tar.h | 47 | ||||
| -rw-r--r-- | tar/write_header.c | 260 | 
4 files changed, 520 insertions, 0 deletions
| diff --git a/tar/Makemodule.am b/tar/Makemodule.am new file mode 100644 index 0000000..d192380 --- /dev/null +++ b/tar/Makemodule.am @@ -0,0 +1,5 @@ +sqfs2tar_SOURCES = tar/sqfs2tar.c tar/tar.h tar/write_header.c +sqfs2tar_LDADD = libsquashfs.a libfstree.a libcompress.a libutil.a +sqfs2tar_LDADD += $(XZ_LIBS) $(ZLIB_LIBS) $(LZO_LIBS) $(LZ4_LIBS) $(ZSTD_LIBS) + +bin_PROGRAMS += sqfs2tar diff --git a/tar/sqfs2tar.c b/tar/sqfs2tar.c new file mode 100644 index 0000000..f7f7e71 --- /dev/null +++ b/tar/sqfs2tar.c @@ -0,0 +1,208 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +#include "meta_reader.h" +#include "data_reader.h" +#include "highlevel.h" +#include "compress.h" +#include "fstree.h" +#include "util.h" +#include "tar.h" + +#include <getopt.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> + +static struct option long_opts[] = { +	{ "help", no_argument, NULL, 'h' }, +	{ "version", no_argument, NULL, 'V' }, +}; + +static const char *short_opts = "hV"; + +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" +"  --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 void process_args(int argc, char **argv) +{ +	int i; + +	for (;;) { +		i = getopt_long(argc, argv, short_opts, long_opts, NULL); +		if (i == -1) +			break; + +		switch (i) { +		case 'h': +			fputs(usagestr, stdout); +			exit(EXIT_SUCCESS); +		case 'V': +			print_version(); +			exit(EXIT_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; +	} +	return; +fail_arg: +	fputs("Try `sqfs2tar --help' for more information.\n", stderr); +	exit(EXIT_FAILURE); +} + +static int terminate_archive(void) +{ +	char buffer[1024]; +	ssize_t ret; + +	memset(buffer, '\0', sizeof(buffer)); + +	ret = write_retry(STDOUT_FILENO, buffer, sizeof(buffer)); + +	if (ret < 0) { +		perror("adding archive terminator"); +		return -1; +	} + +	if ((size_t)ret < sizeof(buffer)) { +		fputs("adding archive terminator: truncated write\n", stderr); +		return -1; +	} + +	return 0; +} + +static int write_tree_dfs(fstree_t *fs, tree_node_t *n, data_reader_t *data) +{ +	char *name; +	int ret; + +	if (n->parent != NULL || !S_ISDIR(n->mode)) { +		name = fstree_get_path(n); +		if (name == NULL) { +			perror("resolving tree node path"); +			return -1; +		} + +		assert(canonicalize_name(name) == 0); + +		ret = write_tar_header(STDOUT_FILENO, fs, n, name); +		free(name); + +		if (ret < 0) +			return -1; +		if (ret > 0) +			return 0; + +		if (S_ISREG(n->mode)) { +			if (data_reader_dump_file(data, n->data.file, +						  STDOUT_FILENO)) { +				return -1; +			} + +			if (padd_file(STDOUT_FILENO, n->data.file->size, 512)) +				return -1; +		} +	} + +	if (S_ISDIR(n->mode)) { +		for (n = n->data.dir->children; n != NULL; n = n->next) { +			if (write_tree_dfs(fs, n, data)) +				return -1; +		} +	} + +	return 0; +} + +int main(int argc, char **argv) +{ +	data_reader_t *data = NULL; +	int status = EXIT_FAILURE; +	sqfs_super_t super; +	compressor_t *cmp; +	fstree_t fs; +	int sqfsfd; + +	process_args(argc, argv); + +	sqfsfd = open(filename, O_RDONLY); +	if (sqfsfd < 0) { +		perror(filename); +		return EXIT_FAILURE; +	} + +	if (sqfs_super_read(&super, sqfsfd)) +		goto out_fd; + +	if (!compressor_exists(super.compression_id)) { +		fputs("Image uses a compressor that has not been built in\n", +		      stderr); +		goto out_fd; +	} + +	cmp = compressor_create(super.compression_id, false, +				super.block_size, NULL); +	if (cmp == NULL) +		goto out_fd; + +	if (super.flags & SQFS_FLAG_COMPRESSOR_OPTIONS) { +		if (cmp->read_options(cmp, sqfsfd)) +			goto out_cmp; +	} + +	if (deserialize_fstree(&fs, &super, cmp, sqfsfd, 0)) +		goto out_cmp; + +	data = data_reader_create(sqfsfd, &super, cmp); +	if (data == NULL) +		goto out_fs; + +	if (write_tree_dfs(&fs, fs.root, data)) +		goto out_data; + +	if (terminate_archive()) +		goto out_data; + +	status = EXIT_SUCCESS; +out_data: +	data_reader_destroy(data); +out_fs: +	fstree_cleanup(&fs); +out_cmp: +	cmp->destroy(cmp); +out_fd: +	close(sqfsfd); +	return status; +} diff --git a/tar/tar.h b/tar/tar.h new file mode 100644 index 0000000..62c612a --- /dev/null +++ b/tar/tar.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +#ifndef TAR_H +#define TAR_H + +#include "fstree.h" + +typedef struct { +	char name[100]; +	char mode[8]; +	char uid[8]; +	char gid[8]; +	char size[12]; +	char mtime[12]; +	char chksum[8]; +	char typeflag; +	char linkname[100]; +	char magic[6]; +	char version[2]; +	char uname[32]; +	char gname[32]; +	char devmajor[8]; +	char devminor[8]; +	char prefix[155]; +	char padding[12]; +} tar_header_t; + +#define TAR_TYPE_FILE '0' +#define TAR_TYPE_LINK '1' +#define TAR_TYPE_SLINK '2' +#define TAR_TYPE_CHARDEV '3' +#define TAR_TYPE_BLOCKDEV '4' +#define TAR_TYPE_DIR '5' +#define TAR_TYPE_FIFO '6' + +#define TAR_TYPE_PAX 'x' + +#define TAR_MAGIC "ustar" +#define TAR_VERSION "00" + +/* +  Returns < 0 on failure, > 0 if cannot encode, 0 on success. +  Prints error/warning messages to stderr. +*/ +int write_tar_header(int fd, const fstree_t *fs, const tree_node_t *n, +		     const char *name); + +#endif /* TAR_H */ diff --git a/tar/write_header.c b/tar/write_header.c new file mode 100644 index 0000000..9073056 --- /dev/null +++ b/tar/write_header.c @@ -0,0 +1,260 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +#include "util.h" +#include "tar.h" + +#include <sys/sysmacros.h> +#include <string.h> +#include <stdio.h> + +static unsigned long pax_hdr_counter = 0; +static char buffer[4096]; + +static void write_octal(char *dst, unsigned int value, int digits) +{ +	char temp[64]; + +	sprintf(temp, "%0*o ", digits, value); +	memcpy(dst, temp, strlen(temp)); +} + +static void init_header(tar_header_t *hdr, const fstree_t *fs, +			const tree_node_t *n) +{ +	memset(hdr, 0, sizeof(*hdr)); + +	memcpy(hdr->magic, TAR_MAGIC, strlen(TAR_MAGIC)); +	memcpy(hdr->version, TAR_VERSION, strlen(TAR_VERSION)); + +	write_octal(hdr->mode, n->mode & ~S_IFMT, 6); +	write_octal(hdr->uid, n->uid, 6); +	write_octal(hdr->gid, n->gid, 6); +	write_octal(hdr->size, 0, 11); +	write_octal(hdr->mtime, fs->default_mtime, 11); +	write_octal(hdr->devmajor, 0, 6); +	write_octal(hdr->devminor, 0, 6); + +	sprintf(hdr->uname, "%u", n->uid); +	sprintf(hdr->gname, "%u", n->gid); +} + +static void update_checksum(tar_header_t *hdr) +{ +	unsigned int chksum = 0; +	size_t i; + +	memset(hdr->chksum, ' ', sizeof(hdr->chksum)); + +	for (i = 0; i < sizeof(*hdr); ++i) +		chksum += ((unsigned char *)hdr)[i]; + +	write_octal(hdr->chksum, chksum, 6); +	hdr->chksum[6] = '\0'; +	hdr->chksum[7] = ' '; +} + +static int name_to_tar_header(tar_header_t *hdr, const char *path) +{ +	size_t len = strlen(path); +	const char *ptr; + +	if ((len + 1) <= sizeof(hdr->name)) { +		memcpy(hdr->name, path, len); +		return 0; +	} + +	for (ptr = path; ; ++ptr) { +		ptr = strchr(ptr, '/'); +		if (ptr == NULL) +			return -1; + +		len = ptr - path; +		if (len >= sizeof(hdr->prefix)) +			continue; +		if (strlen(ptr + 1) >= sizeof(hdr->name)) +			continue; +		break; +	} + +	memcpy(hdr->prefix, path, ptr - path); +	memcpy(hdr->name, ptr + 1, strlen(ptr + 1)); +	return 0; +} + +static bool need_pax_header(const tree_node_t *n, const char *name) +{ +	tar_header_t temp; + +	if (n->uid > 0777777 || n->gid > 0777777) +		return true; + +	if (S_ISREG(n->mode) && n->data.file->size > 077777777777UL) +		return true; + +	if (S_ISLNK(n->mode)) { +		if (strlen(n->data.slink_target) >= sizeof(temp.linkname)) +			return true; +	} + +	if (name_to_tar_header(&temp, name)) +		return true; + +	return false; +} + +static char *write_pax_entry(char *dst, const char *key, const char *value) +{ +	size_t i, len, prefix = 0, oldprefix; + +	do { +		len = prefix + 1 + strlen(key) + 1 + strlen(value) + 1; + +		oldprefix = prefix; +		prefix = 1; + +		for (i = len; i >= 10; i /= 10) +			++prefix; +	} while (oldprefix != prefix); + +	sprintf(dst, "%zu %s=%s\n", len, key, value); + +	return dst + len; +} + +static int write_pax_header(int fd, const fstree_t *fs, const tree_node_t *n, +			    const char *name) +{ +	char temp[64], *ptr; +	tar_header_t hdr; +	ssize_t ret; +	size_t len; + +	memset(buffer, 0, sizeof(buffer)); +	memset(&hdr, 0, sizeof(hdr)); + +	sprintf(hdr.name, "pax%lu", pax_hdr_counter); +	memcpy(hdr.magic, TAR_MAGIC, strlen(TAR_MAGIC)); +	memcpy(hdr.version, TAR_VERSION, strlen(TAR_VERSION)); +	write_octal(hdr.mode, 0644, 6); +	write_octal(hdr.uid, 0, 6); +	write_octal(hdr.gid, 0, 6); +	write_octal(hdr.mtime, 0, 11); +	write_octal(hdr.devmajor, 0, 6); +	write_octal(hdr.devminor, 0, 6); +	sprintf(hdr.uname, "%u", 0); +	sprintf(hdr.gname, "%u", 0); +	hdr.typeflag = TAR_TYPE_PAX; + +	ptr = buffer; +	sprintf(temp, "%u", n->uid); +	ptr = write_pax_entry(ptr, "uid", temp); +	ptr = write_pax_entry(ptr, "uname", temp); + +	sprintf(temp, "%u", fs->default_mtime); +	ptr = write_pax_entry(ptr, "mtime", temp); + +	sprintf(temp, "%u", n->gid); +	ptr = write_pax_entry(ptr, "gid", temp); +	ptr = write_pax_entry(ptr, "gname", temp); + +	ptr = write_pax_entry(ptr, "path", name); + +	if (S_ISLNK(n->mode)) { +		ptr = write_pax_entry(ptr, "linkpath", n->data.slink_target); +	} else if (S_ISREG(n->mode)) { +		sprintf(temp, "%lu", n->data.file->size); +		ptr = write_pax_entry(ptr, "size", temp); +	} + +	len = strlen(buffer); +	write_octal(hdr.size, len, 11); +	update_checksum(&hdr); + +	ret = write_retry(fd, &hdr, sizeof(hdr)); +	if (ret < 0) +		goto fail_wr; +	if ((size_t)ret < sizeof(hdr)) +		goto fail_trunc; + +	ret = write_retry(fd, buffer, len); +	if (ret < 0) +		goto fail_wr; +	if ((size_t)ret < len) +		goto fail_trunc; + +	return padd_file(fd, len, 512); +fail_wr: +	perror("writing pax header"); +	return -1; +fail_trunc: +	fputs("writing pax header: truncated write\n", stderr); +	return -1; +} + +int write_tar_header(int fd, const fstree_t *fs, const tree_node_t *n, +		     const char *name) +{ +	const char *reason; +	tar_header_t hdr; +	ssize_t ret; + +	if (need_pax_header(n, name)) { +		if (write_pax_header(fd, fs, n, name)) +			return -1; + +		init_header(&hdr, fs, n); +		sprintf(hdr.name, "pax%lu_data", pax_hdr_counter++); +	} else { +		init_header(&hdr, fs, n); +		name_to_tar_header(&hdr, name); +	} + +	switch (n->mode & S_IFMT) { +	case S_IFCHR: +	case S_IFBLK: +		write_octal(hdr.devmajor, major(n->data.devno), 6); +		write_octal(hdr.devminor, minor(n->data.devno), 6); +		hdr.typeflag = S_ISBLK(n->mode) ? TAR_TYPE_BLOCKDEV : +			TAR_TYPE_CHARDEV; +		break; +	case S_IFLNK: +		if (strlen(n->data.slink_target) < sizeof(hdr.linkname)) +			strcpy(hdr.linkname, n->data.slink_target); + +		hdr.typeflag = TAR_TYPE_SLINK; +		break; +	case S_IFREG: +		write_octal(hdr.size, n->data.file->size & 077777777777UL, 11); +		hdr.typeflag = TAR_TYPE_FILE; +		break; +	case S_IFDIR: +		hdr.typeflag = TAR_TYPE_DIR; +		break; +	case S_IFIFO: +		hdr.typeflag = TAR_TYPE_FIFO; +		break; +	case S_IFSOCK: +		reason = "cannot pack socket"; +		goto out_skip; +	default: +		reason = "unknown type"; +		goto out_skip; +	} + +	update_checksum(&hdr); + +	ret = write_retry(fd, &hdr, sizeof(hdr)); + +	if (ret < 0) { +		perror("writing header record"); +	} else if ((size_t)ret < sizeof(hdr)) { +		fputs("writing header record: truncated write\n", stderr); +		ret = -1; +	} else { +		ret = 0; +	} + +	return ret; +out_skip: +	fprintf(stderr, "WARNING: skipping '%s' (%s)\n", name, reason); +	return 1; +} | 
