From 9899d4181084b0cb488fe6161ac3ffe9ccd55c29 Mon Sep 17 00:00:00 2001 From: David Oberhollenzer Date: Wed, 3 Jul 2019 19:27:08 +0200 Subject: sqfs2tar: add option to extract subdirectories Signed-off-by: David Oberhollenzer --- doc/sqfs2tar.1 | 10 +++ tar/sqfs2tar.c | 191 +++++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 161 insertions(+), 40 deletions(-) diff --git a/doc/sqfs2tar.1 b/doc/sqfs2tar.1 index c7389ed..2cf9f9c 100644 --- a/doc/sqfs2tar.1 +++ b/doc/sqfs2tar.1 @@ -13,6 +13,16 @@ work on tar archives. .PP Possible options: .TP +\fB\-\-subdir\fR, \fB\-d\fR +Unpack the given sub directory instead of the filesystem root. Can be specified +more than once to select multiple directories. If only one is specified, it +becomes the new root of node of the archive file system tree. +.TP +\fB\-\-keep\-as\-dir\fR, \fB\-k\fR +If \fB\-\-subdir\fR is used only once, don't make the subdir the archive root, +instead keep it as prefix for all unpacked files. Using \fB\-\-subdir\fR more +than once implies \fB\-\-keep\-as\-dir\fR. +.TP \fB\-\-no\-skip\fR, \fB\-s\fR Abort if file cannot be stored in a tar record instead of skipping it. .TP diff --git a/tar/sqfs2tar.c b/tar/sqfs2tar.c index 76c0d74..110c8c4 100644 --- a/tar/sqfs2tar.c +++ b/tar/sqfs2tar.c @@ -17,12 +17,14 @@ #include static struct option long_opts[] = { + { "subdir", required_argument, NULL, 'd' }, + { "keep-as-dir", no_argument, NULL, 'k' }, { "no-skip", no_argument, NULL, 's' }, { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, }; -static const char *short_opts = "shV"; +static const char *short_opts = "d:kshV"; static const char *usagestr = "Usage: sqfs2tar [OPTIONS...] \n" @@ -32,9 +34,21 @@ static const char *usagestr = "\n" "Possible options:\n" "\n" +" --subdir, -d Unpack the given sub directory instead of the\n" +" filesystem root. Can be specified more than\n" +" once to select multiple directories. If only\n" +" one is specified, it becomes the new root of\n" +" node of the archive file system tree.\n" +"\n" +" --keep-as-dir, -k If --subdir is used only once, don't make the\n" +" subdir the archive root, instead keep it as\n" +" prefix for all unpacked files.\n" +" Using --subdir more than once implies\n" +" --keep-as-dir.\n" +"\n" " --no-skip Abort if a file cannot be stored in a tar\n" " archive. By default, it is simply skipped\n" -" and a warning is written to stderr." +" and a warning is written to stderr.\n" "\n" " --help, -h Print help text and exit.\n" " --version, -V Print version information and exit.\n" @@ -49,10 +63,19 @@ static const char *usagestr = static const char *filename; static unsigned int record_counter; static bool dont_skip = false; +static bool keep_as_dir = false; + +static const char *current_subdir = NULL; + +static char **subdirs = NULL; +static size_t num_subdirs = 0; +static size_t max_subdirs = 0; static void process_args(int argc, char **argv) { - int i; + size_t idx, new_count; + int i, ret; + void *new; for (;;) { i = getopt_long(argc, argv, short_opts, long_opts, NULL); @@ -60,15 +83,41 @@ static void process_args(int argc, char **argv) break; switch (i) { + case 'd': + if (num_subdirs == max_subdirs) { + new_count = max_subdirs ? max_subdirs * 2 : 16; + new = realloc(subdirs, + new_count * sizeof(subdirs[0])); + if (new == NULL) + goto fail_errno; + + max_subdirs = new_count; + subdirs = new; + } + + subdirs[num_subdirs] = strdup(optarg); + if (subdirs[num_subdirs] == NULL) + goto fail_errno; + + if (canonicalize_name(subdirs[num_subdirs])) { + perror(optarg); + goto fail; + } + + ++num_subdirs; + break; + case 'k': + keep_as_dir = true; + break; case 's': dont_skip = true; break; case 'h': fputs(usagestr, stdout); - exit(EXIT_SUCCESS); + goto out_success; case 'V': print_version(); - exit(EXIT_SUCCESS); + goto out_success; default: goto fail_arg; } @@ -85,10 +134,28 @@ static void process_args(int argc, char **argv) fputs("Unknown extra arguments\n", stderr); goto fail_arg; } + + if (num_subdirs > 1) + keep_as_dir = true; + return; +fail_errno: + perror("parsing options"); + goto fail; fail_arg: fputs("Try `sqfs2tar --help' for more information.\n", stderr); - exit(EXIT_FAILURE); + goto fail; +fail: + ret = EXIT_FAILURE; + goto out_exit; +out_success: + ret = EXIT_SUCCESS; + goto out_exit; +out_exit: + for (idx = 0; idx < num_subdirs; ++idx) + free(subdirs[idx]); + free(subdirs); + exit(ret); } static int terminate_archive(void) @@ -115,54 +182,71 @@ static int terminate_archive(void) static int write_tree_dfs(fstree_t *fs, tree_node_t *n, data_reader_t *data) { + size_t len, name_len; char *name, *target; struct stat sb; 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); + if (n->parent == NULL && S_ISDIR(n->mode)) + goto skip_hdr; - fstree_node_stat(fs, n, &sb); + name = fstree_get_path(n); + if (name == NULL) { + perror("resolving tree node path"); + return -1; + } - target = S_ISLNK(sb.st_mode) ? n->data.slink_target : NULL; - ret = write_tar_header(STDOUT_FILENO, &sb, name, target, - record_counter++); + assert(canonicalize_name(name) == 0); - if (ret > 0) { - if (dont_skip) { - fputs("Not allowed to skip files, aborting!\n", - stderr); - ret = -1; - } else { - fprintf(stderr, "Skipping %s\n", name); - ret = 0; - } + if (current_subdir != NULL && !keep_as_dir) { + if (strcmp(name, current_subdir) == 0) { free(name); - return ret; + goto skip_hdr; } - free(name); + len = strlen(current_subdir); + name_len = strlen(name); - if (ret < 0) - return -1; + assert(name_len > len); + assert(name[len] == '/'); - if (S_ISREG(n->mode)) { - if (data_reader_dump_file(data, n->data.file, - STDOUT_FILENO, false)) { - return -1; - } + memmove(name, name + len + 1, name_len - len); + } - if (padd_file(STDOUT_FILENO, n->data.file->size, 512)) - return -1; + fstree_node_stat(fs, n, &sb); + + target = S_ISLNK(sb.st_mode) ? n->data.slink_target : NULL; + ret = write_tar_header(STDOUT_FILENO, &sb, name, target, + record_counter++); + + if (ret > 0) { + if (dont_skip) { + fputs("Not allowed to skip files, aborting!\n", + stderr); + ret = -1; + } else { + fprintf(stderr, "Skipping %s\n", name); + ret = 0; } + free(name); + return ret; } + free(name); + + if (ret < 0) + return -1; + + if (S_ISREG(n->mode)) { + if (data_reader_dump_file(data, n->data.file, + STDOUT_FILENO, false)) { + return -1; + } + + if (padd_file(STDOUT_FILENO, n->data.file->size, 512)) + return -1; + } +skip_hdr: if (S_ISDIR(n->mode)) { for (n = n->data.dir->children; n != NULL; n = n->next) { if (write_tree_dfs(fs, n, data)) @@ -179,8 +263,10 @@ int main(int argc, char **argv) int status = EXIT_FAILURE; sqfs_super_t super; compressor_t *cmp; + tree_node_t *root; fstree_t fs; int sqfsfd; + size_t i; process_args(argc, argv); @@ -216,8 +302,30 @@ int main(int argc, char **argv) if (data == NULL) goto out_fs; - if (write_tree_dfs(&fs, fs.root, data)) - goto out_data; + for (i = 0; i < num_subdirs; ++i) { + root = fstree_node_from_path(&fs, subdirs[i]); + if (root == NULL) { + perror(subdirs[i]); + goto out_data; + } + + if (!S_ISDIR(root->mode)) { + fprintf(stderr, "%s is not a directory\n", subdirs[i]); + goto out_data; + } + + current_subdir = subdirs[i]; + + if (write_tree_dfs(&fs, root, data)) + goto out_data; + } + + current_subdir = NULL; + + if (num_subdirs == 0) { + if (write_tree_dfs(&fs, fs.root, data)) + goto out_data; + } if (terminate_archive()) goto out_data; @@ -231,5 +339,8 @@ out_cmp: cmp->destroy(cmp); out_fd: close(sqfsfd); + for (i = 0; i < num_subdirs; ++i) + free(subdirs[i]); + free(subdirs); return status; } -- cgit v1.2.3