diff options
-rw-r--r-- | archival/libunarchive/get_header_tar.c | 73 | ||||
-rw-r--r-- | archival/libunarchive/header_verbose_list.c | 4 | ||||
-rw-r--r-- | archival/tar.c | 3 |
3 files changed, 76 insertions, 4 deletions
diff --git a/archival/libunarchive/get_header_tar.c b/archival/libunarchive/get_header_tar.c index bf0f92b25..48fc23d8b 100644 --- a/archival/libunarchive/get_header_tar.c +++ b/archival/libunarchive/get_header_tar.c @@ -14,6 +14,74 @@ #include "libbb.h" #include "unarchive.h" +/* + * GNU tar uses "base-256 encoding" for very large numbers (>8 billion). + * Encoding is binary, with highest bit always set as a marker + * and sign in next-highest bit: + * 80 00 .. 00 - zero + * bf ff .. ff - largest positive number + * ff ff .. ff - minus 1 + * c0 00 .. 00 - smallest negative number + * + * We expect it only in size field, where negative numbers don't make sense. + */ +static off_t getBase256_len12(const char *str) +{ + off_t value; + int len; + + /* if (*str & 0x40) error; - caller prevents this */ + + if (sizeof(off_t) >= 12) { + /* Probably 128-bit (16 byte) off_t. Can be optimized. */ + len = 12; + value = *str++ & 0x3f; + while (--len) + value = (value << 8) + (unsigned char) *str++; + return value; + } + +#ifdef CHECK_FOR_OVERFLOW + /* Can be optimized to eat 32-bit chunks */ + char c = *str++ & 0x3f; + len = 12; + while (1) { + if (c) + bb_error_msg_and_die("overflow in base-256 encoded file size"); + if (--len == sizeof(off_t)) + break; + c = *str++; + } +#else + str += (12 - sizeof(off_t)); +#endif + +/* Now str points to sizeof(off_t) least significant bytes. + * + * Example of tar file with 8914993153 (0x213600001) byte file. + * Field starts at offset 7c: + * 00070 30 30 30 00 30 30 30 30 30 30 30 00 80 00 00 00 |000.0000000.....| + * 00080 00 00 00 02 13 60 00 01 31 31 31 32 30 33 33 36 |.....`..11120336| + * + * str is at offset 80 or 84 now (64-bit or 32-bit off_t). + * We (ab)use the fact that value happens to be aligned, + * and fetch it in one go: + */ + if (sizeof(off_t) == 8) { + value = *(off_t*)str; + value = SWAP_BE64(value); + } else if (sizeof(off_t) == 4) { + value = *(off_t*)str; + value = SWAP_BE32(value); + } else { + value = 0; + len = sizeof(off_t); + while (--len) + value = (value << 8) + (unsigned char) *str++; + } + return value; +} + /* NB: _DESTROYS_ str[len] character! */ static unsigned long long getOctal(char *str, int len) { @@ -234,7 +302,10 @@ char FAST_FUNC get_header_tar(archive_handle_t *archive_handle) file_header->gname = tar.gname[0] ? xstrndup(tar.gname, sizeof(tar.gname)) : NULL; #endif file_header->mtime = GET_OCTAL(tar.mtime); - file_header->size = GET_OCTAL(tar.size); + /* Size field: handle GNU tar's "base256 encoding" */ + file_header->size = (*tar.size & 0xc0) == 0x80 /* positive base256? */ + ? getBase256_len12(tar.size) + : GET_OCTAL(tar.size); file_header->gid = GET_OCTAL(tar.gid); file_header->uid = GET_OCTAL(tar.uid); /* Set bits 0-11 of the files mode */ diff --git a/archival/libunarchive/header_verbose_list.c b/archival/libunarchive/header_verbose_list.c index f059dd981..dc3100361 100644 --- a/archival/libunarchive/header_verbose_list.c +++ b/archival/libunarchive/header_verbose_list.c @@ -24,11 +24,11 @@ void FAST_FUNC header_verbose_list(const file_header_t *file_header) snprintf(gid, sizeof(gid), "%u", (unsigned)file_header->gid); group = gid; } - printf("%s %s/%s %9u %4u-%02u-%02u %02u:%02u:%02u %s", + printf("%s %s/%s %9"OFF_FMT"u %4u-%02u-%02u %02u:%02u:%02u %s", bb_mode_string(file_header->mode), user, group, - (unsigned int) file_header->size, + file_header->size, 1900 + mtime->tm_year, 1 + mtime->tm_mon, mtime->tm_mday, diff --git a/archival/tar.c b/archival/tar.c index deb5c89b0..76f1a6240 100644 --- a/archival/tar.c +++ b/archival/tar.c @@ -357,7 +357,8 @@ static int writeTarHeader(struct TarBallInfo *tbInfo, if (tbInfo->verboseFlag) { FILE *vbFd = stdout; - if (tbInfo->tarFd == STDOUT_FILENO) /* If the archive goes to stdout, verbose to stderr */ + /* If archive goes to stdout, verbose goes to stderr */ + if (tbInfo->tarFd == STDOUT_FILENO) vbFd = stderr; /* GNU "tar cvvf" prints "extended" listing a-la "ls -l" */ /* We don't have such excesses here: for us "v" == "vv" */ |