diff options
author | David Oberhollenzer <david.oberhollenzer@sigma-star.at> | 2023-01-31 11:21:30 +0100 |
---|---|---|
committer | David Oberhollenzer <david.oberhollenzer@sigma-star.at> | 2023-01-31 13:51:49 +0100 |
commit | cdccc69c62579b0c13b35fad0728079652b8f3c9 (patch) | |
tree | 9fa54c710f73c5e08a9c8466e7a712eb63ee07ac /lib/tar/src | |
parent | 2182129c8f359c4fa1390eaba7a65b595ccd4182 (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.c | 48 | ||||
-rw-r--r-- | lib/tar/src/cleanup.c | 42 | ||||
-rw-r--r-- | lib/tar/src/internal.h | 46 | ||||
-rw-r--r-- | lib/tar/src/number.c | 79 | ||||
-rw-r--r-- | lib/tar/src/padd_file.c | 19 | ||||
-rw-r--r-- | lib/tar/src/pax_header.c | 402 | ||||
-rw-r--r-- | lib/tar/src/read_header.c | 304 | ||||
-rw-r--r-- | lib/tar/src/read_sparse_map_new.c | 115 | ||||
-rw-r--r-- | lib/tar/src/read_sparse_map_old.c | 99 | ||||
-rw-r--r-- | lib/tar/src/record_to_memory.c | 41 | ||||
-rw-r--r-- | lib/tar/src/write_header.c | 282 |
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)); +} |