aboutsummaryrefslogtreecommitdiff
path: root/lib/tar/src
diff options
context:
space:
mode:
authorDavid Oberhollenzer <david.oberhollenzer@sigma-star.at>2023-01-31 11:21:30 +0100
committerDavid Oberhollenzer <david.oberhollenzer@sigma-star.at>2023-01-31 13:51:49 +0100
commitcdccc69c62579b0c13b35fad0728079652b8f3c9 (patch)
tree9fa54c710f73c5e08a9c8466e7a712eb63ee07ac /lib/tar/src
parent2182129c8f359c4fa1390eaba7a65b595ccd4182 (diff)
Move library source into src sub-directory
Signed-off-by: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
Diffstat (limited to 'lib/tar/src')
-rw-r--r--lib/tar/src/checksum.c48
-rw-r--r--lib/tar/src/cleanup.c42
-rw-r--r--lib/tar/src/internal.h46
-rw-r--r--lib/tar/src/number.c79
-rw-r--r--lib/tar/src/padd_file.c19
-rw-r--r--lib/tar/src/pax_header.c402
-rw-r--r--lib/tar/src/read_header.c304
-rw-r--r--lib/tar/src/read_sparse_map_new.c115
-rw-r--r--lib/tar/src/read_sparse_map_old.c99
-rw-r--r--lib/tar/src/record_to_memory.c41
-rw-r--r--lib/tar/src/write_header.c282
11 files changed, 1477 insertions, 0 deletions
diff --git a/lib/tar/src/checksum.c b/lib/tar/src/checksum.c
new file mode 100644
index 0000000..6541373
--- /dev/null
+++ b/lib/tar/src/checksum.c
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * checksum.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "config.h"
+
+#include "tar/format.h"
+
+#include <stdio.h>
+
+static unsigned int get_checksum(const tar_header_t *hdr)
+{
+ const unsigned char *header_start = (const unsigned char *)hdr;
+ const unsigned char *chksum_start = (const unsigned char *)hdr->chksum;
+ const unsigned char *header_end = header_start + sizeof(*hdr);
+ const unsigned char *chksum_end = chksum_start + sizeof(hdr->chksum);
+ const unsigned char *p;
+ unsigned int chksum = 0;
+
+ for (p = header_start; p < chksum_start; p++)
+ chksum += *p;
+ for (; p < chksum_end; p++)
+ chksum += ' ';
+ for (; p < header_end; p++)
+ chksum += *p;
+ return chksum;
+}
+
+void update_checksum(tar_header_t *hdr)
+{
+ unsigned int chksum = get_checksum(hdr);
+
+ sprintf(hdr->chksum, "%06o", chksum);
+ hdr->chksum[6] = '\0';
+ hdr->chksum[7] = ' ';
+}
+
+bool is_checksum_valid(const tar_header_t *hdr)
+{
+ unsigned int calculated_chksum = get_checksum(hdr);
+ sqfs_u64 read_chksum;
+
+ if (read_octal(hdr->chksum, sizeof(hdr->chksum), &read_chksum))
+ return 0;
+ return read_chksum == calculated_chksum;
+}
diff --git a/lib/tar/src/cleanup.c b/lib/tar/src/cleanup.c
new file mode 100644
index 0000000..9f33336
--- /dev/null
+++ b/lib/tar/src/cleanup.c
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * cleanup.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "config.h"
+
+#include "internal.h"
+#include <stdlib.h>
+#include <string.h>
+
+void free_sparse_list(sparse_map_t *sparse)
+{
+ sparse_map_t *old;
+
+ while (sparse != NULL) {
+ old = sparse;
+ sparse = sparse->next;
+ free(old);
+ }
+}
+
+void free_xattr_list(tar_xattr_t *list)
+{
+ tar_xattr_t *old;
+
+ while (list != NULL) {
+ old = list;
+ list = list->next;
+ free(old);
+ }
+}
+
+void clear_header(tar_header_decoded_t *hdr)
+{
+ free_xattr_list(hdr->xattr);
+ free_sparse_list(hdr->sparse);
+ free(hdr->name);
+ free(hdr->link_target);
+ memset(hdr, 0, sizeof(*hdr));
+}
diff --git a/lib/tar/src/internal.h b/lib/tar/src/internal.h
new file mode 100644
index 0000000..c5483fe
--- /dev/null
+++ b/lib/tar/src/internal.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * internal.h
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#ifndef INTERNAL_H
+#define INTERNAL_H
+
+#include "config.h"
+
+#include "tar/tar.h"
+#include "tar/format.h"
+#include "util/util.h"
+
+enum {
+ PAX_SIZE = 0x001,
+ PAX_UID = 0x002,
+ PAX_GID = 0x004,
+ PAX_DEV_MAJ = 0x008,
+ PAX_DEV_MIN = 0x010,
+ PAX_NAME = 0x020,
+ PAX_SLINK_TARGET = 0x040,
+ PAX_MTIME = 0x100,
+ PAX_SPARSE_SIZE = 0x400,
+
+ PAX_SPARSE_GNU_1_X = 0x800,
+};
+
+enum {
+ ETV_UNKNOWN = 0,
+ ETV_V7_UNIX,
+ ETV_PRE_POSIX,
+ ETV_POSIX,
+};
+
+sparse_map_t *read_gnu_old_sparse(istream_t *fp, tar_header_t *hdr);
+
+sparse_map_t *read_gnu_new_sparse(istream_t *fp, tar_header_decoded_t *out);
+
+char *record_to_memory(istream_t *fp, size_t size);
+
+int read_pax_header(istream_t *fp, sqfs_u64 entsize, unsigned int *set_by_pax,
+ tar_header_decoded_t *out);
+
+#endif /* INTERNAL_H */
diff --git a/lib/tar/src/number.c b/lib/tar/src/number.c
new file mode 100644
index 0000000..2f179df
--- /dev/null
+++ b/lib/tar/src/number.c
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * number.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "config.h"
+
+#include "tar/format.h"
+
+#include <ctype.h>
+#include <stdio.h>
+
+int read_octal(const char *str, int digits, sqfs_u64 *out)
+{
+ sqfs_u64 result = 0;
+
+ while (digits > 0 && isspace(*str)) {
+ ++str;
+ --digits;
+ }
+
+ while (digits > 0 && *str >= '0' && *str <= '7') {
+ if (result > 0x1FFFFFFFFFFFFFFFUL) {
+ fputs("numeric overflow parsing tar header\n", stderr);
+ return -1;
+ }
+
+ result = (result << 3) | (*(str++) - '0');
+ --digits;
+ }
+
+ *out = result;
+ return 0;
+}
+
+static int read_binary(const char *str, int digits, sqfs_u64 *out)
+{
+ sqfs_u64 x, ov, result = 0;
+ bool first = true;
+
+ while (digits > 0) {
+ x = *((const unsigned char *)str++);
+ --digits;
+
+ if (first) {
+ first = false;
+ if (x == 0xFF) {
+ result = 0xFFFFFFFFFFFFFFFFUL;
+ } else {
+ x &= 0x7F;
+ result = 0;
+ if (digits > 7 && x != 0)
+ goto fail_ov;
+ }
+ }
+
+ ov = (result >> 56) & 0xFF;
+
+ if (ov != 0 && ov != 0xFF)
+ goto fail_ov;
+
+ result = (result << 8) | x;
+ }
+
+ *out = result;
+ return 0;
+fail_ov:
+ fputs("numeric overflow parsing tar header\n", stderr);
+ return -1;
+}
+
+int read_number(const char *str, int digits, sqfs_u64 *out)
+{
+ if (*((const unsigned char *)str) & 0x80)
+ return read_binary(str, digits, out);
+
+ return read_octal(str, digits, out);
+}
diff --git a/lib/tar/src/padd_file.c b/lib/tar/src/padd_file.c
new file mode 100644
index 0000000..053ff1e
--- /dev/null
+++ b/lib/tar/src/padd_file.c
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * padd_file.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "config.h"
+#include "tar/tar.h"
+#include "tar/format.h"
+
+int padd_file(ostream_t *fp, sqfs_u64 size)
+{
+ size_t padd_sz = size % TAR_RECORD_SIZE;
+
+ if (padd_sz == 0)
+ return 0;
+
+ return ostream_append_sparse(fp, TAR_RECORD_SIZE - padd_sz);
+}
diff --git a/lib/tar/src/pax_header.c b/lib/tar/src/pax_header.c
new file mode 100644
index 0000000..b61aab6
--- /dev/null
+++ b/lib/tar/src/pax_header.c
@@ -0,0 +1,402 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * pax_header.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "config.h"
+
+#include "internal.h"
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+
+static int pax_read_decimal(const char *str, sqfs_u64 *out)
+{
+ sqfs_u64 result = 0;
+
+ while (*str >= '0' && *str <= '9') {
+ if (result > 0xFFFFFFFFFFFFFFFFUL / 10) {
+ fputs("numeric overflow parsing pax header\n", stderr);
+ return -1;
+ }
+
+ result = (result * 10) + (*(str++) - '0');
+ }
+
+ *out = result;
+ return 0;
+}
+
+static void urldecode(char *str)
+{
+ unsigned char *out = (unsigned char *)str;
+ char *in = str;
+
+ while (*in != '\0') {
+ sqfs_u8 x = *(in++);
+
+ if (x == '%' && isxdigit(in[0]) && isxdigit(in[1])) {
+ hex_decode(in, 2, &x, 1);
+ in += 2;
+ }
+
+ *(out++) = x;
+ }
+
+ *out = '\0';
+}
+
+static int pax_uid(tar_header_decoded_t *out, sqfs_u64 id)
+{
+ out->uid = id;
+ return 0;
+}
+
+static int pax_gid(tar_header_decoded_t *out, sqfs_u64 id)
+{
+ out->gid = id;
+ return 0;
+}
+
+static int pax_size(tar_header_decoded_t *out, sqfs_u64 size)
+{
+ out->record_size = size;
+ return 0;
+}
+
+static int pax_mtime(tar_header_decoded_t *out, sqfs_s64 mtime)
+{
+ out->mtime = mtime;
+ return 0;
+}
+
+static int pax_rsize(tar_header_decoded_t *out, sqfs_u64 size)
+{
+ out->actual_size = size;
+ return 0;
+}
+
+static int pax_path(tar_header_decoded_t *out, char *path)
+{
+ free(out->name);
+ out->name = path;
+ return 0;
+}
+
+static int pax_slink(tar_header_decoded_t *out, char *path)
+{
+ free(out->link_target);
+ out->link_target = path;
+ return 0;
+}
+
+static int pax_sparse_map(tar_header_decoded_t *out, const char *line)
+{
+ sparse_map_t *last = NULL, *list = NULL, *ent = NULL;
+
+ free_sparse_list(out->sparse);
+ out->sparse = NULL;
+
+ do {
+ ent = calloc(1, sizeof(*ent));
+ if (ent == NULL)
+ goto fail_errno;
+
+ if (pax_read_decimal(line, &ent->offset))
+ goto fail_format;
+
+ while (isdigit(*line))
+ ++line;
+
+ if (*(line++) != ',')
+ goto fail_format;
+
+ if (pax_read_decimal(line, &ent->count))
+ goto fail_format;
+
+ while (isdigit(*line))
+ ++line;
+
+ if (last == NULL) {
+ list = last = ent;
+ } else {
+ last->next = ent;
+ last = ent;
+ }
+ } while (*(line++) == ',');
+
+ out->sparse = list;
+ return 0;
+fail_errno:
+ perror("parsing GNU pax sparse file record");
+ goto fail;
+fail_format:
+ fputs("malformed GNU pax sparse file record\n", stderr);
+ goto fail;
+fail:
+ free_sparse_list(list);
+ free(ent);
+ return -1;
+}
+
+static int pax_xattr_schily(tar_header_decoded_t *out,
+ tar_xattr_t *xattr)
+{
+ xattr->next = out->xattr;
+ out->xattr = xattr;
+ return 0;
+}
+
+static int pax_xattr_libarchive(tar_header_decoded_t *out,
+ tar_xattr_t *xattr)
+{
+ int ret;
+
+ ret = base64_decode((const char *)xattr->value, xattr->value_len,
+ xattr->value, &xattr->value_len);
+ if (ret)
+ return -1;
+
+ urldecode(xattr->key);
+
+ xattr->value[xattr->value_len] = '\0';
+ xattr->next = out->xattr;
+ out->xattr = xattr;
+ return 0;
+}
+
+enum {
+ PAX_TYPE_SINT = 0,
+ PAX_TYPE_UINT,
+ PAX_TYPE_STRING,
+ PAX_TYPE_CONST_STRING,
+ PAX_TYPE_PREFIXED_XATTR,
+ PAX_TYPE_IGNORE,
+};
+
+static const struct pax_handler_t {
+ const char *name;
+ int flag;
+ int type;
+ union {
+ int (*sint)(tar_header_decoded_t *out, sqfs_s64 sval);
+ int (*uint)(tar_header_decoded_t *out, sqfs_u64 uval);
+ int (*str)(tar_header_decoded_t *out, char *str);
+ int (*cstr)(tar_header_decoded_t *out, const char *str);
+ int (*xattr)(tar_header_decoded_t *out, tar_xattr_t *xattr);
+ } cb;
+} pax_fields[] = {
+ { "uid", PAX_UID, PAX_TYPE_UINT, { .uint = pax_uid } },
+ { "gid", PAX_GID, PAX_TYPE_UINT, { .uint = pax_gid } },
+ { "path", PAX_NAME, PAX_TYPE_STRING, { .str = pax_path } },
+ { "size", PAX_SIZE, PAX_TYPE_UINT, { .uint = pax_size } },
+ { "linkpath", PAX_SLINK_TARGET, PAX_TYPE_STRING, { .str = pax_slink } },
+ { "mtime", PAX_MTIME, PAX_TYPE_SINT, { .sint = pax_mtime } },
+ { "GNU.sparse.name", PAX_NAME, PAX_TYPE_STRING, { .str = pax_path } },
+ { "GNU.sparse.size", PAX_SPARSE_SIZE, PAX_TYPE_UINT,
+ {.uint = pax_rsize} },
+ { "GNU.sparse.realsize", PAX_SPARSE_SIZE, PAX_TYPE_UINT,
+ {.uint = pax_rsize} },
+ { "GNU.sparse.major", PAX_SPARSE_GNU_1_X, PAX_TYPE_IGNORE,
+ { .str = NULL } },
+ { "GNU.sparse.minor", PAX_SPARSE_GNU_1_X, PAX_TYPE_IGNORE,
+ { .str = NULL }},
+ { "SCHILY.xattr", 0, PAX_TYPE_PREFIXED_XATTR,
+ { .xattr = pax_xattr_schily } },
+ { "LIBARCHIVE.xattr", 0, PAX_TYPE_PREFIXED_XATTR,
+ { .xattr = pax_xattr_libarchive } },
+ { "GNU.sparse.map", 0, PAX_TYPE_CONST_STRING,
+ { .cstr = pax_sparse_map } },
+};
+
+static const struct pax_handler_t *find_handler(const char *key)
+{
+ size_t i, fieldlen;
+
+ for (i = 0; i < sizeof(pax_fields) / sizeof(pax_fields[0]); ++i) {
+ if (pax_fields[i].type == PAX_TYPE_PREFIXED_XATTR) {
+ fieldlen = strlen(pax_fields[i].name);
+
+ if (strncmp(key, pax_fields[i].name, fieldlen))
+ continue;
+
+ if (key[fieldlen] != '.')
+ continue;
+
+ return pax_fields + i;
+ }
+
+ if (!strcmp(key, pax_fields[i].name))
+ return pax_fields + i;
+ }
+
+ return NULL;
+}
+
+static tar_xattr_t *mkxattr(const char *key,
+ const char *value, size_t valuelen)
+{
+ size_t keylen = strlen(key);
+ tar_xattr_t *xattr;
+
+ xattr = calloc(1, sizeof(*xattr) + keylen + 1 + valuelen + 1);
+ if (xattr == NULL)
+ return NULL;
+
+ xattr->key = xattr->data;
+ memcpy(xattr->key, key, keylen);
+ xattr->key[keylen] = '\0';
+
+ xattr->value = (sqfs_u8 *)xattr->key + keylen + 1;
+ memcpy(xattr->value, value, valuelen);
+ xattr->value[valuelen] = '\0';
+
+ xattr->value_len = valuelen;
+ return xattr;
+}
+
+static int apply_handler(tar_header_decoded_t *out,
+ const struct pax_handler_t *field, const char *key,
+ const char *value, size_t valuelen)
+{
+ tar_xattr_t *xattr;
+ sqfs_s64 s64val;
+ sqfs_u64 uval;
+ char *copy;
+
+ switch (field->type) {
+ case PAX_TYPE_SINT:
+ if (value[0] == '-') {
+ if (pax_read_decimal(value + 1, &uval))
+ return -1;
+ s64val = -((sqfs_s64)uval);
+ } else {
+ if (pax_read_decimal(value, &uval))
+ return -1;
+ s64val = (sqfs_s64)uval;
+ }
+ return field->cb.sint(out, s64val);
+ case PAX_TYPE_UINT:
+ if (pax_read_decimal(value, &uval))
+ return -1;
+ return field->cb.uint(out, uval);
+ case PAX_TYPE_CONST_STRING:
+ return field->cb.cstr(out, value);
+ case PAX_TYPE_STRING:
+ copy = strdup(value);
+ if (copy == NULL) {
+ perror("processing pax header");
+ return -1;
+ }
+ if (field->cb.str(out, copy)) {
+ free(copy);
+ return -1;
+ }
+ break;
+ case PAX_TYPE_PREFIXED_XATTR:
+ xattr = mkxattr(key + strlen(field->name) + 1,
+ value, valuelen);
+ if (xattr == NULL) {
+ perror("reading pax xattr field");
+ return -1;
+ }
+ if (field->cb.xattr(out, xattr)) {
+ free(xattr);
+ return -1;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+int read_pax_header(istream_t *fp, sqfs_u64 entsize, unsigned int *set_by_pax,
+ tar_header_decoded_t *out)
+{
+ char *buffer, *line, *key, *ptr, *value, *end;
+ sparse_map_t *sparse_last = NULL, *sparse;
+ sqfs_u64 offset = 0, num_bytes = 0;
+ const struct pax_handler_t *field;
+ long len;
+
+ buffer = record_to_memory(fp, entsize);
+ if (buffer == NULL)
+ return -1;
+
+ end = buffer + entsize;
+
+ for (line = buffer; line < end; line += len) {
+ len = strtol(line, &ptr, 10);
+ if (ptr == line || !isspace(*ptr) || len <= 0)
+ goto fail_malformed;
+
+ if (len > (end - line))
+ goto fail_ov;
+
+ line[len - 1] = '\0';
+
+ while (ptr < end && isspace(*ptr))
+ ++ptr;
+
+ if (ptr >= end || (ptr - line) >= len)
+ goto fail_malformed;
+
+ key = ptr;
+
+ while (*ptr != '\0' && *ptr != '=')
+ ++ptr;
+
+ if (ptr == key || *ptr != '=')
+ goto fail_malformed;
+
+ *(ptr++) = '\0';
+ value = ptr;
+
+ field = find_handler(key);
+
+ if (field != NULL) {
+ if (apply_handler(out, field, key, value,
+ len - (value - line) - 1)) {
+ goto fail;
+ }
+
+ *set_by_pax |= field->flag;
+ } else if (!strcmp(key, "GNU.sparse.offset")) {
+ if (pax_read_decimal(value, &offset))
+ goto fail;
+ } else if (!strcmp(key, "GNU.sparse.numbytes")) {
+ if (pax_read_decimal(value, &num_bytes))
+ goto fail;
+ sparse = calloc(1, sizeof(*sparse));
+ if (sparse == NULL)
+ goto fail_errno;
+ sparse->offset = offset;
+ sparse->count = num_bytes;
+ if (sparse_last == NULL) {
+ free_sparse_list(out->sparse);
+ out->sparse = sparse_last = sparse;
+ } else {
+ sparse_last->next = sparse;
+ sparse_last = sparse;
+ }
+ }
+ }
+
+ free(buffer);
+ return 0;
+fail_malformed:
+ fputs("Found a malformed PAX header.\n", stderr);
+ goto fail;
+fail_ov:
+ fputs("Numeric overflow in PAX header.\n", stderr);
+ goto fail;
+fail_errno:
+ perror("reading pax header");
+ goto fail;
+fail:
+ free(buffer);
+ return -1;
+}
diff --git a/lib/tar/src/read_header.c b/lib/tar/src/read_header.c
new file mode 100644
index 0000000..ea4873b
--- /dev/null
+++ b/lib/tar/src/read_header.c
@@ -0,0 +1,304 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * read_header.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "config.h"
+
+#include "internal.h"
+#include <string.h>
+#include <stdlib.h>
+
+static bool is_zero_block(const tar_header_t *hdr)
+{
+ const unsigned char *ptr = (const unsigned char *)hdr;
+
+ return ptr[0] == '\0' && memcmp(ptr, ptr + 1, sizeof(*hdr) - 1) == 0;
+}
+
+static int check_version(const tar_header_t *hdr)
+{
+ char buffer[sizeof(hdr->magic) + sizeof(hdr->version)];
+
+ memset(buffer, '\0', sizeof(buffer));
+ if (memcmp(hdr->magic, buffer, sizeof(hdr->magic)) == 0 &&
+ memcmp(hdr->version, buffer, sizeof(hdr->version)) == 0)
+ return ETV_V7_UNIX;
+
+ if (memcmp(hdr->magic, TAR_MAGIC, sizeof(hdr->magic)) == 0 &&
+ memcmp(hdr->version, TAR_VERSION, sizeof(hdr->version)) == 0)
+ return ETV_POSIX;
+
+ if (memcmp(hdr->magic, TAR_MAGIC_OLD, sizeof(hdr->magic)) == 0 &&
+ memcmp(hdr->version, TAR_VERSION_OLD, sizeof(hdr->version)) == 0)
+ return ETV_PRE_POSIX;
+
+ return ETV_UNKNOWN;
+}
+
+static int decode_header(const tar_header_t *hdr, unsigned int set_by_pax,
+ tar_header_decoded_t *out, int version)
+{
+ size_t len1, len2;
+ sqfs_u64 field;
+
+ if (!(set_by_pax & PAX_NAME)) {
+ if (hdr->tail.posix.prefix[0] != '\0' &&
+ version == ETV_POSIX) {
+ len1 = strnlen(hdr->name, sizeof(hdr->name));
+ len2 = strnlen(hdr->tail.posix.prefix,
+ sizeof(hdr->tail.posix.prefix));
+
+ out->name = malloc(len1 + 1 + len2 + 1);
+
+ if (out->name != NULL) {
+ memcpy(out->name, hdr->tail.posix.prefix, len2);
+ out->name[len2] = '/';
+ memcpy(out->name + len2 + 1, hdr->name, len1);
+ out->name[len1 + 1 + len2] = '\0';
+ }
+ } else {
+ out->name = strndup(hdr->name, sizeof(hdr->name));
+ }
+
+ if (out->name == NULL) {
+ perror("decoding filename");
+ return -1;
+ }
+ }
+
+ if (!(set_by_pax & PAX_SIZE)) {
+ if (read_number(hdr->size, sizeof(hdr->size), &out->record_size))
+ return -1;
+ }
+
+ if (!(set_by_pax & PAX_UID)) {
+ if (read_number(hdr->uid, sizeof(hdr->uid), &field))
+ return -1;
+ out->uid = field;
+ }
+
+ if (!(set_by_pax & PAX_GID)) {
+ if (read_number(hdr->gid, sizeof(hdr->gid), &field))
+ return -1;
+ out->gid = field;
+ }
+
+ if (!(set_by_pax & PAX_DEV_MAJ)) {
+ if (read_number(hdr->devmajor, sizeof(hdr->devmajor), &field))
+ return -1;
+
+ out->devno = makedev(field, minor(out->devno));
+ }
+
+ if (!(set_by_pax & PAX_DEV_MIN)) {
+ if (read_number(hdr->devminor, sizeof(hdr->devminor), &field))
+ return -1;
+
+ out->devno = makedev(major(out->devno), field);
+ }
+
+ if (!(set_by_pax & PAX_MTIME)) {
+ if (read_number(hdr->mtime, sizeof(hdr->mtime), &field))
+ return -1;
+ if (field & 0x8000000000000000UL) {
+ field = ~field + 1;
+ out->mtime = -((sqfs_s64)field);
+ } else {
+ out->mtime = field;
+ }
+ }
+
+ if (read_octal(hdr->mode, sizeof(hdr->mode), &field))
+ return -1;
+
+ out->mode = field & 07777;
+
+ if (hdr->typeflag == TAR_TYPE_LINK ||
+ hdr->typeflag == TAR_TYPE_SLINK) {
+ if (!(set_by_pax & PAX_SLINK_TARGET)) {
+ out->link_target = strndup(hdr->linkname,
+ sizeof(hdr->linkname));
+ if (out->link_target == NULL) {
+ perror("decoding symlink target");
+ return -1;
+ }
+ }
+ }
+
+ out->unknown_record = false;
+
+ switch (hdr->typeflag) {
+ case '\0':
+ case TAR_TYPE_FILE:
+ case TAR_TYPE_GNU_SPARSE:
+ out->mode |= S_IFREG;
+ break;
+ case TAR_TYPE_LINK:
+ out->is_hard_link = true;
+ break;
+ case TAR_TYPE_SLINK:
+ out->mode = S_IFLNK | 0777;
+ break;
+ case TAR_TYPE_CHARDEV:
+ out->mode |= S_IFCHR;
+ break;
+ case TAR_TYPE_BLOCKDEV:
+ out->mode |= S_IFBLK;
+ break;
+ case TAR_TYPE_DIR:
+ out->mode |= S_IFDIR;
+ break;
+ case TAR_TYPE_FIFO:
+ out->mode |= S_IFIFO;
+ break;
+ default:
+ out->unknown_record = true;
+ break;
+ }
+
+ return 0;
+}
+
+int read_header(istream_t *fp, tar_header_decoded_t *out)
+{
+ unsigned int set_by_pax = 0;
+ bool prev_was_zero = false;
+ sqfs_u64 pax_size;
+ tar_header_t hdr;
+ int version, ret;
+
+ memset(out, 0, sizeof(*out));
+
+ for (;;) {
+ ret = istream_read(fp, &hdr, sizeof(hdr));
+ if (ret < 0)
+ goto fail;
+
+ if ((size_t)ret < sizeof(hdr))
+ goto out_eof;
+
+ if (is_zero_block(&hdr)) {
+ if (prev_was_zero)
+ goto out_eof;
+ prev_was_zero = true;
+ continue;
+ }
+
+ prev_was_zero = false;
+ version = check_version(&hdr);
+
+ if (version == ETV_UNKNOWN)
+ goto fail_magic;
+
+ if (!is_checksum_valid(&hdr))
+ goto fail_chksum;
+
+ switch (hdr.typeflag) {
+ case TAR_TYPE_GNU_SLINK:
+ if (read_number(hdr.size, sizeof(hdr.size), &pax_size))
+ goto fail;
+ if (pax_size < 1 || pax_size > TAR_MAX_SYMLINK_LEN)
+ goto fail_slink_len;
+ free(out->link_target);
+ out->link_target = record_to_memory(fp, pax_size);
+ if (out->link_target == NULL)
+ goto fail;
+ set_by_pax |= PAX_SLINK_TARGET;
+ continue;
+ case TAR_TYPE_GNU_PATH:
+ if (read_number(hdr.size, sizeof(hdr.size), &pax_size))
+ goto fail;
+ if (pax_size < 1 || pax_size > TAR_MAX_PATH_LEN)
+ goto fail_path_len;
+ free(out->name);
+ out->name = record_to_memory(fp, pax_size);
+ if (out->name == NULL)
+ goto fail;
+ set_by_pax |= PAX_NAME;
+ continue;
+ case TAR_TYPE_PAX_GLOBAL:
+ if (read_number(hdr.size, sizeof(hdr.size), &pax_size))
+ goto fail;
+ skip_entry(fp, pax_size);
+ continue;
+ case TAR_TYPE_PAX:
+ clear_header(out);
+ if (read_number(hdr.size, sizeof(hdr.size), &pax_size))
+ goto fail;
+ if (pax_size < 1 || pax_size > TAR_MAX_PAX_LEN)
+ goto fail_pax_len;
+ set_by_pax = 0;
+ if (read_pax_header(fp, pax_size, &set_by_pax, out))
+ goto fail;
+ continue;
+ case TAR_TYPE_GNU_SPARSE:
+ free_sparse_list(out->sparse);
+ out->sparse = read_gnu_old_sparse(fp, &hdr);
+ if (out->sparse == NULL)
+ goto fail;
+ if (read_number(hdr.tail.gnu.realsize,
+ sizeof(hdr.tail.gnu.realsize),
+ &out->actual_size))
+ goto fail;
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+
+ if (decode_header(&hdr, set_by_pax, out, version))
+ goto fail;
+
+ if (set_by_pax & PAX_SPARSE_GNU_1_X) {
+ free_sparse_list(out->sparse);
+ out->sparse = read_gnu_new_sparse(fp, out);
+ if (out->sparse == NULL)
+ goto fail;
+ }
+
+ if (out->sparse == NULL)
+ out->actual_size = out->record_size;
+
+ return 0;
+out_eof:
+ clear_header(out);
+ return 1;
+fail_slink_len:
+ fprintf(stderr, "rejecting GNU symlink header with size %lu\n",
+ (unsigned long)pax_size);
+ goto fail;
+fail_path_len:
+ fprintf(stderr, "rejecting GNU long path header with size %lu\n",
+ (unsigned long)pax_size);
+ goto fail;
+fail_pax_len:
+ fprintf(stderr, "rejecting PAX header with size %lu\n",
+ (unsigned long)pax_size);
+ goto fail;
+fail_magic:
+ fputs("input is not a ustar tar archive!\n", stderr);
+ goto fail;
+fail_chksum:
+ fputs("invalid tar header checksum!\n", stderr);
+ goto fail;
+fail:
+ clear_header(out);
+ return -1;
+}
+
+int skip_padding(istream_t *fp, sqfs_u64 size)
+{
+ size_t tail = size % 512;
+
+ return tail ? istream_skip(fp, 512 - tail) : 0;
+}
+
+int skip_entry(istream_t *fp, sqfs_u64 size)
+{
+ size_t tail = size % 512;
+
+ return istream_skip(fp, tail ? (size + 512 - tail) : size);
+}
diff --git a/lib/tar/src/read_sparse_map_new.c b/lib/tar/src/read_sparse_map_new.c
new file mode 100644
index 0000000..de1b6a4
--- /dev/null
+++ b/lib/tar/src/read_sparse_map_new.c
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * read_sparse_map_new.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "config.h"
+#include "internal.h"
+
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+
+static int decode(const char *str, size_t len, size_t *out)
+{
+ size_t count = 0;
+
+ *out = 0;
+
+ while (count < len && isdigit(*str)) {
+ if (SZ_MUL_OV(*out, 10, out))
+ return -1;
+ if (SZ_ADD_OV(*out, (*(str++) - '0'), out))
+ return -1;
+ ++count;
+ }
+
+ if (count == 0 || count == len)
+ return 0;
+
+ return (*str == '\n') ? ((int)count + 1) : -1;
+}
+
+sparse_map_t *read_gnu_new_sparse(istream_t *fp, tar_header_decoded_t *out)
+{
+ sparse_map_t *last = NULL, *list = NULL, *ent = NULL;
+ size_t i, count, value;
+ char buffer[1024];
+ int diff, ret;
+
+ if (out->record_size < 512)
+ goto fail_format;
+
+ ret = istream_read(fp, buffer, 512);
+ if (ret < 0)
+ goto fail;
+
+ if (ret < 512)
+ goto fail_format;
+
+ diff = decode(buffer, 512, &count);
+ if (diff <= 0)
+ goto fail_format;
+
+ out->record_size -= 512;
+
+ if (count == 0 || count > TAR_MAX_SPARSE_ENT)
+ goto fail_format;
+
+ for (i = 0; i < (count * 2); ++i) {
+ ret = decode(buffer + diff, 512 - diff, &value);
+ if (ret < 0)
+ goto fail_format;
+
+ if (ret > 0) {
+ diff += ret;
+ } else {
+ if (out->record_size < 512)
+ goto fail_format;
+
+ ret = istream_read(fp, buffer + 512, 512);
+ if (ret < 0)
+ goto fail;
+
+ if (ret < 512)
+ goto fail_format;
+
+ ret = decode(buffer + diff, 1024 - diff, &value);
+ if (ret <= 0)
+ goto fail_format;
+
+ memcpy(buffer, buffer + 512, 512);
+ diff = diff + ret - 512;
+ out->record_size -= 512;
+ }
+
+ if ((i & 0x01) == 0) {
+ ent = calloc(1, sizeof(*ent));
+ if (ent == NULL)
+ goto fail_errno;
+
+ if (list == NULL) {
+ list = last = ent;
+ } else {
+ last->next = ent;
+ last = ent;
+ }
+
+ ent->offset = value;
+ } else {
+ ent->count = value;
+ }
+ }
+
+ return list;
+fail_errno:
+ perror("parsing GNU 1.0 style sparse file record");
+ goto fail;
+fail_format:
+ fputs("Malformed GNU 1.0 style sparse file map.\n", stderr);
+ goto fail;
+fail:
+ free_sparse_list(list);
+ return NULL;
+}
diff --git a/lib/tar/src/read_sparse_map_old.c b/lib/tar/src/read_sparse_map_old.c
new file mode 100644
index 0000000..3dd3300
--- /dev/null
+++ b/lib/tar/src/read_sparse_map_old.c
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * read_sparse_map_old.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "config.h"
+#include "internal.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+
+sparse_map_t *read_gnu_old_sparse(istream_t *fp, tar_header_t *hdr)
+{
+ sparse_map_t *list = NULL, *end = NULL, *node;
+ gnu_sparse_t sph;
+ sqfs_u64 off, sz;
+ int i, ret;
+
+ for (i = 0; i < 4; ++i) {
+ if (!isdigit(hdr->tail.gnu.sparse[i].offset[0]))
+ break;
+ if (!isdigit(hdr->tail.gnu.sparse[i].numbytes[0]))
+ break;
+
+ if (read_octal(hdr->tail.gnu.sparse[i].offset,
+ sizeof(hdr->tail.gnu.sparse[i].offset), &off))
+ goto fail;
+ if (read_octal(hdr->tail.gnu.sparse[i].numbytes,
+ sizeof(hdr->tail.gnu.sparse[i].numbytes), &sz))
+ goto fail;
+
+ node = calloc(1, sizeof(*node));
+ if (node == NULL)
+ goto fail_errno;
+
+ node->offset = off;
+ node->count = sz;
+
+ if (list == NULL) {
+ list = end = node;
+ } else {
+ end->next = node;
+ end = node;
+ }
+ }
+
+ if (hdr->tail.gnu.isextended == 0)
+ return list;
+
+ do {
+ ret = istream_read(fp, &sph, sizeof(sph));
+ if (ret < 0)
+ goto fail;
+
+ if ((size_t)ret < sizeof(sph)) {
+ fputs("reading GNU sparse header: "
+ "unexpected end-of-file\n",
+ stderr);
+ goto fail;
+ }
+
+ for (i = 0; i < 21; ++i) {
+ if (!isdigit(sph.sparse[i].offset[0]))
+ break;
+ if (!isdigit(sph.sparse[i].numbytes[0]))
+ break;
+
+ if (read_octal(sph.sparse[i].offset,
+ sizeof(sph.sparse[i].offset), &off))
+ goto fail;
+ if (read_octal(sph.sparse[i].numbytes,
+ sizeof(sph.sparse[i].numbytes), &sz))
+ goto fail;
+
+ node = calloc(1, sizeof(*node));
+ if (node == NULL)
+ goto fail_errno;
+
+ node->offset = off;
+ node->count = sz;
+
+ if (list == NULL) {
+ list = end = node;
+ } else {
+ end->next = node;
+ end = node;
+ }
+ }
+ } while (sph.isextended != 0);
+
+ return list;
+fail_errno:
+ perror("parsing GNU sparse header");
+ goto fail;
+fail:
+ free_sparse_list(list);
+ return NULL;
+}
diff --git a/lib/tar/src/record_to_memory.c b/lib/tar/src/record_to_memory.c
new file mode 100644
index 0000000..ba422de
--- /dev/null
+++ b/lib/tar/src/record_to_memory.c
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * record_to_memory.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "config.h"
+
+#include "tar/tar.h"
+#include "internal.h"
+#include <stdlib.h>
+
+char *record_to_memory(istream_t *fp, size_t size)
+{
+ char *buffer = malloc(size + 1);
+ int ret;
+
+ if (buffer == NULL)
+ goto fail_errno;
+
+ ret = istream_read(fp, buffer, size);
+ if (ret < 0)
+ goto fail;
+
+ if ((size_t)ret < size) {
+ fputs("Reading tar record: unexpected end-of-file.\n", stderr);
+ goto fail;
+ }
+
+ if (skip_padding(fp, size))
+ goto fail;
+
+ buffer[size] = '\0';
+ return buffer;
+fail_errno:
+ perror("reading tar record");
+ goto fail;
+fail:
+ free(buffer);
+ return NULL;
+}
diff --git a/lib/tar/src/write_header.c b/lib/tar/src/write_header.c
new file mode 100644
index 0000000..b0711b3
--- /dev/null
+++ b/lib/tar/src/write_header.c
@@ -0,0 +1,282 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * write_header.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "config.h"
+
+#include "internal.h"
+#include <string.h>
+
+static void write_binary(char *dst, sqfs_u64 value, int digits)
+{
+ memset(dst, 0, digits);
+
+ while (digits > 0) {
+ ((unsigned char *)dst)[digits - 1] = value & 0xFF;
+ --digits;
+ value >>= 8;
+ }
+
+ ((unsigned char *)dst)[0] |= 0x80;
+}
+
+static void write_number(char *dst, sqfs_u64 value, int digits)
+{
+ sqfs_u64 mask = 0;
+ char buffer[64];
+ int i;
+
+ for (i = 0; i < (digits - 1); ++i)
+ mask = (mask << 3) | 7;
+
+ if (value <= mask) {
+ sprintf(buffer, "%0*lo ", digits - 1, (unsigned long)value);
+ memcpy(dst, buffer, digits);
+ } else if (value <= ((mask << 3) | 7)) {
+ sprintf(buffer, "%0*lo", digits, (unsigned long)value);
+ memcpy(dst, buffer, digits);
+ } else {
+ write_binary(dst, value, digits);
+ }
+}
+
+static void write_number_signed(char *dst, sqfs_s64 value, int digits)
+{
+ sqfs_u64 neg;
+
+ if (value < 0) {
+ neg = -value;
+ write_binary(dst, ~neg + 1, digits);
+ } else {
+ write_number(dst, value, digits);
+ }
+}
+
+static int write_header(ostream_t *fp, const struct stat *sb, const char *name,
+ const char *slink_target, int type)
+{
+ int maj = 0, min = 0;
+ sqfs_u64 size = 0;
+ tar_header_t hdr;
+
+ 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));
+
+ strncpy(hdr.name, name, sizeof(hdr.name) - 1);
+ 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));
+
+ update_checksum(&hdr);
+
+ return ostream_append(fp, &hdr, sizeof(hdr));
+}
+
+static int write_gnu_header(ostream_t *fp, const struct stat *orig,
+ const char *payload, size_t payload_len,
+ int type, const char *name)
+{
+ struct stat sb;
+
+ sb = *orig;
+ sb.st_mode = S_IFREG | 0644;
+ sb.st_size = payload_len;
+
+ if (write_header(fp, &sb, name, NULL, type))
+ return -1;
+
+ if (ostream_append(fp, payload, payload_len))
+ return -1;
+
+ return padd_file(fp, payload_len);
+}
+
+static size_t num_digits(size_t num)
+{
+ size_t i = 1;
+
+ while (num >= 10) {
+ num /= 10;
+ ++i;
+ }
+
+ return i;
+}
+
+static size_t prefix_digit_len(size_t len)
+{
+ size_t old_ndigit, ndigit = 0;
+
+ do {
+ old_ndigit = ndigit;
+ ndigit = num_digits(len + ndigit);
+ } while (old_ndigit != ndigit);
+
+ return ndigit;
+}
+
+static int write_schily_xattr(ostream_t *fp, const struct stat *orig,
+ const char *name, const tar_xattr_t *xattr)
+{
+ static const char *prefix = "SCHILY.xattr.";
+ size_t len, total_size = 0;
+ const tar_xattr_t *it;
+ struct stat sb;
+
+ for (it = xattr; it != NULL; it = it->next) {
+ len = strlen(prefix) + strlen(it->key) + it->value_len + 3;
+
+ total_size += len + prefix_digit_len(len);
+ }
+
+ sb = *orig;
+ sb.st_mode = S_IFREG | 0644;
+ sb.st_size = total_size;
+
+ if (write_header(fp, &sb, name, NULL, TAR_TYPE_PAX))
+ return -1;
+
+ for (it = xattr; it != NULL; it = it->next) {
+ len = strlen(prefix) + strlen(it->key) + it->value_len + 3;
+ len += prefix_digit_len(len);
+
+ if (ostream_printf(fp, PRI_SZ " %s%s=",
+ len, prefix, it->key) < 0) {
+ return -1;
+ }
+ if (ostream_append(fp, it->value, it->value_len))
+ return -1;
+ if (ostream_append(fp, "\n", 1))
+ return -1;
+ }
+
+ return padd_file(fp, total_size);
+}
+
+int write_tar_header(ostream_t *fp, const struct stat *sb, const char *name,
+ const char *slink_target, const tar_xattr_t *xattr,
+ unsigned int counter)
+{
+ const char *reason;
+ char buffer[64];
+ int type;
+
+ if (xattr != NULL) {
+ sprintf(buffer, "pax/xattr%u", counter);
+
+ if (write_schily_xattr(fp, sb, buffer, xattr))
+ return -1;
+ }
+
+ 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(fp, 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);
+
+ if (write_gnu_header(fp, sb, name, strlen(name),
+ TAR_TYPE_GNU_PATH, buffer)) {
+ return -1;
+ }
+
+ sprintf(buffer, "gnu/data%u", counter);
+ name = buffer;
+ }
+
+ switch (sb->st_mode & S_IFMT) {
+ 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;
+ default:
+ reason = "unknown type";
+ goto out_skip;
+ }
+
+ return write_header(fp, sb, name, slink_target, type);
+out_skip:
+ fprintf(stderr, "WARNING: %s: %s\n", name, reason);
+ return 1;
+}
+
+int write_hard_link(ostream_t *fp, const struct stat *sb, const char *name,
+ const char *target, unsigned int counter)
+{
+ tar_header_t hdr;
+ char buffer[64];
+ size_t len;
+
+ memset(&hdr, 0, sizeof(hdr));
+
+ len = strlen(target);
+ if (len >= 100) {
+ sprintf(buffer, "gnu/target%u", counter);
+ if (write_gnu_header(fp, sb, target, len,
+ TAR_TYPE_GNU_SLINK, buffer))
+ return -1;
+ sprintf(hdr.linkname, "hardlink_%u", counter);
+ } else {
+ memcpy(hdr.linkname, target, len);
+ }
+
+ len = strlen(name);
+ if (len >= 100) {
+ sprintf(buffer, "gnu/name%u", counter);
+ if (write_gnu_header(fp, sb, name, len,
+ TAR_TYPE_GNU_PATH, buffer)) {
+ return -1;
+ }
+ sprintf(hdr.name, "gnu/data%u", counter);
+ } else {
+ memcpy(hdr.name, name, len);
+ }
+
+ 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, 0, sizeof(hdr.size));
+ write_number_signed(hdr.mtime, sb->st_mtime, sizeof(hdr.mtime));
+ hdr.typeflag = TAR_TYPE_LINK;
+ 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, 0, sizeof(hdr.devmajor));
+ write_number(hdr.devminor, 0, sizeof(hdr.devminor));
+
+ update_checksum(&hdr);
+ return ostream_append(fp, &hdr, sizeof(hdr));
+}