busybox/archival/libarchive/data_extract_all.c
James Byrne 6937487be7 libbb: reduce the overhead of single parameter bb_error_msg() calls
Back in 2007, commit 0c97c9d437 ("'simple' error message functions by
Loic Grenie") introduced bb_simple_perror_msg() to allow for a lower
overhead call to bb_perror_msg() when only a string was being printed
with no parameters. This saves space for some CPU architectures because
it avoids the overhead of a call to a variadic function. However there
has never been a simple version of bb_error_msg(), and since 2007 many
new calls to bb_perror_msg() have been added that only take a single
parameter and so could have been using bb_simple_perror_message().

This changeset introduces 'simple' versions of bb_info_msg(),
bb_error_msg(), bb_error_msg_and_die(), bb_herror_msg() and
bb_herror_msg_and_die(), and replaces all calls that only take a
single parameter, or use something like ("%s", arg), with calls to the
corresponding 'simple' version.

Since it is likely that single parameter calls to the variadic functions
may be accidentally reintroduced in the future a new debugging config
option WARN_SIMPLE_MSG has been introduced. This uses some macro magic
which will cause any such calls to generate a warning, but this is
turned off by default to avoid use of the unpleasant macros in normal
circumstances.

This is a large changeset due to the number of calls that have been
replaced. The only files that contain changes other than simple
substitution of function calls are libbb.h, libbb/herror_msg.c,
libbb/verror_msg.c and libbb/xfuncs_printf.c. In miscutils/devfsd.c,
networking/udhcp/common.h and util-linux/mdev.c additonal macros have
been added for logging so that single parameter and multiple parameter
logging variants exist.

The amount of space saved varies considerably by architecture, and was
found to be as follows (for 'defconfig' using GCC 7.4):

Arm:     -92 bytes
MIPS:    -52 bytes
PPC:   -1836 bytes
x86_64: -938 bytes

Note that for the MIPS architecture only an exception had to be made
disabling the 'simple' calls for 'udhcp' (in networking/udhcp/common.h)
because it made these files larger on MIPS.

Signed-off-by: James Byrne <james.byrne@origamienergy.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
2019-07-02 11:35:03 +02:00

256 lines
6.9 KiB
C

/* vi: set sw=4 ts=4: */
/*
* Licensed under GPLv2 or later, see file LICENSE in this source tree.
*/
#include "libbb.h"
#include "bb_archive.h"
void FAST_FUNC data_extract_all(archive_handle_t *archive_handle)
{
file_header_t *file_header = archive_handle->file_header;
int dst_fd;
int res;
char *hard_link;
#if ENABLE_FEATURE_TAR_LONG_OPTIONS
char *dst_name;
#else
# define dst_name (file_header->name)
#endif
#if ENABLE_FEATURE_TAR_SELINUX
char *sctx = archive_handle->tar__sctx[PAX_NEXT_FILE];
if (!sctx)
sctx = archive_handle->tar__sctx[PAX_GLOBAL];
if (sctx) { /* setfscreatecon is 4 syscalls, avoid if possible */
setfscreatecon(sctx);
free(archive_handle->tar__sctx[PAX_NEXT_FILE]);
archive_handle->tar__sctx[PAX_NEXT_FILE] = NULL;
}
#endif
/* Hard links are encoded as regular files of size 0
* with a nonempty link field */
hard_link = NULL;
if (S_ISREG(file_header->mode) && file_header->size == 0)
hard_link = file_header->link_target;
#if ENABLE_FEATURE_TAR_LONG_OPTIONS
dst_name = file_header->name;
if (archive_handle->tar__strip_components) {
unsigned n = archive_handle->tar__strip_components;
do {
dst_name = strchr(dst_name, '/');
if (!dst_name || dst_name[1] == '\0') {
data_skip(archive_handle);
goto ret;
}
dst_name++;
/*
* Link target is shortened only for hardlinks:
* softlinks restored unchanged.
*/
if (hard_link) {
// GNU tar 1.26 does not check that we reached end of link name:
// if "dir/hardlink" is hardlinked to "file",
// tar xvf a.tar --strip-components=1 says:
// tar: hardlink: Cannot hard link to '': No such file or directory
// and continues processing. We silently skip such entries.
hard_link = strchr(hard_link, '/');
if (!hard_link || hard_link[1] == '\0') {
data_skip(archive_handle);
goto ret;
}
hard_link++;
}
} while (--n != 0);
}
#endif
if (archive_handle->ah_flags & ARCHIVE_CREATE_LEADING_DIRS) {
char *slash = strrchr(dst_name, '/');
if (slash) {
*slash = '\0';
bb_make_directory(dst_name, -1, FILEUTILS_RECUR);
*slash = '/';
}
}
if (archive_handle->ah_flags & ARCHIVE_UNLINK_OLD) {
/* Remove the entry if it exists */
if (!S_ISDIR(file_header->mode)) {
if (hard_link) {
/* Ugly special case:
* tar cf t.tar hardlink1 hardlink2 hardlink1
* results in this tarball structure:
* hardlink1
* hardlink2 -> hardlink1
* hardlink1 -> hardlink1 <== !!!
*/
if (strcmp(hard_link, dst_name) == 0)
goto ret;
}
/* Proceed with deleting */
if (unlink(dst_name) == -1
&& errno != ENOENT
) {
bb_perror_msg_and_die("can't remove old file %s",
dst_name);
}
}
}
else if (archive_handle->ah_flags & ARCHIVE_EXTRACT_NEWER) {
/* Remove the existing entry if its older than the extracted entry */
struct stat existing_sb;
if (lstat(dst_name, &existing_sb) == -1) {
if (errno != ENOENT) {
bb_simple_perror_msg_and_die("can't stat old file");
}
}
else if (existing_sb.st_mtime >= file_header->mtime) {
if (!S_ISDIR(file_header->mode)) {
bb_error_msg("%s not created: newer or "
"same age file exists", dst_name);
}
data_skip(archive_handle);
goto ret;
}
else if ((unlink(dst_name) == -1) && (errno != EISDIR)) {
bb_perror_msg_and_die("can't remove old file %s",
dst_name);
}
}
/* Handle hard links separately */
if (hard_link) {
create_or_remember_link(&archive_handle->link_placeholders,
hard_link,
dst_name,
1);
/* Hardlinks have no separate mode/ownership, skip chown/chmod */
goto ret;
}
/* Create the filesystem entry */
switch (file_header->mode & S_IFMT) {
case S_IFREG: {
/* Regular file */
char *dst_nameN;
int flags = O_WRONLY | O_CREAT | O_EXCL;
if (archive_handle->ah_flags & ARCHIVE_O_TRUNC)
flags = O_WRONLY | O_CREAT | O_TRUNC;
dst_nameN = dst_name;
#ifdef ARCHIVE_REPLACE_VIA_RENAME
if (archive_handle->ah_flags & ARCHIVE_REPLACE_VIA_RENAME)
/* rpm-style temp file name */
dst_nameN = xasprintf("%s;%x", dst_name, (int)getpid());
#endif
dst_fd = xopen3(dst_nameN,
flags,
file_header->mode
);
bb_copyfd_exact_size(archive_handle->src_fd, dst_fd, file_header->size);
close(dst_fd);
#ifdef ARCHIVE_REPLACE_VIA_RENAME
if (archive_handle->ah_flags & ARCHIVE_REPLACE_VIA_RENAME) {
xrename(dst_nameN, dst_name);
free(dst_nameN);
}
#endif
break;
}
case S_IFDIR:
res = mkdir(dst_name, file_header->mode);
if ((res != 0)
&& (errno != EISDIR) /* btw, Linux doesn't return this */
&& (errno != EEXIST)
) {
bb_perror_msg("can't make dir %s", dst_name);
}
break;
case S_IFLNK:
/* Symlink */
//TODO: what if file_header->link_target == NULL (say, corrupted tarball?)
/* To avoid a directory traversal attack via symlinks,
* do not restore symlinks with ".." components
* or symlinks starting with "/", unless a magic
* envvar is set.
*
* For example, consider a .tar created via:
* $ tar cvf bug.tar anything.txt
* $ ln -s /tmp symlink
* $ tar --append -f bug.tar symlink
* $ rm symlink
* $ mkdir symlink
* $ tar --append -f bug.tar symlink/evil.py
*
* This will result in an archive that contains:
* $ tar --list -f bug.tar
* anything.txt
* symlink [-> /tmp]
* symlink/evil.py
*
* Untarring bug.tar would otherwise place evil.py in '/tmp'.
*/
create_or_remember_link(&archive_handle->link_placeholders,
file_header->link_target,
dst_name,
0);
break;
case S_IFSOCK:
case S_IFBLK:
case S_IFCHR:
case S_IFIFO:
res = mknod(dst_name, file_header->mode, file_header->device);
if (res != 0) {
bb_perror_msg("can't create node %s", dst_name);
}
break;
default:
bb_simple_error_msg_and_die("unrecognized file type");
}
if (!S_ISLNK(file_header->mode)) {
if (!(archive_handle->ah_flags & ARCHIVE_DONT_RESTORE_OWNER)) {
uid_t uid = file_header->uid;
gid_t gid = file_header->gid;
#if ENABLE_FEATURE_TAR_UNAME_GNAME
if (!(archive_handle->ah_flags & ARCHIVE_NUMERIC_OWNER)) {
if (file_header->tar__uname) {
//TODO: cache last name/id pair?
struct passwd *pwd = getpwnam(file_header->tar__uname);
if (pwd) uid = pwd->pw_uid;
}
if (file_header->tar__gname) {
struct group *grp = getgrnam(file_header->tar__gname);
if (grp) gid = grp->gr_gid;
}
}
#endif
/* GNU tar 1.15.1 uses chown, not lchown */
chown(dst_name, uid, gid);
}
/* uclibc has no lchmod, glibc is even stranger -
* it has lchmod which seems to do nothing!
* so we use chmod... */
if (!(archive_handle->ah_flags & ARCHIVE_DONT_RESTORE_PERM)) {
chmod(dst_name, file_header->mode);
}
if (archive_handle->ah_flags & ARCHIVE_RESTORE_DATE) {
struct timeval t[2];
t[1].tv_sec = t[0].tv_sec = file_header->mtime;
t[1].tv_usec = t[0].tv_usec = 0;
utimes(dst_name, t);
}
}
ret: ;
#if ENABLE_FEATURE_TAR_SELINUX
if (sctx) {
/* reset the context after creating an entry */
setfscreatecon(NULL);
}
#endif
}