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