From cdccc69c62579b0c13b35fad0728079652b8f3c9 Mon Sep 17 00:00:00 2001 From: David Oberhollenzer Date: Tue, 31 Jan 2023 11:21:30 +0100 Subject: Move library source into src sub-directory Signed-off-by: David Oberhollenzer --- bin/tar2sqfs/src/options.c | 257 +++++++++++++++++++++++++++ bin/tar2sqfs/src/process_tarball.c | 346 +++++++++++++++++++++++++++++++++++++ bin/tar2sqfs/src/tar2sqfs.c | 104 +++++++++++ bin/tar2sqfs/src/tar2sqfs.h | 39 +++++ 4 files changed, 746 insertions(+) create mode 100644 bin/tar2sqfs/src/options.c create mode 100644 bin/tar2sqfs/src/process_tarball.c create mode 100644 bin/tar2sqfs/src/tar2sqfs.c create mode 100644 bin/tar2sqfs/src/tar2sqfs.h (limited to 'bin/tar2sqfs/src') diff --git a/bin/tar2sqfs/src/options.c b/bin/tar2sqfs/src/options.c new file mode 100644 index 0000000..f2185a6 --- /dev/null +++ b/bin/tar2sqfs/src/options.c @@ -0,0 +1,257 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * options.c + * + * Copyright (C) 2019 David Oberhollenzer + */ +#include "tar2sqfs.h" + +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-symlink-retarget", no_argument, NULL, 'S' }, + { "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:sxekfqSThV"; + +static const char *usagestr = +"Usage: tar2sqfs [OPTIONS...] \n" +"\n" +"Read a tar archive from stdin and turn it into a squashfs filesystem image.\n" +"\n" +"Possible options:\n" +"\n" +" --root-becomes, -r 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" +" --no-symlink-retarget, -S If --root-becomes is used, link targets are\n" +" adjusted if they are prefixed by the root\n" +" path. If this flag is set, symlinks are left\n" +" untouched and only hard links are changed.\n" +"\n" +" --compressor, -c Select the compressor to use.\n" +" A list of available compressors is below.\n" +" --comp-extra, -X 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 Number of compressor jobs to create.\n" +" --queue-backlog, -Q 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 Block size to use for Squashfs image.\n" +" Defaults to %u.\n" +" --dev-block-size, -B Device block size to padd the image to.\n" +" Defaults to %u.\n" +" --defaults, -d A comma separated list of default values for\n" +" implicitly created directories.\n" +"\n" +" Possible options:\n" +" uid= 0 if not set.\n" +" gid= 0 if not set.\n" +" mode= 0755 if not set.\n" +" mtime= 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"; + +bool dont_skip = false; +bool keep_time = true; +bool no_tail_pack = false; +bool no_symlink_retarget = false; +sqfs_writer_cfg_t cfg; +char *root_becomes = NULL; + +static void input_compressor_print_available(void) +{ + int i = XFRM_COMPRESSOR_MIN; + const char *name; + + fputs("\nSupported tar compression formats:\n", stdout); + + while (i <= XFRM_COMPRESSOR_MAX) { + name = xfrm_compressor_name_from_id(i); + + if (name != NULL) + printf("\t%s\n", name); + + ++i; + } + + fputs("\tuncompressed\n", stdout); + fputc('\n', stdout); +} + +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 'S': + no_symlink_retarget = true; + break; + 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(); + input_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 specified.\n", stderr); + goto fail_arg; + } + return; +fail_arg: + fputs("Try `tar2sqfs --help' for more information.\n", stderr); + exit(EXIT_FAILURE); +} diff --git a/bin/tar2sqfs/src/process_tarball.c b/bin/tar2sqfs/src/process_tarball.c new file mode 100644 index 0000000..6aaa24b --- /dev/null +++ b/bin/tar2sqfs/src/process_tarball.c @@ -0,0 +1,346 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * process_tarball.c + * + * Copyright (C) 2019 David Oberhollenzer + */ +#include "tar2sqfs.h" + +static int write_file(istream_t *input_file, sqfs_writer_t *sqfs, + const tar_header_decoded_t *hdr, + file_info_t *fi, sqfs_u64 filesize) +{ + const sparse_map_t *list; + int flags = 0, ret = 0; + sqfs_u64 offset, diff; + bool sparse_region; + ostream_t *out; + + if (no_tail_pack && filesize > cfg.block_size) + flags |= SQFS_BLK_DONT_FRAGMENT; + + out = data_writer_ostream_create(hdr->name, sqfs->data, &fi->inode, + flags); + + if (out == NULL) + return -1; + + list = hdr->sparse; + + for (offset = 0; offset < filesize; offset += diff) { + if (hdr->sparse != NULL) { + if (list == NULL) { + sparse_region = true; + diff = filesize - offset; + } else if (offset < list->offset) { + sparse_region = true; + diff = list->offset - offset; + } else if (offset - list->offset >= list->count) { + list = list->next; + diff = 0; + continue; + } else { + sparse_region = false; + diff = list->count - (offset - list->offset); + } + } else { + sparse_region = false; + diff = filesize - offset; + } + + if (diff > 0x7FFFFFFFUL) + diff = 0x7FFFFFFFUL; + + if (sparse_region) { + ret = ostream_append_sparse(out, diff); + } else { + ret = ostream_append_from_istream(out, input_file, + diff); + + if (ret == 0) { + fprintf(stderr, "%s: unexpected end-of-file\n", + hdr->name); + ret = -1; + } else if (ret > 0) { + diff = ret; + ret = 0; + } + } + + if (ret < 0) + break; + } + + ostream_flush(out); + sqfs_drop(out); + + if (ret) + return -1; + + return skip_padding(input_file, hdr->sparse == NULL ? + filesize : hdr->record_size); +} + +static int copy_xattr(sqfs_writer_t *sqfs, tree_node_t *node, + const tar_header_decoded_t *hdr) +{ + tar_xattr_t *xattr; + int ret; + + ret = sqfs_xattr_writer_begin(sqfs->xwr, 0); + 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(istream_t *input_file, + sqfs_writer_t *sqfs, + tar_header_decoded_t *hdr) +{ + tree_node_t *node; + struct stat sb; + + 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->mtime = sqfs->fs.defaults.st_mtime; + } + + memset(&sb, 0, sizeof(sb)); + sb.st_mode = hdr->mode; + sb.st_uid = hdr->uid; + sb.st_gid = hdr->gid; + sb.st_rdev = hdr->devno; + sb.st_size = hdr->actual_size; + sb.st_mtime = hdr->mtime; + + node = fstree_add_generic(&sqfs->fs, hdr->name, + &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(sqfs, node, hdr)) + return -1; + } + + if (S_ISREG(hdr->mode)) { + if (write_file(input_file, sqfs, hdr, &node->data.file, + hdr->actual_size)) { + return -1; + } + } + + return 0; +fail_errno: + perror(hdr->name); + return -1; +} + +static int set_root_attribs(sqfs_writer_t *sqfs, + const tar_header_decoded_t *hdr) +{ + if (hdr->is_hard_link || !S_ISDIR(hdr->mode)) { + fprintf(stderr, "'%s' is not a directory!\n", hdr->name); + return -1; + } + + sqfs->fs.root->uid = hdr->uid; + sqfs->fs.root->gid = hdr->gid; + sqfs->fs.root->mode = hdr->mode; + + if (keep_time) + sqfs->fs.root->mod_time = hdr->mtime; + + if (!cfg.no_xattr) { + if (copy_xattr(sqfs, sqfs->fs.root, hdr)) + return -1; + } + + return 0; +} + +int process_tarball(istream_t *input_file, sqfs_writer_t *sqfs) +{ + bool skip, is_root, is_prefixed; + tar_header_decoded_t hdr; + sqfs_u64 offset, count; + sparse_map_t *m; + size_t rootlen; + char *target; + 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; + + 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; + } else 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; + } + + if (hdr.link_target != NULL && + (hdr.is_hard_link || !no_symlink_retarget)) { + target = strdup(hdr.link_target); + if (target == NULL) { + fprintf(stderr, "packing '%s': %s\n", + hdr.name, strerror(errno)); + goto fail; + } + + if (canonicalize_name(target) == 0 && + !strncmp(target, root_becomes, rootlen) && + target[rootlen] == '/') { + memmove(hdr.link_target, + target + rootlen, + strlen(target + rootlen) + 1); + } + + free(target); + } + } else if (hdr.name[0] == '\0') { + is_root = true; + } + + if (!is_prefixed) { + if (skip_entry(input_file, hdr.record_size)) + goto fail; + clear_header(&hdr); + continue; + } + + if (is_root) { + if (set_root_attribs(sqfs, &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.record_size)) + goto fail; + + clear_header(&hdr); + continue; + } + + if (create_node_and_repack_data(input_file, sqfs, &hdr)) + goto fail; + + clear_header(&hdr); + } + + return 0; +fail: + clear_header(&hdr); + return -1; +} diff --git a/bin/tar2sqfs/src/tar2sqfs.c b/bin/tar2sqfs/src/tar2sqfs.c new file mode 100644 index 0000000..9257fed --- /dev/null +++ b/bin/tar2sqfs/src/tar2sqfs.c @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * tar2sqfs.c + * + * Copyright (C) 2019 David Oberhollenzer + */ +#include "tar2sqfs.h" + +static int tar_probe(const sqfs_u8 *data, size_t size) +{ + size_t i, offset; + + if (size >= TAR_RECORD_SIZE) { + for (i = 0; i < TAR_RECORD_SIZE; ++i) { + if (data[i] != 0x00) + break; + } + + if (i == TAR_RECORD_SIZE) { + data += TAR_RECORD_SIZE; + size -= TAR_RECORD_SIZE; + } + } + + offset = offsetof(tar_header_t, magic); + + if (offset + 5 <= size) { + if (memcmp(data + offset, "ustar", 5) == 0) + return 1; + } + + return 0; +} + +static istream_t *magic_autowrap(istream_t *strm) +{ + xfrm_stream_t *xfrm = NULL; + istream_t *wrapper = NULL; + const sqfs_u8 *data; + size_t avail; + int ret; + + ret = istream_precache(strm); + if (ret != 0) + goto out; + + data = strm->buffer + strm->buffer_offset; + avail = strm->buffer_used - strm->buffer_offset; + + ret = tar_probe(data, avail); + if (ret > 0) + return strm; + + ret = xfrm_compressor_id_from_magic(data, avail); + if (ret <= 0) + return strm; + + xfrm = decompressor_stream_create(ret); + if (xfrm == NULL) + goto out; + + wrapper = istream_xfrm_create(strm, xfrm); +out: + sqfs_drop(strm); + sqfs_drop(xfrm); + return wrapper; +} + +int main(int argc, char **argv) +{ + int status = EXIT_FAILURE; + istream_t *input_file = NULL; + sqfs_writer_t sqfs; + + process_args(argc, argv); + + input_file = istream_open_stdin(); + if (input_file == NULL) + return EXIT_FAILURE; + + input_file = magic_autowrap(input_file); + if (input_file == NULL) + return EXIT_FAILURE; + + memset(&sqfs, 0, sizeof(sqfs)); + if (sqfs_writer_init(&sqfs, &cfg)) + goto out_if; + + if (process_tarball(input_file, &sqfs)) + 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); +out_if: + sqfs_drop(input_file); + return status; +} diff --git a/bin/tar2sqfs/src/tar2sqfs.h b/bin/tar2sqfs/src/tar2sqfs.h new file mode 100644 index 0000000..a21774b --- /dev/null +++ b/bin/tar2sqfs/src/tar2sqfs.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * tar2sqfs.h + * + * Copyright (C) 2019 David Oberhollenzer + */ +#ifndef TAR2SQFS_H +#define TAR2SQFS_H + +#include "config.h" +#include "common.h" +#include "compat.h" + +#include "util/util.h" +#include "tar/tar.h" +#include "tar/format.h" +#include "xfrm/compress.h" +#include "io/xfrm.h" + +#include +#include +#include +#include +#include + +/* options.c */ +extern bool dont_skip; +extern bool keep_time; +extern bool no_tail_pack; +extern bool no_symlink_retarget; +extern sqfs_writer_cfg_t cfg; +extern char *root_becomes; + +void process_args(int argc, char **argv); + +/* process_tarball.c */ +int process_tarball(istream_t *input_file, sqfs_writer_t *sqfs); + +#endif /* TAR2SQFS_H */ -- cgit v1.2.3