From cdccc69c62579b0c13b35fad0728079652b8f3c9 Mon Sep 17 00:00:00 2001
From: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
Date: Tue, 31 Jan 2023 11:21:30 +0100
Subject: Move library source into src sub-directory

Signed-off-by: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
---
 lib/tar/src/checksum.c            |  48 +++++
 lib/tar/src/cleanup.c             |  42 ++++
 lib/tar/src/internal.h            |  46 +++++
 lib/tar/src/number.c              |  79 ++++++++
 lib/tar/src/padd_file.c           |  19 ++
 lib/tar/src/pax_header.c          | 402 ++++++++++++++++++++++++++++++++++++++
 lib/tar/src/read_header.c         | 304 ++++++++++++++++++++++++++++
 lib/tar/src/read_sparse_map_new.c | 115 +++++++++++
 lib/tar/src/read_sparse_map_old.c |  99 ++++++++++
 lib/tar/src/record_to_memory.c    |  41 ++++
 lib/tar/src/write_header.c        | 282 ++++++++++++++++++++++++++
 11 files changed, 1477 insertions(+)
 create mode 100644 lib/tar/src/checksum.c
 create mode 100644 lib/tar/src/cleanup.c
 create mode 100644 lib/tar/src/internal.h
 create mode 100644 lib/tar/src/number.c
 create mode 100644 lib/tar/src/padd_file.c
 create mode 100644 lib/tar/src/pax_header.c
 create mode 100644 lib/tar/src/read_header.c
 create mode 100644 lib/tar/src/read_sparse_map_new.c
 create mode 100644 lib/tar/src/read_sparse_map_old.c
 create mode 100644 lib/tar/src/record_to_memory.c
 create mode 100644 lib/tar/src/write_header.c

(limited to 'lib/tar/src')

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));
+}
-- 
cgit v1.2.3