d6772501db
(GNU extensions 'K' and 'L'). We correctly handle them when untarring now, but unfortunately we still don't use them when tarring! That stupid 100 char limit is still there! The biggest problem is that we don't support 'pax' tar format. Linux kernel tarballs are in this format... shame
254 lines
7.1 KiB
C
254 lines
7.1 KiB
C
/* vi: set sw=4 ts=4: */
|
|
/* Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
|
|
*
|
|
* FIXME:
|
|
* In privileged mode if uname and gname map to a uid and gid then use the
|
|
* mapped value instead of the uid/gid values in tar header
|
|
*
|
|
* References:
|
|
* GNU tar and star man pages,
|
|
* Opengroup's ustar interchange format,
|
|
* http://www.opengroup.org/onlinepubs/007904975/utilities/pax.html
|
|
*/
|
|
|
|
#include "libbb.h"
|
|
#include "unarchive.h"
|
|
|
|
#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS
|
|
static char *longname;
|
|
static char *linkname;
|
|
#else
|
|
enum {
|
|
longname = 0,
|
|
linkname = 0,
|
|
};
|
|
#endif
|
|
|
|
/* NB: _DESTROYS_ str[len] character! */
|
|
static unsigned long long getOctal(char *str, int len)
|
|
{
|
|
unsigned long long v;
|
|
/* Actually, tar header allows leading spaces also.
|
|
* Oh well, we will be liberal and skip this...
|
|
* The only downside probably is that we allow "-123" too :)
|
|
if (*str < '0' || *str > '7')
|
|
bb_error_msg_and_die("corrupted octal value in tar header");
|
|
*/
|
|
str[len] = '\0';
|
|
v = strtoull(str, &str, 8);
|
|
if (*str)
|
|
bb_error_msg_and_die("corrupted octal value in tar header");
|
|
return v;
|
|
}
|
|
|
|
void BUG_tar_header_size(void);
|
|
char get_header_tar(archive_handle_t *archive_handle)
|
|
{
|
|
static int end = 0;
|
|
|
|
file_header_t *file_header = archive_handle->file_header;
|
|
struct {
|
|
/* ustar header, Posix 1003.1 */
|
|
char name[100]; /* 0-99 */
|
|
char mode[8]; /* 100-107 */
|
|
char uid[8]; /* 108-115 */
|
|
char gid[8]; /* 116-123 */
|
|
char size[12]; /* 124-135 */
|
|
char mtime[12]; /* 136-147 */
|
|
char chksum[8]; /* 148-155 */
|
|
char typeflag; /* 156-156 */
|
|
char linkname[100]; /* 157-256 */
|
|
char magic[6]; /* 257-262 */
|
|
char version[2]; /* 263-264 */
|
|
char uname[32]; /* 265-296 */
|
|
char gname[32]; /* 297-328 */
|
|
char devmajor[8]; /* 329-336 */
|
|
char devminor[8]; /* 337-344 */
|
|
char prefix[155]; /* 345-499 */
|
|
char padding[12]; /* 500-512 */
|
|
} tar;
|
|
char *cp;
|
|
int sum, i;
|
|
#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS
|
|
int parse_names;
|
|
#else
|
|
enum { parse_names = 0 };
|
|
#endif
|
|
|
|
if (sizeof(tar) != 512)
|
|
BUG_tar_header_size();
|
|
|
|
/* Align header */
|
|
data_align(archive_handle, 512);
|
|
|
|
xread(archive_handle->src_fd, &tar, 512);
|
|
archive_handle->offset += 512;
|
|
|
|
/* If there is no filename its an empty header */
|
|
if (tar.name[0] == 0) {
|
|
if (end) {
|
|
/* This is the second consecutive empty header! End of archive!
|
|
* Read until the end to empty the pipe from gz or bz2
|
|
*/
|
|
while (full_read(archive_handle->src_fd, &tar, 512) == 512)
|
|
/* repeat */;
|
|
return EXIT_FAILURE;
|
|
}
|
|
end = 1;
|
|
return EXIT_SUCCESS;
|
|
}
|
|
end = 0;
|
|
|
|
/* Check header has valid magic, "ustar" is for the proper tar
|
|
* 0's are for the old tar format
|
|
*/
|
|
if (strncmp(tar.magic, "ustar", 5) != 0) {
|
|
#if ENABLE_FEATURE_TAR_OLDGNU_COMPATIBILITY
|
|
if (memcmp(tar.magic, "\0\0\0\0", 5) != 0)
|
|
#endif
|
|
bb_error_msg_and_die("invalid tar magic");
|
|
}
|
|
/* Do checksum on headers */
|
|
sum = ' ' * sizeof(tar.chksum);
|
|
for (i = 0; i < 148 ; i++) {
|
|
sum += ((char*)&tar)[i];
|
|
}
|
|
for (i = 156; i < 512 ; i++) {
|
|
sum += ((char*)&tar)[i];
|
|
}
|
|
/* This field does not need special treatment (getOctal) */
|
|
if (sum != xstrtoul(tar.chksum, 8)) {
|
|
bb_error_msg_and_die("invalid tar header checksum");
|
|
}
|
|
|
|
#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS
|
|
parse_names = (tar.typeflag != 'L' && tar.typeflag != 'K');
|
|
#endif
|
|
|
|
/* getOctal trashes subsequent field, therefore we call it
|
|
* on fields in reverse order */
|
|
#define GET_OCTAL(a) getOctal((a), sizeof(a))
|
|
if (tar.devmajor[0]) {
|
|
unsigned minor = GET_OCTAL(tar.devminor);
|
|
unsigned major = GET_OCTAL(tar.devmajor);
|
|
file_header->device = makedev(major, minor);
|
|
}
|
|
file_header->link_name = NULL;
|
|
if (!linkname && parse_names && tar.linkname[0]) {
|
|
/* we trash magic[0] here, it's ok */
|
|
tar.linkname[sizeof(tar.linkname)] = '\0';
|
|
file_header->link_name = xstrdup(tar.linkname);
|
|
/* FIXME: what if we have non-link object with link_name? */
|
|
/* Will link_name be free()ed? */
|
|
}
|
|
file_header->mtime = GET_OCTAL(tar.mtime);
|
|
file_header->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 */
|
|
file_header->mode = 07777 & GET_OCTAL(tar.mode);
|
|
#undef GET_OCTAL
|
|
|
|
if (!longname && parse_names) {
|
|
/* we trash mode[0] here, it's ok */
|
|
tar.name[sizeof(tar.name)] = '\0';
|
|
if (tar.prefix[0])
|
|
file_header->name = concat_path_file(tar.prefix, tar.name);
|
|
else
|
|
file_header->name = xstrdup(tar.name);
|
|
}
|
|
|
|
/* Set bits 12-15 of the files mode */
|
|
/* (typeflag was not trashed because chksum does not use getOctal) */
|
|
switch (tar.typeflag) {
|
|
/* busybox identifies hard links as being regular files with 0 size and a link name */
|
|
case '1':
|
|
file_header->mode |= S_IFREG;
|
|
break;
|
|
case '7':
|
|
/* Reserved for high performance files, treat as normal file */
|
|
case 0:
|
|
case '0':
|
|
#if ENABLE_FEATURE_TAR_OLDGNU_COMPATIBILITY
|
|
if (last_char_is(file_header->name, '/')) {
|
|
file_header->mode |= S_IFDIR;
|
|
} else
|
|
#endif
|
|
file_header->mode |= S_IFREG;
|
|
break;
|
|
case '2':
|
|
file_header->mode |= S_IFLNK;
|
|
break;
|
|
case '3':
|
|
file_header->mode |= S_IFCHR;
|
|
break;
|
|
case '4':
|
|
file_header->mode |= S_IFBLK;
|
|
break;
|
|
case '5':
|
|
file_header->mode |= S_IFDIR;
|
|
break;
|
|
case '6':
|
|
file_header->mode |= S_IFIFO;
|
|
break;
|
|
#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS
|
|
case 'L':
|
|
/* paranoia: tar with several consecutive longnames */
|
|
free(longname);
|
|
longname = xzalloc(file_header->size + 1);
|
|
xread(archive_handle->src_fd, longname, file_header->size);
|
|
archive_handle->offset += file_header->size;
|
|
return get_header_tar(archive_handle);
|
|
case 'K':
|
|
free(linkname);
|
|
linkname = xzalloc(file_header->size + 1);
|
|
xread(archive_handle->src_fd, linkname, file_header->size);
|
|
archive_handle->offset += file_header->size;
|
|
return get_header_tar(archive_handle);
|
|
case 'D': /* GNU dump dir */
|
|
case 'M': /* Continuation of multi volume archive */
|
|
case 'N': /* Old GNU for names > 100 characters */
|
|
case 'S': /* Sparse file */
|
|
case 'V': /* Volume header */
|
|
#endif
|
|
case 'g': /* pax global header */
|
|
case 'x': /* pax extended header */
|
|
bb_error_msg("ignoring extension type %c", tar.typeflag);
|
|
break;
|
|
default:
|
|
bb_error_msg("unknown typeflag: 0x%x", tar.typeflag);
|
|
}
|
|
|
|
#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS
|
|
if (longname) {
|
|
file_header->name = longname;
|
|
longname = NULL;
|
|
}
|
|
if (linkname) {
|
|
file_header->link_name = linkname;
|
|
linkname = NULL;
|
|
}
|
|
#endif
|
|
|
|
/* Strip trailing '/' in directories */
|
|
/* Must be done after mode is set as '/' is used to check if its a directory */
|
|
cp = last_char_is(file_header->name, '/');
|
|
|
|
if (archive_handle->filter(archive_handle) == EXIT_SUCCESS) {
|
|
archive_handle->action_header(archive_handle->file_header);
|
|
/* Note that we kill the '/' only after action_header() */
|
|
/* (like GNU tar 1.15.1: verbose mode outputs "dir/dir/") */
|
|
if (cp) *cp = '\0';
|
|
archive_handle->flags |= ARCHIVE_EXTRACT_QUIET;
|
|
archive_handle->action_data(archive_handle);
|
|
llist_add_to(&(archive_handle->passed), file_header->name);
|
|
} else {
|
|
data_skip(archive_handle);
|
|
}
|
|
archive_handle->offset += file_header->size;
|
|
|
|
free(file_header->link_name);
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|