summaryrefslogtreecommitdiff
path: root/lib/tar/write_header.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tar/write_header.c')
-rw-r--r--lib/tar/write_header.c248
1 files changed, 248 insertions, 0 deletions
diff --git a/lib/tar/write_header.c b/lib/tar/write_header.c
new file mode 100644
index 0000000..80db327
--- /dev/null
+++ b/lib/tar/write_header.c
@@ -0,0 +1,248 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+#include "util.h"
+#include "tar.h"
+
+#include <sys/sysmacros.h>
+#include <string.h>
+#include <stdio.h>
+
+static unsigned long pax_hdr_counter = 0;
+static char buffer[4096];
+
+static void write_octal(char *dst, unsigned int value, int digits)
+{
+ char temp[64];
+
+ sprintf(temp, "%0*o ", digits, value);
+ memcpy(dst, temp, strlen(temp));
+}
+
+static int name_to_tar_header(tar_header_t *hdr, const char *path)
+{
+ size_t len = strlen(path);
+ const char *ptr;
+
+ if ((len + 1) <= sizeof(hdr->name)) {
+ memcpy(hdr->name, path, len);
+ return 0;
+ }
+
+ for (ptr = path; ; ++ptr) {
+ ptr = strchr(ptr, '/');
+ if (ptr == NULL)
+ return -1;
+
+ len = ptr - path;
+ if (len >= sizeof(hdr->prefix))
+ continue;
+ if (strlen(ptr + 1) >= sizeof(hdr->name))
+ continue;
+ break;
+ }
+
+ memcpy(hdr->prefix, path, ptr - path);
+ memcpy(hdr->name, ptr + 1, strlen(ptr + 1));
+ return 0;
+}
+
+static void init_header(tar_header_t *hdr, const struct stat *sb,
+ const char *name, const char *slink_target)
+{
+ 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;
+ }
+
+ sprintf(hdr->uname, "%u", sb->st_uid);
+ sprintf(hdr->gname, "%u", sb->st_gid);
+}
+
+static void update_checksum(tar_header_t *hdr)
+{
+ unsigned int chksum = 0;
+ size_t i;
+
+ memset(hdr->chksum, ' ', sizeof(hdr->chksum));
+
+ for (i = 0; i < sizeof(*hdr); ++i)
+ chksum += ((unsigned char *)hdr)[i];
+
+ write_octal(hdr->chksum, chksum, 6);
+ hdr->chksum[6] = '\0';
+ hdr->chksum[7] = ' ';
+}
+
+static bool need_pax_header(const struct stat *sb, const char *name)
+{
+ 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;
+
+ return false;
+}
+
+static char *write_pax_entry(char *dst, const char *key, const char *value)
+{
+ size_t i, len, prefix = 0, oldprefix;
+
+ do {
+ len = prefix + 1 + strlen(key) + 1 + strlen(value) + 1;
+
+ oldprefix = prefix;
+ prefix = 1;
+
+ for (i = len; i >= 10; i /= 10)
+ ++prefix;
+ } while (oldprefix != prefix);
+
+ sprintf(dst, "%zu %s=%s\n", len, key, value);
+
+ return dst + len;
+}
+
+static int write_pax_header(int fd, const struct stat *sb, const char *name,
+ const char *slink_target)
+{
+ char temp[64], *ptr;
+ struct stat fakesb;
+ tar_header_t hdr;
+ 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);
+
+ sprintf(temp, "%u", sb->st_gid);
+ ptr = write_pax_entry(ptr, "gid", temp);
+ ptr = write_pax_entry(ptr, "gname", temp);
+
+ ptr = write_pax_entry(ptr, "path", name);
+
+ if (S_ISLNK(sb->st_mode)) {
+ ptr = write_pax_entry(ptr, "linkpath", slink_target);
+ } else if (S_ISREG(sb->st_mode)) {
+ sprintf(temp, "%lu", sb->st_size);
+ ptr = write_pax_entry(ptr, "size", temp);
+ }
+
+ len = strlen(buffer);
+ write_octal(hdr.size, len, 11);
+ update_checksum(&hdr);
+
+ 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;
+}
+
+int write_tar_header(int fd, const struct stat *sb, const char *name,
+ const char *slink_target)
+{
+ const char *reason;
+ tar_header_t hdr;
+ ssize_t ret;
+
+ if (need_pax_header(sb, name)) {
+ if (write_pax_header(fd, sb, name, slink_target))
+ return -1;
+
+ sprintf(buffer, "pax%lu_data", pax_hdr_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_IFSOCK:
+ reason = "cannot pack socket";
+ goto out_skip;
+ default:
+ reason = "unknown type";
+ 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;
+out_skip:
+ fprintf(stderr, "WARNING: skipping '%s' (%s)\n", name, reason);
+ return 1;
+}