aboutsummaryrefslogtreecommitdiff
path: root/lib/tar/src/pax_header.c
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/pax_header.c
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/pax_header.c')
-rw-r--r--lib/tar/src/pax_header.c402
1 files changed, 402 insertions, 0 deletions
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;
+}