summaryrefslogtreecommitdiffstats
path: root/lib/dpkg/compress.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dpkg/compress.c')
-rw-r--r--lib/dpkg/compress.c1446
1 files changed, 1446 insertions, 0 deletions
diff --git a/lib/dpkg/compress.c b/lib/dpkg/compress.c
new file mode 100644
index 0000000..e56f6c1
--- /dev/null
+++ b/lib/dpkg/compress.c
@@ -0,0 +1,1446 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * compress.c - compression support functions
+ *
+ * Copyright © 2000 Wichert Akkerman <wakkerma@debian.org>
+ * Copyright © 2004 Scott James Remnant <scott@netsplit.com>
+ * Copyright © 2006-2023 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#if USE_LIBZ_IMPL != USE_LIBZ_IMPL_NONE
+#include <compat-zlib.h>
+#endif
+#ifdef WITH_LIBLZMA
+#include <lzma.h>
+#endif
+#ifdef WITH_LIBZSTD
+#include <zstd.h>
+#define DPKG_ZSTD_MAX_LEVEL ZSTD_maxCLevel()
+#else
+#define DPKG_ZSTD_MAX_LEVEL 22
+#define ZSTD_CLEVEL_DEFAULT 3
+#endif
+#ifdef WITH_LIBBZ2
+#include <bzlib.h>
+#endif
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/error.h>
+#include <dpkg/varbuf.h>
+#include <dpkg/fdio.h>
+#include <dpkg/buffer.h>
+#include <dpkg/meminfo.h>
+#include <dpkg/command.h>
+#include <dpkg/compress.h>
+#if USE_LIBZ_IMPL == USE_LIBZ_IMPL_NONE || \
+ !defined(WITH_LIBLZMA) || \
+ !defined(WITH_LIBZSTD) || \
+ !defined(WITH_LIBBZ2)
+#include <dpkg/subproc.h>
+
+static void
+fd_fd_filter(struct command *cmd, int fd_in, int fd_out, const char *delenv[])
+{
+ pid_t pid;
+ int i;
+
+ pid = subproc_fork();
+ if (pid == 0) {
+ if (fd_in != 0) {
+ m_dup2(fd_in, 0);
+ close(fd_in);
+ }
+ if (fd_out != 1) {
+ m_dup2(fd_out, 1);
+ close(fd_out);
+ }
+
+ for (i = 0; delenv[i]; i++)
+ unsetenv(delenv[i]);
+
+ command_exec(cmd);
+ }
+ subproc_reap(pid, cmd->name, 0);
+}
+
+static void
+command_compress_init(struct command *cmd, const char *name, const char *desc,
+ int level)
+{
+ static char combuf[6];
+
+ command_init(cmd, name, desc);
+ command_add_arg(cmd, name);
+
+ snprintf(combuf, sizeof(combuf), "-c%d", level);
+ command_add_arg(cmd, combuf);
+}
+
+static void
+command_decompress_init(struct command *cmd, const char *name, const char *desc)
+{
+ command_init(cmd, name, desc);
+ command_add_arg(cmd, name);
+ command_add_arg(cmd, "-dc");
+}
+#endif
+
+#if defined(WITH_LIBLZMA) || defined(WITH_LIBZSTD)
+enum dpkg_stream_filter {
+ DPKG_STREAM_COMPRESS = 1,
+ DPKG_STREAM_DECOMPRESS = 2,
+};
+
+enum dpkg_stream_action {
+ DPKG_STREAM_INIT = 0,
+ DPKG_STREAM_RUN = 1,
+ DPKG_STREAM_FINISH = 2,
+};
+
+enum dpkg_stream_status {
+ DPKG_STREAM_OK,
+ DPKG_STREAM_END,
+ DPKG_STREAM_ERROR,
+};
+#endif
+
+struct compressor {
+ const char *name;
+ const char *extension;
+ int default_level;
+ void (*fixup_params)(struct compress_params *params);
+ void (*compress)(struct compress_params *params,
+ int fd_in, int fd_out, const char *desc);
+ void (*decompress)(struct compress_params *params,
+ int fd_in, int fd_out, const char *desc);
+};
+
+/*
+ * No compressor (pass-through).
+ */
+
+static void
+fixup_none_params(struct compress_params *params)
+{
+}
+
+static void
+decompress_none(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct dpkg_error err;
+
+ if (fd_fd_copy(fd_in, fd_out, -1, &err) < 0)
+ ohshit(_("%s: pass-through copy error: %s"), desc, err.str);
+}
+
+static void
+compress_none(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct dpkg_error err;
+
+ if (fd_fd_copy(fd_in, fd_out, -1, &err) < 0)
+ ohshit(_("%s: pass-through copy error: %s"), desc, err.str);
+}
+
+static const struct compressor compressor_none = {
+ .name = "none",
+ .extension = "",
+ .default_level = 0,
+ .fixup_params = fixup_none_params,
+ .compress = compress_none,
+ .decompress = decompress_none,
+};
+
+/*
+ * Gzip compressor.
+ */
+
+#define GZIP "gzip"
+
+static void
+fixup_gzip_params(struct compress_params *params)
+{
+ /* Normalize compression level. */
+ if (params->level == 0)
+ params->type = COMPRESSOR_TYPE_NONE;
+}
+
+#if USE_LIBZ_IMPL != USE_LIBZ_IMPL_NONE
+static void
+decompress_gzip(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ char *buffer;
+ size_t bufsize = DPKG_BUFFER_SIZE;
+ int z_errnum;
+ gzFile gzfile = gzdopen(fd_in, "r");
+
+ if (gzfile == NULL)
+ ohshit(_("%s: error binding input to gzip stream"), desc);
+
+ buffer = m_malloc(bufsize);
+
+ for (;;) {
+ int actualread, actualwrite;
+
+ actualread = gzread(gzfile, buffer, bufsize);
+ if (actualread < 0) {
+ const char *errmsg = gzerror(gzfile, &z_errnum);
+
+ if (z_errnum == Z_ERRNO)
+ errmsg = strerror(errno);
+ ohshit(_("%s: internal gzip read error: '%s'"), desc,
+ errmsg);
+ }
+ if (actualread == 0) /* EOF. */
+ break;
+
+ actualwrite = fd_write(fd_out, buffer, actualread);
+ if (actualwrite != actualread)
+ ohshite(_("%s: internal gzip write error"), desc);
+ }
+
+ free(buffer);
+
+ z_errnum = gzclose(gzfile);
+ if (z_errnum) {
+ const char *errmsg;
+
+ if (z_errnum == Z_ERRNO)
+ errmsg = strerror(errno);
+ else
+ errmsg = zError(z_errnum);
+ ohshit(_("%s: internal gzip read error: %s"), desc, errmsg);
+ }
+
+ if (close(fd_out))
+ ohshite(_("%s: internal gzip write error"), desc);
+}
+
+static void
+compress_gzip(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ char *buffer;
+ char combuf[6];
+ size_t bufsize = DPKG_BUFFER_SIZE;
+ int strategy;
+ int z_errnum;
+ gzFile gzfile;
+
+ if (params->strategy == COMPRESSOR_STRATEGY_FILTERED)
+ strategy = 'f';
+ else if (params->strategy == COMPRESSOR_STRATEGY_HUFFMAN)
+ strategy = 'h';
+ else if (params->strategy == COMPRESSOR_STRATEGY_RLE)
+ strategy = 'R';
+ else if (params->strategy == COMPRESSOR_STRATEGY_FIXED)
+ strategy = 'F';
+ else
+ strategy = ' ';
+
+ snprintf(combuf, sizeof(combuf), "w%d%c", params->level, strategy);
+ gzfile = gzdopen(fd_out, combuf);
+ if (gzfile == NULL)
+ ohshit(_("%s: error binding output to gzip stream"), desc);
+
+ buffer = m_malloc(bufsize);
+
+ for (;;) {
+ int actualread, actualwrite;
+
+ actualread = fd_read(fd_in, buffer, bufsize);
+ if (actualread < 0)
+ ohshite(_("%s: internal gzip read error"), desc);
+ if (actualread == 0) /* EOF. */
+ break;
+
+ actualwrite = gzwrite(gzfile, buffer, actualread);
+ if (actualwrite != actualread) {
+ const char *errmsg = gzerror(gzfile, &z_errnum);
+
+ if (z_errnum == Z_ERRNO)
+ errmsg = strerror(errno);
+ ohshit(_("%s: internal gzip write error: '%s'"), desc,
+ errmsg);
+ }
+ }
+
+ free(buffer);
+
+ z_errnum = gzclose(gzfile);
+ if (z_errnum) {
+ const char *errmsg;
+
+ if (z_errnum == Z_ERRNO)
+ errmsg = strerror(errno);
+ else
+ errmsg = zError(z_errnum);
+ ohshit(_("%s: internal gzip write error: %s"), desc, errmsg);
+ }
+}
+#else
+static const char *env_gzip[] = { "GZIP", NULL };
+
+static void
+decompress_gzip(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct command cmd;
+
+ command_decompress_init(&cmd, GZIP, desc);
+
+ fd_fd_filter(&cmd, fd_in, fd_out, env_gzip);
+
+ command_destroy(&cmd);
+}
+
+static void
+compress_gzip(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct command cmd;
+
+ command_compress_init(&cmd, GZIP, desc, params->level);
+ command_add_arg(&cmd, "-n");
+
+ fd_fd_filter(&cmd, fd_in, fd_out, env_gzip);
+
+ command_destroy(&cmd);
+}
+#endif
+
+static const struct compressor compressor_gzip = {
+ .name = "gzip",
+ .extension = ".gz",
+ .default_level = 9,
+ .fixup_params = fixup_gzip_params,
+ .compress = compress_gzip,
+ .decompress = decompress_gzip,
+};
+
+/*
+ * Bzip2 compressor.
+ */
+
+#define BZIP2 "bzip2"
+
+static void
+fixup_bzip2_params(struct compress_params *params)
+{
+ /* Normalize compression level. */
+ if (params->level == 0)
+ params->level = 1;
+}
+
+#ifdef WITH_LIBBZ2
+static void
+decompress_bzip2(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ char *buffer;
+ size_t bufsize = DPKG_BUFFER_SIZE;
+ BZFILE *bzfile = BZ2_bzdopen(fd_in, "r");
+
+ if (bzfile == NULL)
+ ohshit(_("%s: error binding input to bzip2 stream"), desc);
+
+ buffer = m_malloc(bufsize);
+
+ for (;;) {
+ int actualread, actualwrite;
+
+ actualread = BZ2_bzread(bzfile, buffer, bufsize);
+ if (actualread < 0) {
+ int bz_errnum = 0;
+ const char *errmsg = BZ2_bzerror(bzfile, &bz_errnum);
+
+ if (bz_errnum == BZ_IO_ERROR)
+ errmsg = strerror(errno);
+ ohshit(_("%s: internal bzip2 read error: '%s'"), desc,
+ errmsg);
+ }
+ if (actualread == 0) /* EOF. */
+ break;
+
+ actualwrite = fd_write(fd_out, buffer, actualread);
+ if (actualwrite != actualread)
+ ohshite(_("%s: internal bzip2 write error"), desc);
+ }
+
+ free(buffer);
+
+ BZ2_bzclose(bzfile);
+
+ if (close(fd_out))
+ ohshite(_("%s: internal bzip2 write error"), desc);
+}
+
+static void
+compress_bzip2(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ char *buffer;
+ char combuf[6];
+ size_t bufsize = DPKG_BUFFER_SIZE;
+ int bz_errnum;
+ BZFILE *bzfile;
+
+ snprintf(combuf, sizeof(combuf), "w%d", params->level);
+ bzfile = BZ2_bzdopen(fd_out, combuf);
+ if (bzfile == NULL)
+ ohshit(_("%s: error binding output to bzip2 stream"), desc);
+
+ buffer = m_malloc(bufsize);
+
+ for (;;) {
+ int actualread, actualwrite;
+
+ actualread = fd_read(fd_in, buffer, bufsize);
+ if (actualread < 0)
+ ohshite(_("%s: internal bzip2 read error"), desc);
+ if (actualread == 0) /* EOF. */
+ break;
+
+ actualwrite = BZ2_bzwrite(bzfile, buffer, actualread);
+ if (actualwrite != actualread) {
+ const char *errmsg = BZ2_bzerror(bzfile, &bz_errnum);
+
+ if (bz_errnum == BZ_IO_ERROR)
+ errmsg = strerror(errno);
+ ohshit(_("%s: internal bzip2 write error: '%s'"), desc,
+ errmsg);
+ }
+ }
+
+ free(buffer);
+
+ BZ2_bzWriteClose(&bz_errnum, bzfile, 0, NULL, NULL);
+ if (bz_errnum != BZ_OK) {
+ const char *errmsg = _("unexpected bzip2 error");
+
+ if (bz_errnum == BZ_IO_ERROR)
+ errmsg = strerror(errno);
+ ohshit(_("%s: internal bzip2 write error: '%s'"), desc,
+ errmsg);
+ }
+
+ /* Because BZ2_bzWriteClose has done a fflush on the file handle,
+ * doing a close on the file descriptor associated with it should
+ * be safe™. */
+ if (close(fd_out))
+ ohshite(_("%s: internal bzip2 write error"), desc);
+}
+#else
+static const char *env_bzip2[] = { "BZIP", "BZIP2", NULL };
+
+static void
+decompress_bzip2(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct command cmd;
+
+ command_decompress_init(&cmd, BZIP2, desc);
+
+ fd_fd_filter(&cmd, fd_in, fd_out, env_bzip2);
+
+ command_destroy(&cmd);
+}
+
+static void
+compress_bzip2(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct command cmd;
+
+ command_compress_init(&cmd, BZIP2, desc, params->level);
+
+ fd_fd_filter(&cmd, fd_in, fd_out, env_bzip2);
+
+ command_destroy(&cmd);
+}
+#endif
+
+static const struct compressor compressor_bzip2 = {
+ .name = "bzip2",
+ .extension = ".bz2",
+ .default_level = 9,
+ .fixup_params = fixup_bzip2_params,
+ .compress = compress_bzip2,
+ .decompress = decompress_bzip2,
+};
+
+/*
+ * Xz compressor.
+ */
+
+#define XZ "xz"
+
+#ifdef WITH_LIBLZMA
+struct io_lzma {
+ const char *desc;
+
+ struct compress_params *params;
+
+ enum dpkg_stream_filter filter;
+ enum dpkg_stream_action action;
+ enum dpkg_stream_status status;
+
+ void (*init)(struct io_lzma *io, lzma_stream *s);
+ void (*code)(struct io_lzma *io, lzma_stream *s);
+ void (*done)(struct io_lzma *io, lzma_stream *s);
+};
+
+/* XXX: liblzma does not expose error messages. */
+static const char *
+dpkg_lzma_strerror(struct io_lzma *io, lzma_ret code)
+{
+ const char *const impossible = _("internal error (bug)");
+
+ switch (code) {
+ case LZMA_MEM_ERROR:
+ return strerror(ENOMEM);
+ case LZMA_MEMLIMIT_ERROR:
+ if (io->action == DPKG_STREAM_RUN)
+ return _("memory usage limit reached");
+ return impossible;
+ case LZMA_OPTIONS_ERROR:
+ if (io->filter == DPKG_STREAM_COMPRESS &&
+ io->action == DPKG_STREAM_INIT)
+ return _("unsupported compression preset");
+ if (io->filter == DPKG_STREAM_DECOMPRESS &&
+ io->action == DPKG_STREAM_RUN)
+ return _("unsupported options in file header");
+ return impossible;
+ case LZMA_DATA_ERROR:
+ if (io->action == DPKG_STREAM_RUN)
+ return _("compressed data is corrupt");
+ return impossible;
+ case LZMA_BUF_ERROR:
+ if (io->action == DPKG_STREAM_RUN)
+ return _("unexpected end of input");
+ return impossible;
+ case LZMA_FORMAT_ERROR:
+ if (io->filter == DPKG_STREAM_DECOMPRESS &&
+ io->action == DPKG_STREAM_RUN)
+ return _("file format not recognized");
+ return impossible;
+ case LZMA_UNSUPPORTED_CHECK:
+ if (io->filter == DPKG_STREAM_COMPRESS &&
+ io->action == DPKG_STREAM_INIT)
+ return _("unsupported type of integrity check");
+ return impossible;
+ default:
+ return impossible;
+ }
+}
+
+static void
+filter_lzma(struct io_lzma *io, int fd_in, int fd_out)
+{
+ uint8_t *buf_in;
+ uint8_t *buf_out;
+ size_t buf_size = DPKG_BUFFER_SIZE;
+ lzma_stream s = LZMA_STREAM_INIT;
+
+ buf_in = m_malloc(buf_size);
+ buf_out = m_malloc(buf_size);
+
+ s.next_out = buf_out;
+ s.avail_out = buf_size;
+
+ io->status = DPKG_STREAM_OK;
+ io->action = DPKG_STREAM_INIT;
+ io->init(io, &s);
+ io->action = DPKG_STREAM_RUN;
+
+ do {
+ ssize_t len;
+
+ if (s.avail_in == 0 && io->action != DPKG_STREAM_FINISH) {
+ len = fd_read(fd_in, buf_in, buf_size);
+ if (len < 0)
+ ohshite(_("%s: lzma read error"), io->desc);
+ if (len == 0)
+ io->action = DPKG_STREAM_FINISH;
+ s.next_in = buf_in;
+ s.avail_in = len;
+ }
+
+ io->code(io, &s);
+
+ if (s.avail_out == 0 || io->status == DPKG_STREAM_END) {
+ len = fd_write(fd_out, buf_out, s.next_out - buf_out);
+ if (len < 0)
+ ohshite(_("%s: lzma write error"), io->desc);
+ s.next_out = buf_out;
+ s.avail_out = buf_size;
+ }
+ } while (io->status != DPKG_STREAM_END);
+
+ io->done(io, &s);
+
+ free(buf_in);
+ free(buf_out);
+
+ if (close(fd_out))
+ ohshite(_("%s: lzma close error"), io->desc);
+}
+
+static void DPKG_ATTR_NORET
+filter_lzma_error(struct io_lzma *io, lzma_ret ret)
+{
+ ohshit(_("%s: lzma error: %s"), io->desc,
+ dpkg_lzma_strerror(io, ret));
+}
+
+#ifdef HAVE_LZMA_MT_ENCODER
+static uint64_t
+filter_xz_get_memlimit(void)
+{
+ uint64_t mt_memlimit;
+
+ /* Ask the kernel what is currently available for us. If this fails
+ * initialize the memory limit to half the physical RAM, or to 128 MiB
+ * if we cannot infer the number. */
+ if (meminfo_get_available(&mt_memlimit) < 0) {
+ mt_memlimit = lzma_physmem() / 2;
+ if (mt_memlimit == 0)
+ mt_memlimit = 128 * 1024 * 1024;
+ }
+ /* Clamp the multi-threaded memory limit to half the addressable
+ * memory on this architecture. */
+ if (mt_memlimit > INTPTR_MAX)
+ mt_memlimit = INTPTR_MAX;
+
+ return mt_memlimit;
+}
+
+static uint32_t
+filter_xz_get_cputhreads(struct compress_params *params)
+{
+ long threads_max;
+
+ threads_max = lzma_cputhreads();
+ if (threads_max == 0)
+ threads_max = 1;
+
+ if (params->threads_max >= 0)
+ return clamp(params->threads_max, 1, threads_max);
+
+ return threads_max;
+}
+#endif
+
+static void
+filter_unxz_init(struct io_lzma *io, lzma_stream *s)
+{
+#ifdef HAVE_LZMA_MT_DECODER
+ lzma_mt mt_options = {
+ .flags = 0,
+ .block_size = 0,
+ .timeout = 0,
+ .filters = NULL,
+ };
+#else
+ uint64_t memlimit = UINT64_MAX;
+#endif
+ lzma_ret ret;
+
+ io->filter = DPKG_STREAM_DECOMPRESS;
+
+#ifdef HAVE_LZMA_MT_DECODER
+ mt_options.memlimit_stop = UINT64_MAX;
+ mt_options.memlimit_threading = filter_xz_get_memlimit();
+ mt_options.threads = filter_xz_get_cputhreads(io->params);
+
+ ret = lzma_stream_decoder_mt(s, &mt_options);
+#else
+ ret = lzma_stream_decoder(s, memlimit, 0);
+#endif
+ if (ret != LZMA_OK)
+ filter_lzma_error(io, ret);
+}
+
+static void
+filter_xz_init(struct io_lzma *io, lzma_stream *s)
+{
+ uint32_t preset;
+ lzma_check check = LZMA_CHECK_CRC64;
+#ifdef HAVE_LZMA_MT_ENCODER
+ uint64_t mt_memlimit;
+ lzma_mt mt_options = {
+ .flags = 0,
+ .block_size = 0,
+ .timeout = 0,
+ .filters = NULL,
+ .check = check,
+ };
+#endif
+ lzma_ret ret;
+
+ io->filter = DPKG_STREAM_COMPRESS;
+
+ preset = io->params->level;
+ if (io->params->strategy == COMPRESSOR_STRATEGY_EXTREME)
+ preset |= LZMA_PRESET_EXTREME;
+
+#ifdef HAVE_LZMA_MT_ENCODER
+ mt_options.preset = preset;
+ mt_memlimit = filter_xz_get_memlimit();
+ mt_options.threads = filter_xz_get_cputhreads(io->params);
+
+ /* Guess whether we have enough RAM to use the multi-threaded encoder,
+ * and decrease them up to single-threaded to reduce memory usage. */
+ for (; mt_options.threads > 1; mt_options.threads--) {
+ uint64_t mt_memusage;
+
+ mt_memusage = lzma_stream_encoder_mt_memusage(&mt_options);
+ if (mt_memusage < mt_memlimit)
+ break;
+ }
+
+ ret = lzma_stream_encoder_mt(s, &mt_options);
+#else
+ ret = lzma_easy_encoder(s, preset, check);
+#endif
+
+ if (ret != LZMA_OK)
+ filter_lzma_error(io, ret);
+}
+
+static void
+filter_lzma_code(struct io_lzma *io, lzma_stream *s)
+{
+ lzma_ret ret;
+ lzma_action action;
+
+ if (io->action == DPKG_STREAM_RUN)
+ action = LZMA_RUN;
+ else if (io->action == DPKG_STREAM_FINISH)
+ action = LZMA_FINISH;
+ else
+ internerr("unknown stream filter action %d\n", io->action);
+
+ ret = lzma_code(s, action);
+
+ if (ret == LZMA_STREAM_END)
+ io->status = DPKG_STREAM_END;
+ else if (ret != LZMA_OK)
+ filter_lzma_error(io, ret);
+}
+
+static void
+filter_lzma_done(struct io_lzma *io, lzma_stream *s)
+{
+ lzma_end(s);
+}
+
+static void
+decompress_xz(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct io_lzma io;
+
+ io.init = filter_unxz_init;
+ io.code = filter_lzma_code;
+ io.done = filter_lzma_done;
+ io.desc = desc;
+ io.params = params;
+
+ filter_lzma(&io, fd_in, fd_out);
+}
+
+static void
+compress_xz(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct io_lzma io;
+
+ io.init = filter_xz_init;
+ io.code = filter_lzma_code;
+ io.done = filter_lzma_done;
+ io.desc = desc;
+ io.params = params;
+
+ filter_lzma(&io, fd_in, fd_out);
+}
+#else
+static const char *env_xz[] = { "XZ_DEFAULTS", "XZ_OPT", NULL };
+
+static void
+decompress_xz(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct command cmd;
+ char *threads_opt = NULL;
+
+ command_decompress_init(&cmd, XZ, desc);
+
+ if (params->threads_max > 0) {
+ threads_opt = str_fmt("-T%d", params->threads_max);
+ command_add_arg(&cmd, threads_opt);
+ }
+
+ fd_fd_filter(&cmd, fd_in, fd_out, env_xz);
+
+ command_destroy(&cmd);
+ free(threads_opt);
+}
+
+static void
+compress_xz(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct command cmd;
+ char *threads_opt = NULL;
+
+ command_compress_init(&cmd, XZ, desc, params->level);
+
+ if (params->strategy == COMPRESSOR_STRATEGY_EXTREME)
+ command_add_arg(&cmd, "-e");
+
+ if (params->threads_max > 0) {
+ /* Do not generate warnings when adjusting memory usage, nor
+ * exit with non-zero due to those not emitted warnings. */
+ command_add_arg(&cmd, "--quiet");
+ command_add_arg(&cmd, "--no-warn");
+
+ /* Do not let xz fallback to single-threaded mode, to avoid
+ * non-reproducible output. */
+ command_add_arg(&cmd, "--no-adjust");
+
+ /* The xz -T1 option selects a single-threaded mode which
+ * generates different output than in multi-threaded mode.
+ * To avoid the non-reproducible output we pass -T+1
+ * (supported with xz >= 5.4.0) to request multi-threaded
+ * mode with a single thread. */
+ if (params->threads_max == 1)
+ threads_opt = m_strdup("-T+1");
+ else
+ threads_opt = str_fmt("-T%d", params->threads_max);
+ command_add_arg(&cmd, threads_opt);
+ }
+
+ fd_fd_filter(&cmd, fd_in, fd_out, env_xz);
+
+ command_destroy(&cmd);
+ free(threads_opt);
+}
+#endif
+
+static const struct compressor compressor_xz = {
+ .name = "xz",
+ .extension = ".xz",
+ .default_level = 6,
+ .fixup_params = fixup_none_params,
+ .compress = compress_xz,
+ .decompress = decompress_xz,
+};
+
+/*
+ * Lzma compressor.
+ */
+
+#ifdef WITH_LIBLZMA
+static void
+filter_unlzma_init(struct io_lzma *io, lzma_stream *s)
+{
+ uint64_t memlimit = UINT64_MAX;
+ lzma_ret ret;
+
+ io->filter = DPKG_STREAM_DECOMPRESS;
+
+ ret = lzma_alone_decoder(s, memlimit);
+ if (ret != LZMA_OK)
+ filter_lzma_error(io, ret);
+}
+
+static void
+filter_lzma_init(struct io_lzma *io, lzma_stream *s)
+{
+ uint32_t preset;
+ lzma_options_lzma options;
+ lzma_ret ret;
+
+ io->filter = DPKG_STREAM_COMPRESS;
+
+ preset = io->params->level;
+ if (io->params->strategy == COMPRESSOR_STRATEGY_EXTREME)
+ preset |= LZMA_PRESET_EXTREME;
+ if (lzma_lzma_preset(&options, preset))
+ filter_lzma_error(io, LZMA_OPTIONS_ERROR);
+
+ ret = lzma_alone_encoder(s, &options);
+ if (ret != LZMA_OK)
+ filter_lzma_error(io, ret);
+}
+
+static void
+decompress_lzma(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct io_lzma io;
+
+ io.init = filter_unlzma_init;
+ io.code = filter_lzma_code;
+ io.done = filter_lzma_done;
+ io.desc = desc;
+ io.params = params;
+
+ filter_lzma(&io, fd_in, fd_out);
+}
+
+static void
+compress_lzma(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct io_lzma io;
+
+ io.init = filter_lzma_init;
+ io.code = filter_lzma_code;
+ io.done = filter_lzma_done;
+ io.desc = desc;
+ io.params = params;
+
+ filter_lzma(&io, fd_in, fd_out);
+}
+#else
+static void
+decompress_lzma(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct command cmd;
+
+ command_decompress_init(&cmd, XZ, desc);
+ command_add_arg(&cmd, "--format=lzma");
+
+ fd_fd_filter(&cmd, fd_in, fd_out, env_xz);
+
+ command_destroy(&cmd);
+}
+
+static void
+compress_lzma(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct command cmd;
+
+ command_compress_init(&cmd, XZ, desc, params->level);
+ command_add_arg(&cmd, "--format=lzma");
+
+ fd_fd_filter(&cmd, fd_in, fd_out, env_xz);
+
+ command_destroy(&cmd);
+}
+#endif
+
+static const struct compressor compressor_lzma = {
+ .name = "lzma",
+ .extension = ".lzma",
+ .default_level = 6,
+ .fixup_params = fixup_none_params,
+ .compress = compress_lzma,
+ .decompress = decompress_lzma,
+};
+
+/*
+ * ZStandard compressor.
+ */
+
+#define ZSTD "zstd"
+
+#ifdef WITH_LIBZSTD
+struct io_zstd_stream {
+ enum dpkg_stream_filter filter;
+ enum dpkg_stream_action action;
+ enum dpkg_stream_status status;
+
+ union {
+ ZSTD_CCtx *c;
+ ZSTD_DCtx *d;
+ } ctx;
+
+ const uint8_t *next_in;
+ size_t avail_in;
+ uint8_t *next_out;
+ size_t avail_out;
+};
+
+struct io_zstd {
+ const char *desc;
+
+ struct compress_params *params;
+
+ void (*init)(struct io_zstd *io, struct io_zstd_stream *s);
+ void (*code)(struct io_zstd *io, struct io_zstd_stream *s);
+ void (*done)(struct io_zstd *io, struct io_zstd_stream *s);
+};
+
+static void DPKG_ATTR_NORET
+filter_zstd_error(struct io_zstd *io, size_t ret)
+{
+ ohshit(_("%s: zstd error: %s"), io->desc, ZSTD_getErrorName(ret));
+}
+
+static uint32_t
+filter_zstd_get_cputhreads(struct compress_params *params)
+{
+ ZSTD_bounds workers;
+ long threads_max = 1;
+
+ /* The shared library has not been built with multi-threading. */
+ workers = ZSTD_cParam_getBounds(ZSTD_c_nbWorkers);
+ if (workers.upperBound == 0)
+ return 1;
+
+#ifdef _SC_NPROCESSORS_ONLN
+ threads_max = sysconf(_SC_NPROCESSORS_ONLN);
+ if (threads_max < 0)
+ return 1;
+#endif
+
+ if (params->threads_max >= 0)
+ return clamp(params->threads_max, 1, threads_max);
+
+ return threads_max;
+}
+
+static size_t
+filter_zstd_get_buf_in_size(struct io_zstd_stream *s)
+{
+ if (s->filter == DPKG_STREAM_DECOMPRESS)
+ return ZSTD_DStreamInSize();
+ else
+ return ZSTD_CStreamInSize();
+}
+
+static size_t
+filter_zstd_get_buf_out_size(struct io_zstd_stream *s)
+{
+ if (s->filter == DPKG_STREAM_DECOMPRESS)
+ return ZSTD_DStreamOutSize();
+ else
+ return ZSTD_CStreamOutSize();
+}
+
+static void
+filter_unzstd_init(struct io_zstd *io, struct io_zstd_stream *s)
+{
+ s->filter = DPKG_STREAM_DECOMPRESS;
+ s->action = DPKG_STREAM_RUN;
+ s->status = DPKG_STREAM_OK;
+
+ s->ctx.d = ZSTD_createDCtx();
+ if (s->ctx.d == NULL)
+ ohshit(_("%s: cannot create zstd decompression context"),
+ io->desc);
+}
+
+static void
+filter_unzstd_code(struct io_zstd *io, struct io_zstd_stream *s)
+{
+ ZSTD_inBuffer buf_in = { s->next_in, s->avail_in, 0 };
+ ZSTD_outBuffer buf_out = { s->next_out, s->avail_out, 0 };
+ size_t ret;
+
+ ret = ZSTD_decompressStream(s->ctx.d, &buf_out, &buf_in);
+ if (ZSTD_isError(ret))
+ filter_zstd_error(io, ret);
+
+ s->next_in += buf_in.pos;
+ s->avail_in -= buf_in.pos;
+ s->next_out += buf_out.pos;
+ s->avail_out -= buf_out.pos;
+
+ if (ret == 0)
+ s->status = DPKG_STREAM_END;
+}
+
+static void
+filter_unzstd_done(struct io_zstd *io, struct io_zstd_stream *s)
+{
+ ZSTD_freeDCtx(s->ctx.d);
+}
+
+static void
+filter_zstd_init(struct io_zstd *io, struct io_zstd_stream *s)
+{
+ int clevel = io->params->level;
+ uint32_t nthreads;
+ size_t ret;
+
+ s->filter = DPKG_STREAM_COMPRESS;
+ s->action = DPKG_STREAM_RUN;
+ s->status = DPKG_STREAM_OK;
+
+ s->ctx.c = ZSTD_createCCtx();
+ if (s->ctx.c == NULL)
+ ohshit(_("%s: cannot create zstd compression context"),
+ io->desc);
+
+ ret = ZSTD_CCtx_setParameter(s->ctx.c, ZSTD_c_compressionLevel, clevel);
+ if (ZSTD_isError(ret))
+ filter_zstd_error(io, ret);
+ ret = ZSTD_CCtx_setParameter(s->ctx.c, ZSTD_c_checksumFlag, 1);
+ if (ZSTD_isError(ret))
+ filter_zstd_error(io, ret);
+
+ nthreads = filter_zstd_get_cputhreads(io->params);
+ if (nthreads > 1)
+ ZSTD_CCtx_setParameter(s->ctx.c, ZSTD_c_nbWorkers, nthreads);
+}
+
+static void
+filter_zstd_code(struct io_zstd *io, struct io_zstd_stream *s)
+{
+ ZSTD_inBuffer buf_in = { s->next_in, s->avail_in, 0 };
+ ZSTD_outBuffer buf_out = { s->next_out, s->avail_out, 0 };
+ ZSTD_EndDirective action;
+ size_t ret;
+
+ if (s->action == DPKG_STREAM_FINISH)
+ action = ZSTD_e_end;
+ else
+ action = ZSTD_e_continue;
+
+ ret = ZSTD_compressStream2(s->ctx.c, &buf_out, &buf_in, action);
+ if (ZSTD_isError(ret))
+ filter_zstd_error(io, ret);
+
+ s->next_in += buf_in.pos;
+ s->avail_in -= buf_in.pos;
+ s->next_out += buf_out.pos;
+ s->avail_out -= buf_out.pos;
+
+ if (s->action == DPKG_STREAM_FINISH && ret == 0)
+ s->status = DPKG_STREAM_END;
+}
+
+static void
+filter_zstd_done(struct io_zstd *io, struct io_zstd_stream *s)
+{
+ ZSTD_freeCCtx(s->ctx.c);
+}
+
+static void
+filter_zstd(struct io_zstd *io, int fd_in, int fd_out)
+{
+ ssize_t buf_in_size;
+ ssize_t buf_out_size;
+ uint8_t *buf_in;
+ uint8_t *buf_out;
+ struct io_zstd_stream s = {
+ .action = DPKG_STREAM_INIT,
+ };
+
+ io->init(io, &s);
+
+ buf_in_size = filter_zstd_get_buf_in_size(&s);
+ buf_in = m_malloc(buf_in_size);
+ buf_out_size = filter_zstd_get_buf_out_size(&s);
+ buf_out = m_malloc(buf_out_size);
+
+ s.next_out = buf_out;
+ s.avail_out = buf_out_size;
+
+ do {
+ ssize_t len;
+
+ if (s.avail_in == 0 && s.action != DPKG_STREAM_FINISH) {
+ len = fd_read(fd_in, buf_in, buf_in_size);
+ if (len < 0)
+ ohshite(_("%s: zstd read error"), io->desc);
+ if (len < buf_in_size)
+ s.action = DPKG_STREAM_FINISH;
+ s.next_in = buf_in;
+ s.avail_in = len;
+ }
+
+ io->code(io, &s);
+
+ if (s.avail_out == 0 || s.status == DPKG_STREAM_END) {
+ len = fd_write(fd_out, buf_out, s.next_out - buf_out);
+ if (len < 0)
+ ohshite(_("%s: zstd write error"), io->desc);
+ s.next_out = buf_out;
+ s.avail_out = buf_out_size;
+ }
+ } while (s.status != DPKG_STREAM_END);
+
+ io->done(io, &s);
+
+ free(buf_in);
+ free(buf_out);
+
+ if (close(fd_out))
+ ohshite(_("%s: zstd close error"), io->desc);
+}
+
+static void
+decompress_zstd(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct io_zstd io;
+
+ io.init = filter_unzstd_init;
+ io.code = filter_unzstd_code;
+ io.done = filter_unzstd_done;
+ io.desc = desc;
+ io.params = params;
+
+ filter_zstd(&io, fd_in, fd_out);
+}
+
+static void
+compress_zstd(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct io_zstd io;
+
+ io.init = filter_zstd_init;
+ io.code = filter_zstd_code;
+ io.done = filter_zstd_done;
+ io.desc = desc;
+ io.params = params;
+
+ filter_zstd(&io, fd_in, fd_out);
+}
+#else
+static const char *env_zstd[] = {
+ "ZSTD_CLEVEL",
+ "ZSTD_NBTHREADS",
+ NULL,
+};
+
+static void
+decompress_zstd(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct command cmd;
+ char *threads_opt = NULL;
+
+ command_decompress_init(&cmd, ZSTD, desc);
+ command_add_arg(&cmd, "-q");
+
+ if (params->threads_max > 0) {
+ threads_opt = str_fmt("-T%d", params->threads_max);
+ command_add_arg(&cmd, threads_opt);
+ }
+
+ fd_fd_filter(&cmd, fd_in, fd_out, env_zstd);
+
+ command_destroy(&cmd);
+ free(threads_opt);
+}
+
+static void
+compress_zstd(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc)
+{
+ struct command cmd;
+ char *threads_opt = NULL;
+
+ command_compress_init(&cmd, ZSTD, desc, params->level);
+ command_add_arg(&cmd, "-q");
+
+ if (params->level > 19)
+ command_add_arg(&cmd, "--ultra");
+
+ if (params->threads_max > 0) {
+ threads_opt = str_fmt("-T%d", params->threads_max);
+ command_add_arg(&cmd, threads_opt);
+ }
+
+ fd_fd_filter(&cmd, fd_in, fd_out, env_zstd);
+
+ command_destroy(&cmd);
+ free(threads_opt);
+}
+#endif
+
+static const struct compressor compressor_zstd = {
+ .name = "zstd",
+ .extension = ".zst",
+ .default_level = ZSTD_CLEVEL_DEFAULT,
+ .fixup_params = fixup_none_params,
+ .compress = compress_zstd,
+ .decompress = decompress_zstd,
+};
+
+/*
+ * Generic compressor filter.
+ */
+
+static const struct compressor *compressor_array[] = {
+ [COMPRESSOR_TYPE_NONE] = &compressor_none,
+ [COMPRESSOR_TYPE_GZIP] = &compressor_gzip,
+ [COMPRESSOR_TYPE_XZ] = &compressor_xz,
+ [COMPRESSOR_TYPE_ZSTD] = &compressor_zstd,
+ [COMPRESSOR_TYPE_BZIP2] = &compressor_bzip2,
+ [COMPRESSOR_TYPE_LZMA] = &compressor_lzma,
+};
+
+static const struct compressor *
+compressor(enum compressor_type type)
+{
+ const enum compressor_type max_type = array_count(compressor_array);
+
+ if (type < 0 || type >= max_type)
+ internerr("compressor_type %d is out of range", type);
+
+ return compressor_array[type];
+}
+
+const char *
+compressor_get_name(enum compressor_type type)
+{
+ return compressor(type)->name;
+}
+
+const char *
+compressor_get_extension(enum compressor_type type)
+{
+ return compressor(type)->extension;
+}
+
+enum compressor_type
+compressor_find_by_name(const char *name)
+{
+ size_t i;
+
+ for (i = 0; i < array_count(compressor_array); i++)
+ if (strcmp(compressor_array[i]->name, name) == 0)
+ return i;
+
+ return COMPRESSOR_TYPE_UNKNOWN;
+}
+
+enum compressor_type
+compressor_find_by_extension(const char *extension)
+{
+ size_t i;
+
+ for (i = 0; i < array_count(compressor_array); i++)
+ if (strcmp(compressor_array[i]->extension, extension) == 0)
+ return i;
+
+ return COMPRESSOR_TYPE_UNKNOWN;
+}
+
+enum compressor_strategy
+compressor_get_strategy(const char *name)
+{
+ if (strcmp(name, "none") == 0)
+ return COMPRESSOR_STRATEGY_NONE;
+ if (strcmp(name, "filtered") == 0)
+ return COMPRESSOR_STRATEGY_FILTERED;
+ if (strcmp(name, "huffman") == 0)
+ return COMPRESSOR_STRATEGY_HUFFMAN;
+ if (strcmp(name, "rle") == 0)
+ return COMPRESSOR_STRATEGY_RLE;
+ if (strcmp(name, "fixed") == 0)
+ return COMPRESSOR_STRATEGY_FIXED;
+ if (strcmp(name, "extreme") == 0)
+ return COMPRESSOR_STRATEGY_EXTREME;
+
+ return COMPRESSOR_STRATEGY_UNKNOWN;
+}
+
+static void
+compressor_fixup_params(struct compress_params *params)
+{
+ compressor(params->type)->fixup_params(params);
+
+ if (params->level < 0)
+ params->level = compressor(params->type)->default_level;
+}
+
+bool
+compressor_check_params(struct compress_params *params, struct dpkg_error *err)
+{
+ compressor_fixup_params(params);
+
+ if ((params->type == COMPRESSOR_TYPE_ZSTD &&
+ params->level > DPKG_ZSTD_MAX_LEVEL) ||
+ (params->type != COMPRESSOR_TYPE_ZSTD &&
+ params->level > 9)) {
+ dpkg_put_error(err, _("invalid compression level %d"),
+ params->level);
+ return false;
+ }
+
+ if (params->strategy == COMPRESSOR_STRATEGY_NONE)
+ return true;
+
+ if (params->type == COMPRESSOR_TYPE_GZIP &&
+ (params->strategy == COMPRESSOR_STRATEGY_FILTERED ||
+ params->strategy == COMPRESSOR_STRATEGY_HUFFMAN ||
+ params->strategy == COMPRESSOR_STRATEGY_RLE ||
+ params->strategy == COMPRESSOR_STRATEGY_FIXED))
+ return true;
+
+ if (params->type == COMPRESSOR_TYPE_XZ &&
+ params->strategy == COMPRESSOR_STRATEGY_EXTREME)
+ return true;
+
+ dpkg_put_error(err, _("unknown compression strategy"));
+ return false;
+}
+
+void
+decompress_filter(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc_fmt, ...)
+{
+ va_list args;
+ struct varbuf desc = VARBUF_INIT;
+
+ va_start(args, desc_fmt);
+ varbuf_vprintf(&desc, desc_fmt, args);
+ va_end(args);
+
+ compressor(params->type)->decompress(params, fd_in, fd_out, desc.buf);
+
+ varbuf_destroy(&desc);
+}
+
+void
+compress_filter(struct compress_params *params, int fd_in, int fd_out,
+ const char *desc_fmt, ...)
+{
+ va_list args;
+ struct varbuf desc = VARBUF_INIT;
+
+ va_start(args, desc_fmt);
+ varbuf_vprintf(&desc, desc_fmt, args);
+ va_end(args);
+
+ compressor(params->type)->compress(params, fd_in, fd_out, desc.buf);
+
+ varbuf_destroy(&desc);
+}