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 /lib | |
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>
Diffstat (limited to 'lib')
-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 |
4 files changed, 120 insertions, 174 deletions
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; |