busybox/archival/libarchive/open_transformer.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

387 lines
9.6 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 init_transformer_state(transformer_state_t *xstate)
{
memset(xstate, 0, sizeof(*xstate));
}
int FAST_FUNC check_signature16(transformer_state_t *xstate, unsigned magic16)
{
if (!xstate->signature_skipped) {
uint16_t magic2;
if (full_read(xstate->src_fd, &magic2, 2) != 2 || magic2 != magic16) {
bb_simple_error_msg("invalid magic");
return -1;
}
xstate->signature_skipped = 2;
}
return 0;
}
ssize_t FAST_FUNC transformer_write(transformer_state_t *xstate, const void *buf, size_t bufsize)
{
ssize_t nwrote;
if (xstate->mem_output_size_max != 0) {
size_t pos = xstate->mem_output_size;
size_t size;
size = (xstate->mem_output_size += bufsize);
if (size > xstate->mem_output_size_max) {
free(xstate->mem_output_buf);
xstate->mem_output_buf = NULL;
bb_perror_msg("buffer %u too small", (unsigned)xstate->mem_output_size_max);
nwrote = -1;
goto ret;
}
xstate->mem_output_buf = xrealloc(xstate->mem_output_buf, size + 1);
memcpy(xstate->mem_output_buf + pos, buf, bufsize);
xstate->mem_output_buf[size] = '\0';
nwrote = bufsize;
} else {
nwrote = full_write(xstate->dst_fd, buf, bufsize);
if (nwrote != (ssize_t)bufsize) {
bb_simple_perror_msg("write");
nwrote = -1;
goto ret;
}
}
ret:
return nwrote;
}
ssize_t FAST_FUNC xtransformer_write(transformer_state_t *xstate, const void *buf, size_t bufsize)
{
ssize_t nwrote = transformer_write(xstate, buf, bufsize);
if (nwrote != (ssize_t)bufsize) {
xfunc_die();
}
return nwrote;
}
void check_errors_in_children(int signo)
{
int status;
if (!signo) {
/* block waiting for any child */
if (wait(&status) < 0)
//FIXME: check EINTR?
return; /* probably there are no children */
goto check_status;
}
/* Wait for any child without blocking */
for (;;) {
if (wait_any_nohang(&status) < 0)
//FIXME: check EINTR?
/* wait failed?! I'm confused... */
return;
check_status:
/*if (WIFEXITED(status) && WEXITSTATUS(status) == 0)*/
/* On Linux, the above can be checked simply as: */
if (status == 0)
/* this child exited with 0 */
continue;
/* Cannot happen:
if (!WIFSIGNALED(status) && !WIFEXITED(status)) ???;
*/
bb_got_signal = 1;
}
}
/* transformer(), more than meets the eye */
#if BB_MMU
void FAST_FUNC fork_transformer(int fd,
int signature_skipped,
IF_DESKTOP(long long) int FAST_FUNC (*transformer)(transformer_state_t *xstate)
)
#else
void FAST_FUNC fork_transformer(int fd, const char *transform_prog)
#endif
{
struct fd_pair fd_pipe;
int pid;
xpiped_pair(fd_pipe);
pid = BB_MMU ? xfork() : xvfork();
if (pid == 0) {
/* Child */
close(fd_pipe.rd); /* we don't want to read from the parent */
// FIXME: error check?
#if BB_MMU
{
IF_DESKTOP(long long) int r;
transformer_state_t xstate;
init_transformer_state(&xstate);
xstate.signature_skipped = signature_skipped;
xstate.src_fd = fd;
xstate.dst_fd = fd_pipe.wr;
r = transformer(&xstate);
if (ENABLE_FEATURE_CLEAN_UP) {
close(fd_pipe.wr); /* send EOF */
close(fd);
}
/* must be _exit! bug was actually seen here */
_exit(/*error if:*/ r < 0);
}
#else
{
char *argv[4];
xmove_fd(fd, 0);
xmove_fd(fd_pipe.wr, 1);
argv[0] = (char*)transform_prog;
argv[1] = (char*)"-cf";
argv[2] = (char*)"-";
argv[3] = NULL;
BB_EXECVP(transform_prog, argv);
bb_perror_msg_and_die("can't execute '%s'", transform_prog);
}
#endif
/* notreached */
}
/* parent process */
close(fd_pipe.wr); /* don't want to write to the child */
xmove_fd(fd_pipe.rd, fd);
}
#if SEAMLESS_COMPRESSION
/* Used by e.g. rpm which gives us a fd without filename,
* thus we can't guess the format from filename's extension.
*/
static transformer_state_t *setup_transformer_on_fd(int fd, int fail_if_not_compressed)
{
transformer_state_t *xstate;
xstate = xzalloc(sizeof(*xstate));
xstate->src_fd = fd;
/* .gz and .bz2 both have 2-byte signature, and their
* unpack_XXX_stream wants this header skipped. */
xstate->signature_skipped = 2;
xread(fd, xstate->magic.b16, 2);
if (ENABLE_FEATURE_SEAMLESS_GZ
&& xstate->magic.b16[0] == GZIP_MAGIC
) {
xstate->xformer = unpack_gz_stream;
USE_FOR_NOMMU(xstate->xformer_prog = "gunzip";)
goto found_magic;
}
if (ENABLE_FEATURE_SEAMLESS_Z
&& xstate->magic.b16[0] == COMPRESS_MAGIC
) {
xstate->xformer = unpack_Z_stream;
USE_FOR_NOMMU(xstate->xformer_prog = "uncompress";)
goto found_magic;
}
if (ENABLE_FEATURE_SEAMLESS_BZ2
&& xstate->magic.b16[0] == BZIP2_MAGIC
) {
xstate->xformer = unpack_bz2_stream;
USE_FOR_NOMMU(xstate->xformer_prog = "bunzip2";)
goto found_magic;
}
if (ENABLE_FEATURE_SEAMLESS_XZ
&& xstate->magic.b16[0] == XZ_MAGIC1
) {
uint32_t v32;
xstate->signature_skipped = 6;
xread(fd, &xstate->magic.b16[1], 4);
move_from_unaligned32(v32, &xstate->magic.b16[1]);
if (v32 == XZ_MAGIC2) {
xstate->xformer = unpack_xz_stream;
USE_FOR_NOMMU(xstate->xformer_prog = "unxz";)
goto found_magic;
}
}
/* No known magic seen */
if (fail_if_not_compressed)
bb_simple_error_msg_and_die("no gzip"
IF_FEATURE_SEAMLESS_BZ2("/bzip2")
IF_FEATURE_SEAMLESS_XZ("/xz")
" magic");
/* Some callers expect this function to "consume" fd
* even if data is not compressed. In this case,
* we return a state with trivial transformer.
*/
// USE_FOR_MMU(xstate->xformer = copy_stream;)
// USE_FOR_NOMMU(xstate->xformer_prog = "cat";)
found_magic:
return xstate;
}
static void fork_transformer_and_free(transformer_state_t *xstate)
{
# if BB_MMU
fork_transformer_with_no_sig(xstate->src_fd, xstate->xformer);
# else
/* NOMMU version of fork_transformer execs
* an external unzipper that wants
* file position at the start of the file.
*/
xlseek(xstate->src_fd, - xstate->signature_skipped, SEEK_CUR);
xstate->signature_skipped = 0;
fork_transformer_with_sig(xstate->src_fd, xstate->xformer, xstate->xformer_prog);
# endif
free(xstate);
}
/* Used by e.g. rpm which gives us a fd without filename,
* thus we can't guess the format from filename's extension.
*/
int FAST_FUNC setup_unzip_on_fd(int fd, int fail_if_not_compressed)
{
transformer_state_t *xstate = setup_transformer_on_fd(fd, fail_if_not_compressed);
if (!xstate->xformer) {
free(xstate);
return 1;
}
fork_transformer_and_free(xstate);
return 0;
}
#if ENABLE_FEATURE_SEAMLESS_LZMA
/* ...and custom version for LZMA */
void FAST_FUNC setup_lzma_on_fd(int fd)
{
transformer_state_t *xstate = xzalloc(sizeof(*xstate));
xstate->src_fd = fd;
xstate->xformer = unpack_lzma_stream;
USE_FOR_NOMMU(xstate->xformer_prog = "unlzma";)
fork_transformer_and_free(xstate);
}
#endif
static transformer_state_t *open_transformer(const char *fname, int fail_if_not_compressed)
{
transformer_state_t *xstate;
int fd;
fd = open(fname, O_RDONLY);
if (fd < 0)
return NULL;
if (ENABLE_FEATURE_SEAMLESS_LZMA) {
/* .lzma has no header/signature, can only detect it by extension */
if (is_suffixed_with(fname, ".lzma")) {
xstate = xzalloc(sizeof(*xstate));
xstate->src_fd = fd;
xstate->xformer = unpack_lzma_stream;
USE_FOR_NOMMU(xstate->xformer_prog = "unlzma";)
return xstate;
}
}
xstate = setup_transformer_on_fd(fd, fail_if_not_compressed);
return xstate;
}
int FAST_FUNC open_zipped(const char *fname, int fail_if_not_compressed)
{
int fd;
transformer_state_t *xstate;
xstate = open_transformer(fname, fail_if_not_compressed);
if (!xstate)
return -1;
fd = xstate->src_fd;
# if BB_MMU
if (xstate->xformer) {
fork_transformer_with_no_sig(fd, xstate->xformer);
} else {
/* the file is not compressed */
xlseek(fd, - xstate->signature_skipped, SEEK_CUR);
xstate->signature_skipped = 0;
}
# else
/* NOMMU can't avoid the seek :( */
xlseek(fd, - xstate->signature_skipped, SEEK_CUR);
xstate->signature_skipped = 0;
if (xstate->xformer) {
fork_transformer_with_sig(fd, xstate->xformer, xstate->xformer_prog);
} /* else: the file is not compressed */
# endif
free(xstate);
return fd;
}
void* FAST_FUNC xmalloc_open_zipped_read_close(const char *fname, size_t *maxsz_p)
{
# if 1
transformer_state_t *xstate;
char *image;
xstate = open_transformer(fname, /*fail_if_not_compressed:*/ 0);
if (!xstate) /* file open error */
return NULL;
image = NULL;
if (xstate->xformer) {
/* In-memory decompression */
xstate->mem_output_size_max = maxsz_p ? *maxsz_p : (size_t)(INT_MAX - 4095);
xstate->xformer(xstate);
if (xstate->mem_output_buf) {
image = xstate->mem_output_buf;
if (maxsz_p)
*maxsz_p = xstate->mem_output_size;
}
} else {
/* File is not compressed.
* We already read first few bytes, account for that.
* Example where it happens:
* "modinfo MODULE.ko" (not compressed)
* open("MODULE.ko", O_RDONLY|O_LARGEFILE) = 4
* read(4, "\177E", 2) = 2
* fstat64(4, ...)
* mmap(...)
* read(4, "LF\2\1\1\0\0\0\0"...
* ...and we avoided seeking on the fd! :)
*/
image = xmalloc_read_with_initial_buf(
xstate->src_fd,
maxsz_p,
xmemdup(&xstate->magic, xstate->signature_skipped),
xstate->signature_skipped
);
xstate->signature_skipped = 0;
}
if (!image)
bb_perror_msg("read error from '%s'", fname);
close(xstate->src_fd);
free(xstate);
return image;
# else
/* This version forks a subprocess - much more expensive */
int fd;
char *image;
fd = open_zipped(fname, /*fail_if_not_compressed:*/ 0);
if (fd < 0)
return NULL;
image = xmalloc_read(fd, maxsz_p);
if (!image)
bb_perror_msg("read error from '%s'", fname);
close(fd);
return image;
# endif
}
#endif /* SEAMLESS_COMPRESSION */