From e2f9334cd84ff8dd4c65c850825e089a6c747601 Mon Sep 17 00:00:00 2001 From: David Oberhollenzer 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 --- .gitignore | 1 + Makefile.am | 1 + tar/Makemodule.am | 5 ++ tar/sqfs2tar.c | 208 ++++++++++++++++++++++++++++++++++++++++++ tar/tar.h | 47 ++++++++++ tar/write_header.c | 260 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 522 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 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 +#include +#include +#include +#include +#include +#include +#include + +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...] \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 +#include +#include + +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