summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--doc/sqfs2tar.110
-rw-r--r--doc/tar2sqfs.112
-rw-r--r--tar/sqfs2tar.c99
-rw-r--r--tar/tar2sqfs.c105
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) {