diff options
author | David Oberhollenzer <david.oberhollenzer@sigma-star.at> | 2020-09-02 11:19:11 +0200 |
---|---|---|
committer | David Oberhollenzer <david.oberhollenzer@sigma-star.at> | 2020-09-02 11:19:11 +0200 |
commit | 84ff0984bb5b471d0b4cbc0b0fca156c29273bf5 (patch) | |
tree | e6c4d1e394bfef5924c8b59fe8fad8691c9738bc /lib | |
parent | ba5e71a40af4bcc0f2427dc0b4575802da09af56 (diff) |
Fix nonexistant gnu tar sparse format 1.0 support
Contrary to previous claims, support for the GNU tar sparse format 1.0
was missing entirely (the newest of their 3 different sparse mapping
formats). This oversight wasn't caught, because the unit test was
compiling the wrong source file and tar2sqfs had no problem processing
the test file because it is still a valid POSIX-ish tar archive (but
the sparse part was missing and the mapping embedded in the file).
Signed-off-by: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/tar/Makemodule.am | 1 | ||||
-rw-r--r-- | lib/tar/internal.h | 4 | ||||
-rw-r--r-- | lib/tar/pax_header.c | 7 | ||||
-rw-r--r-- | lib/tar/read_header.c | 7 | ||||
-rw-r--r-- | lib/tar/read_sparse_map_new.c | 96 |
5 files changed, 115 insertions, 0 deletions
diff --git a/lib/tar/Makemodule.am b/lib/tar/Makemodule.am index a47b07a..fe18895 100644 --- a/lib/tar/Makemodule.am +++ b/lib/tar/Makemodule.am @@ -4,6 +4,7 @@ libtar_a_SOURCES += lib/tar/read_sparse_map.c lib/tar/read_sparse_map_old.c libtar_a_SOURCES += lib/tar/base64.c lib/tar/urldecode.c lib/tar/internal.h libtar_a_SOURCES += lib/tar/padd_file.c lib/tar/read_retry.c include/tar.h libtar_a_SOURCES += lib/tar/write_retry.c lib/tar/pax_header.c +libtar_a_SOURCES += lib/tar/read_sparse_map_new.c libtar_a_CFLAGS = $(AM_CFLAGS) libtar_a_CPPFLAGS = $(AM_CPPFLAGS) diff --git a/lib/tar/internal.h b/lib/tar/internal.h index e79596b..65e5d45 100644 --- a/lib/tar/internal.h +++ b/lib/tar/internal.h @@ -27,6 +27,8 @@ enum { PAX_SLINK_TARGET = 0x040, PAX_MTIME = 0x100, PAX_SPARSE_SIZE = 0x400, + + PAX_SPARSE_GNU_1_X = 0x800, }; enum { @@ -58,6 +60,8 @@ sparse_map_t *read_sparse_map(const char *line); sparse_map_t *read_gnu_old_sparse(FILE *fp, tar_header_t *hdr); +sparse_map_t *read_gnu_new_sparse(FILE *fp, tar_header_decoded_t *out); + void free_sparse_list(sparse_map_t *sparse); size_t base64_decode(sqfs_u8 *out, const char *in, size_t len); diff --git a/lib/tar/pax_header.c b/lib/tar/pax_header.c index 4eeabf5..448976d 100644 --- a/lib/tar/pax_header.c +++ b/lib/tar/pax_header.c @@ -110,6 +110,13 @@ int read_pax_header(FILE *fp, sqfs_u64 entsize, unsigned int *set_by_pax, if (pax_read_decimal(ptr + 16, &out->actual_size)) goto fail; *set_by_pax |= PAX_SPARSE_SIZE; + } else if (!strncmp(ptr, "GNU.sparse.realsize=", 20)) { + if (pax_read_decimal(ptr + 20, &out->actual_size)) + goto fail; + *set_by_pax |= PAX_SPARSE_SIZE; + } else if (!strncmp(ptr, "GNU.sparse.major=", 17) || + !strncmp(ptr, "GNU.sparse.minor=", 17)) { + *set_by_pax |= PAX_SPARSE_GNU_1_X; } else if (!strncmp(ptr, "GNU.sparse.offset=", 18)) { if (pax_read_decimal(ptr + 18, &offset)) goto fail; diff --git a/lib/tar/read_header.c b/lib/tar/read_header.c index e8e49c2..14752ea 100644 --- a/lib/tar/read_header.c +++ b/lib/tar/read_header.c @@ -255,6 +255,13 @@ int read_header(FILE *fp, tar_header_decoded_t *out) 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->sb.st_size = out->actual_size; } else { diff --git a/lib/tar/read_sparse_map_new.c b/lib/tar/read_sparse_map_new.c new file mode 100644 index 0000000..246f8a5 --- /dev/null +++ b/lib/tar/read_sparse_map_new.c @@ -0,0 +1,96 @@ +/* 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" + +static int decode(const char *str, size_t len, size_t *out) +{ + size_t count = 0; + + *out = 0; + + while (count < len && isdigit(*str)) { + if (*out > 0xFFFFFFFFFFFFFFFFUL / 10) + return -1; + *out = (*out) * 10 + (*(str++) - '0'); + ++count; + } + + if (count == 0 || count == len) + return 0; + + return (*str == '\n') ? ((int)count + 1) : -1; +} + +sparse_map_t *read_gnu_new_sparse(FILE *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 (read_retry("reading GNU sparse map", fp, buffer, 512)) + return NULL; + + diff = decode(buffer, 512, &count); + if (diff <= 0) + goto fail_format; + + out->record_size -= 512; + + 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 (read_retry("reading GNU sparse map", fp, + buffer + 512, 512)) { + return NULL; + } + + 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; +} |