From e2f9334cd84ff8dd4c65c850825e089a6c747601 Mon Sep 17 00:00:00 2001
From: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
Date: Wed, 12 Jun 2019 17:11:08 +0200
Subject: Add utility to turn a squashfs image into a POSIX tar archvie

Signed-off-by: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
---
 tar/Makemodule.am  |   5 ++
 tar/sqfs2tar.c     | 208 ++++++++++++++++++++++++++++++++++++++++++
 tar/tar.h          |  47 ++++++++++
 tar/write_header.c | 260 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 520 insertions(+)
 create mode 100644 tar/Makemodule.am
 create mode 100644 tar/sqfs2tar.c
 create mode 100644 tar/tar.h
 create mode 100644 tar/write_header.c

(limited to 'tar')

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;
+}
-- 
cgit v1.2.3