|
|
@@ -129,7 +129,11 @@ struct tar {
|
|
|
int64_t entry_offset;
|
|
|
int64_t entry_padding;
|
|
|
int64_t entry_bytes_unconsumed;
|
|
|
- int64_t realsize;
|
|
|
+ int64_t disk_size;
|
|
|
+ int64_t GNU_sparse_realsize;
|
|
|
+ int64_t GNU_sparse_size;
|
|
|
+ int64_t SCHILY_sparse_realsize;
|
|
|
+ int64_t pax_size;
|
|
|
struct sparse_block *sparse_list;
|
|
|
struct sparse_block *sparse_last;
|
|
|
int64_t sparse_offset;
|
|
|
@@ -138,6 +142,7 @@ struct tar {
|
|
|
int sparse_gnu_minor;
|
|
|
char sparse_gnu_attributes_seen;
|
|
|
char filetype;
|
|
|
+ char size_fields; /* Bits defined below */
|
|
|
|
|
|
struct archive_string localname;
|
|
|
struct archive_string_conv *opt_sconv;
|
|
|
@@ -148,9 +153,15 @@ struct tar {
|
|
|
int compat_2x;
|
|
|
int process_mac_extensions;
|
|
|
int read_concatenated_archives;
|
|
|
- int realsize_override;
|
|
|
};
|
|
|
|
|
|
+/* Track which size fields were present in the headers */
|
|
|
+#define TAR_SIZE_PAX_SIZE 1
|
|
|
+#define TAR_SIZE_GNU_SPARSE_REALSIZE 2
|
|
|
+#define TAR_SIZE_GNU_SPARSE_SIZE 4
|
|
|
+#define TAR_SIZE_SCHILY_SPARSE_REALSIZE 8
|
|
|
+
|
|
|
+
|
|
|
static int archive_block_is_null(const char *p);
|
|
|
static char *base64_decode(const char *, size_t, size_t *);
|
|
|
static int gnu_add_sparse_entry(struct archive_read *, struct tar *,
|
|
|
@@ -529,8 +540,7 @@ archive_read_format_tar_read_header(struct archive_read *a,
|
|
|
tar = (struct tar *)(a->format->data);
|
|
|
tar->entry_offset = 0;
|
|
|
gnu_clear_sparse_list(tar);
|
|
|
- tar->realsize = -1; /* Mark this as "unset" */
|
|
|
- tar->realsize_override = 0;
|
|
|
+ tar->size_fields = 0; /* We don't have any size info yet */
|
|
|
|
|
|
/* Setup default string conversion. */
|
|
|
tar->sconv = tar->opt_sconv;
|
|
|
@@ -622,7 +632,7 @@ archive_read_format_tar_read_data(struct archive_read *a,
|
|
|
tar->entry_padding = 0;
|
|
|
*buff = NULL;
|
|
|
*size = 0;
|
|
|
- *offset = tar->realsize;
|
|
|
+ *offset = tar->disk_size;
|
|
|
return (ARCHIVE_EOF);
|
|
|
}
|
|
|
|
|
|
@@ -750,7 +760,7 @@ tar_read_header(struct archive_read *a, struct tar *tar,
|
|
|
* if there's no regular header, then this is
|
|
|
* a premature EOF. */
|
|
|
archive_set_error(&a->archive, EINVAL,
|
|
|
- "Damaged tar archive");
|
|
|
+ "Damaged tar archive (end-of-archive within a sequence of headers)");
|
|
|
return (ARCHIVE_FATAL);
|
|
|
} else {
|
|
|
return (ARCHIVE_EOF);
|
|
|
@@ -760,7 +770,7 @@ tar_read_header(struct archive_read *a, struct tar *tar,
|
|
|
archive_set_error(&a->archive,
|
|
|
ARCHIVE_ERRNO_FILE_FORMAT,
|
|
|
"Truncated tar archive"
|
|
|
- " detected while reading next heaader");
|
|
|
+ " detected while reading next header");
|
|
|
return (ARCHIVE_FATAL);
|
|
|
}
|
|
|
*unconsumed += 512;
|
|
|
@@ -787,7 +797,8 @@ tar_read_header(struct archive_read *a, struct tar *tar,
|
|
|
/* This is NOT a null block, so it must be a valid header. */
|
|
|
if (!checksum(a, h)) {
|
|
|
tar_flush_unconsumed(a, unconsumed);
|
|
|
- archive_set_error(&a->archive, EINVAL, "Damaged tar archive");
|
|
|
+ archive_set_error(&a->archive, EINVAL,
|
|
|
+ "Damaged tar archive (bad header checksum)");
|
|
|
/* If we've read some critical information (pax headers, etc)
|
|
|
* and _then_ see a bad header, we can't really recover. */
|
|
|
if (eof_fatal) {
|
|
|
@@ -1038,7 +1049,7 @@ header_Solaris_ACL(struct archive_read *a, struct tar *tar,
|
|
|
struct archive_string acl_text;
|
|
|
size_t size;
|
|
|
int err, acl_type;
|
|
|
- int64_t type;
|
|
|
+ uint64_t type;
|
|
|
char *acl, *p;
|
|
|
|
|
|
header = (const struct archive_entry_header_ustar *)h;
|
|
|
@@ -1075,7 +1086,7 @@ header_Solaris_ACL(struct archive_read *a, struct tar *tar,
|
|
|
}
|
|
|
p++;
|
|
|
}
|
|
|
- switch ((int)type & ~0777777) {
|
|
|
+ switch (type & ~0777777) {
|
|
|
case 01000000:
|
|
|
/* POSIX.1e ACL */
|
|
|
acl_type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS;
|
|
|
@@ -1086,8 +1097,8 @@ header_Solaris_ACL(struct archive_read *a, struct tar *tar,
|
|
|
break;
|
|
|
default:
|
|
|
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
|
|
|
- "Malformed Solaris ACL attribute (unsupported type %o)",
|
|
|
- (int)type);
|
|
|
+ "Malformed Solaris ACL attribute (unsupported type %"
|
|
|
+ PRIo64 ")", type);
|
|
|
archive_string_free(&acl_text);
|
|
|
return (ARCHIVE_WARN);
|
|
|
}
|
|
|
@@ -1145,7 +1156,9 @@ header_gnu_longlink(struct archive_read *a, struct tar *tar,
|
|
|
struct archive_string linkpath;
|
|
|
archive_string_init(&linkpath);
|
|
|
err = read_body_to_string(a, tar, &linkpath, h, unconsumed);
|
|
|
- archive_entry_set_link(entry, linkpath.s);
|
|
|
+ if (err == ARCHIVE_OK) {
|
|
|
+ archive_entry_set_link(entry, linkpath.s);
|
|
|
+ }
|
|
|
archive_string_free(&linkpath);
|
|
|
return (err);
|
|
|
}
|
|
|
@@ -1287,6 +1300,11 @@ read_body_to_string(struct archive_read *a, struct tar *tar,
|
|
|
* allows header_old_tar and header_ustar
|
|
|
* to handle filenames differently, while still putting most of the
|
|
|
* common parsing into one place.
|
|
|
+ *
|
|
|
+ * This is called _after_ ustar, GNU tar, Schily, etc, special
|
|
|
+ * fields have already been parsed into the `tar` structure.
|
|
|
+ * So we can make final decisions here about how to reconcile
|
|
|
+ * size, mode, etc, information.
|
|
|
*/
|
|
|
static int
|
|
|
header_common(struct archive_read *a, struct tar *tar,
|
|
|
@@ -1308,34 +1326,73 @@ header_common(struct archive_read *a, struct tar *tar,
|
|
|
archive_entry_set_perm(entry,
|
|
|
(mode_t)tar_atol(header->mode, sizeof(header->mode)));
|
|
|
}
|
|
|
+
|
|
|
+ /* Set uid, gid, mtime if not already set */
|
|
|
if (!archive_entry_uid_is_set(entry)) {
|
|
|
archive_entry_set_uid(entry, tar_atol(header->uid, sizeof(header->uid)));
|
|
|
}
|
|
|
if (!archive_entry_gid_is_set(entry)) {
|
|
|
archive_entry_set_gid(entry, tar_atol(header->gid, sizeof(header->gid)));
|
|
|
}
|
|
|
+ if (!archive_entry_mtime_is_set(entry)) {
|
|
|
+ archive_entry_set_mtime(entry, tar_atol(header->mtime, sizeof(header->mtime)), 0);
|
|
|
+ }
|
|
|
|
|
|
- tar->entry_bytes_remaining = tar_atol(header->size, sizeof(header->size));
|
|
|
+ /* Reconcile the size info. */
|
|
|
+ /* First, how big is the file on disk? */
|
|
|
+ if ((tar->size_fields & TAR_SIZE_GNU_SPARSE_REALSIZE) != 0) {
|
|
|
+ /* GNU sparse format 1.0 uses `GNU.sparse.realsize`
|
|
|
+ * to hold the size of the file on disk. */
|
|
|
+ tar->disk_size = tar->GNU_sparse_realsize;
|
|
|
+ } else if ((tar->size_fields & TAR_SIZE_GNU_SPARSE_SIZE) != 0
|
|
|
+ && (tar->sparse_gnu_major == 0)) {
|
|
|
+ /* GNU sparse format 0.0 and 0.1 use `GNU.sparse.size`
|
|
|
+ * to hold the size of the file on disk. */
|
|
|
+ tar->disk_size = tar->GNU_sparse_size;
|
|
|
+ } else if ((tar->size_fields & TAR_SIZE_SCHILY_SPARSE_REALSIZE) != 0) {
|
|
|
+ tar->disk_size = tar->SCHILY_sparse_realsize;
|
|
|
+ } else if ((tar->size_fields & TAR_SIZE_PAX_SIZE) != 0) {
|
|
|
+ tar->disk_size = tar->pax_size;
|
|
|
+ } else {
|
|
|
+ /* There wasn't a suitable pax header, so use the ustar info */
|
|
|
+ tar->disk_size = tar_atol(header->size, sizeof(header->size));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (tar->disk_size < 0) {
|
|
|
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
|
|
|
+ "Tar entry has negative file size");
|
|
|
+ return (ARCHIVE_FATAL);
|
|
|
+ } else if (tar->disk_size > entry_limit) {
|
|
|
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
|
|
|
+ "Tar entry size overflow");
|
|
|
+ return (ARCHIVE_FATAL);
|
|
|
+ } else {
|
|
|
+ archive_entry_set_size(entry, tar->disk_size);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Second, how big is the data in the archive? */
|
|
|
+ if ((tar->size_fields & TAR_SIZE_GNU_SPARSE_SIZE) != 0
|
|
|
+ && (tar->sparse_gnu_major == 1)) {
|
|
|
+ /* GNU sparse format 1.0 uses `GNU.sparse.size`
|
|
|
+ * to hold the size of the data in the archive. */
|
|
|
+ tar->entry_bytes_remaining = tar->GNU_sparse_size;
|
|
|
+ } else if ((tar->size_fields & TAR_SIZE_PAX_SIZE) != 0) {
|
|
|
+ tar->entry_bytes_remaining = tar->pax_size;
|
|
|
+ } else {
|
|
|
+ tar->entry_bytes_remaining
|
|
|
+ = tar_atol(header->size, sizeof(header->size));
|
|
|
+ }
|
|
|
if (tar->entry_bytes_remaining < 0) {
|
|
|
tar->entry_bytes_remaining = 0;
|
|
|
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
|
|
|
- "Tar entry has negative size");
|
|
|
+ "Tar entry has negative size");
|
|
|
return (ARCHIVE_FATAL);
|
|
|
- }
|
|
|
- if (tar->entry_bytes_remaining > entry_limit) {
|
|
|
+ } else if (tar->entry_bytes_remaining > entry_limit) {
|
|
|
tar->entry_bytes_remaining = 0;
|
|
|
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
|
|
|
- "Tar entry size overflow");
|
|
|
+ "Tar entry size overflow");
|
|
|
return (ARCHIVE_FATAL);
|
|
|
}
|
|
|
- if (!tar->realsize_override) {
|
|
|
- tar->realsize = tar->entry_bytes_remaining;
|
|
|
- }
|
|
|
- archive_entry_set_size(entry, tar->realsize);
|
|
|
-
|
|
|
- if (!archive_entry_mtime_is_set(entry)) {
|
|
|
- archive_entry_set_mtime(entry, tar_atol(header->mtime, sizeof(header->mtime)), 0);
|
|
|
- }
|
|
|
|
|
|
/* Handle the tar type flag appropriately. */
|
|
|
tar->filetype = header->typeflag[0];
|
|
|
@@ -2290,10 +2347,13 @@ pax_attribute(struct archive_read *a, struct tar *tar, struct archive_entry *ent
|
|
|
}
|
|
|
else if (key_length == 4 && memcmp(key, "size", 4) == 0) {
|
|
|
/* GNU.sparse.size */
|
|
|
+ /* This is either the size of stored entry OR the size of data on disk,
|
|
|
+ * depending on which GNU sparse format version is in use.
|
|
|
+ * Since pax attributes can be in any order, we may not actually
|
|
|
+ * know at this point how to interpret this. */
|
|
|
if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) {
|
|
|
- tar->realsize = t;
|
|
|
- archive_entry_set_size(entry, tar->realsize);
|
|
|
- tar->realsize_override = 1;
|
|
|
+ tar->GNU_sparse_size = t;
|
|
|
+ tar->size_fields |= TAR_SIZE_GNU_SPARSE_SIZE;
|
|
|
}
|
|
|
return (err);
|
|
|
}
|
|
|
@@ -2361,11 +2421,10 @@ pax_attribute(struct archive_read *a, struct tar *tar, struct archive_entry *ent
|
|
|
return (err);
|
|
|
}
|
|
|
else if (key_length == 8 && memcmp(key, "realsize", 8) == 0) {
|
|
|
- /* GNU.sparse.realsize */
|
|
|
+ /* GNU.sparse.realsize = size of file on disk */
|
|
|
if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) {
|
|
|
- tar->realsize = t;
|
|
|
- archive_entry_set_size(entry, tar->realsize);
|
|
|
- tar->realsize_override = 1;
|
|
|
+ tar->GNU_sparse_realsize = t;
|
|
|
+ tar->size_fields |= TAR_SIZE_GNU_SPARSE_REALSIZE;
|
|
|
}
|
|
|
return (err);
|
|
|
}
|
|
|
@@ -2546,12 +2605,12 @@ pax_attribute(struct archive_read *a, struct tar *tar, struct archive_entry *ent
|
|
|
}
|
|
|
else if (key_length == 8 && memcmp(key, "realsize", 8) == 0) {
|
|
|
if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) {
|
|
|
- tar->realsize = t;
|
|
|
- tar->realsize_override = 1;
|
|
|
- archive_entry_set_size(entry, tar->realsize);
|
|
|
+ tar->SCHILY_sparse_realsize = t;
|
|
|
+ tar->size_fields |= TAR_SIZE_SCHILY_SPARSE_REALSIZE;
|
|
|
}
|
|
|
return (err);
|
|
|
}
|
|
|
+ /* TODO: Is there a SCHILY.sparse.size similar to GNU.sparse.size ? */
|
|
|
else if (key_length > 6 && memcmp(key, "xattr.", 6) == 0) {
|
|
|
key_length -= 6;
|
|
|
key += 6;
|
|
|
@@ -2718,19 +2777,8 @@ pax_attribute(struct archive_read *a, struct tar *tar, struct archive_entry *ent
|
|
|
if (key_length == 4 && memcmp(key, "size", 4) == 0) {
|
|
|
/* "size" is the size of the data in the entry. */
|
|
|
if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) {
|
|
|
- tar->entry_bytes_remaining = t;
|
|
|
- /*
|
|
|
- * The "size" pax header keyword always overrides the
|
|
|
- * "size" field in the tar header.
|
|
|
- * GNU.sparse.realsize, GNU.sparse.size and
|
|
|
- * SCHILY.realsize override this value.
|
|
|
- */
|
|
|
- if (!tar->realsize_override) {
|
|
|
- archive_entry_set_size(entry,
|
|
|
- tar->entry_bytes_remaining);
|
|
|
- tar->realsize
|
|
|
- = tar->entry_bytes_remaining;
|
|
|
- }
|
|
|
+ tar->pax_size = t;
|
|
|
+ tar->size_fields |= TAR_SIZE_PAX_SIZE;
|
|
|
}
|
|
|
else if (t == INT64_MAX) {
|
|
|
/* Note: pax_attr_read_number returns INT64_MAX on overflow or < 0 */
|
|
|
@@ -2842,11 +2890,6 @@ header_gnutar(struct archive_read *a, struct tar *tar,
|
|
|
* filename is stored as in old-style archives.
|
|
|
*/
|
|
|
|
|
|
- /* Grab fields common to all tar variants. */
|
|
|
- err = header_common(a, tar, entry, h);
|
|
|
- if (err == ARCHIVE_FATAL)
|
|
|
- return (err);
|
|
|
-
|
|
|
/* Copy filename over (to ensure null termination). */
|
|
|
header = (const struct archive_entry_header_gnutar *)h;
|
|
|
const char *existing_pathname = archive_entry_pathname(entry);
|
|
|
@@ -2895,8 +2938,6 @@ header_gnutar(struct archive_read *a, struct tar *tar,
|
|
|
archive_entry_set_rdev(entry, 0);
|
|
|
}
|
|
|
|
|
|
- tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining);
|
|
|
-
|
|
|
/* Grab GNU-specific fields. */
|
|
|
if (!archive_entry_atime_is_set(entry)) {
|
|
|
t = tar_atol(header->atime, sizeof(header->atime));
|
|
|
@@ -2910,10 +2951,10 @@ header_gnutar(struct archive_read *a, struct tar *tar,
|
|
|
}
|
|
|
|
|
|
if (header->realsize[0] != 0) {
|
|
|
- tar->realsize
|
|
|
+ /* Treat as a synonym for the pax GNU.sparse.realsize attr */
|
|
|
+ tar->GNU_sparse_realsize
|
|
|
= tar_atol(header->realsize, sizeof(header->realsize));
|
|
|
- archive_entry_set_size(entry, tar->realsize);
|
|
|
- tar->realsize_override = 1;
|
|
|
+ tar->size_fields |= TAR_SIZE_GNU_SPARSE_REALSIZE;
|
|
|
}
|
|
|
|
|
|
if (header->sparse[0].offset[0] != 0) {
|
|
|
@@ -2926,6 +2967,13 @@ header_gnutar(struct archive_read *a, struct tar *tar,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /* Grab fields common to all tar variants. */
|
|
|
+ err = header_common(a, tar, entry, h);
|
|
|
+ if (err == ARCHIVE_FATAL)
|
|
|
+ return (err);
|
|
|
+
|
|
|
+ tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining);
|
|
|
+
|
|
|
return (err);
|
|
|
}
|
|
|
|
|
|
@@ -3105,8 +3153,7 @@ gnu_sparse_01_parse(struct archive_read *a, struct tar *tar, const char *p, size
|
|
|
* it's not possible to support both variants. This code supports
|
|
|
* the later variant at the expense of not supporting the former.
|
|
|
*
|
|
|
- * This variant also replaced GNU.sparse.size with GNU.sparse.realsize
|
|
|
- * and introduced the GNU.sparse.major/GNU.sparse.minor attributes.
|
|
|
+ * This variant also introduced the GNU.sparse.major/GNU.sparse.minor attributes.
|
|
|
*/
|
|
|
|
|
|
/*
|