summaryrefslogtreecommitdiff
path: root/lib/tar
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tar')
-rw-r--r--lib/tar/Makemodule.am1
-rw-r--r--lib/tar/internal.h4
-rw-r--r--lib/tar/pax_header.c7
-rw-r--r--lib/tar/read_header.c7
-rw-r--r--lib/tar/read_sparse_map_new.c96
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;
+}