diff options
author | David Oberhollenzer <david.oberhollenzer@sigma-star.at> | 2019-07-03 15:08:15 +0200 |
---|---|---|
committer | David Oberhollenzer <david.oberhollenzer@sigma-star.at> | 2019-07-03 15:17:57 +0200 |
commit | 70dcf39f5926a66d76eb9fde2cbaef4b6a23a9e1 (patch) | |
tree | e4bfefd6c4559ad3abbd89af546099cb56a24040 | |
parent | b8d4983147141310383d423b1bcb9d19a490977a (diff) |
tar writer: replace PAX headers with GNU extensions
Some experiments seem to indicate that the various GNU extensions are
more widely supported than their POSIX equivalents[1]. Possibly because
they are easier to implement and possibly because of the wide spread
use of GNU tar.
This commit replaces the PAX writer in the write_tar_header implementation
with a GNU extension based writer.
The writer is also cleaned up by removing all global state. The record
counter is moved outside into the tar2sqfs program and passed in as
function argument.
[1] https://dev.gentoo.org/~mgorny/articles/portability-of-tar-features.html
Signed-off-by: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
-rw-r--r-- | doc/sqfs2tar.1 | 9 | ||||
-rw-r--r-- | include/tar.h | 5 | ||||
-rw-r--r-- | lib/tar/checksum.c | 2 | ||||
-rw-r--r-- | lib/tar/internal.h | 2 | ||||
-rw-r--r-- | lib/tar/number.c | 8 | ||||
-rw-r--r-- | lib/tar/write_header.c | 282 | ||||
-rw-r--r-- | tar/sqfs2tar.c | 4 |
7 files changed, 132 insertions, 180 deletions
diff --git a/doc/sqfs2tar.1 b/doc/sqfs2tar.1 index a999dff..b732d06 100644 --- a/doc/sqfs2tar.1 +++ b/doc/sqfs2tar.1 @@ -18,10 +18,11 @@ Print help text and exit. .TP \fB\-\-version\fR, \fB\-V\fR Print version information and exit. -.SH LIMITATIONS -To be compatible with as many tools as possible, the output format is POSIX tar -using pax extensions if necessary, so the tools you intend to use should -support the pax format. +.SH COMPATIBILITY +To be compatible with as many tools as possible, the output format is pre-POSIX +ustar archive using GNU extensions where necessary. This seems to be more +widely supported by many tar programs (besides GNU tar), even more than the +newer POSIX format and PAX extensions. It is not possible to store socket files in a tar or pax archive. Also, in the current implementation, all extended attribuates are lost. diff --git a/include/tar.h b/include/tar.h index ef4fdec..fd701d9 100644 --- a/include/tar.h +++ b/include/tar.h @@ -89,9 +89,12 @@ typedef struct { /* Returns < 0 on failure, > 0 if cannot encode, 0 on success. Prints error/warning messages to stderr. + + The counter is an incremental record counter used if additional + headers need to be generated. */ int write_tar_header(int fd, const struct stat *sb, const char *name, - const char *slink_target); + const char *slink_target, unsigned int counter); /* calcuate and skip the zero padding */ int skip_padding(int fd, uint64_t size); diff --git a/lib/tar/checksum.c b/lib/tar/checksum.c index 925f942..a2a101a 100644 --- a/lib/tar/checksum.c +++ b/lib/tar/checksum.c @@ -11,7 +11,7 @@ void update_checksum(tar_header_t *hdr) for (i = 0; i < sizeof(*hdr); ++i) chksum += ((unsigned char *)hdr)[i]; - write_octal(hdr->chksum, chksum, 6); + sprintf(hdr->chksum, "%06o", chksum); hdr->chksum[6] = '\0'; hdr->chksum[7] = ' '; } diff --git a/lib/tar/internal.h b/lib/tar/internal.h index 6596987..3c0e27f 100644 --- a/lib/tar/internal.h +++ b/lib/tar/internal.h @@ -38,8 +38,6 @@ int read_binary(const char *str, int digits, uint64_t *out); int read_number(const char *str, int digits, uint64_t *out); -void write_octal(char *dst, unsigned int value, int digits); - int pax_read_decimal(const char *str, uint64_t *out); void update_checksum(tar_header_t *hdr); diff --git a/lib/tar/number.c b/lib/tar/number.c index e85866d..009a4de 100644 --- a/lib/tar/number.c +++ b/lib/tar/number.c @@ -84,11 +84,3 @@ int pax_read_decimal(const char *str, uint64_t *out) *out = result; return 0; } - -void write_octal(char *dst, unsigned int value, int digits) -{ - char temp[64]; - - sprintf(temp, "%0*o ", digits, value); - memcpy(dst, temp, strlen(temp)); -} diff --git a/lib/tar/write_header.c b/lib/tar/write_header.c index b84324b..79fa49a 100644 --- a/lib/tar/write_header.c +++ b/lib/tar/write_header.c @@ -1,197 +1,166 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ #include "internal.h" -static unsigned long pax_hdr_counter = 0; -static char buffer[4096]; - -static int name_to_tar_header(tar_header_t *hdr, const char *path) +static void write_binary(char *dst, uint64_t value, int digits) { - size_t len = strlen(path); - const char *ptr; + memset(dst, 0, digits); - if ((len + 1) <= sizeof(hdr->name)) { - memcpy(hdr->name, path, len); - return 0; + while (digits > 0) { + ((unsigned char *)dst)[digits - 1] = value & 0xFF; + --digits; + value >>= 8; } - for (ptr = path; ; ++ptr) { - ptr = strchr(ptr, '/'); - if (ptr == NULL) - return -1; - - len = ptr - path; - if (len >= sizeof(hdr->tail.posix.prefix)) - continue; - if (strlen(ptr + 1) >= sizeof(hdr->name)) - continue; - break; - } - - memcpy(hdr->tail.posix.prefix, path, ptr - path); - memcpy(hdr->name, ptr + 1, strlen(ptr + 1)); - return 0; + ((unsigned char *)dst)[0] |= 0x80; } -static void init_header(tar_header_t *hdr, const struct stat *sb, - const char *name, const char *slink_target) +static void write_number(char *dst, uint64_t value, int digits) { - memset(hdr, 0, sizeof(*hdr)); - - name_to_tar_header(hdr, name); - memcpy(hdr->magic, TAR_MAGIC, sizeof(hdr->magic)); - memcpy(hdr->version, TAR_VERSION, sizeof(hdr->version)); - write_octal(hdr->mode, sb->st_mode & ~S_IFMT, 6); - write_octal(hdr->uid, sb->st_uid, 6); - write_octal(hdr->gid, sb->st_gid, 6); - write_octal(hdr->mtime, sb->st_mtime, 11); - write_octal(hdr->size, 0, 11); - write_octal(hdr->devmajor, 0, 6); - write_octal(hdr->devminor, 0, 6); - - switch (sb->st_mode & S_IFMT) { - case S_IFREG: - write_octal(hdr->size, sb->st_size & 077777777777L, 11); - break; - case S_IFLNK: - if (sb->st_size < (off_t)sizeof(hdr->linkname)) - strcpy(hdr->linkname, slink_target); - break; - case S_IFCHR: - case S_IFBLK: - write_octal(hdr->devmajor, major(sb->st_rdev), 6); - write_octal(hdr->devminor, minor(sb->st_rdev), 6); - break; + uint64_t mask = 0; + char buffer[64]; + int i; + + for (i = 0; i < (digits - 1); ++i) + mask = (mask << 3) | 7; + + if (value <= mask) { + sprintf(buffer, "%0*o ", digits - 1, (unsigned int)value); + memcpy(dst, buffer, digits); + } else if (value <= ((mask << 3) | 7)) { + sprintf(buffer, "%0*o", digits, (unsigned int)value); + memcpy(dst, buffer, digits); + } else { + write_binary(dst, value, digits); } - - sprintf(hdr->uname, "%u", sb->st_uid); - sprintf(hdr->gname, "%u", sb->st_gid); } -static bool need_pax_header(const struct stat *sb, const char *name) +static void write_number_signed(char *dst, int64_t value, int digits) { - tar_header_t tmp; - - if (sb->st_uid > 0777777 || sb->st_gid > 0777777) - return true; - - if (S_ISREG(sb->st_mode) && sb->st_size > 077777777777L) - return true; - - if (S_ISLNK(sb->st_mode) && sb->st_size >= (off_t)sizeof(tmp.linkname)) - return true; - - if (name_to_tar_header(&tmp, name)) - return true; + uint64_t neg; - return false; + if (value < 0) { + neg = -value; + write_binary(dst, ~neg + 1, digits); + } else { + write_number(dst, value, digits); + } } -static char *write_pax_entry(char *dst, const char *key, const char *value) +static int write_header(int fd, const struct stat *sb, const char *name, + const char *slink_target, int type) { - size_t i, len, prefix = 0, oldprefix; + int maj = 0, min = 0; + uint64_t size = 0; + tar_header_t hdr; + ssize_t ret; - do { - len = prefix + 1 + strlen(key) + 1 + strlen(value) + 1; + if (S_ISCHR(sb->st_mode) || S_ISBLK(sb->st_mode)) { + maj = major(sb->st_rdev); + min = minor(sb->st_rdev); + } + + if (S_ISREG(sb->st_mode)) + size = sb->st_size; + + memset(&hdr, 0, sizeof(hdr)); + + memcpy(hdr.name, name, strlen(name)); + write_number(hdr.mode, sb->st_mode & ~S_IFMT, sizeof(hdr.mode)); + write_number(hdr.uid, sb->st_uid, sizeof(hdr.uid)); + write_number(hdr.gid, sb->st_gid, sizeof(hdr.gid)); + write_number(hdr.size, size, sizeof(hdr.size)); + write_number_signed(hdr.mtime, sb->st_mtime, sizeof(hdr.mtime)); + hdr.typeflag = type; + if (slink_target != NULL) + memcpy(hdr.linkname, slink_target, sb->st_size); + memcpy(hdr.magic, TAR_MAGIC_OLD, sizeof(hdr.magic)); + memcpy(hdr.version, TAR_VERSION_OLD, sizeof(hdr.version)); + sprintf(hdr.uname, "%u", sb->st_uid); + sprintf(hdr.gname, "%u", sb->st_gid); + write_number(hdr.devmajor, maj, sizeof(hdr.devmajor)); + write_number(hdr.devminor, min, sizeof(hdr.devminor)); - oldprefix = prefix; - prefix = 1; + update_checksum(&hdr); - for (i = len; i >= 10; i /= 10) - ++prefix; - } while (oldprefix != prefix); + ret = write_retry(fd, &hdr, sizeof(hdr)); + if (ret < 0) { + perror("writing header record"); + return -1; + } - sprintf(dst, "%zu %s=%s\n", len, key, value); + if ((size_t)ret < sizeof(hdr)) { + fputs("writing header record: truncated write\n", stderr); + return -1; + } - return dst + len; + return 0; } -static int write_pax_header(int fd, const struct stat *sb, const char *name, - const char *slink_target) +static int write_gnu_header(int fd, const struct stat *orig, + const char *payload, size_t payload_len, + int type, const char *name) { - char temp[64], *ptr; - struct stat fakesb; - tar_header_t hdr; + struct stat sb; ssize_t ret; - size_t len; - - memset(buffer, 0, sizeof(buffer)); - memset(&fakesb, 0, sizeof(fakesb)); - fakesb.st_mode = S_IFREG | 0644; - - sprintf(temp, "pax%lu", pax_hdr_counter); - init_header(&hdr, &fakesb, temp, NULL); - hdr.typeflag = TAR_TYPE_PAX; - - sprintf(temp, "%u", sb->st_uid); - ptr = buffer; - ptr = write_pax_entry(ptr, "uid", temp); - ptr = write_pax_entry(ptr, "uname", temp); - sprintf(temp, "%lu", sb->st_mtime); - ptr = write_pax_entry(ptr, "mtime", temp); + sb = *orig; + sb.st_mode = S_IFREG | 0644; + sb.st_size = payload_len; - sprintf(temp, "%u", sb->st_gid); - ptr = write_pax_entry(ptr, "gid", temp); - ptr = write_pax_entry(ptr, "gname", temp); + if (write_header(fd, &sb, name, NULL, type)) + return -1; - ptr = write_pax_entry(ptr, "path", name); - - if (S_ISLNK(sb->st_mode)) { - write_pax_entry(ptr, "linkpath", slink_target); - } else if (S_ISREG(sb->st_mode)) { - sprintf(temp, "%lu", sb->st_size); - write_pax_entry(ptr, "size", temp); + ret = write_retry(fd, payload, payload_len); + if (ret < 0) { + perror("writing GNU extension header"); + return -1; } - len = strlen(buffer); - write_octal(hdr.size, len, 11); - update_checksum(&hdr); + if ((size_t)ret < payload_len) { + fputs("writing GNU extension header: truncated write\n", stderr); + return -1; + } - 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; + return padd_file(fd, payload_len, 512); } int write_tar_header(int fd, const struct stat *sb, const char *name, - const char *slink_target) + const char *slink_target, unsigned int counter) { const char *reason; - tar_header_t hdr; - ssize_t ret; + char buffer[64]; + int type; - if (need_pax_header(sb, name)) { - if (write_pax_header(fd, sb, name, slink_target)) + if (!S_ISLNK(sb->st_mode)) + slink_target = NULL; + + if (S_ISLNK(sb->st_mode) && sb->st_size >= 100) { + sprintf(buffer, "gnu/target%u", counter); + if (write_gnu_header(fd, sb, slink_target, sb->st_size, + TAR_TYPE_GNU_SLINK, buffer)) return -1; + slink_target = NULL; + } + + if (strlen(name) >= 100) { + sprintf(buffer, "gnu/name%u", counter); - sprintf(buffer, "pax%lu_data", pax_hdr_counter++); + if (write_gnu_header(fd, sb, name, strlen(name), + TAR_TYPE_GNU_PATH, buffer)) { + return -1; + } + + sprintf(buffer, "gnu/data%u", counter); name = buffer; } - init_header(&hdr, sb, name, slink_target); - switch (sb->st_mode & S_IFMT) { - case S_IFCHR: hdr.typeflag = TAR_TYPE_CHARDEV; break; - case S_IFBLK: hdr.typeflag = TAR_TYPE_BLOCKDEV; break; - case S_IFLNK: hdr.typeflag = TAR_TYPE_SLINK; break; - case S_IFREG: 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_IFCHR: type = TAR_TYPE_CHARDEV; break; + case S_IFBLK: type = TAR_TYPE_BLOCKDEV; break; + case S_IFLNK: type = TAR_TYPE_SLINK; break; + case S_IFREG: type = TAR_TYPE_FILE; break; + case S_IFDIR: type = TAR_TYPE_DIR; break; + case S_IFIFO: type = TAR_TYPE_FIFO; break; case S_IFSOCK: reason = "cannot pack socket"; goto out_skip; @@ -200,20 +169,7 @@ int write_tar_header(int fd, const struct stat *sb, const char *name, 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; + return write_header(fd, sb, name, slink_target, type); out_skip: fprintf(stderr, "WARNING: skipping '%s' (%s)\n", name, reason); return 1; diff --git a/tar/sqfs2tar.c b/tar/sqfs2tar.c index e8b8736..e568bb4 100644 --- a/tar/sqfs2tar.c +++ b/tar/sqfs2tar.c @@ -42,6 +42,7 @@ static const char *usagestr = "\n"; static const char *filename; +static unsigned int record_counter; static void process_args(int argc, char **argv) { @@ -121,7 +122,8 @@ static int write_tree_dfs(fstree_t *fs, tree_node_t *n, data_reader_t *data) 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); + ret = write_tar_header(STDOUT_FILENO, &sb, name, target, + record_counter++); free(name); if (ret < 0) |