diff options
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | doc/sqfs2tar.1 | 10 | ||||
-rw-r--r-- | doc/tar2sqfs.1 | 12 | ||||
-rw-r--r-- | tar/sqfs2tar.c | 99 | ||||
-rw-r--r-- | tar/tar2sqfs.c | 105 |
5 files changed, 194 insertions, 33 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f8a300..d82d8b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Port to Windows using MinGW cross compilation toolchain. - Port to BSD systems. - Explicit argument invalid error code in `libsquashfs`. +- A `--root-becomes` option to `tar2sqfs` and `sqfs2tar`. ### Changed - Return propper error code from `sqfs_get_xattr_prefix_id`. diff --git a/doc/sqfs2tar.1 b/doc/sqfs2tar.1 index 7d91396..f42b266 100644 --- a/doc/sqfs2tar.1 +++ b/doc/sqfs2tar.1 @@ -11,6 +11,16 @@ archives. The resulting archive is written to stdout. .PP Possible options: .TP +\fB\-\-root\-becomes\fR, \fB\-r\fR <dir> +Prefix all paths in the tarball with the given directory name and add an +entry for this directory that receives all meta data (permissions, ownership, +extended attributes, et cetera) of the root inode. + +The special value \fB.\fR can be used since many tar archivers themselves pack +the attributes of the root directory that way and naturally support this. + +If this option is not used, all meta data from the root inode IS LOST! +.TP \fB\-\-subdir\fR, \fB\-d\fR <dir> 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 diff --git a/doc/tar2sqfs.1 b/doc/tar2sqfs.1 index c2c7f72..70f2006 100644 --- a/doc/tar2sqfs.1 +++ b/doc/tar2sqfs.1 @@ -14,6 +14,18 @@ SquashFS. .PP Possible options: .TP +\fB\-\-root\-becomes\fR, \fB\-r\fR <dir> +If set, only pack entries that are underneath the specified directory and the +prefix is stripped. The meta data for the directory itself is copied to the +root inode, i.e. the ownership, permissions, extended attributes (unless +\f\-\-no\-xattr\R is set), and modification time +(unless \fB\-\-no\-keep\-time\fR is set). + +If this option is not set, tar2sqfs implicitly treats \fB./\fR or absolute +paths this way, i.e. if the archive contains an entry for \fB./\fR, it becomes +the root node and the prefix is stripped from all paths (and similar for +absolute paths and \fB/\fR). +.TP \fB\-\-compressor\fR, \fB\-c\fR <name> Select the compressor to use. Run \fBtar2sqfs \-\-help\fR to get a list of all available compressors diff --git a/tar/sqfs2tar.c b/tar/sqfs2tar.c index 9a12a96..8cfad62 100644 --- a/tar/sqfs2tar.c +++ b/tar/sqfs2tar.c @@ -19,13 +19,14 @@ static struct option long_opts[] = { { "subdir", required_argument, NULL, 'd' }, { "keep-as-dir", no_argument, NULL, 'k' }, + { "root-becomes", required_argument, NULL, 'r' }, { "no-skip", no_argument, NULL, 's' }, { "no-xattr", no_argument, NULL, 'X' }, { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, }; -static const char *short_opts = "d:ksXhV"; +static const char *short_opts = "d:kr:sXhV"; static const char *usagestr = "Usage: sqfs2tar [OPTIONS...] <sqfsfile>\n" @@ -41,6 +42,14 @@ static const char *usagestr = " one is specified, it becomes the new root of\n" " node of the archive file system tree.\n" "\n" +" --root-becomes, -r <dir> Turn the root inode into a directory with the\n" +" specified name. Everything else will be stored\n" +" inside this directory. The special value '.' is\n" +" allowed to prefix all tar paths with './' and\n" +" add an entry named '.' for the root inode.\n" +" If this option isn't used, all meta data stored\n" +" in the root inode IS LOST!\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" @@ -68,6 +77,7 @@ static bool dont_skip = false; static bool keep_as_dir = false; static bool no_xattr = false; +static char *root_becomes = NULL; static char **subdirs = NULL; static size_t num_subdirs = 0; static size_t max_subdirs = 0; @@ -114,6 +124,26 @@ static void process_args(int argc, char **argv) ++num_subdirs; break; + case 'r': + free(root_becomes); + root_becomes = strdup(optarg); + if (root_becomes == NULL) + goto fail_errno; + + if (strcmp(root_becomes, "./") == 0) + root_becomes[1] = '\0'; + + if (strcmp(root_becomes, ".") == 0) + break; + + if (canonicalize_name(root_becomes) != 0 || + strlen(root_becomes) == 0) { + fprintf(stderr, + "Invalid root directory '%s'.\n", + optarg); + goto fail_arg; + } + break; case 'k': keep_as_dir = true; break; @@ -165,6 +195,7 @@ out_success: out_exit: for (idx = 0; idx < num_subdirs; ++idx) free(subdirs[idx]); + free(root_becomes); free(subdirs); exit(ret); } @@ -260,31 +291,62 @@ fail: static int write_tree_dfs(const sqfs_tree_node_t *n) { tar_xattr_t *xattr = NULL, *xit; - char *name, *target; + char *name, *target, *temp; struct stat sb; + size_t len; int ret; - if (n->parent == NULL && S_ISDIR(n->inode->base.mode)) - goto skip_hdr; + if (n->parent == NULL) { + if (root_becomes == NULL) + goto skip_hdr; - if (!is_filename_sane((const char *)n->name)) { - fprintf(stderr, "Found a file named '%s', skipping.\n", - n->name); - if (dont_skip) { - fputs("Not allowed to skip files, aborting!\n", stderr); + len = strlen(root_becomes); + name = malloc(len + 2); + if (name == NULL) { + perror("creating root directory"); return -1; } - return 0; - } - name = sqfs_tree_node_get_path(n); - if (name == NULL) { - perror("resolving tree node path"); - return -1; - } + memcpy(name, root_becomes, len); + name[len] = '/'; + name[len + 1] = '\0'; + } else { + if (!is_filename_sane((const char *)n->name)) { + fprintf(stderr, "Found a file named '%s', skipping.\n", + n->name); + if (dont_skip) { + fputs("Not allowed to skip files, aborting!\n", + stderr); + return -1; + } + return 0; + } - if (canonicalize_name(name)) - goto out_skip; + name = sqfs_tree_node_get_path(n); + if (name == NULL) { + perror("resolving tree node path"); + return -1; + } + + if (canonicalize_name(name)) + goto out_skip; + + if (root_becomes != NULL) { + len = strlen(root_becomes); + temp = realloc(name, strlen(name) + len + 2); + + if (temp == NULL) { + perror("assembling tar entry filename"); + free(name); + return -1; + } + + name = temp; + memmove(name + len + 1, name, strlen(name) + 1); + memcpy(name, root_becomes, len); + name[len] = '/'; + } + } inode_stat(n, &sb); @@ -565,5 +627,6 @@ out_dirs: for (i = 0; i < num_subdirs; ++i) free(subdirs[i]); free(subdirs); + free(root_becomes); return status; } diff --git a/tar/tar2sqfs.c b/tar/tar2sqfs.c index 56ae5dc..cc0ccfb 100644 --- a/tar/tar2sqfs.c +++ b/tar/tar2sqfs.c @@ -20,6 +20,7 @@ #endif 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' }, @@ -37,7 +38,7 @@ static struct option long_opts[] = { { "version", no_argument, NULL, 'V' }, }; -static const char *short_opts = "c:b:B:d:X:j:Q:sxekfqhV"; +static const char *short_opts = "r:c:b:B:d:X:j:Q:sxekfqhV"; static const char *usagestr = "Usage: tar2sqfs [OPTIONS...] <sqfsfile>\n" @@ -47,6 +48,13 @@ static const char *usagestr = "\n" "Possible options:\n" "\n" +" --root-becomes, -r <dir> 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" +"\n" " --compressor, -c <name> Select the compressor to use.\n" " A list of available compressors is below.\n" " --comp-extra, -X <options> A comma seperated list of extra options for\n" @@ -93,6 +101,7 @@ static bool keep_time = true; static sqfs_writer_cfg_t cfg; static sqfs_writer_t sqfs; static FILE *input_file = NULL; +static char *root_becomes = NULL; static void process_args(int argc, char **argv) { @@ -156,6 +165,22 @@ static void process_args(int argc, char **argv) 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; @@ -273,7 +298,7 @@ static int write_file(tar_header_decoded_t *hdr, file_info_t *fi, filesize : hdr->record_size); } -static int copy_xattr(tree_node_t *node, tar_header_decoded_t *hdr) +static int copy_xattr(tree_node_t *node, const tar_header_decoded_t *hdr) { tar_xattr_t *xattr; int ret; @@ -346,14 +371,39 @@ fail_errno: return -1; } +static int set_root_attribs(const tar_header_decoded_t *hdr) +{ + if (!S_ISDIR(hdr->sb.st_mode)) { + fprintf(stderr, "'%s' is not a directory!\n", hdr->name); + return -1; + } + + sqfs.fs.root->uid = hdr->sb.st_uid; + sqfs.fs.root->gid = hdr->sb.st_gid; + sqfs.fs.root->mode = hdr->sb.st_mode; + + if (keep_time) + sqfs.fs.root->mod_time = hdr->sb.st_mtime; + + if (!cfg.no_xattr) { + if (copy_xattr(sqfs.fs.root, hdr)) + return -1; + } + + return 0; +} + static int process_tar_ball(void) { + bool skip, is_root, is_prefixed; tar_header_decoded_t hdr; sqfs_u64 offset, count; sparse_map_t *m; - bool skip; + size_t rootlen; int ret; + rootlen = root_becomes == NULL ? 0 : strlen(root_becomes); + for (;;) { ret = read_header(input_file, &hdr); if (ret > 0) @@ -362,15 +412,8 @@ static int process_tar_ball(void) return -1; skip = false; - - if (hdr.name != NULL && strcmp(hdr.name, "./") == 0 && - S_ISDIR(hdr.sb.st_mode)) { - /* XXX: tar entries might be prefixed with ./ which is - stripped by cannonicalize_name, but the tar file may - contain a directory entry named './' */ - clear_header(&hdr); - continue; - } + is_root = false; + is_prefixed = true; if (hdr.name == NULL || canonicalize_name(hdr.name) != 0) { fprintf(stderr, "skipping '%s' (invalid name)\n", @@ -378,9 +421,41 @@ static int process_tar_ball(void) skip = true; } - if (hdr.name[0] == '\0') { - fputs("skipping entry with empty name\n", stderr); - skip = true; + 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; + } + } else if (hdr.name[0] == '\0') { + is_root = true; + } + + if (!is_prefixed) { + clear_header(&hdr); + continue; + } + + if (is_root) { + if (set_root_attribs(&hdr)) + goto fail; + clear_header(&hdr); + continue; } if (!skip && hdr.unknown_record) { |