summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile.am1
-rw-r--r--tar/Makemodule.am5
-rw-r--r--tar/sqfs2tar.c208
-rw-r--r--tar/tar.h47
-rw-r--r--tar/write_header.c260
6 files changed, 522 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 2d67338..532b328 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,4 @@ config.h
*~
gensquashfs
rdsquashfs
+sqfs2tar
diff --git a/Makefile.am b/Makefile.am
index 6a91bf5..2af68c1 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -11,5 +11,6 @@ EXTRA_DIST = autogen.sh LICENSE README
include doc/Makemodule.am
include lib/Makemodule.am
+include tar/Makemodule.am
include mkfs/Makemodule.am
include unpack/Makemodule.am
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;
+}