tar: add support for FEATURE_TAR_GNU_EXTENSIONS so than we can save
long names now. We were able to read such tars, but not create. +275 bytes. Without FEATURE_TAR_GNU_EXTENSIONS: -25 bytes. We still cannot unpack Linux kernels, but not for long ;)
This commit is contained in:
parent
2d27e4ccbb
commit
7725d0ff5c
123
archival/tar.c
123
archival/tar.c
@ -169,6 +169,72 @@ static void putOctal(char *cp, int len, off_t value)
|
|||||||
/* Copy the string to the field */
|
/* Copy the string to the field */
|
||||||
memcpy(cp, tempString, len);
|
memcpy(cp, tempString, len);
|
||||||
}
|
}
|
||||||
|
#define PUT_OCTAL(a, b) putOctal((a), sizeof(a), (b))
|
||||||
|
|
||||||
|
static void chksum_and_xwrite(int fd, struct TarHeader* hp)
|
||||||
|
{
|
||||||
|
const unsigned char *cp;
|
||||||
|
int chksum, size;
|
||||||
|
|
||||||
|
strcpy(hp->magic, "ustar ");
|
||||||
|
|
||||||
|
/* Calculate and store the checksum (i.e., the sum of all of the bytes of
|
||||||
|
* the header). The checksum field must be filled with blanks for the
|
||||||
|
* calculation. The checksum field is formatted differently from the
|
||||||
|
* other fields: it has 6 digits, a null, then a space -- rather than
|
||||||
|
* digits, followed by a null like the other fields... */
|
||||||
|
memset(hp->chksum, ' ', sizeof(hp->chksum));
|
||||||
|
cp = (const unsigned char *) hp;
|
||||||
|
chksum = 0;
|
||||||
|
size = sizeof(*hp);
|
||||||
|
do { chksum += *cp++; } while (--size);
|
||||||
|
putOctal(hp->chksum, sizeof(hp->chksum)-1, chksum);
|
||||||
|
|
||||||
|
/* Now write the header out to disk */
|
||||||
|
xwrite(fd, hp, sizeof(*hp));
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS
|
||||||
|
static void writeLongname(int fd, int type, const char *name, int dir)
|
||||||
|
{
|
||||||
|
static const struct {
|
||||||
|
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 */
|
||||||
|
} prefilled = {
|
||||||
|
"0000000",
|
||||||
|
"0000000",
|
||||||
|
"0000000",
|
||||||
|
"00000000000",
|
||||||
|
"00000000000",
|
||||||
|
};
|
||||||
|
struct TarHeader header;
|
||||||
|
int size;
|
||||||
|
|
||||||
|
dir = !!dir; /* normalize: 0/1 */
|
||||||
|
size = strlen(name) + 1 + dir; /* GNU tar uses strlen+1 */
|
||||||
|
/* + dir: account for possible '/' */
|
||||||
|
|
||||||
|
bzero(&header, sizeof(header));
|
||||||
|
strcpy(header.name, "././@LongLink");
|
||||||
|
memcpy(header.mode, prefilled.mode, sizeof(prefilled));
|
||||||
|
PUT_OCTAL(header.size, size);
|
||||||
|
header.typeflag = type;
|
||||||
|
chksum_and_xwrite(fd, &header);
|
||||||
|
|
||||||
|
/* Write filename[/] and pad the block. */
|
||||||
|
/* dir=0: writes 'name<NUL>', pads */
|
||||||
|
/* dir=1: writes 'name', writes '/<NUL>', pads */
|
||||||
|
dir *= 2;
|
||||||
|
xwrite(fd, name, size - dir);
|
||||||
|
xwrite(fd, "/", dir);
|
||||||
|
size = (-size) & (TAR_BLOCK_SIZE-1);
|
||||||
|
bzero(&header, size);
|
||||||
|
xwrite(fd, &header, size);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Write out a tar header for the specified file/directory/whatever */
|
/* Write out a tar header for the specified file/directory/whatever */
|
||||||
void BUG_tar_header_size(void);
|
void BUG_tar_header_size(void);
|
||||||
@ -176,25 +242,20 @@ static int writeTarHeader(struct TarBallInfo *tbInfo,
|
|||||||
const char *header_name, const char *fileName, struct stat *statbuf)
|
const char *header_name, const char *fileName, struct stat *statbuf)
|
||||||
{
|
{
|
||||||
struct TarHeader header;
|
struct TarHeader header;
|
||||||
const unsigned char *cp;
|
|
||||||
int chksum;
|
|
||||||
int size;
|
|
||||||
|
|
||||||
if (sizeof(header) != 512)
|
if (sizeof(header) != 512)
|
||||||
BUG_tar_header_size();
|
BUG_tar_header_size();
|
||||||
|
|
||||||
bzero(&header, sizeof(struct TarHeader));
|
bzero(&header, sizeof(struct TarHeader));
|
||||||
|
|
||||||
safe_strncpy(header.name, header_name, sizeof(header.name));
|
strncpy(header.name, header_name, sizeof(header.name));
|
||||||
|
|
||||||
#define PUT_OCTAL(a, b) putOctal((a), sizeof(a), (b))
|
|
||||||
/* POSIX says to mask mode with 07777. */
|
/* POSIX says to mask mode with 07777. */
|
||||||
PUT_OCTAL(header.mode, statbuf->st_mode & 07777);
|
PUT_OCTAL(header.mode, statbuf->st_mode & 07777);
|
||||||
PUT_OCTAL(header.uid, statbuf->st_uid);
|
PUT_OCTAL(header.uid, statbuf->st_uid);
|
||||||
PUT_OCTAL(header.gid, statbuf->st_gid);
|
PUT_OCTAL(header.gid, statbuf->st_gid);
|
||||||
memset(header.size, '0', sizeof(header.size)-1); /* Regular file size is handled later */
|
memset(header.size, '0', sizeof(header.size)-1); /* Regular file size is handled later */
|
||||||
PUT_OCTAL(header.mtime, statbuf->st_mtime);
|
PUT_OCTAL(header.mtime, statbuf->st_mtime);
|
||||||
strcpy(header.magic, "ustar ");
|
|
||||||
|
|
||||||
/* Enter the user and group names */
|
/* Enter the user and group names */
|
||||||
safe_strncpy(header.uname, get_cached_username(statbuf->st_uid), sizeof(header.uname));
|
safe_strncpy(header.uname, get_cached_username(statbuf->st_uid), sizeof(header.uname));
|
||||||
@ -205,24 +266,36 @@ static int writeTarHeader(struct TarBallInfo *tbInfo,
|
|||||||
header.typeflag = LNKTYPE;
|
header.typeflag = LNKTYPE;
|
||||||
strncpy(header.linkname, tbInfo->hlInfo->name,
|
strncpy(header.linkname, tbInfo->hlInfo->name,
|
||||||
sizeof(header.linkname));
|
sizeof(header.linkname));
|
||||||
|
#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS
|
||||||
|
/* Write out long linkname if needed */
|
||||||
|
if (header.linkname[sizeof(header.linkname)-1])
|
||||||
|
writeLongname(tbInfo->tarFd, GNULONGLINK,
|
||||||
|
tbInfo->hlInfo->name, 0);
|
||||||
|
#endif
|
||||||
} else if (S_ISLNK(statbuf->st_mode)) {
|
} else if (S_ISLNK(statbuf->st_mode)) {
|
||||||
char *lpath = xreadlink(fileName);
|
char *lpath = xreadlink(fileName);
|
||||||
if (!lpath) /* Already printed err msg inside xreadlink() */
|
if (!lpath) /* Already printed err msg inside xreadlink() */
|
||||||
return FALSE;
|
return FALSE;
|
||||||
header.typeflag = SYMTYPE;
|
header.typeflag = SYMTYPE;
|
||||||
strncpy(header.linkname, lpath, sizeof(header.linkname));
|
strncpy(header.linkname, lpath, sizeof(header.linkname));
|
||||||
|
#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS
|
||||||
|
/* Write out long linkname if needed */
|
||||||
|
if (header.linkname[sizeof(header.linkname)-1])
|
||||||
|
writeLongname(tbInfo->tarFd, GNULONGLINK, lpath, 0);
|
||||||
|
#else
|
||||||
/* If it is larger than 100 bytes, bail out */
|
/* If it is larger than 100 bytes, bail out */
|
||||||
if (header.linkname[sizeof(header.linkname)-1] /* at least 100? */
|
if (header.linkname[sizeof(header.linkname)-1]) {
|
||||||
&& lpath[sizeof(header.linkname)] /* and 101th is also not zero */
|
|
||||||
) {
|
|
||||||
free(lpath);
|
free(lpath);
|
||||||
bb_error_msg("names longer than "NAME_SIZE_STR" chars not supported");
|
bb_error_msg("names longer than "NAME_SIZE_STR" chars not supported");
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
free(lpath);
|
free(lpath);
|
||||||
} else if (S_ISDIR(statbuf->st_mode)) {
|
} else if (S_ISDIR(statbuf->st_mode)) {
|
||||||
header.typeflag = DIRTYPE;
|
header.typeflag = DIRTYPE;
|
||||||
strncat(header.name, "/", sizeof(header.name));
|
/* Append '/' only if there is a space for it */
|
||||||
|
if (!header.name[sizeof(header.name)-1])
|
||||||
|
header.name[strlen(header.name)] = '/';
|
||||||
} else if (S_ISCHR(statbuf->st_mode)) {
|
} else if (S_ISCHR(statbuf->st_mode)) {
|
||||||
header.typeflag = CHRTYPE;
|
header.typeflag = CHRTYPE;
|
||||||
PUT_OCTAL(header.devmajor, major(statbuf->st_rdev));
|
PUT_OCTAL(header.devmajor, major(statbuf->st_rdev));
|
||||||
@ -247,22 +320,17 @@ static int writeTarHeader(struct TarBallInfo *tbInfo,
|
|||||||
bb_error_msg("%s: unknown file type", fileName);
|
bb_error_msg("%s: unknown file type", fileName);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
#undef PUT_OCTAL
|
|
||||||
|
|
||||||
/* Calculate and store the checksum (i.e., the sum of all of the bytes of
|
#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS
|
||||||
* the header). The checksum field must be filled with blanks for the
|
/* Write out long name if needed */
|
||||||
* calculation. The checksum field is formatted differently from the
|
/* (we, like GNU tar, output long linkname *before* long name) */
|
||||||
* other fields: it has [6] digits, a null, then a space -- rather than
|
if (header.name[sizeof(header.name)-1])
|
||||||
* digits, followed by a null like the other fields... */
|
writeLongname(tbInfo->tarFd, GNULONGNAME,
|
||||||
memset(header.chksum, ' ', sizeof(header.chksum));
|
header_name, S_ISDIR(statbuf->st_mode));
|
||||||
cp = (const unsigned char *) &header;
|
#endif
|
||||||
chksum = 0;
|
|
||||||
size = sizeof(struct TarHeader);
|
|
||||||
do { chksum += *cp++; } while (--size);
|
|
||||||
putOctal(header.chksum, sizeof(header.chksum)-1, chksum);
|
|
||||||
|
|
||||||
/* Now write the header out to disk */
|
/* Now write the header out to disk */
|
||||||
xwrite(tbInfo->tarFd, &header, sizeof(struct TarHeader));
|
chksum_and_xwrite(tbInfo->tarFd, &header);
|
||||||
|
|
||||||
/* Now do the verbose thing (or not) */
|
/* Now do the verbose thing (or not) */
|
||||||
if (tbInfo->verboseFlag) {
|
if (tbInfo->verboseFlag) {
|
||||||
@ -272,7 +340,9 @@ static int writeTarHeader(struct TarBallInfo *tbInfo,
|
|||||||
vbFd = stderr;
|
vbFd = stderr;
|
||||||
/* GNU "tar cvvf" prints "extended" listing a-la "ls -l" */
|
/* GNU "tar cvvf" prints "extended" listing a-la "ls -l" */
|
||||||
/* We don't have such excesses here: for us "v" == "vv" */
|
/* We don't have such excesses here: for us "v" == "vv" */
|
||||||
fprintf(vbFd, "%s\n", header.name);
|
/* '/' is probably a GNUism */
|
||||||
|
fprintf(vbFd, "%s%s\n", header_name,
|
||||||
|
S_ISDIR(statbuf->st_mode) ? "/" : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
@ -352,10 +422,12 @@ static int writeFileToTarball(const char *fileName, struct stat *statbuf,
|
|||||||
header_name++;
|
header_name++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !ENABLE_FEATURE_TAR_GNU_EXTENSIONS
|
||||||
if (strlen(fileName) >= NAME_SIZE) {
|
if (strlen(fileName) >= NAME_SIZE) {
|
||||||
bb_error_msg("names longer than "NAME_SIZE_STR" chars not supported");
|
bb_error_msg("names longer than "NAME_SIZE_STR" chars not supported");
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (header_name[0] == '\0')
|
if (header_name[0] == '\0')
|
||||||
return TRUE;
|
return TRUE;
|
||||||
@ -384,7 +456,8 @@ static int writeFileToTarball(const char *fileName, struct stat *statbuf,
|
|||||||
|
|
||||||
/* write the file to the archive */
|
/* write the file to the archive */
|
||||||
readSize = bb_copyfd_size(inputFileFd, tbInfo->tarFd, statbuf->st_size);
|
readSize = bb_copyfd_size(inputFileFd, tbInfo->tarFd, statbuf->st_size);
|
||||||
if (readSize != statbuf->st_size) {
|
/* readSize < 0 means that error was already reported */
|
||||||
|
if (readSize != statbuf->st_size && readSize >= 0) {
|
||||||
/* Deadly. We record size into header first, */
|
/* Deadly. We record size into header first, */
|
||||||
/* and then write out file. If file shrinks in between, */
|
/* and then write out file. If file shrinks in between, */
|
||||||
/* tar will be corrupted. So bail out. */
|
/* tar will be corrupted. So bail out. */
|
||||||
|
Loading…
Reference in New Issue
Block a user