Skip to content
Commits on Source (1)
  • Alexey Sheplyakov's avatar
    Implemented in-process lzma decompression · 611dbe37
    Alexey Sheplyakov authored
    Some distros (mostly rpm based ones) compress man pages with xz.
    man-db is very slow on those distros (setting seccomp sandbox
    per a man page is very expensive). Decompress xz compressed
    pages with liblzma to avoid the problem.
    611dbe37
......@@ -401,6 +401,7 @@ AC_DEFINE_UNQUOTED([PROG_UNXZ], ["$unxz"], [Program to use as unxz.])
AC_DEFINE_UNQUOTED([PROG_UNLZIP], ["$unlzip"], [Program to use as unlzip.])
AC_DEFINE_UNQUOTED([PROG_UNZSTD], ["$unzstd"], [Program to use as unzstd.])
MAN_COMPRESS_LIB([z], [gzopen])
MAN_COMPRESS_LIB([lzma], [lzma_stream_decoder])
dnl To add more decompressors just follow the scheme above.
# Check for various header files and associated libraries.
......
......@@ -37,6 +37,9 @@
#ifdef HAVE_LIBZ
# include "zlib.h"
#endif /* HAVE_LIBZ */
#ifdef HAVE_LIBLZMA
# include <lzma.h>
#endif
#include "pipeline.h"
......@@ -84,7 +87,7 @@ static decompress *decompress_new_pipeline (pipeline *p)
return d;
}
#ifdef HAVE_LIBZ
#if defined(HAVE_LIBZ) || defined(HAVE_LIBLZMA)
/* Create a new in-process decompressor. Takes ownership of buf. */
static decompress *decompress_new_inprocess (char *buf, size_t len)
......@@ -100,6 +103,7 @@ static decompress *decompress_new_inprocess (char *buf, size_t len)
return d;
}
#ifdef HAVE_LIBZ
static void decompress_zlib (void *data MAYBE_UNUSED)
{
gzFile zlibfile;
......@@ -127,6 +131,65 @@ static void decompress_zlib (void *data MAYBE_UNUSED)
gzclose (zlibfile);
return;
}
#endif
#ifdef HAVE_LIBLZMA
static void decompress_lzma (void *data MAYBE_UNUSED)
{
lzma_ret ret;
char inbuf[4096];
char buffer[4096];
lzma_action action = LZMA_RUN;
lzma_stream strm = LZMA_STREAM_INIT;
static const uint64_t RAM_LIMIT = 64*1024*1024;
ret = lzma_stream_decoder(&strm, RAM_LIMIT, LZMA_CONCATENATED);
if (ret != LZMA_OK) {
return;
}
strm.next_in = NULL;
strm.avail_in = 0;
strm.next_out = buffer;
strm.avail_out = sizeof(buffer);
ret = LZMA_OK;
for (;;) {
if (0 == strm.avail_in && !feof(stdin)) {
strm.next_in = inbuf;
strm.avail_in = fread(inbuf, 1, sizeof(inbuf), stdin);
if (ferror(stdin)) {
goto out;
}
if (feof(stdin)) {
/* no more input expected */
action = LZMA_FINISH;
}
}
ret = lzma_code(&strm, action);
if (0 == strm.avail_out || LZMA_STREAM_END == ret) {
size_t write_size = sizeof(buffer) - strm.avail_out;
if (fwrite(buffer, 1, write_size, stdout) != write_size) {
break;
}
strm.next_out = buffer;
strm.avail_out = sizeof(buffer);
}
if (ret != LZMA_OK) {
/* either LZMA_STREAM_END or an error */
goto out;
}
}
out:
lzma_end(&strm);
}
#endif
/* The largest number of uncompressed bytes we're prepared to read into
* memory. (We actually allow at most one fewer byte than this, for easy
......@@ -144,6 +207,92 @@ static void decompress_zlib (void *data MAYBE_UNUSED)
*/
#define MAX_INPROCESS 1048576
#ifdef HAVE_LIBLZMA
static decompress *decompress_try_lzma (const char *filename)
{
lzma_ret ret;
lzma_action action = LZMA_RUN;
uint8_t inbuf[4096];
/* We only ever call this from the parent process (and don't
* currently use threads), and this lets us skip per-file memory
* allocation.
*/
static uint8_t outbuf[MAX_INPROCESS];
size_t actual_size = 0;
static const uint64_t max_ram = 64*1024*1024;
FILE *xzfile = NULL;
int err = 0;
lzma_stream strm = LZMA_STREAM_INIT;
ret = lzma_stream_decoder(&strm, max_ram, LZMA_CONCATENATED);
if (ret != LZMA_OK) {
return NULL;
}
xzfile = fopen(filename, "rb");
if (!xzfile) {
err = 1;
goto out;
}
strm.next_in = NULL;
strm.avail_in = 0;
strm.next_out = outbuf;
strm.avail_out = sizeof(outbuf);
ret = LZMA_OK;
while (true) {
if (0 == strm.avail_in && !feof(xzfile)) {
strm.next_in = inbuf;
strm.avail_in = fread(inbuf, 1, sizeof(inbuf), xzfile);
if (ferror(xzfile)) {
err = 1;
goto out;
}
if (feof(xzfile)) {
action = LZMA_FINISH;
}
}
ret = lzma_code(&strm, action);
switch (ret) {
case LZMA_OK:
if (0 == strm.avail_out) {
err = 1;
goto out;
}
break;
case LZMA_STREAM_END:
actual_size = sizeof(outbuf) - strm.avail_out;
goto out;
break;
default:
err = 1;
goto out;
}
}
out:
lzma_end(&strm);
if (xzfile) {
fclose(xzfile);
xzfile = NULL;
}
if (err) {
return NULL;
} else {
assert(actual_size > 0);
return decompress_new_inprocess(xmemdup(outbuf, actual_size),
actual_size);
}
}
#endif
#ifdef HAVE_LIBZ
static decompress *decompress_try_zlib (const char *filename)
{
gzFile zlibfile;
......@@ -183,11 +332,12 @@ static decompress *decompress_try_zlib (const char *filename)
return decompress_new_inprocess (xmemdup (buffer, (size_t) len),
(size_t) len);
}
#endif
#define OPEN_FLAGS_UNUSED
#else /* !HAVE_LIBZ */
#else /* !HAVE_LIBZ && !HAVE_LIBLZMA */
#define OPEN_FLAGS_UNUSED MAYBE_UNUSED
#endif /* HAVE_LIBZ */
#endif /* HAVE_LIBZ || HAVE_LIBLZMA */
extern man_sandbox *sandbox;
......@@ -196,7 +346,7 @@ decompress *decompress_open (const char *filename, int flags OPEN_FLAGS_UNUSED)
pipecmd *cmd;
pipeline *p;
struct stat st;
#ifdef HAVE_LIBZ
#if defined(HAVE_LIBZ) || defined(HAVE_LIBLZMA)
size_t filename_len;
#endif /* HAVE_LIBZ */
char *ext;
......@@ -221,7 +371,21 @@ decompress *decompress_open (const char *filename, int flags OPEN_FLAGS_UNUSED)
goto got_pipeline;
}
#endif /* HAVE_LIBZ */
#ifdef HAVE_LIBLZMA
filename_len = strlen (filename);
if (filename_len > 3 && STREQ (filename + filename_len - 3, ".xz")) {
if (flags & DECOMPRESS_ALLOW_INPROCESS) {
decompress *d = decompress_try_lzma(filename);
if (d)
return d;
}
cmd = pipecmd_new_function ("xzcat", &decompress_lzma, NULL, NULL);
pipecmd_pre_exec (cmd, sandbox_load, sandbox_free, sandbox);
p = pipeline_new_commands (cmd, (void *) 0);
goto got_pipeline;
}
#endif
ext = strrchr (filename, '.');
if (ext) {
++ext;
......