aboutsummaryrefslogtreecommitdiff
path: root/src/lzma
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lzma/Makefile.am63
-rw-r--r--src/lzma/alloc.c106
-rw-r--r--src/lzma/alloc.h42
-rw-r--r--src/lzma/args.c566
-rw-r--r--src/lzma/args.h64
-rw-r--r--src/lzma/error.c156
-rw-r--r--src/lzma/error.h67
-rw-r--r--src/lzma/hardware.c99
-rw-r--r--src/lzma/hardware.h31
-rw-r--r--src/lzma/help.c178
-rw-r--r--src/lzma/help.h32
-rw-r--r--src/lzma/io.c664
-rw-r--r--src/lzma/io.h60
-rw-r--r--src/lzma/list.c477
-rw-r--r--src/lzma/main.c254
-rw-r--r--src/lzma/options.c346
-rw-r--r--src/lzma/options.h46
-rw-r--r--src/lzma/private.h55
-rw-r--r--src/lzma/process.c458
-rw-r--r--src/lzma/process.h30
-rw-r--r--src/lzma/suffix.c145
-rw-r--r--src/lzma/suffix.h25
-rw-r--r--src/lzma/util.c182
-rw-r--r--src/lzma/util.h32
-rw-r--r--src/lzmadec/Makefile.am27
-rw-r--r--src/lzmadec/lzmadec.c515
26 files changed, 4720 insertions, 0 deletions
diff --git a/src/lzma/Makefile.am b/src/lzma/Makefile.am
new file mode 100644
index 00000000..5fbd3358
--- /dev/null
+++ b/src/lzma/Makefile.am
@@ -0,0 +1,63 @@
+##
+## Copyright (C) 2007 Lasse Collin
+##
+## This program is free software; you can redistribute it and/or
+## modify it under the terms of the GNU Lesser General Public
+## License as published by the Free Software Foundation; either
+## version 2.1 of the License, or (at your option) any later version.
+##
+## This program 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
+## Lesser General Public License for more details.
+##
+
+bin_PROGRAMS = lzma
+
+lzma_SOURCES = \
+ alloc.c \
+ alloc.h \
+ args.c \
+ args.h \
+ error.c \
+ error.h \
+ hardware.c \
+ hardware.h \
+ help.c \
+ help.h \
+ io.c \
+ io.h \
+ main.c \
+ options.c \
+ options.h \
+ private.h \
+ process.c \
+ process.h \
+ suffix.c \
+ suffix.h \
+ util.c \
+ util.h
+
+## It must be able to find sysdefs.h, lzma_adv.h, and possible
+## replacement headers.
+lzma_CPPFLAGS = \
+ -DLOCALEDIR=\"$(localedir)\" \
+ -I@top_srcdir@/src/common \
+ -I@top_srcdir@/src/liblzma/api \
+ -I@top_builddir@/lib \
+ -I@top_srcdir@/lib
+
+lzma_CFLAGS = @PTHREAD_CFLAGS@
+
+## Always link the command line tool statically against liblzma. It is
+## faster on x86, because no need for PIC. We also have one dependency less,
+## which allows users to more freely copy the lzma binary to other boxes.
+lzma_LDFLAGS = -static
+lzma_LDADD = \
+ @top_builddir@/src/liblzma/liblzma.la \
+ @LTLIBINTL@ \
+ @PTHREAD_LIBS@
+
+if COND_GNULIB
+lzma_LDADD += @top_builddir@/lib/libgnu_nls.a
+endif
diff --git a/src/lzma/alloc.c b/src/lzma/alloc.c
new file mode 100644
index 00000000..d0fee68b
--- /dev/null
+++ b/src/lzma/alloc.c
@@ -0,0 +1,106 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file alloc.c
+/// \brief Memory allocation functions
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "private.h"
+
+
+/// Called when memory allocation fails. Prints and error message and
+/// quits the application.
+static void lzma_attribute((noreturn))
+xerror(void)
+{
+ errmsg(V_ERROR, "%s", strerror(errno));
+ my_exit(ERROR);
+}
+
+
+extern void *
+xmalloc(size_t size)
+{
+ if (size < 1) {
+ errno = EINVAL;
+ xerror();
+ }
+
+ void *p = malloc(size);
+ if (p == NULL)
+ xerror();
+
+ return p;
+}
+
+
+/*
+extern void *
+xrealloc(void *ptr, size_t size)
+{
+ if (size < 1) {
+ errno = EINVAL;
+ xerror();
+ }
+
+ ptr = realloc(ptr, size);
+ if (ptr == NULL)
+ xerror();
+
+ return ptr;
+}
+*/
+
+
+extern char *
+xstrdup(const char *src)
+{
+ if (src == NULL) {
+ errno = EINVAL;
+ xerror();
+ }
+
+ const size_t size = strlen(src) + 1;
+ char *dest = malloc(size);
+ if (dest == NULL)
+ xerror();
+
+ memcpy(dest, src, size);
+
+ return dest;
+}
+
+
+extern void
+xstrcpy(char **dest, const char *src)
+{
+ size_t len = strlen(src) + 1;
+
+ *dest = realloc(*dest, len);
+ if (*dest == NULL)
+ xerror();
+
+ memcpy(*dest, src, len + 1);
+
+ return;
+}
+
+
+extern void *
+allocator(void *opaque lzma_attribute((unused)),
+ size_t nmemb lzma_attribute((unused)), size_t size)
+{
+ return xmalloc(size);
+}
diff --git a/src/lzma/alloc.h b/src/lzma/alloc.h
new file mode 100644
index 00000000..80317269
--- /dev/null
+++ b/src/lzma/alloc.h
@@ -0,0 +1,42 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file alloc.h
+/// \brief Memory allocation functions
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef ALLOC_H
+#define ALLOC_H
+
+#include "private.h"
+
+
+/// Safe malloc() that never returns NULL.
+extern void *xmalloc(size_t size);
+
+/// Safe realloc() that never returns NULL.
+extern void *xrealloc(void *ptr, size_t size);
+
+/// Safe strdup() that never returns NULL.
+extern char *xstrdup(const char *src);
+
+/// xrealloc()s *dest to the size needed by src, and copies src to *dest.
+extern void xstrcpy(char **dest, const char *src);
+
+/// Function for lzma_allocator.alloc. This uses xmalloc().
+extern void *allocator(void *opaque lzma_attribute((unused)),
+ size_t nmemb lzma_attribute((unused)), size_t size);
+
+#endif
diff --git a/src/lzma/args.c b/src/lzma/args.c
new file mode 100644
index 00000000..d6163ae7
--- /dev/null
+++ b/src/lzma/args.c
@@ -0,0 +1,566 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file args.c
+/// \brief Argument parsing
+///
+/// \note Filter-specific options parsing is in options.c.
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "private.h"
+
+#include "getopt.h"
+#include <ctype.h>
+
+
+enum tool_mode opt_mode = MODE_COMPRESS;
+enum header_type opt_header = HEADER_AUTO;
+
+char *opt_suffix = NULL;
+
+char *opt_files_name = NULL;
+char opt_files_split = '\0';
+FILE *opt_files_file = NULL;
+
+bool opt_stdout = false;
+bool opt_force = false;
+bool opt_keep_original = false;
+bool opt_preserve_name = false;
+
+lzma_check_type opt_check = LZMA_CHECK_CRC64;
+lzma_options_filter opt_filters[8];
+
+// We don't modify or free() this, but we need to assign it in some
+// non-const pointers.
+const char *stdin_filename = "(stdin)";
+
+static size_t preset_number = 7 - 1;
+static bool preset_default = true;
+static size_t filter_count = 0;
+
+
+enum {
+ OPT_COPY = INT_MIN,
+ OPT_SUBBLOCK,
+ OPT_X86,
+ OPT_POWERPC,
+ OPT_IA64,
+ OPT_ARM,
+ OPT_ARMTHUMB,
+ OPT_SPARC,
+ OPT_DELTA,
+ OPT_LZMA,
+
+ OPT_FILES,
+ OPT_FILES0,
+};
+
+
+static const char short_opts[] = "cC:dfFhlLkqrStT:vVz123456789";
+
+
+static const struct option long_opts[] = {
+ // gzip-like options
+ { "fast", no_argument, NULL, '1' },
+ { "best", no_argument, NULL, '9' },
+ { "memory", required_argument, NULL, 'M' },
+ { "name", no_argument, NULL, 'N' },
+ { "suffix", required_argument, NULL, 'S' },
+ { "threads", required_argument, NULL, 'T' },
+ { "version", no_argument, NULL, 'V' },
+ { "stdout", no_argument, NULL, 'c' },
+ { "to-stdout", no_argument, NULL, 'c' },
+ { "decompress", no_argument, NULL, 'd' },
+ { "uncompress", no_argument, NULL, 'd' },
+ { "force", no_argument, NULL, 'f' },
+ { "help", no_argument, NULL, 'h' },
+ { "list", no_argument, NULL, 'l' },
+ { "info", no_argument, NULL, 'l' },
+ { "keep", no_argument, NULL, 'k' },
+ { "no-name", no_argument, NULL, 'n' },
+ { "quiet", no_argument, NULL, 'q' },
+// { "recursive", no_argument, NULL, 'r' }, // TODO
+ { "test", no_argument, NULL, 't' },
+ { "verbose", no_argument, NULL, 'v' },
+ { "compress", no_argument, NULL, 'z' },
+
+ // Filters
+ { "copy", no_argument, NULL, OPT_COPY },
+ { "subblock", optional_argument, NULL, OPT_SUBBLOCK },
+ { "x86", no_argument, NULL, OPT_X86 },
+ { "bcj", no_argument, NULL, OPT_X86 },
+ { "powerpc", no_argument, NULL, OPT_POWERPC },
+ { "ppc", no_argument, NULL, OPT_POWERPC },
+ { "ia64", no_argument, NULL, OPT_IA64 },
+ { "itanium", no_argument, NULL, OPT_IA64 },
+ { "arm", no_argument, NULL, OPT_ARM },
+ { "armthumb", no_argument, NULL, OPT_ARMTHUMB },
+ { "sparc", no_argument, NULL, OPT_SPARC },
+ { "delta", optional_argument, NULL, OPT_DELTA },
+ { "lzma", optional_argument, NULL, OPT_LZMA },
+
+ // Other
+ { "format", required_argument, NULL, 'F' },
+ { "check", required_argument, NULL, 'C' },
+ { "files", optional_argument, NULL, OPT_FILES },
+ { "files0", optional_argument, NULL, OPT_FILES0 },
+
+ { NULL, 0, NULL, 0 }
+};
+
+
+static void
+add_filter(lzma_vli id, const char *opt_str)
+{
+ if (filter_count == 7) {
+ errmsg(V_ERROR, _("Maximum number of filters is seven"));
+ my_exit(ERROR);
+ }
+
+ opt_filters[filter_count].id = id;
+
+ switch (id) {
+ case LZMA_FILTER_SUBBLOCK:
+ opt_filters[filter_count].options
+ = parse_options_subblock(opt_str);
+ break;
+
+ case LZMA_FILTER_DELTA:
+ opt_filters[filter_count].options
+ = parse_options_delta(opt_str);
+ break;
+
+ case LZMA_FILTER_LZMA:
+ opt_filters[filter_count].options
+ = parse_options_lzma(opt_str);
+ break;
+
+ default:
+ assert(opt_str == NULL);
+ opt_filters[filter_count].options = NULL;
+ break;
+ }
+
+ ++filter_count;
+ preset_default = false;
+ return;
+}
+
+
+static void
+parse_real(int argc, char **argv)
+{
+ int c;
+
+ while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL))
+ != -1) {
+ switch (c) {
+ // gzip-like options
+
+ case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ preset_number = c - '1';
+ preset_default = false;
+ break;
+
+ // --memory
+ case 'M':
+ opt_memory = str_to_uint64("memory", optarg,
+ 1, SIZE_MAX);
+ break;
+
+ case 'N':
+ opt_preserve_name = true;
+ break;
+
+ // --suffix
+ case 'S':
+ // Empty suffix and suffixes having a slash are
+ // rejected. Such suffixes would break things later.
+ if (optarg[0] == '\0' || strchr(optarg, '/') != NULL) {
+ errmsg(V_ERROR, _("%s: Invalid filename "
+ "suffix"), optarg);
+ my_exit(ERROR);
+ }
+
+ free(opt_suffix);
+ opt_suffix = xstrdup(optarg);
+ break;
+
+ case 'T':
+ opt_threads = str_to_uint64("threads", optarg,
+ 1, SIZE_MAX);
+ break;
+
+ // --version
+ case 'V':
+ // This doesn't return.
+ show_version();
+
+ // --stdout
+ case 'c':
+ opt_stdout = true;
+ break;
+
+ // --decompress
+ case 'd':
+ opt_mode = MODE_DECOMPRESS;
+ break;
+
+ // --force
+ case 'f':
+ opt_force = true;
+ break;
+
+ // --help
+ case 'h':
+ // This doesn't return.
+ show_help();
+
+ // --list
+ case 'l':
+ opt_mode = MODE_LIST;
+ break;
+
+ // --keep
+ case 'k':
+ opt_keep_original = true;
+ break;
+
+ case 'n':
+ opt_preserve_name = false;
+ break;
+
+ // --quiet
+ case 'q':
+ if (verbosity > V_SILENT)
+ --verbosity;
+
+ break;
+
+ case 't':
+ opt_mode = MODE_TEST;
+ break;
+
+ // --verbose
+ case 'v':
+ if (verbosity < V_DEBUG)
+ ++verbosity;
+
+ break;
+
+ case 'z':
+ opt_mode = MODE_COMPRESS;
+ break;
+
+ // Filter setup
+
+ case OPT_COPY:
+ add_filter(LZMA_FILTER_COPY, NULL);
+ break;
+
+ case OPT_SUBBLOCK:
+ add_filter(LZMA_FILTER_SUBBLOCK, optarg);
+ break;
+
+ case OPT_X86:
+ add_filter(LZMA_FILTER_X86, NULL);
+ break;
+
+ case OPT_POWERPC:
+ add_filter(LZMA_FILTER_POWERPC, NULL);
+ break;
+
+ case OPT_IA64:
+ add_filter(LZMA_FILTER_IA64, NULL);
+ break;
+
+ case OPT_ARM:
+ add_filter(LZMA_FILTER_ARM, NULL);
+ break;
+
+ case OPT_ARMTHUMB:
+ add_filter(LZMA_FILTER_ARMTHUMB, NULL);
+ break;
+
+ case OPT_SPARC:
+ add_filter(LZMA_FILTER_SPARC, NULL);
+ break;
+
+ case OPT_DELTA:
+ add_filter(LZMA_FILTER_DELTA, optarg);
+ break;
+
+ case OPT_LZMA:
+ add_filter(LZMA_FILTER_LZMA, optarg);
+ break;
+
+ // Other
+
+ // --format
+ case 'F': {
+ static const char *types[] = {
+ "auto",
+ "native",
+ "single",
+ "multi",
+ "alone",
+// "gzip",
+ NULL
+ };
+
+ opt_header = 0;
+ while (strcmp(types[opt_header], optarg) != 0) {
+ if (types[++opt_header] == NULL) {
+ errmsg(V_ERROR, _("%s: Unknown file "
+ "format type"),
+ optarg);
+ my_exit(ERROR);
+ }
+ }
+
+ break;
+ }
+
+ // --check
+ case 'C': {
+ static const struct {
+ const char *str;
+ unsigned int value;
+ } types[] = {
+ { "none", LZMA_CHECK_NONE },
+ { "crc32", LZMA_CHECK_CRC32 },
+ { "crc64", LZMA_CHECK_CRC64 },
+ { "sha256", LZMA_CHECK_SHA256 },
+ { NULL, 0 }
+ };
+
+ size_t i = 0;
+ while (strcmp(types[i].str, optarg) != 0) {
+ if (types[++i].str == NULL) {
+ errmsg(V_ERROR, _("%s: Unknown "
+ "integrity check "
+ "type"), optarg);
+ my_exit(ERROR);
+ }
+ }
+
+ opt_check = types[i].value;
+ break;
+ }
+
+ case OPT_FILES:
+ opt_files_split = '\n';
+
+ // Fall through
+
+ case OPT_FILES0:
+ if (opt_files_name != NULL) {
+ errmsg(V_ERROR, _("Only one file can be "
+ "specified with `--files'"
+ "or `--files0'."));
+ my_exit(ERROR);
+ }
+
+ if (optarg == NULL) {
+ opt_files_name = (char *)stdin_filename;
+ opt_files_file = stdin;
+ } else {
+ opt_files_name = optarg;
+ opt_files_file = fopen(optarg,
+ c == OPT_FILES ? "r" : "rb");
+ if (opt_files_file == NULL) {
+ errmsg(V_ERROR, "%s: %s", optarg,
+ strerror(errno));
+ my_exit(ERROR);
+ }
+ }
+
+ break;
+
+ default:
+ show_try_help();
+ my_exit(ERROR);
+ }
+ }
+
+ return;
+}
+
+
+static void
+parse_environment(void)
+{
+ char *env = getenv("LZMA_OPT");
+ if (env == NULL)
+ return;
+
+ env = xstrdup(env);
+
+ // Calculate the number of arguments in env.
+ unsigned int argc = 1;
+ bool prev_was_space = true;
+ for (size_t i = 0; env[i] != '\0'; ++i) {
+ if (isspace(env[i])) {
+ prev_was_space = true;
+ } else if (prev_was_space) {
+ prev_was_space = false;
+ if (++argc > (unsigned int)(INT_MAX)) {
+ errmsg(V_ERROR, _("The environment variable "
+ "LZMA_OPT contains too many "
+ "arguments"));
+ my_exit(ERROR);
+ }
+ }
+ }
+
+ char **argv = xmalloc((argc + 1) * sizeof(char*));
+ argv[0] = argv0;
+ argv[argc] = NULL;
+
+ argc = 1;
+ prev_was_space = true;
+ for (size_t i = 0; env[i] != '\0'; ++i) {
+ if (isspace(env[i])) {
+ prev_was_space = true;
+ } else if (prev_was_space) {
+ prev_was_space = false;
+ argv[argc++] = env + i;
+ }
+ }
+
+ parse_real((int)(argc), argv);
+
+ free(env);
+
+ return;
+}
+
+
+static void
+set_compression_settings(void)
+{
+ if (filter_count == 0) {
+ opt_filters[0].id = LZMA_FILTER_LZMA;
+ opt_filters[0].options = (lzma_options_lzma *)(
+ lzma_preset_lzma + preset_number);
+ filter_count = 1;
+ }
+
+ // Terminate the filter options array.
+ opt_filters[filter_count].id = LZMA_VLI_VALUE_UNKNOWN;
+
+ // Optimize the filter chain a little by removing all
+ // Copy filters.
+ for (size_t i = 0; opt_filters[i].id != LZMA_VLI_VALUE_UNKNOWN; ++i) {
+ while (opt_filters[i].id == LZMA_FILTER_COPY) {
+ size_t j = i;
+ do {
+ opt_filters[j] = opt_filters[j + 1];
+ } while (opt_filters[++j].id
+ != LZMA_VLI_VALUE_UNKNOWN);
+ }
+ }
+
+ const uint32_t memory_limit = opt_memory / (1024 * 1024) + 1;
+ uint32_t memory_usage = lzma_memory_usage(opt_filters, true);
+
+ // Don't go over the memory limits when the default
+ // setting is used.
+ if (preset_default) {
+ while (memory_usage > memory_limit) {
+ if (preset_number == 0) {
+ errmsg(V_ERROR, _("Memory usage limit is too "
+ "small for any internal "
+ "filter preset"));
+ my_exit(ERROR);
+ }
+
+ --preset_number;
+ opt_filters[0].options = (lzma_options_lzma *)(
+ lzma_preset_lzma
+ + preset_number);
+ memory_usage = lzma_memory_usage(opt_filters,
+ true);
+ }
+ } else {
+ if (memory_usage > memory_limit) {
+ errmsg(V_ERROR, _("Memory usage limit is too small "
+ "for the given filter setup"));
+ my_exit(ERROR);
+ }
+ }
+
+ // Limit the number of worked threads so that memory usage
+ // limit isn't exceeded.
+ // FIXME: Probably should use bytes instead of mebibytes for
+ // memory_usage and memory_limit.
+ if (memory_usage == 0)
+ memory_usage = 1;
+
+ size_t thread_limit = memory_limit / memory_usage;
+ if (thread_limit == 0)
+ thread_limit = 1;
+
+ if (opt_threads > thread_limit)
+ opt_threads = thread_limit;
+
+ return;
+}
+
+
+extern char **
+parse_args(int argc, char **argv)
+{
+ // Check how we were called.
+ {
+ const char *name = str_filename(argv[0]);
+ if (name != NULL) {
+ if (strstr(name, "cat") != NULL) {
+ opt_mode = MODE_DECOMPRESS;
+ opt_stdout = true;
+ } else if (strstr(name, "un") != NULL) {
+ opt_mode = MODE_DECOMPRESS;
+ }
+ }
+ }
+
+ // First the flags from environment
+ parse_environment();
+
+ // Then from the command line
+ optind = 1;
+ parse_real(argc, argv);
+
+ // Never remove the source file when the destination is not on disk.
+ // In test mode the data is written nowhere, but setting opt_stdout
+ // will make the rest of the code behave well.
+ if (opt_stdout || opt_mode == MODE_TEST) {
+ opt_keep_original = true;
+ opt_stdout = true;
+ }
+
+ if (opt_mode == MODE_COMPRESS)
+ set_compression_settings();
+
+ // If no filenames are given, use stdin.
+ if (argv[optind] == NULL && opt_files_name == NULL) {
+ // We don't modify or free() the "-" constant.
+ static char *argv_stdin[2] = { (char *)"-", NULL };
+ return argv_stdin;
+ }
+
+ return argv + optind;
+}
diff --git a/src/lzma/args.h b/src/lzma/args.h
new file mode 100644
index 00000000..4f19a01e
--- /dev/null
+++ b/src/lzma/args.h
@@ -0,0 +1,64 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file args.h
+/// \brief Argument parsing
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef ARGS_H
+#define ARGS_H
+
+#include "private.h"
+
+
+enum tool_mode {
+ MODE_COMPRESS,
+ MODE_DECOMPRESS,
+ MODE_TEST,
+ MODE_LIST,
+};
+
+enum header_type {
+ HEADER_AUTO,
+ HEADER_NATIVE,
+ HEADER_SINGLE,
+ HEADER_MULTI,
+ HEADER_ALONE,
+ // HEADER_GZIP,
+};
+
+
+extern char *opt_suffix;
+
+extern char *opt_files_name;
+extern char opt_files_split;
+extern FILE *opt_files_file;
+
+extern bool opt_stdout;
+extern bool opt_force;
+extern bool opt_keep_original;
+extern bool opt_preserve_name;
+// extern bool opt_recursive;
+extern enum tool_mode opt_mode;
+extern enum header_type opt_header;
+
+extern lzma_check_type opt_check;
+extern lzma_options_filter opt_filters[8];
+
+extern const char *stdin_filename;
+
+extern char **parse_args(int argc, char **argv);
+
+#endif
diff --git a/src/lzma/error.c b/src/lzma/error.c
new file mode 100644
index 00000000..a83de27a
--- /dev/null
+++ b/src/lzma/error.c
@@ -0,0 +1,156 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file error.c
+/// \brief Error message printing
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "private.h"
+#include <stdarg.h>
+
+
+exit_status_type exit_status = SUCCESS;
+verbosity_type verbosity = V_WARNING;
+char *argv0 = NULL;
+volatile sig_atomic_t user_abort = 0;
+
+
+extern const char *
+str_strm_error(lzma_ret code)
+{
+ switch (code) {
+ case LZMA_OK:
+ return _("Operation successful");
+
+ case LZMA_STREAM_END:
+ return _("Operation finished successfully");
+
+ case LZMA_PROG_ERROR:
+ return _("Internal error (bug)");
+
+ case LZMA_DATA_ERROR:
+ return _("Compressed data is corrupt");
+
+ case LZMA_MEM_ERROR:
+ return strerror(ENOMEM);
+
+ case LZMA_BUF_ERROR:
+ return _("Unexpected end of input");
+
+ case LZMA_HEADER_ERROR:
+ return _("Unsupported options");
+
+ case LZMA_UNSUPPORTED_CHECK:
+ return _("Unsupported integrity check type");
+
+ default:
+ return NULL;
+ }
+}
+
+
+extern void
+set_exit_status(exit_status_type new_status)
+{
+ static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+ pthread_mutex_lock(&mutex);
+
+ if (new_status != WARNING || exit_status == SUCCESS)
+ exit_status = new_status;
+
+ pthread_mutex_unlock(&mutex);
+ return;
+}
+
+
+extern void lzma_attribute((noreturn))
+my_exit(int status)
+{
+ // Close stdout. If something goes wrong, print an error message
+ // to stderr.
+ {
+ const int ferror_err = ferror(stdout);
+ const int fclose_err = fclose(stdout);
+ if (fclose_err) {
+ errmsg(V_ERROR, _("Writing to standard output "
+ "failed: %s"), strerror(errno));
+ status = ERROR;
+ } else if (ferror_err) {
+ // Some error has occurred but we have no clue about
+ // the reason since fclose() succeeded.
+ errmsg(V_ERROR, _("Writing to standard output "
+ "failed: %s"), "Unknown error");
+ status = ERROR;
+ }
+ }
+
+ // Close stderr. If something goes wrong, there's nothing where we
+ // could print an error message. Just set the exit status.
+ {
+ const int ferror_err = ferror(stderr);
+ const int fclose_err = fclose(stderr);
+ if (fclose_err || ferror_err)
+ status = ERROR;
+ }
+
+ exit(status);
+}
+
+
+extern void lzma_attribute((format(printf, 2, 3)))
+errmsg(verbosity_type v, const char *fmt, ...)
+{
+ va_list ap;
+
+ if (v <= verbosity) {
+ va_start(ap, fmt);
+
+ static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+ pthread_mutex_lock(&mutex);
+
+ fprintf(stderr, "%s: ", argv0);
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+
+ pthread_mutex_unlock(&mutex);
+
+ va_end(ap);
+ }
+
+ if (v == V_ERROR)
+ set_exit_status(ERROR);
+ else if (v == V_WARNING)
+ set_exit_status(WARNING);
+
+ return;
+}
+
+
+extern void
+out_of_memory(void)
+{
+ errmsg(V_ERROR, "%s", strerror(ENOMEM));
+ user_abort = 1;
+ return;
+}
+
+
+extern void
+internal_error(void)
+{
+ errmsg(V_ERROR, _("Internal error (bug)"));
+ user_abort = 1;
+ return;
+}
diff --git a/src/lzma/error.h b/src/lzma/error.h
new file mode 100644
index 00000000..34ec30e1
--- /dev/null
+++ b/src/lzma/error.h
@@ -0,0 +1,67 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file error.c
+/// \brief Error message printing
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef ERROR_H
+#define ERROR_H
+
+#include "private.h"
+
+
+typedef enum {
+ SUCCESS = 0,
+ ERROR = 1,
+ WARNING = 2,
+} exit_status_type;
+
+
+typedef enum {
+ V_SILENT,
+ V_ERROR,
+ V_WARNING,
+ V_VERBOSE,
+ V_DEBUG,
+} verbosity_type;
+
+
+extern exit_status_type exit_status;
+
+extern verbosity_type verbosity;
+
+/// Like GNU's program_invocation_name but portable
+extern char *argv0;
+
+/// Once this is non-zero, all threads must shutdown and clean up incomplete
+/// output files from the disk.
+extern volatile sig_atomic_t user_abort;
+
+
+extern const char * str_strm_error(lzma_ret code);
+
+extern void errmsg(verbosity_type v, const char *fmt, ...)
+ lzma_attribute((format(printf, 2, 3)));
+
+extern void set_exit_status(exit_status_type new_status);
+
+extern void my_exit(int status) lzma_attribute((noreturn));
+
+extern void out_of_memory(void);
+
+extern void internal_error(void);
+
+#endif
diff --git a/src/lzma/hardware.c b/src/lzma/hardware.c
new file mode 100644
index 00000000..6cb3cdfc
--- /dev/null
+++ b/src/lzma/hardware.c
@@ -0,0 +1,99 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file hardware.c
+/// \brief Detection of available hardware resources
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "private.h"
+#include "physmem.h"
+
+
+/// Maximum number of free *coder* threads. This can be set with
+/// the --threads=NUM command line option.
+size_t opt_threads = 1;
+
+
+/// Number of bytes of memory to use at maximum (only a rough limit).
+/// This can be set with the --memory=NUM command line option.
+/// If no better value can be determined, the default is 14 MiB, which
+/// should be quite safe even for older systems while still allowing
+/// reasonable compression ratio.
+size_t opt_memory = 14 * 1024 * 1024;
+
+
+/// Get the amount of physical memory, and set opt_memory to 1/3 of it.
+/// User can then override this with --memory command line option.
+static void
+hardware_memory(void)
+{
+ uint64_t mem = physmem();
+ if (mem != 0) {
+ mem /= 3;
+
+#if UINT64_MAX > SIZE_MAX
+ if (mem > SIZE_MAX)
+ mem = SIZE_MAX;
+#endif
+
+ opt_memory = mem;
+ }
+
+ return;
+}
+
+
+/// Get the number of CPU cores, and set opt_threads to default to that value.
+/// User can then override this with --threads command line option.
+static void
+hardware_cores(void)
+{
+#if defined(HAVE_NUM_PROCESSORS_SYSCONF)
+ const long cpus = sysconf(_SC_NPROCESSORS_ONLN);
+ if (cpus > 0)
+ opt_threads = (size_t)(cpus);
+
+#elif defined(HAVE_NUM_PROCESSORS_SYSCTL)
+ int name[2] = { CTL_HW, HW_NCPU };
+ int cpus;
+ size_t cpus_size = sizeof(cpus);
+ if (!sysctl(name, &cpus, &cpus_size, NULL, NULL)
+ && cpus_size == sizeof(cpus) && cpus > 0)
+ opt_threads = (size_t)(cpus);
+#endif
+
+ // Limit opt_threads so that maximum number of threads doesn't exceed.
+
+#if defined(_SC_THREAD_THREADS_MAX)
+ const long threads_max = sysconf(_SC_THREAD_THREADS_MAX);
+ if (threads_max > 0 && (size_t)(threads_max) < opt_threads)
+ opt_threads = (size_t)(threads_max);
+
+#elif defined(PTHREAD_THREADS_MAX)
+ if (opt_threads > PTHREAD_THREADS_MAX)
+ opt_threads = PTHREAD_THREADS_MAX;
+#endif
+
+ return;
+}
+
+
+extern void
+hardware_init(void)
+{
+ hardware_memory();
+ hardware_cores();
+ return;
+}
diff --git a/src/lzma/hardware.h b/src/lzma/hardware.h
new file mode 100644
index 00000000..d47bd29f
--- /dev/null
+++ b/src/lzma/hardware.h
@@ -0,0 +1,31 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file hardware.c
+/// \brief Detection of available hardware resources
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef HARDWARE_H
+#define HARDWARE_H
+
+#include "private.h"
+
+
+extern size_t opt_threads;
+extern size_t opt_memory;
+
+extern void hardware_init(void);
+
+#endif
diff --git a/src/lzma/help.c b/src/lzma/help.c
new file mode 100644
index 00000000..ad7dd861
--- /dev/null
+++ b/src/lzma/help.c
@@ -0,0 +1,178 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file help.c
+/// \brief Help messages
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "private.h"
+
+
+extern void
+show_try_help(void)
+{
+ // Print this with V_WARNING instead of V_ERROR to prevent it from
+ // showing up when --quiet has been specified.
+ errmsg(V_WARNING, _("Try `%s --help' for more information."), argv0);
+ return;
+}
+
+
+extern void lzma_attribute((noreturn))
+show_help(void)
+{
+ printf(_("Usage: %s [OPTION]... [FILE]...\n"
+ "Compress or decompress FILEs in the .lzma format.\n"
+ "\n"), argv0);
+
+ puts(_("Mandatory arguments to long options are mandatory for "
+ "short options too.\n"));
+
+ puts(_(
+" Operation mode:\n"
+"\n"
+" -z, --compress force compression\n"
+" -d, --decompress force decompression\n"
+" -t, --test test compressed file integrity\n"
+" -l, --list list block sizes, total sizes, and possible metadata\n"
+));
+
+ puts(_(
+" Operation modifiers:\n"
+"\n"
+" -k, --keep keep (don't delete) input files\n"
+" -f, --force force overwrite of output file and (de)compress links\n"
+" -c, --stdout write to standard output and don't delete input files\n"
+" -S, --suffix=.SUF use suffix `.SUF' on compressed files instead of `.lzma'\n"
+" -F, --format=FMT file format to encode or decode; possible values are\n"
+" `auto', `native', `single', `multi', and `alone'\n"
+" --files=[FILE] read filenames to process from FILE; if FILE is\n"
+" omitted, filenames are read from the standard input;\n"
+" filenames must be terminated with the newline character\n"
+" --files0=[FILE] like --files but use the nul byte as terminator\n"
+));
+
+ puts(_(
+" Compression presets and basic compression options:\n"
+"\n"
+" -1 .. -2 fast compression\n"
+" -3 .. -6 good compression\n"
+" -7 .. -9 excellent compression, but needs a lot of memory;\n"
+" default is -7 if memory limit allows\n"
+"\n"
+" -C, --check=CHECK integrity check type: `crc32', `crc64' (default),\n"
+" or `sha256'\n"
+));
+
+ puts(_(
+" Custom filter chain for compression (alternative for using presets):\n"
+"\n"
+" --lzma=[OPTS] LZMA filter; OPTS is a comma-separated list of zero or\n"
+" more of the following options (valid values; default):\n"
+" dict=NUM dictionary size in bytes (1 - 1Gi; 8Mi)\n"
+" lc=NUM number of literal context bits (0-8; 3)\n"
+" lp=NUM number of literal position bits (0-4; 0)\n"
+" pb=NUM number of position bits (0-4; 2)\n"
+" mode=MODE compression mode (`fast' or `best'; `best')\n"
+" fb=NUM number of fast bytes (5-273; 128)\n"
+" mf=NAME match finder (hc3, hc4, bt2, bt3, bt4; bt4)\n"
+" mfc=NUM match finder cycles; 0=automatic (default)\n"
+"\n"
+" --x86 x86 filter (sometimes called BCJ filter)\n"
+" --powerpc PowerPC (big endian) filter\n"
+" --ia64 IA64 (Itanium) filter\n"
+" --arm ARM filter\n"
+" --armthumb ARM-Thumb filter\n"
+" --sparc SPARC filter\n"
+"\n"
+" --copy No filtering (useful only when specified alone)\n"
+" --subblock=[OPTS] Subblock filter; valid OPTS (valid values; default):\n"
+" size=NUM number of bytes of data per subblock\n"
+" (1 - 256Mi; 4Ki)\n"
+" rle=NUM run-length encoder chunk size (0-256; 0)\n"
+));
+
+/*
+These aren't implemented yet.
+
+ puts(_(
+" Metadata options:\n"
+"\n"
+" -N, --name save or restore the original filename and time stamp\n"
+" -n, --no-name do not save or restore filename and time stamp (default)\n"
+" -S, --sign=KEY sign the data with GnuPG when compressing, or verify\n"
+" the signature when decompressing\n"));
+*/
+
+ puts(_(
+" Resource usage options:\n"
+"\n"
+" -M, --memory=NUM use roughly NUM bytes of memory at maximum\n"
+" -T, --threads=NUM use at maximum of NUM (de)compression threads\n"
+// " --threading=STR threading style; possible values are `auto' (default),\n"
+// " `files', and `stream'
+));
+
+ puts(_(
+" Other options:\n"
+"\n"
+" -q, --quiet suppress warnings; specify twice to suppress errors too\n"
+" -v, --verbose be verbose; specify twice for even more verbose\n"
+"\n"
+" -h, --help display this help and exit\n"
+" -V, --version display version and license information and exit\n"));
+
+ puts(_("With no FILE, or when FILE is -, read standard input.\n"));
+
+ size_t mem_limit = opt_memory / (1024 * 1024);
+ if (mem_limit == 0)
+ mem_limit = 1;
+
+ puts(_("On this system and configuration, the tool will use"));
+ printf(_(" * roughly %zu MiB of memory at maximum; and\n"),
+ mem_limit);
+ printf(N_(
+ " * at maximum of one thread for (de)compression.\n\n",
+ " * at maximum of %zu threads for (de)compression.\n\n",
+ opt_threads), opt_threads);
+
+ printf(_("Report bugs to <%s> (in English or Finnish).\n"),
+ PACKAGE_BUGREPORT);
+
+ my_exit(SUCCESS);
+}
+
+
+extern void lzma_attribute((noreturn))
+show_version(void)
+{
+ printf(
+"lzma (LZMA Utils) " PACKAGE_VERSION "\n"
+"\n"
+"Copyright (C) 1999-2006 Igor Pavlov\n"
+"Copyright (C) 2007 Lasse Collin\n"
+"\n"
+"This program is free software; you can redistribute it and/or modify\n"
+"it under the terms of the GNU General Public License as published by\n"
+"the Free Software Foundation; either version 2 of the License, or\n"
+"(at your option) any later version.\n"
+"\n"
+"This program is distributed in the hope that it will be useful,\n"
+"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
+"GNU General Public License for more details.\n"
+"\n");
+ my_exit(SUCCESS);
+}
diff --git a/src/lzma/help.h b/src/lzma/help.h
new file mode 100644
index 00000000..659c66a0
--- /dev/null
+++ b/src/lzma/help.h
@@ -0,0 +1,32 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file help.h
+/// \brief Help messages
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef HELP_H
+#define HELP_H
+
+#include "private.h"
+
+
+extern void show_try_help(void);
+
+extern void show_help(void) lzma_attribute((noreturn));
+
+extern void show_version(void) lzma_attribute((noreturn));
+
+#endif
diff --git a/src/lzma/io.c b/src/lzma/io.c
new file mode 100644
index 00000000..a7683fcc
--- /dev/null
+++ b/src/lzma/io.c
@@ -0,0 +1,664 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file io.c
+/// \brief File opening, unlinking, and closing
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "private.h"
+
+#if defined(HAVE_FUTIMES) || defined(HAVE_FUTIMESAT)
+# include <sys/time.h>
+#endif
+
+#ifndef O_SEARCH
+# define O_SEARCH O_RDONLY
+#endif
+
+
+/// \brief Number of open file_pairs
+///
+/// Once the main() function has requested processing of all files,
+/// we wait that open_pairs drops back to zero. Then it is safe to
+/// exit from the program.
+static size_t open_pairs = 0;
+
+
+/// \brief mutex for file system operations
+///
+/// All file system operations are done via the functions in this file.
+/// They use fchdir() to avoid some race conditions (more portable than
+/// openat() & co.).
+///
+/// Synchronizing all file system operations shouldn't affect speed notably,
+/// since the actual reading from and writing to files is done in parallel.
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+
+
+/// This condition is invoked when a file is closed and the value of
+/// the open_files variable has dropped to zero. The only listener for
+/// this condition is io_finish() which is called from main().
+static pthread_cond_t io_cond = PTHREAD_COND_INITIALIZER;
+
+
+/// True when stdout is being used by some thread
+static bool stdout_in_use = false;
+
+
+/// This condition is signalled when a thread releases stdout (no longer
+/// writes data to it).
+static pthread_cond_t stdout_cond = PTHREAD_COND_INITIALIZER;
+
+
+/// \brief Directory where we were started
+///
+/// This is needed when a new file, whose name was given on command line,
+/// is opened.
+static int start_dir;
+
+
+static uid_t uid;
+static gid_t gid;
+
+
+extern void
+io_init(void)
+{
+ start_dir = open(".", O_SEARCH | O_NOCTTY);
+ if (start_dir == -1) {
+ errmsg(V_ERROR, _("Cannot get file descriptor of the current "
+ "directory: %s"), strerror(errno));
+ my_exit(ERROR);
+ }
+
+ uid = getuid();
+ gid = getgid();
+
+ return;
+}
+
+
+/// Waits until the number of open file_pairs has dropped to zero.
+extern void
+io_finish(void)
+{
+ pthread_mutex_lock(&mutex);
+
+ while (open_pairs != 0)
+ pthread_cond_wait(&io_cond, &mutex);
+
+ (void)close(start_dir);
+
+ pthread_mutex_unlock(&mutex);
+
+ return;
+}
+
+
+/// \brief Unlinks a file
+///
+/// \param dir_fd File descriptor of the directory containing the file
+/// \param name Name of the file with or without path
+///
+/// \return Zero on success. On error, -1 is returned and errno set.
+///
+static void
+io_unlink(int dir_fd, const char *name, ino_t ino)
+{
+ const char *base = str_filename(name);
+ if (base == NULL) {
+ // This shouldn't happen.
+ errmsg(V_ERROR, _("%s: Invalid filename"), name);
+ return;
+ }
+
+ pthread_mutex_lock(&mutex);
+
+ if (fchdir(dir_fd)) {
+ errmsg(V_ERROR, _("Cannot change directory: %s"),
+ strerror(errno));
+ } else {
+ struct stat st;
+ if (lstat(base, &st) || st.st_ino != ino)
+ errmsg(V_ERROR, _("%s: File seems to be moved, "
+ "not removing"), name);
+
+ // There's a race condition between lstat() and unlink()
+ // but at least we have tried to avoid removing wrong file.
+ else if (unlink(base))
+ errmsg(V_ERROR, _("%s: Cannot remove: %s"),
+ name, strerror(errno));
+ }
+
+ pthread_mutex_unlock(&mutex);
+
+ return;
+}
+
+
+/// \brief Copies owner/group and permissions
+///
+/// \todo ACL and EA support
+///
+static void
+io_copy_attrs(const file_pair *pair)
+{
+ // This function is more tricky than you may think at first.
+ // Blindly copying permissions may permit users to access the
+ // destination file who didn't have permission to access the
+ // source file.
+
+ if (uid == 0 && fchown(pair->dest_fd, pair->src_st.st_uid, -1))
+ errmsg(V_WARNING, _("%s: Cannot set the file owner: %s"),
+ pair->dest_name, strerror(errno));
+
+ mode_t mode;
+
+ if (fchown(pair->dest_fd, -1, pair->src_st.st_gid)) {
+ errmsg(V_WARNING, _("%s: Cannot set the file group: %s"),
+ pair->dest_name, strerror(errno));
+ // We can still safely copy some additional permissions:
+ // `group' must be at least as strict as `other' and
+ // also vice versa.
+ //
+ // NOTE: After this, the owner of the source file may
+ // get additional permissions. This shouldn't be too bad,
+ // because the owner would have had permission to chmod
+ // the original file anyway.
+ mode = ((pair->src_st.st_mode & 0070) >> 3)
+ & (pair->src_st.st_mode & 0007);
+ mode = (pair->src_st.st_mode & 0700) | (mode << 3) | mode;
+ } else {
+ // Drop the setuid, setgid, and sticky bits.
+ mode = pair->src_st.st_mode & 0777;
+ }
+
+ if (fchmod(pair->dest_fd, mode))
+ errmsg(V_WARNING, _("%s: Cannot set the file permissions: %s"),
+ pair->dest_name, strerror(errno));
+
+ // Copy the timestamps only if we have a secure function to do it.
+#if defined(HAVE_FUTIMES) || defined(HAVE_FUTIMESAT)
+ struct timeval tv[2];
+ tv[0].tv_sec = pair->src_st.st_atime;
+ tv[1].tv_sec = pair->src_st.st_mtime;
+
+# if defined(HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC)
+ tv[0].tv_usec = pair->src_st.st_atim.tv_nsec / 1000;
+# elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC)
+ tv[0].tv_usec = pair->src_st.st_atimespec.tv_nsec / 1000;
+# else
+ tv[0].tv_usec = 0;
+# endif
+
+# if defined(HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC)
+ tv[1].tv_usec = pair->src_st.st_mtim.tv_nsec / 1000;
+# elif defined(HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC)
+ tv[1].tv_usec = pair->src_st.st_mtimespec.tv_nsec / 1000;
+# else
+ tv[1].tv_usec = 0;
+# endif
+
+# ifdef HAVE_FUTIMES
+ (void)futimes(pair->dest_fd, tv);
+# else
+ (void)futimesat(pair->dest_fd, NULL, tv);
+# endif
+#endif
+
+ return;
+}
+
+
+/// Opens and changes into the directory containing the source file.
+static int
+io_open_dir(file_pair *pair)
+{
+ if (pair->src_name == stdin_filename)
+ return 0;
+
+ if (fchdir(start_dir)) {
+ errmsg(V_ERROR, _("Cannot change directory: %s"),
+ strerror(errno));
+ return -1;
+ }
+
+ const char *split = strrchr(pair->src_name, '/');
+ if (split == NULL) {
+ pair->dir_fd = start_dir;
+ } else {
+ // Copy also the slash. It's needed to support filenames
+ // like "/foo" (dirname being "/"), and it never hurts anyway.
+ const size_t dirname_len = split - pair->src_name + 1;
+ char dirname[dirname_len + 1];
+ memcpy(dirname, pair->src_name, dirname_len);
+ dirname[dirname_len] = '\0';
+
+ // Open the directory and change into it.
+ pair->dir_fd = open(dirname, O_SEARCH | O_NOCTTY);
+ if (pair->dir_fd == -1 || fchdir(pair->dir_fd)) {
+ errmsg(V_ERROR, _("%s: Cannot open the directory "
+ "containing the file: %s"),
+ pair->src_name, strerror(errno));
+ (void)close(pair->dir_fd);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+
+static void
+io_close_dir(file_pair *pair)
+{
+ if (pair->dir_fd != start_dir)
+ (void)close(pair->dir_fd);
+
+ return;
+}
+
+
+/// Opens the source file. The file is opened using the plain filename without
+/// path, thus the file must be in the current working directory. This is
+/// ensured because io_open_dir() is always called before this function.
+static int
+io_open_src(file_pair *pair)
+{
+ if (pair->src_name == stdin_filename) {
+ pair->src_fd = STDIN_FILENO;
+ } else {
+ // Strip the pathname. Thanks to io_open_dir(), the file
+ // is now in the current working directory.
+ const char *filename = str_filename(pair->src_name);
+ if (filename == NULL)
+ return -1;
+
+ // Symlinks are followed if --stdout or --force has been
+ // specified.
+ const bool follow_symlinks = opt_stdout || opt_force;
+ pair->src_fd = open(filename, O_RDONLY | O_NOCTTY
+ | (follow_symlinks ? 0 : O_NOFOLLOW));
+ if (pair->src_fd == -1) {
+ // Give an understandable error message in if reason
+ // for failing was that the file was a symbolic link.
+ // - Linux, OpenBSD, Solaris: ELOOP
+ // - FreeBSD: EMLINK
+ // - Tru64: ENOTSUP
+ // It seems to be safe to check for all these, since
+ // those errno values aren't used for other purporses
+ // on any of the listed operating system *when* the
+ // above flags are used with open().
+ if (!follow_symlinks
+ && (errno == ELOOP
+#ifdef EMLINK
+ || errno == EMLINK
+#endif
+#ifdef ENOTSUP
+ || errno == ENOTSUP
+#endif
+ )) {
+ errmsg(V_WARNING, _("%s: Is a symbolic link, "
+ "skipping"), pair->src_name);
+ } else {
+ errmsg(V_ERROR, "%s: %s", pair->src_name,
+ strerror(errno));
+ }
+
+ return -1;
+ }
+
+ if (fstat(pair->src_fd, &pair->src_st)) {
+ errmsg(V_ERROR, "%s: %s", pair->src_name,
+ strerror(errno));
+ goto error;
+ }
+
+ if (S_ISDIR(pair->src_st.st_mode)) {
+ errmsg(V_WARNING, _("%s: Is a directory, skipping"),
+ pair->src_name);
+ goto error;
+ }
+
+ if (!opt_stdout) {
+ if (!opt_force && !S_ISREG(pair->src_st.st_mode)) {
+ errmsg(V_WARNING, _("%s: Not a regular file, "
+ "skipping"), pair->src_name);
+ goto error;
+ }
+
+ if (pair->src_st.st_mode & (S_ISUID | S_ISGID)) {
+ // Setuid and setgid files are rejected even
+ // with --force. This is good for security
+ // (hopefully) but it's a bit weird to reject
+ // file when --force was given. At least this
+ // matches gzip's behavior.
+ errmsg(V_WARNING, _("%s: File has setuid or "
+ "setgid bit set, skipping"),
+ pair->src_name);
+ goto error;
+ }
+
+ if (!opt_force && (pair->src_st.st_mode & S_ISVTX)) {
+ errmsg(V_WARNING, _("%s: File has sticky bit "
+ "set, skipping"),
+ pair->src_name);
+ goto error;
+ }
+
+ if (pair->src_st.st_nlink > 1) {
+ errmsg(V_WARNING, _("%s: Input file has more "
+ "than one hard link, "
+ "skipping"), pair->src_name);
+ goto error;
+ }
+ }
+ }
+
+ return 0;
+
+error:
+ (void)close(pair->src_fd);
+ return -1;
+}
+
+
+/// \brief Closes source file of the file_pair structure
+///
+/// \param pair File whose src_fd should be closed
+/// \param success If true, the file will be removed from the disk if
+/// closing succeeds and --keep hasn't been used.
+static void
+io_close_src(file_pair *pair, bool success)
+{
+ if (pair->src_fd == STDIN_FILENO || pair->src_fd == -1)
+ return;
+
+ if (close(pair->src_fd)) {
+ errmsg(V_ERROR, _("%s: Closing the file failed: %s"),
+ pair->src_name, strerror(errno));
+ } else if (success && !opt_keep_original) {
+ io_unlink(pair->dir_fd, pair->src_name, pair->src_st.st_ino);
+ }
+
+ return;
+}
+
+
+static int
+io_open_dest(file_pair *pair)
+{
+ if (opt_stdout || pair->src_fd == STDIN_FILENO) {
+ // We don't modify or free() this.
+ pair->dest_name = (char *)"(stdout)";
+ pair->dest_fd = STDOUT_FILENO;
+
+ // Synchronize the order in which files get written to stdout.
+ // Unlocking the mutex is safe, because opening the file_pair
+ // can no longer fail.
+ while (stdout_in_use)
+ pthread_cond_wait(&stdout_cond, &mutex);
+
+ stdout_in_use = true;
+
+ } else {
+ pair->dest_name = get_dest_name(pair->src_name);
+ if (pair->dest_name == NULL)
+ return -1;
+
+ // This cannot fail, because get_dest_name() doesn't return
+ // invalid names.
+ const char *filename = str_filename(pair->dest_name);
+ assert(filename != NULL);
+
+ pair->dest_fd = open(filename, O_WRONLY | O_NOCTTY | O_CREAT
+ | (opt_force ? O_TRUNC : O_EXCL),
+ S_IRUSR | S_IWUSR);
+ if (pair->dest_fd == -1) {
+ errmsg(V_ERROR, "%s: %s", pair->dest_name,
+ strerror(errno));
+ free(pair->dest_name);
+ return -1;
+ }
+
+ // If this really fails... well, we have a safe fallback.
+ struct stat st;
+ if (fstat(pair->dest_fd, &st))
+ pair->dest_ino = 0;
+ else
+ pair->dest_ino = st.st_ino;
+ }
+
+ return 0;
+}
+
+
+/// \brief Closes destination file of the file_pair structure
+///
+/// \param pair File whose dest_fd should be closed
+/// \param success If false, the file will be removed from the disk.
+///
+/// \return Zero if closing succeeds. On error, -1 is returned and
+/// error message printed.
+static int
+io_close_dest(file_pair *pair, bool success)
+{
+ if (pair->dest_fd == -1)
+ return 0;
+
+ if (pair->dest_fd == STDOUT_FILENO) {
+ stdout_in_use = false;
+ pthread_cond_signal(&stdout_cond);
+ return 0;
+ }
+
+ if (close(pair->dest_fd)) {
+ errmsg(V_ERROR, _("%s: Closing the file failed: %s"),
+ pair->dest_name, strerror(errno));
+
+ // Closing destination file failed, so we cannot trust its
+ // contents. Get rid of junk:
+ io_unlink(pair->dir_fd, pair->dest_name, pair->dest_ino);
+ free(pair->dest_name);
+ return -1;
+ }
+
+ // If the operation using this file wasn't successful, we git rid
+ // of the junk file.
+ if (!success)
+ io_unlink(pair->dir_fd, pair->dest_name, pair->dest_ino);
+
+ free(pair->dest_name);
+
+ return 0;
+}
+
+
+extern file_pair *
+io_open(const char *src_name)
+{
+ if (is_empty_filename(src_name))
+ return NULL;
+
+ file_pair *pair = malloc(sizeof(file_pair));
+ if (pair == NULL) {
+ out_of_memory();
+ return NULL;
+ }
+
+ *pair = (file_pair){
+ .src_name = src_name,
+ .dest_name = NULL,
+ .dir_fd = -1,
+ .src_fd = -1,
+ .dest_fd = -1,
+ .src_eof = false,
+ };
+
+ pthread_mutex_lock(&mutex);
+
+ ++open_pairs;
+
+ if (io_open_dir(pair))
+ goto error_dir;
+
+ if (io_open_src(pair))
+ goto error_src;
+
+ if (user_abort || io_open_dest(pair))
+ goto error_dest;
+
+ pthread_mutex_unlock(&mutex);
+
+ return pair;
+
+error_dest:
+ io_close_src(pair, false);
+error_src:
+ io_close_dir(pair);
+error_dir:
+ --open_pairs;
+ pthread_mutex_unlock(&mutex);
+ free(pair);
+ return NULL;
+}
+
+
+/// \brief Closes the file descriptors and frees the structure
+extern void
+io_close(file_pair *pair, bool success)
+{
+ if (success && pair->dest_fd != STDOUT_FILENO)
+ io_copy_attrs(pair);
+
+ // Close the destination first. If it fails, we must not remove
+ // the source file!
+ if (!io_close_dest(pair, success)) {
+ // Closing destination file succeeded. Remove the source file
+ // if the operation using this file pair was successful
+ // and we haven't been requested to keep the source file.
+ io_close_src(pair, success);
+ } else {
+ // We don't care if operation using this file pair was
+ // successful or not, since closing the destination file
+ // failed. Don't remove the original file.
+ io_close_src(pair, false);
+ }
+
+ io_close_dir(pair);
+
+ free(pair);
+
+ pthread_mutex_lock(&mutex);
+
+ if (--open_pairs == 0)
+ pthread_cond_signal(&io_cond);
+
+ pthread_mutex_unlock(&mutex);
+
+ return;
+}
+
+
+/// \brief Reads from a file to a buffer
+///
+/// \param pair File pair having the sourcefile open for reading
+/// \param buf Destination buffer to hold the read data
+/// \param size Size of the buffer; assumed be smaller than SSIZE_MAX
+///
+/// \return On success, number of bytes read is returned. On end of
+/// file zero is returned and pair->src_eof set to true.
+/// On error, SIZE_MAX is returned and error message printed.
+///
+/// \note This does no locking, thus two threads must not read from
+/// the same file. This no problem in this program.
+extern size_t
+io_read(file_pair *pair, uint8_t *buf, size_t size)
+{
+ // We use small buffers here.
+ assert(size < SSIZE_MAX);
+
+ size_t left = size;
+
+ while (left > 0) {
+ const ssize_t amount = read(pair->src_fd, buf, left);
+
+ if (amount == 0) {
+ pair->src_eof = true;
+ break;
+ }
+
+ if (amount == -1) {
+ if (errno == EINTR) {
+ if (user_abort)
+ return SIZE_MAX;
+
+ continue;
+ }
+
+ errmsg(V_ERROR, _("%s: Read error: %s"),
+ pair->src_name, strerror(errno));
+
+ // FIXME Is this needed?
+ pair->src_eof = true;
+
+ return SIZE_MAX;
+ }
+
+ buf += (size_t)(amount);
+ left -= (size_t)(amount);
+ }
+
+ return size - left;
+}
+
+
+/// \brief Writes a buffer to a file
+///
+/// \param pair File pair having the destination file open for writing
+/// \param buf Buffer containing the data to be written
+/// \param size Size of the buffer; assumed be smaller than SSIZE_MAX
+///
+/// \return On success, zero is returned. On error, -1 is returned
+/// and error message printed.
+///
+/// \note This does no locking, thus two threads must not write to
+/// the same file. This no problem in this program.
+extern int
+io_write(const file_pair *pair, const uint8_t *buf, size_t size)
+{
+ assert(size < SSIZE_MAX);
+
+ while (size > 0) {
+ const ssize_t amount = write(pair->dest_fd, buf, size);
+ if (amount == -1) {
+ if (errno == EINTR) {
+ if (user_abort)
+ return -1;
+
+ continue;
+ }
+
+ errmsg(V_ERROR, _("%s: Write error: %s"),
+ pair->dest_name, strerror(errno));
+ return -1;
+ }
+
+ buf += (size_t)(amount);
+ size -= (size_t)(amount);
+ }
+
+ return 0;
+}
diff --git a/src/lzma/io.h b/src/lzma/io.h
new file mode 100644
index 00000000..d1aa17f4
--- /dev/null
+++ b/src/lzma/io.h
@@ -0,0 +1,60 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file io.h
+/// \brief I/O types and functions
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef IO_H
+#define IO_H
+
+#include "private.h"
+
+#if BUFSIZ <= 1024
+# define IO_BUFFER_SIZE 8192
+#else
+# define IO_BUFFER_SIZE BUFSIZ
+#endif
+
+
+typedef struct {
+ const char *src_name;
+ char *dest_name;
+
+ int dir_fd;
+ int src_fd;
+ int dest_fd;
+
+ struct stat src_st;
+ ino_t dest_ino;
+
+ bool src_eof;
+} file_pair;
+
+
+extern void io_init(void);
+
+extern void io_finish(void);
+
+extern file_pair *io_open(const char *src_name);
+
+extern void io_close(file_pair *pair, bool success);
+
+extern size_t io_read(file_pair *pair, uint8_t *buf, size_t size);
+
+extern int io_write(const file_pair *pair, const uint8_t *buf, size_t size);
+
+
+#endif
diff --git a/src/lzma/list.c b/src/lzma/list.c
new file mode 100644
index 00000000..61eb5702
--- /dev/null
+++ b/src/lzma/list.c
@@ -0,0 +1,477 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file list.c
+/// \brief Listing information about .lzma files
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "private.h"
+
+
+/*
+
+1. Check the file type: native, alone, unknown
+
+Alone:
+1. Show info about header. Don't look for concatenated parts.
+
+Native:
+1. Check that Stream Header is valid.
+2. Seek to the end of the file.
+3. Skip padding.
+4. Reverse decode Stream Footer.
+5. Seek Backward Size bytes.
+6.
+
+*/
+
+
+static void
+unsupported_file(file_handle *handle)
+{
+ errmsg(V_ERROR, "%s: Unsupported file type", handle->name);
+ set_exit_status(ERROR);
+ (void)io_close(handle);
+ return;
+}
+
+
+/// Primitive escaping function, that escapes only ASCII control characters.
+static void
+print_escaped(const uint8_t *str)
+{
+ while (*str != '\0') {
+ if (*str <= 0x1F || *str == 0x7F)
+ printf("\\x%02X", *str);
+ else
+ putchar(*str);
+
+ ++str;
+ }
+
+ return;
+}
+
+
+static void
+list_native(file_handle *handle)
+{
+ lzma_stream strm = LZMA_STREAM_INIT;
+ lzma_stream_flags flags;
+ lzma_ret ret = lzma_stream_header_decoder(&strm, &flags);
+
+}
+
+
+static void
+list_alone(const listing_handle *handle)
+{
+ if (handle->buffer[0] > (4 * 5 + 4) * 9 + 8) {
+ unsupported_file(handle);
+ return;
+ }
+
+ const unsigned int pb = handle->buffer[0] / (9 * 5);
+ handle->buffer[0] -= pb * 9 * 5;
+ const unsigned int lp = handle->buffer[0] / 9;
+ const unsigned int lc = handle->buffer[0] - lp * 9;
+
+ uint32_t dict = 0;
+ for (size_t i = 1; i < 5; ++i) {
+ dict <<= 8;
+ dict |= header[i];
+ }
+
+ if (dict > LZMA_DICTIONARY_SIZE_MAX) {
+ unsupported_file(handle);
+ return;
+ }
+
+ uint64_t uncompressed_size = 0;
+ for (size_t i = 5; i < 13; ++i) {
+ uncompressed_size <<= 8;
+ uncompressed_size |= header[i];
+ }
+
+ // Reject files with uncompressed size of 256 GiB or more. It's
+ // an arbitrary limit trying to avoid at least some false positives.
+ if (uncompressed_size != UINT64_MAX
+ && uncompressed_size >= (UINT64_C(1) << 38)) {
+ unsupported_file(handle);
+ return;
+ }
+
+ if (verbosity < V_WARNING) {
+ printf("name=");
+ print_escaped(handle->name);
+ printf("\nformat=alone\n");
+
+ if (uncompressed_size == UINT64_MAX)
+ printf("uncompressed_size=unknown\n");
+ else
+ printf("uncompressed_size=%" PRIu64 "\n",
+ uncompressed_size);
+
+ printf("dict=%" PRIu32 "\n", dict);
+
+ printf("lc=%u\nlp=%u\npb=%u\n\n", lc, lp, pb);
+
+ } else {
+ printf("File name: ");
+ print_escaped(handle->name);
+ printf("\nFile format: LZMA_Alone\n")
+
+ printf("Uncompressed size: ");
+ if (uncompressed_size == UINT64_MAX)
+ printf("unknown\n");
+ else
+ printf("%," PRIu64 " bytes (%" PRIu64 " MiB)\n",
+ uncompressed_size,
+ (uncompressed_size + 1024 * 512)
+ / (1024 * 1024));
+
+ printf("Dictionary size: %," PRIu32 " bytes "
+ "(%" PRIu32 " MiB)\n",
+ dict, (dict + 1024 * 512) / (1024 * 1024));
+
+ printf("Literal context bits (lc): %u\n", lc);
+ printf("Literal position bits (lc): %u\n", lp);
+ printf("Position bits (pb): %u\n", pb);
+ }
+
+ return;
+}
+
+
+
+
+typedef struct {
+ const char *filename;
+ struct stat st;
+ int fd;
+
+ lzma_stream strm;
+ lzma_stream_flags stream_flags;
+ lzma_info *info;
+
+ lzma_vli backward_size;
+ lzma_vli uncompressed_size;
+
+ size_t buffer_size;
+ uint8_t buffer[IO_BUFFER_SIZE];
+} listing_handle;
+
+
+static bool
+listing_pread(listing_handle *handle, uint64_t offset)
+{
+ if (offset >= (uint64_t)(handle->st.st_size)) {
+ errmsg(V_ERROR, "%s: Trying to read past the end of "
+ "the file.", handle->filename);
+ return true;
+ }
+
+#ifdef HAVE_PREAD
+ const ssize_t ret = pread(handle->fd, handle->buffer, IO_BUFFER_SIZE,
+ (off_t)(offset));
+#else
+ // Use lseek() + read() since we don't have pread(). We don't care
+ // to which offset the reading position is left.
+ if (lseek(handle->fd, (off_t)(offset), SEEK_SET) == -1) {
+ errmsg(V_ERROR, "%s: %s", handle->filename, strerror(errno));
+ return true;
+ }
+
+ const ssize_t ret = read(handle->fd, handle->buffer, IO_BUFFER_SIZE);
+#endif
+
+ if (ret == -1) {
+ errmsg(V_ERROR, "%s: %s", handle->filename, strerror(errno));
+ return true;
+ }
+
+ if (ret == 0) {
+ errmsg(V_ERROR, "%s: Trying to read past the end of "
+ "the file.", handle->filename);
+ return true;
+ }
+
+ handle->buffer_size = (size_t)(ret);
+ return false;
+}
+
+
+
+static bool
+parse_stream_header(listing_handle *handle)
+{
+ if (listing_pread(handle, 0))
+ return true;
+
+ // TODO Got enough input?
+
+ lzma_ret ret = lzma_stream_header_decoder(
+ &handle->strm, &handle->stream_flags);
+ if (ret != LZMA_OK) {
+ errmsg(V_ERROR, "%s: %s", handle->name, str_strm_error(ret));
+ return true;
+ }
+
+ handle->strm.next_in = handle->buffer;
+ handle->strm.avail_in = handle->buffer_size;
+ ret = lzma_code(&handle->strm, LZMA_RUN);
+ if (ret != LZMA_STREAM_END) {
+ assert(ret != LZMA_OK);
+ errmsg(V_ERROR, "%s: %s", handle->name, str_strm_error(ret));
+ return true;
+ }
+
+ return false;
+}
+
+
+static bool
+parse_stream_tail(listing_handle *handle)
+{
+ uint64_t offset = (uint64_t)(handle->st.st_size);
+
+ // Skip padding
+ do {
+ if (offset == 0) {
+ errmsg(V_ERROR, "%s: %s", handle->name,
+ str_strm_error(LZMA_DATA_ERROR));
+ return true;
+ }
+
+ if (offset < IO_BUFFER_SIZE)
+ offset = 0;
+ else
+ offset -= IO_BUFFER_SIZE;
+
+ if (listing_pread(handle, offset))
+ return true;
+
+ while (handle->buffer_size > 0
+ && handle->buffer[handle->buffer_size - 1]
+ == '\0')
+ --handle->buffer_size;
+
+ } while (handle->buffer_size == 0);
+
+ if (handle->buffer_size < LZMA_STREAM_TAIL_SIZE) {
+ // TODO
+ }
+
+ lzma_stream_flags stream_flags;
+ lzma_ret ret = lzma_stream_tail_decoder(&handle->strm, &stream_flags);
+ if (ret != LZMA_OK) {
+ errmsg(V_ERROR, "%s: %s", handle->name, str_strm_error(ret));
+ return true;
+ }
+
+ handle->strm.next_in = handle->buffer + handle->buffer_size
+ - LZMA_STREAM_TAIL_SIZE;
+ handle->strm.avail_in = LZMA_STREAM_TAIL_SIZE;
+ handle->buffer_size -= LZMA_STREAM_TAIL_SIZE;
+ ret = lzma_code(&handle->strm, LZMA_RUN);
+ if (ret != LZMA_OK) {
+ assert(ret != LZMA_OK);
+ errmsg(V_ERROR, "%s: %s", handle->name, str_strm_error(ret));
+ return true;
+ }
+
+ if (!lzma_stream_flags_is_equal(handle->stream_flags, stream_flags)) {
+ // TODO
+ // Possibly corrupt, possibly concatenated file.
+ }
+
+ handle->backward_size = 0;
+ ret = lzma_vli_reverse_decode(&handle->backward_size, handle->buffer,
+ &handle->buffer_size);
+ if (ret != LZMA_OK) {
+ // It may be LZMA_BUF_ERROR too, but it doesn't make sense
+ // as an error message displayed to the user.
+ errmsg(V_ERROR, "%s: %s", handle->name,
+ str_strm_error(LZMA_DATA_ERROR));
+ return true;
+ }
+
+ if (!stream_flags.is_multi) {
+ handle->uncompressed_size = 0;
+ size_t tmp = handle->buffer_size;
+ ret = lzma_vli_reverse_decode(&handle->uncompressed_size,
+ handle->buffer, &tmp);
+ if (ret != LZMA_OK)
+ handle->uncompressed_size = LZMA_VLI_VALUE_UNKNOWN;
+ }
+
+ // Calculate the Header Metadata Block start offset.
+
+
+ return false;
+}
+
+
+
+static void
+list_native(listing_handle *handle)
+{
+ lzma_memory_limitter *limitter
+ = lzma_memory_limitter_create(opt_memory);
+ if (limitter == NULL) {
+ errmsg(V_ERROR,
+ }
+ lzma_info *info =
+
+
+ // Parse Stream Header
+ //
+ // Single-Block Stream:
+ // - Parse Block Header
+ // - Parse Stream Footer
+ // - If Backward Size doesn't match, error out
+ //
+ // Multi-Block Stream:
+ // - Parse Header Metadata Block, if any
+ // - Parse Footer Metadata Block
+ // - Parse Stream Footer
+ // - If Footer Metadata Block doesn't match the Stream, error out
+ //
+ // In other words, we don't support concatened files.
+ if (parse_stream_header(handle))
+ return;
+
+ if (parse_block_header(handle))
+ return;
+
+ if (handle->stream_flags.is_multi) {
+ if (handle->block_options.is_metadata) {
+ if (parse_metadata(handle)
+ return;
+ }
+
+ if (my_seek(handle,
+
+ } else {
+ if (handle->block_options.is_metadata) {
+ FILE_IS_CORRUPT();
+ return;
+ }
+
+ if (parse_stream_footer(handle))
+ return;
+
+ // If Uncompressed Size isn't present in Block Header,
+ // it must be present in Stream Footer.
+ if (handle->block_options.uncompressed_size
+ == LZMA_VLI_VALUE_UNKNOWN
+ && handle->stream_flags.uncompressed_size
+ == LZMA_VLI_VALUE_UNKNOWN) {
+ FILE_IS_CORRUPT();
+ return;
+ }
+
+ // Construct a single-Record Index.
+ lzma_index *index = malloc(sizeof(lzma_index));
+ if (index == NULL) {
+ out_of_memory();
+ return;
+ }
+
+ // Pohdintaa:
+ // Jos Block coder hoitaisi Uncompressed ja Backward Sizet,
+ // voisi index->total_sizeksi laittaa suoraan Backward Sizen.
+ index->total_size =
+
+ if () {
+
+ }
+ }
+
+
+ if (handle->block_options.is_metadata) {
+ if (!handle->stream_flags.is_multi) {
+ FILE_IS_CORRUPT();
+ return;
+ }
+
+ if (parse_metadata(handle))
+ return;
+
+ }
+}
+
+
+
+extern void
+list(const char *filename)
+{
+ if (strcmp(filename, "-") == 0) {
+ errmsg(V_ERROR, "%s: --list does not support reading from "
+ "standard input", filename);
+ return;
+ }
+
+ if (is_empty_filename(filename))
+ return;
+
+ listing_handle handle;
+ handle.filename = filename;
+
+ handle.fd = open(filename, O_RDONLY | O_NOCTTY);
+ if (handle.fd == -1) {
+ errmsg(V_ERROR, "%s: %s", filename, strerror(errno));
+ return;
+ }
+
+ if (fstat(handle.fd, &handle.st)) {
+ errmsg(V_ERROR, "%s: %s", filename, strerror(errno));
+ goto out;
+ }
+
+ if (!S_ISREG(handle.st.st_mode)) {
+ errmsg(V_WARNING, _("%s: Not a regular file, skipping"),
+ filename);
+ goto out;
+ }
+
+ if (handle.st.st_size <= 0) {
+ errmsg(V_ERROR, _("%s: File is empty"), filename);
+ goto out;
+ }
+
+ if (listing_pread(&handle, 0))
+ goto out;
+
+ if (handle.buffer[0] == 0xFF) {
+ if (opt_header == HEADER_ALONE) {
+ errmsg(V_ERROR, "%s: FIXME", filename); // FIXME
+ goto out;
+ }
+
+ list_native(&handle);
+ } else {
+ if (opt_header != HEADER_AUTO && opt_header != HEADER_ALONE) {
+ errmsg(V_ERROR, "%s: FIXME", filename); // FIXME
+ goto out;
+ }
+
+ list_alone(&handle);
+ }
+
+out:
+ (void)close(fd);
+ return;
+}
diff --git a/src/lzma/main.c b/src/lzma/main.c
new file mode 100644
index 00000000..26edc47e
--- /dev/null
+++ b/src/lzma/main.c
@@ -0,0 +1,254 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file main.c
+/// \brief main()
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "private.h"
+#include "open_stdxxx.h"
+#include <ctype.h>
+
+static sig_atomic_t exit_signal = 0;
+
+
+static void
+signal_handler(int sig)
+{
+ // FIXME Is this thread-safe together with main()?
+ exit_signal = sig;
+
+ user_abort = 1;
+ return;
+}
+
+
+static void
+establish_signal_handlers(void)
+{
+ struct sigaction sa;
+ sa.sa_handler = &signal_handler;
+ sigfillset(&sa.sa_mask);
+ sa.sa_flags = 0;
+
+ static const int sigs[] = {
+ SIGHUP,
+ SIGINT,
+ SIGPIPE,
+ SIGTERM,
+ SIGXCPU,
+ SIGXFSZ,
+ };
+
+ for (size_t i = 0; i < sizeof(sigs) / sizeof(sigs[0]); ++i) {
+ if (sigaction(sigs[i], &sa, NULL)) {
+ errmsg(V_ERROR, _("Cannot establish signal handlers"));
+ my_exit(ERROR);
+ }
+ }
+
+ /*
+ SIGINFO/SIGUSR1 for status reporting?
+ */
+}
+
+
+static bool
+is_tty_stdin(void)
+{
+ const bool ret = isatty(STDIN_FILENO);
+ if (ret) {
+ // FIXME: Other threads may print between these lines.
+ // Maybe that should be fixed. Not a big issue in practice.
+ errmsg(V_ERROR, _("Compressed data not read from "
+ "a terminal."));
+ errmsg(V_ERROR, _("Use `--force' to force decompression."));
+ show_try_help();
+ }
+
+ return ret;
+}
+
+
+static bool
+is_tty_stdout(void)
+{
+ const bool ret = isatty(STDOUT_FILENO);
+ if (ret) {
+ errmsg(V_ERROR, _("Compressed data not written to "
+ "a terminal."));
+ errmsg(V_ERROR, _("Use `--force' to force decompression."));
+ show_try_help();
+ }
+
+ return ret;
+}
+
+
+static char *
+read_name(void)
+{
+ size_t size = 256;
+ size_t pos = 0;
+ char *name = malloc(size);
+ if (name == NULL) {
+ out_of_memory();
+ return NULL;
+ }
+
+ while (true) {
+ const int c = fgetc(opt_files_file);
+ if (c == EOF) {
+ free(name);
+
+ if (ferror(opt_files_file))
+ errmsg(V_ERROR, _("%s: Error reading "
+ "filenames: %s"),
+ opt_files_name,
+ strerror(errno));
+ else if (pos != 0)
+ errmsg(V_ERROR, _("%s: Unexpected end of "
+ "input when reading "
+ "filenames"), opt_files_name);
+
+ return NULL;
+ }
+
+ if (c == '\0' || c == opt_files_split)
+ break;
+
+ name[pos++] = c;
+
+ if (pos == size) {
+ size *= 2;
+ char *tmp = realloc(name, size);
+ if (tmp == NULL) {
+ free(name);
+ out_of_memory();
+ return NULL;
+ }
+
+ name = tmp;
+ }
+ }
+
+ if (name != NULL)
+ name[pos] = '\0';
+
+ return name;
+}
+
+
+int
+main(int argc, char **argv)
+{
+ // Make sure that stdin, stdout, and and stderr are connected to
+ // a valid file descriptor. Exit immediatelly with exit code ERROR
+ // if we cannot make the file descriptors valid. Maybe we should
+ // print an error message, but our stderr could be screwed anyway.
+ open_stdxxx(ERROR);
+
+ // Set the program invocation name used in various messages.
+ argv0 = argv[0];
+
+ setlocale(LC_ALL, "en_US.UTF-8");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+
+ // Set hardware-dependent default values. These can be overriden
+ // on the command line, thus this must be done before parse_args().
+ hardware_init();
+
+ char **files = parse_args(argc, argv);
+
+ if (opt_mode == MODE_COMPRESS && opt_stdout && is_tty_stdout())
+ return ERROR;
+
+ if (opt_mode == MODE_COMPRESS)
+ lzma_init_encoder();
+ else
+ lzma_init_decoder();
+
+ io_init();
+ process_init();
+
+ if (opt_mode == MODE_LIST) {
+ errmsg(V_ERROR, "--list is not implemented yet.");
+ my_exit(ERROR);
+ }
+
+ // Hook the signal handlers. We don't need these before we start
+ // the actual action, so this is done after parsing the command
+ // line arguments.
+ establish_signal_handlers();
+
+ while (*files != NULL && !user_abort) {
+ if (strcmp("-", *files) == 0) {
+ if (!opt_force) {
+ if (opt_mode == MODE_COMPRESS) {
+ if (is_tty_stdout()) {
+ ++files;
+ continue;
+ }
+ } else if (is_tty_stdin()) {
+ ++files;
+ continue;
+ }
+ }
+
+ if (opt_files_name == stdin_filename) {
+ errmsg(V_ERROR, _("Cannot read data from "
+ "standard input when "
+ "reading filenames "
+ "from standard input"));
+ ++files;
+ continue;
+ }
+
+ *files = (char *)stdin_filename;
+ }
+
+ process_file(*files++);
+ }
+
+ if (opt_files_name != NULL) {
+ while (true) {
+ char *name = read_name();
+ if (name == NULL)
+ break;
+
+ if (name[0] != '\0')
+ process_file(name);
+
+ free(name);
+ }
+
+ if (opt_files_name != stdin_filename)
+ (void)fclose(opt_files_file);
+ }
+
+ io_finish();
+
+ if (exit_signal != 0) {
+ struct sigaction sa;
+ sa.sa_handler = SIG_DFL;
+ sigfillset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigaction(exit_signal, &sa, NULL);
+ raise(exit_signal);
+ }
+
+ my_exit(exit_status);
+}
diff --git a/src/lzma/options.c b/src/lzma/options.c
new file mode 100644
index 00000000..2928aafc
--- /dev/null
+++ b/src/lzma/options.c
@@ -0,0 +1,346 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file options.c
+/// \brief Parser for filter-specific options
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "private.h"
+
+
+///////////////////
+// Generic stuff //
+///////////////////
+
+typedef struct {
+ const char *name;
+ uint64_t id;
+} name_id_map;
+
+
+typedef struct {
+ const char *name;
+ const name_id_map *map;
+ uint64_t min;
+ uint64_t max;
+} option_map;
+
+
+/// Parses option=value pairs that are separated with colons, semicolons,
+/// or commas: opt=val:opt=val;opt=val,opt=val
+///
+/// Each option is a string, that is converted to an integer using the
+/// index where the option string is in the array.
+///
+/// Value can be either a number with minimum and maximum value limit, or
+/// a string-id map mapping a list of possible string values to integers.
+///
+/// When parsing both option and value succeed, a filter-specific function
+/// is called, which should update the given value to filter-specific
+/// options structure.
+///
+/// \param str String containing the options from the command line
+/// \param opts Filter-specific option map
+/// \param set Filter-specific function to update filter_options
+/// \param filter_options Pointer to filter-specific options structure
+///
+/// \return Returns only if no errors occur.
+///
+static void
+parse_options(const char *str, const option_map *opts,
+ void (*set)(void *filter_options,
+ uint32_t key, uint64_t value),
+ void *filter_options)
+{
+ if (str == NULL || str[0] == '\0')
+ return;
+
+ char *s = xstrdup(str);
+ char *name = s;
+
+ while (true) {
+ char *split = strchr(name, ',');
+ if (split != NULL)
+ *split = '\0';
+
+ char *value = strchr(name, '=');
+ if (value != NULL)
+ *value++ = '\0';
+
+ if (value == NULL || value[0] == '\0') {
+ errmsg(V_ERROR, _("%s: Options must be `name=value' "
+ "pairs separated with commas"),
+ str);
+ my_exit(ERROR);
+ }
+
+ // Look for the option name from the option map.
+ bool found = false;
+ for (size_t i = 0; opts[i].name != NULL; ++i) {
+ if (strcmp(name, opts[i].name) != 0)
+ continue;
+
+ if (opts[i].map == NULL) {
+ // value is an integer.
+ const uint64_t v = str_to_uint64(name, value,
+ opts[i].min, opts[i].max);
+ set(filter_options, i, v);
+ } else {
+ // value is a string which we should map
+ // to an integer.
+ size_t j;
+ for (j = 0; opts[i].map[j].name != NULL; ++j) {
+ if (strcmp(opts[i].map[j].name, value)
+ == 0)
+ break;
+ }
+
+ if (opts[i].map[j].name == NULL) {
+ errmsg(V_ERROR, _("%s: Invalid option "
+ "value"), value);
+ my_exit(ERROR);
+ }
+
+ set(filter_options, i, j);
+ }
+
+ found = true;
+ break;
+ }
+
+ if (!found) {
+ errmsg(V_ERROR, _("%s: Invalid option name"), name);
+ my_exit(ERROR);
+ }
+
+ if (split == NULL)
+ break;
+
+ name = split + 1;
+ }
+
+ free(s);
+ return;
+}
+
+
+//////////////
+// Subblock //
+//////////////
+
+enum {
+ OPT_SIZE,
+ OPT_RLE,
+ OPT_ALIGN,
+};
+
+
+static void
+set_subblock(void *options, uint32_t key, uint64_t value)
+{
+ lzma_options_subblock *opt = options;
+
+ switch (key) {
+ case OPT_SIZE:
+ opt->subblock_data_size = value;
+ break;
+
+ case OPT_RLE:
+ opt->rle = value;
+ break;
+
+ case OPT_ALIGN:
+ opt->alignment = value;
+ break;
+ }
+}
+
+
+extern lzma_options_subblock *
+parse_options_subblock(const char *str)
+{
+ static const option_map opts[] = {
+ { "size", NULL, LZMA_SUBBLOCK_DATA_SIZE_MIN,
+ LZMA_SUBBLOCK_DATA_SIZE_MAX },
+ { "rle", NULL, LZMA_SUBBLOCK_RLE_OFF,
+ LZMA_SUBBLOCK_RLE_MAX },
+ { "align",NULL, LZMA_SUBBLOCK_ALIGNMENT_MIN,
+ LZMA_SUBBLOCK_ALIGNMENT_MAX },
+ { NULL, NULL, 0, 0 }
+ };
+
+ lzma_options_subblock *options
+ = xmalloc(sizeof(lzma_options_subblock));
+ *options = (lzma_options_subblock){
+ .allow_subfilters = false,
+ .alignment = LZMA_SUBBLOCK_ALIGNMENT_DEFAULT,
+ .subblock_data_size = LZMA_SUBBLOCK_DATA_SIZE_DEFAULT,
+ .rle = LZMA_SUBBLOCK_RLE_OFF,
+ };
+
+ parse_options(str, opts, &set_subblock, options);
+
+ return options;
+}
+
+
+///////////
+// Delta //
+///////////
+
+enum {
+ OPT_DISTANCE,
+};
+
+
+static void
+set_delta(void *options, uint32_t key, uint64_t value)
+{
+ lzma_options_delta *opt = options;
+ switch (key) {
+ case OPT_DISTANCE:
+ opt->distance = value;
+ break;
+ }
+}
+
+
+extern lzma_options_delta *
+parse_options_delta(const char *str)
+{
+ static const option_map opts[] = {
+ { "distance", NULL, LZMA_DELTA_DISTANCE_MIN,
+ LZMA_DELTA_DISTANCE_MAX },
+ { NULL, NULL, 0, 0 }
+ };
+
+ lzma_options_delta *options = xmalloc(sizeof(lzma_options_subblock));
+ *options = (lzma_options_delta){
+ // It's hard to give a useful default for this.
+ .distance = LZMA_DELTA_DISTANCE_MIN,
+ };
+
+ parse_options(str, opts, &set_delta, options);
+
+ return options;
+}
+
+
+//////////
+// LZMA //
+//////////
+
+enum {
+ OPT_DICT,
+ OPT_LC,
+ OPT_LP,
+ OPT_PB,
+ OPT_MODE,
+ OPT_FB,
+ OPT_MF,
+ OPT_MC
+};
+
+
+static void
+set_lzma(void *options, uint32_t key, uint64_t value)
+{
+ lzma_options_lzma *opt = options;
+
+ switch (key) {
+ case OPT_DICT:
+ opt->dictionary_size = value;
+ break;
+
+ case OPT_LC:
+ opt->literal_context_bits = value;
+ break;
+
+ case OPT_LP:
+ opt->literal_pos_bits = value;
+ break;
+
+ case OPT_PB:
+ opt->pos_bits = value;
+ break;
+
+ case OPT_MODE:
+ opt->mode = value;
+ break;
+
+ case OPT_FB:
+ opt->fast_bytes = value;
+ break;
+
+ case OPT_MF:
+ opt->match_finder = value;
+ break;
+
+ case OPT_MC:
+ opt->match_finder_cycles = value;
+ break;
+ }
+}
+
+
+extern lzma_options_lzma *
+parse_options_lzma(const char *str)
+{
+ static const name_id_map modes[] = {
+ { "fast", LZMA_MODE_FAST },
+ { "best", LZMA_MODE_BEST },
+ { NULL, 0 }
+ };
+
+ static const name_id_map mfs[] = {
+ { "hc3", LZMA_MF_HC3 },
+ { "hc4", LZMA_MF_HC4 },
+ { "bt2", LZMA_MF_BT2 },
+ { "bt3", LZMA_MF_BT3 },
+ { "bt4", LZMA_MF_BT4 },
+ { NULL, 0 }
+ };
+
+ static const option_map opts[] = {
+ { "dict", NULL, LZMA_DICTIONARY_SIZE_MIN,
+ LZMA_DICTIONARY_SIZE_MAX },
+ { "lc", NULL, LZMA_LITERAL_CONTEXT_BITS_MIN,
+ LZMA_LITERAL_CONTEXT_BITS_MAX },
+ { "lp", NULL, LZMA_LITERAL_POS_BITS_MIN,
+ LZMA_LITERAL_POS_BITS_MAX },
+ { "pb", NULL, LZMA_POS_BITS_MIN, LZMA_POS_BITS_MAX },
+ { "mode", modes, 0, 0 },
+ { "fb", NULL, LZMA_FAST_BYTES_MIN, LZMA_FAST_BYTES_MAX },
+ { "mf", mfs, 0, 0 },
+ { "mc", NULL, 0, UINT32_MAX },
+ { NULL, NULL, 0, 0 }
+ };
+
+ lzma_options_lzma *options = xmalloc(sizeof(lzma_options_lzma));
+ *options = (lzma_options_lzma){
+ .dictionary_size = LZMA_DICTIONARY_SIZE_DEFAULT,
+ .literal_context_bits = LZMA_LITERAL_CONTEXT_BITS_DEFAULT,
+ .literal_pos_bits = LZMA_LITERAL_POS_BITS_DEFAULT,
+ .pos_bits = LZMA_POS_BITS_DEFAULT,
+ .mode = LZMA_MODE_BEST,
+ .fast_bytes = LZMA_FAST_BYTES_DEFAULT,
+ .match_finder = LZMA_MF_BT4,
+ .match_finder_cycles = 0,
+ };
+
+ parse_options(str, opts, &set_lzma, options);
+
+ return options;
+}
diff --git a/src/lzma/options.h b/src/lzma/options.h
new file mode 100644
index 00000000..885c5969
--- /dev/null
+++ b/src/lzma/options.h
@@ -0,0 +1,46 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file options.h
+/// \brief Parser for filter-specific options
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef OPTIONS_H
+#define OPTIONS_H
+
+#include "private.h"
+
+
+/// \brief Parser for Subblock options
+///
+/// \return Pointer to allocated options structure.
+/// Doesn't return on error.
+extern lzma_options_subblock *parse_options_subblock(const char *str);
+
+
+/// \brief Parser for Delta options
+///
+/// \return Pointer to allocated options structure.
+/// Doesn't return on error.
+extern lzma_options_delta *parse_options_delta(const char *str);
+
+
+/// \brief Parser for LZMA options
+///
+/// \return Pointer to allocated options structure.
+/// Doesn't return on error.
+extern lzma_options_lzma *parse_options_lzma(const char *str);
+
+#endif
diff --git a/src/lzma/private.h b/src/lzma/private.h
new file mode 100644
index 00000000..89afac9b
--- /dev/null
+++ b/src/lzma/private.h
@@ -0,0 +1,55 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file private.h
+/// \brief Common includes, definions, and prototypes
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef PRIVATE_H
+#define PRIVATE_H
+
+#include "sysdefs.h"
+
+#ifdef HAVE_ERRNO_H
+# include <errno.h>
+#else
+extern int errno;
+#endif
+
+#include <sys/stat.h>
+#include <limits.h>
+#include <signal.h>
+#include <pthread.h>
+#include <locale.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "gettext.h"
+#define _(msgid) gettext(msgid)
+#define N_(msgid1, msgid2, n) ngettext(msgid1, msgid2, n)
+
+#include "alloc.h"
+#include "args.h"
+#include "error.h"
+#include "hardware.h"
+#include "help.h"
+#include "io.h"
+#include "options.h"
+#include "process.h"
+#include "suffix.h"
+#include "util.h"
+
+#endif
diff --git a/src/lzma/process.c b/src/lzma/process.c
new file mode 100644
index 00000000..10a76b74
--- /dev/null
+++ b/src/lzma/process.c
@@ -0,0 +1,458 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file process.c
+/// \brief Compresses or uncompresses a file
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "private.h"
+
+
+typedef struct {
+ lzma_stream strm;
+ void *options;
+
+ file_pair *pair;
+
+ /// We don't need this for *anything* but seems that at least with
+ /// glibc pthread_create() doesn't allow NULL.
+ pthread_t thread;
+
+ bool in_use;
+
+} thread_data;
+
+
+/// Number of available threads
+static size_t free_threads;
+
+/// Thread-specific data
+static thread_data *threads;
+
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+
+/// Attributes of new coder threads. They are created in detached state.
+/// Coder threads signal to the service thread themselves when they are done.
+static pthread_attr_t thread_attr;
+
+
+//////////
+// Init //
+//////////
+
+extern void
+process_init(void)
+{
+ threads = malloc(sizeof(thread_data) * opt_threads);
+ if (threads == NULL) {
+ out_of_memory();
+ my_exit(ERROR);
+ }
+
+ for (size_t i = 0; i < opt_threads; ++i)
+ threads[i] = (thread_data){
+ .strm = LZMA_STREAM_INIT_VAR,
+ .options = NULL,
+ .pair = NULL,
+ .in_use = false,
+ };
+
+ if (pthread_attr_init(&thread_attr)
+ || pthread_attr_setdetachstate(
+ &thread_attr, PTHREAD_CREATE_DETACHED)) {
+ out_of_memory();
+ my_exit(ERROR);
+ }
+
+ free_threads = opt_threads;
+
+ return;
+}
+
+
+//////////////////////////
+// Thread-specific data //
+//////////////////////////
+
+static thread_data *
+get_thread_data(void)
+{
+ pthread_mutex_lock(&mutex);
+
+ while (free_threads == 0) {
+ pthread_cond_wait(&cond, &mutex);
+
+ if (user_abort) {
+ pthread_cond_signal(&cond);
+ pthread_mutex_unlock(&mutex);
+ return NULL;
+ }
+ }
+
+ thread_data *t = threads;
+ while (t->in_use)
+ ++t;
+
+ t->in_use = true;
+ --free_threads;
+
+ pthread_mutex_unlock(&mutex);
+
+ return t;
+}
+
+
+static void
+release_thread_data(thread_data *t)
+{
+ pthread_mutex_lock(&mutex);
+
+ t->in_use = false;
+ ++free_threads;
+
+ pthread_cond_signal(&cond);
+ pthread_mutex_unlock(&mutex);
+
+ return;
+}
+
+
+static int
+create_thread(void *(*func)(thread_data *t), thread_data *t)
+{
+ if (opt_threads == 1) {
+ func(t);
+ } else {
+ const int err = pthread_create(&t->thread, &thread_attr,
+ (void *(*)(void *))(func), t);
+ if (err) {
+ errmsg(V_ERROR, _("Cannot create a thread: %s"),
+ strerror(err));
+ user_abort = 1;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+
+/////////////////////////
+// One thread per file //
+/////////////////////////
+
+static int
+single_init(thread_data *t)
+{
+ lzma_ret ret;
+
+ if (opt_mode == MODE_COMPRESS) {
+ const lzma_vli uncompressed_size
+ = t->pair->src_fd != STDIN_FILENO
+ ? (lzma_vli)(t->pair->src_st.st_size)
+ : LZMA_VLI_VALUE_UNKNOWN;
+
+ // TODO Support Multi-Block Streams to store Extra.
+ if (opt_header == HEADER_ALONE) {
+ lzma_options_alone alone;
+ alone.uncompressed_size = uncompressed_size;
+ memcpy(&alone.lzma, opt_filters[0].options,
+ sizeof(alone.lzma));
+ ret = lzma_alone_encoder(&t->strm, &alone);
+ } else {
+ lzma_options_stream stream = {
+ .check = opt_check,
+ .has_crc32 = true,
+ .uncompressed_size = uncompressed_size,
+ .alignment = 0,
+ };
+ memcpy(stream.filters, opt_filters,
+ sizeof(stream.filters));
+ ret = lzma_stream_encoder_single(&t->strm, &stream);
+ }
+ } else {
+ // TODO Restrict file format if requested on the command line.
+ ret = lzma_auto_decoder(&t->strm, NULL, NULL);
+ }
+
+ if (ret != LZMA_OK) {
+ if (ret == LZMA_MEM_ERROR)
+ out_of_memory();
+ else
+ internal_error();
+
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static lzma_ret
+single_skip_padding(thread_data *t, uint8_t *in_buf)
+{
+ // Handle decoding of concatenated Streams. There can be arbitrary
+ // number of nul-byte padding between the Streams, which must be
+ // ignored.
+ //
+ // NOTE: Concatenating LZMA_Alone files works only if at least
+ // one of lc, lp, and pb is non-zero. Using the concatenation
+ // on LZMA_Alone files is strongly discouraged.
+ while (true) {
+ while (t->strm.avail_in > 0) {
+ if (*t->strm.next_in != '\0')
+ return LZMA_OK;
+
+ ++t->strm.next_in;
+ --t->strm.avail_in;
+ }
+
+ if (t->pair->src_eof)
+ return LZMA_STREAM_END;
+
+ t->strm.next_in = in_buf;
+ t->strm.avail_in = io_read(t->pair, in_buf, BUFSIZ);
+ if (t->strm.avail_in == SIZE_MAX)
+ return LZMA_DATA_ERROR;
+ }
+}
+
+
+static void *
+single(thread_data *t)
+{
+ if (single_init(t)) {
+ io_close(t->pair, false);
+ release_thread_data(t);
+ return NULL;
+ }
+
+ uint8_t in_buf[BUFSIZ];
+ uint8_t out_buf[BUFSIZ];
+ lzma_action action = LZMA_RUN;
+ lzma_ret ret;
+ bool success = false;
+
+ t->strm.avail_in = 0;
+
+ while (!user_abort) {
+ if (t->strm.avail_in == 0 && !t->pair->src_eof) {
+ t->strm.next_in = in_buf;
+ t->strm.avail_in = io_read(t->pair, in_buf, BUFSIZ);
+
+ if (t->strm.avail_in == SIZE_MAX)
+ break;
+ else if (t->pair->src_eof
+ && opt_mode == MODE_COMPRESS)
+ action = LZMA_FINISH;
+ }
+
+ t->strm.next_out = out_buf;
+ t->strm.avail_out = BUFSIZ;
+
+ ret = lzma_code(&t->strm, action);
+
+ if (opt_mode != MODE_TEST)
+ if (io_write(t->pair, out_buf,
+ BUFSIZ - t->strm.avail_out))
+ break;
+
+ if (ret != LZMA_OK) {
+ if (ret == LZMA_STREAM_END) {
+ if (opt_mode == MODE_COMPRESS) {
+ success = true;
+ break;
+ }
+
+ // Support decoding concatenated .lzma files.
+ ret = single_skip_padding(t, in_buf);
+
+ if (ret == LZMA_STREAM_END) {
+ assert(t->pair->src_eof);
+ success = true;
+ break;
+ }
+
+ if (ret == LZMA_OK && !single_init(t))
+ continue;
+
+ break;
+
+ } else {
+ errmsg(V_ERROR, "%s: %s", t->pair->src_name,
+ str_strm_error(ret));
+ break;
+ }
+ }
+ }
+
+ io_close(t->pair, success);
+ release_thread_data(t);
+
+ return NULL;
+}
+
+
+///////////////////////////////
+// Multiple threads per file //
+///////////////////////////////
+
+// TODO
+
+// I'm not sure what would the best way to implement this. Here's one
+// possible way:
+// - Reader thread would read the input data and control the coders threads.
+// - Every coder thread is associated with input and output buffer pools.
+// The input buffer pool is filled by reader thread, and the output buffer
+// pool is emptied by the writer thread.
+// - Writer thread writes the output data of the oldest living coder thread.
+//
+// The per-file thread started by the application's main thread is used as
+// the reader thread. In the beginning, it starts the writer thread and the
+// first coder thread. The coder thread would be left waiting for input from
+// the reader thread, and the writer thread would be waiting for input from
+// the coder thread.
+//
+// The reader thread reads the input data into a ring buffer, whose size
+// depends on the value returned by lzma_chunk_size(). If the ring buffer
+// gets full, the buffer is marked "to be finished", which indicates to
+// the coder thread that no more input is coming. Then a new coder thread
+// would be started.
+//
+// TODO
+
+/*
+typedef struct {
+ /// Buffers
+ uint8_t (*buffers)[BUFSIZ];
+
+ /// Number of buffers
+ size_t buffer_count;
+
+ /// buffers[read_pos] is the buffer currently being read. Once finish
+ /// is true and read_pos == write_pos, end of input has been reached.
+ size_t read_pos;
+
+ /// buffers[write_pos] is the buffer into which data is currently
+ /// being written.
+ size_t write_pos;
+
+ /// This variable matters only when read_pos == write_pos && finish.
+ /// In that case, this variable will contain the size of the
+ /// buffers[read_pos].
+ size_t last_size;
+
+ /// True once no more data is being written to the buffer. When this
+ /// is set, the last_size variable must have been set too.
+ bool finish;
+
+ /// Mutex to protect access to the variables in this structure
+ pthread_mutex_t mutex;
+
+ /// Condition to indicate when another thread can continue
+ pthread_cond_t cond;
+} mem_pool;
+
+
+static foo
+multi_reader(thread_data *t)
+{
+ bool done = false;
+
+ do {
+ const size_t size = io_read(t->pair,
+ m->buffers + m->write_pos, BUFSIZ);
+ if (size == SIZE_MAX) {
+ // TODO
+ } else if (t->pair->src_eof) {
+ m->last_size = size;
+ }
+
+ pthread_mutex_lock(&m->mutex);
+
+ if (++m->write_pos == m->buffer_count)
+ m->write_pos = 0;
+
+ if (m->write_pos == m->read_pos || t->pair->src_eof)
+ m->finish = true;
+
+ pthread_cond_signal(&m->cond);
+ pthread_mutex_unlock(&m->mutex);
+
+ } while (!m->finish);
+
+ return done ? 0 : -1;
+}
+
+
+static foo
+multi_code()
+{
+ lzma_action = LZMA_RUN;
+
+ while (true) {
+ pthread_mutex_lock(&m->mutex);
+
+ while (m->read_pos == m->write_pos && !m->finish)
+ pthread_cond_wait(&m->cond, &m->mutex);
+
+ pthread_mutex_unlock(&m->mutex);
+
+ if (m->finish) {
+ t->strm.avail_in = m->last_size;
+ if (opt_mode == MODE_COMPRESS)
+ action = LZMA_FINISH;
+ } else {
+ t->strm.avail_in = BUFSIZ;
+ }
+
+ t->strm.next_in = m->buffers + m->read_pos;
+
+ const lzma_ret ret = lzma_code(&t->strm, action);
+
+ }
+}
+
+*/
+
+
+///////////////////////
+// Starting new file //
+///////////////////////
+
+extern void
+process_file(const char *filename)
+{
+ thread_data *t = get_thread_data();
+ if (t == NULL)
+ return; // User abort
+
+ // If this fails, it shows appropriate error messages too.
+ t->pair = io_open(filename);
+ if (t->pair == NULL) {
+ release_thread_data(t);
+ return;
+ }
+
+ // TODO Currently only one-thread-per-file mode is implemented.
+
+ if (create_thread(&single, t)) {
+ io_close(t->pair, false);
+ release_thread_data(t);
+ }
+
+ return;
+}
diff --git a/src/lzma/process.h b/src/lzma/process.h
new file mode 100644
index 00000000..7fdfbce6
--- /dev/null
+++ b/src/lzma/process.h
@@ -0,0 +1,30 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file process.c
+/// \brief Compresses or uncompresses a file
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef PROCESS_H
+#define PROCESS_H
+
+#include "private.h"
+
+
+extern void process_init(void);
+
+extern void process_file(const char *filename);
+
+#endif
diff --git a/src/lzma/suffix.c b/src/lzma/suffix.c
new file mode 100644
index 00000000..57afce82
--- /dev/null
+++ b/src/lzma/suffix.c
@@ -0,0 +1,145 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file suffix.c
+/// \brief Checks filename suffix and creates the destination filename
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "private.h"
+
+
+static const struct {
+ const char *compressed;
+ const char *uncompressed;
+} suffixes[] = {
+ { ".lzma", "" },
+ { ".tlz", ".tar" },
+ { ".ylz", ".yar" },
+ { NULL, NULL }
+};
+
+
+/// \brief Checks if src_name has given compressed_suffix
+///
+/// \param suffix Filename suffix to look for
+/// \param src_name Input filename
+/// \param src_len strlen(src_name)
+///
+/// \return If src_name has the suffix, src_len - strlen(suffix) is
+/// returned. It's always a positive integer. Otherwise zero
+/// is returned.
+static size_t
+test_suffix(const char *suffix, const char *src_name, size_t src_len)
+{
+ const size_t suffix_len = strlen(suffix);
+
+ // The filename must have at least one character in addition to
+ // the suffix. src_name may contain path to the filename, so we
+ // need to check for directory separator too.
+ if (src_len <= suffix_len || src_name[src_len - suffix_len - 1] == '/')
+ return 0;
+
+ if (strcmp(suffix, src_name + src_len - suffix_len) == 0)
+ return src_len - suffix_len;
+
+ return 0;
+}
+
+
+/// \brief Removes the filename suffix of the compressed file
+///
+/// \return Name of the uncompressed file, or NULL if file has unknown
+/// suffix.
+static char *
+uncompressed_name(const char *src_name)
+{
+ const char *new_suffix = "";
+ const size_t src_len = strlen(src_name);
+ size_t new_len = 0;
+
+ for (size_t i = 0; suffixes[i].compressed != NULL; ++i) {
+ new_len = test_suffix(suffixes[i].compressed,
+ src_name, src_len);
+ if (new_len != 0) {
+ new_suffix = suffixes[i].uncompressed;
+ break;
+ }
+ }
+
+ if (new_len == 0 && opt_suffix != NULL)
+ new_len = test_suffix(opt_suffix, src_name, src_len);
+
+ if (new_len == 0) {
+ errmsg(V_WARNING, _("%s: Filename has an unknown suffix, "
+ "skipping"), src_name);
+ return NULL;
+ }
+
+ const size_t new_suffix_len = strlen(new_suffix);
+ char *dest_name = malloc(new_len + new_suffix_len + 1);
+ if (dest_name == NULL) {
+ out_of_memory();
+ return NULL;
+ }
+
+ memcpy(dest_name, src_name, new_len);
+ memcpy(dest_name + new_len, new_suffix, new_suffix_len);
+ dest_name[new_len + new_suffix_len] = '\0';
+
+ return dest_name;
+}
+
+
+/// \brief Appends suffix to src_name
+static char *
+compressed_name(const char *src_name)
+{
+ const size_t src_len = strlen(src_name);
+
+ for (size_t i = 0; suffixes[i].compressed != NULL; ++i) {
+ if (test_suffix(suffixes[i].compressed, src_name, src_len)
+ != 0) {
+ errmsg(V_WARNING, _("%s: File already has `%s' "
+ "suffix, skipping"), src_name,
+ suffixes[i].compressed);
+ return NULL;
+ }
+ }
+
+ const char *suffix = opt_suffix != NULL
+ ? opt_suffix : suffixes[0].compressed;
+ const size_t suffix_len = strlen(suffix);
+
+ char *dest_name = malloc(src_len + suffix_len + 1);
+ if (dest_name == NULL) {
+ out_of_memory();
+ return NULL;
+ }
+
+ memcpy(dest_name, src_name, src_len);
+ memcpy(dest_name + src_len, suffix, suffix_len);
+ dest_name[src_len + suffix_len] = '\0';
+
+ return dest_name;
+}
+
+
+extern char *
+get_dest_name(const char *src_name)
+{
+ return opt_mode == MODE_COMPRESS
+ ? compressed_name(src_name)
+ : uncompressed_name(src_name);
+}
diff --git a/src/lzma/suffix.h b/src/lzma/suffix.h
new file mode 100644
index 00000000..08315659
--- /dev/null
+++ b/src/lzma/suffix.h
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file suffix.h
+/// \brief Checks filename suffix and creates the destination filename
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef SUFFIX_H
+#define SUFFIX_H
+
+extern char *get_dest_name(const char *src_name);
+
+#endif
diff --git a/src/lzma/util.c b/src/lzma/util.c
new file mode 100644
index 00000000..6ef6eb0d
--- /dev/null
+++ b/src/lzma/util.c
@@ -0,0 +1,182 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file util.c
+/// \brief Miscellaneous utility functions
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "private.h"
+
+
+/// \brief Fancy version of strtoull()
+///
+/// \param name Name of the option to show in case of an error
+/// \param value String containing the number to be parsed; may
+/// contain suffixes "k", "M", "G", "Ki", "Mi", or "Gi"
+/// \param min Minimum valid value
+/// \param max Maximum valid value
+///
+/// \return Parsed value that is in the range [min, max]. Does not return
+/// if an error occurs.
+///
+extern uint64_t
+str_to_uint64(const char *name, const char *value, uint64_t min, uint64_t max)
+{
+ uint64_t result = 0;
+
+ // Skip blanks.
+ while (*value == ' ' || *value == '\t')
+ ++value;
+
+ if (*value < '0' || *value > '9') {
+ errmsg(V_ERROR, _("%s: Value is not a non-negative "
+ "decimal integer"),
+ value);
+ my_exit(ERROR);
+ }
+
+ do {
+ // Don't overflow.
+ if (result > (UINT64_MAX - 9) / 10)
+ goto error;
+
+ result *= 10;
+ result += *value - '0';
+ ++value;
+ } while (*value >= '0' && *value <= '9');
+
+ if (*value != '\0') {
+ // Look for suffix.
+ static const struct {
+ const char *name;
+ uint64_t multiplier;
+ } suffixes[] = {
+ { "k", UINT64_C(1000) },
+ { "M", UINT64_C(1000000) },
+ { "G", UINT64_C(1000000000) },
+ { "Ki", UINT64_C(1024) },
+ { "Mi", UINT64_C(1048576) },
+ { "Gi", UINT64_C(1073741824) },
+ { NULL, 0 }
+ };
+
+ uint64_t multiplier = 0;
+ for (size_t i = 0; suffixes[i].name != NULL; ++i) {
+ if (strcmp(value, suffixes[i].name) == 0) {
+ multiplier = suffixes[i].multiplier;
+ break;
+ }
+ }
+
+ if (multiplier == 0) {
+ errmsg(V_ERROR, _("%s: Invalid multiplier suffix. "
+ "Valid suffixes:"), value);
+ errmsg(V_ERROR, "`k' (10^3), `M' (10^6), `G' (10^9) "
+ "`Ki' (2^10), `Mi' (2^20), "
+ "`Gi' (2^30)");
+ my_exit(ERROR);
+ }
+
+ // Don't overflow here either.
+ if (result > UINT64_MAX / multiplier)
+ goto error;
+
+ result *= multiplier;
+ }
+
+ if (result < min || result > max)
+ goto error;
+
+ return result;
+
+error:
+ errmsg(V_ERROR, _("Value of the option `%s' must be in the range "
+ "[%llu, %llu]"), name,
+ (unsigned long long)(min),
+ (unsigned long long)(max));
+ my_exit(ERROR);
+}
+
+
+/// \brief Gets filename part from pathname+filename
+///
+/// \return Pointer in the filename where the actual filename starts.
+/// If the last character is a slash, NULL is returned.
+///
+extern const char *
+str_filename(const char *name)
+{
+ const char *base = strrchr(name, '/');
+
+ if (base == NULL) {
+ base = name;
+ } else if (*++base == '\0') {
+ base = NULL;
+ errmsg(V_ERROR, _("%s: Invalid filename"), name);
+ }
+
+ return base;
+}
+
+
+/*
+/// \brief Simple quoting to get rid of ASCII control characters
+///
+/// This is not so cool and locale-dependent, but should be good enough
+/// At least we don't print any control characters on the terminal.
+///
+extern char *
+str_quote(const char *str)
+{
+ size_t dest_len = 0;
+ bool has_ctrl = false;
+
+ while (str[dest_len] != '\0')
+ if (*(unsigned char *)(str + dest_len++) < 0x20)
+ has_ctrl = true;
+
+ char *dest = malloc(dest_len + 1);
+ if (dest != NULL) {
+ if (has_ctrl) {
+ for (size_t i = 0; i < dest_len; ++i)
+ if (*(unsigned char *)(str + i) < 0x20)
+ dest[i] = '?';
+ else
+ dest[i] = str[i];
+
+ dest[dest_len] = '\0';
+
+ } else {
+ // Usually there are no control characters,
+ // so we can optimize.
+ memcpy(dest, str, dest_len + 1);
+ }
+ }
+
+ return dest;
+}
+*/
+
+
+extern bool
+is_empty_filename(const char *filename)
+{
+ if (filename[0] == '\0') {
+ errmsg(V_WARNING, _("Empty filename, skipping"));
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/lzma/util.h b/src/lzma/util.h
new file mode 100644
index 00000000..91bd9ba3
--- /dev/null
+++ b/src/lzma/util.h
@@ -0,0 +1,32 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file util.h
+/// \brief Miscellaneous utility functions
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef UTIL_H
+#define UTIL_H
+
+#include "private.h"
+
+extern uint64_t str_to_uint64(const char *name, const char *value,
+ uint64_t min, uint64_t max);
+
+extern const char *str_filename(const char *filename);
+
+extern bool is_empty_filename(const char *filename);
+
+#endif
diff --git a/src/lzmadec/Makefile.am b/src/lzmadec/Makefile.am
new file mode 100644
index 00000000..63e391d5
--- /dev/null
+++ b/src/lzmadec/Makefile.am
@@ -0,0 +1,27 @@
+##
+## Copyright (C) 2007 Lasse Collin
+##
+## This program is free software; you can redistribute it and/or
+## modify it under the terms of the GNU Lesser General Public
+## License as published by the Free Software Foundation; either
+## version 2.1 of the License, or (at your option) any later version.
+##
+## This program 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
+## Lesser General Public License for more details.
+##
+
+bin_PROGRAMS = lzmadec
+
+lzmadec_SOURCES = lzmadec.c
+lzmadec_CPPFLAGS = \
+ -I@top_srcdir@/src/common \
+ -I@top_srcdir@/src/liblzma/api \
+ -I@top_builddir@/lib
+lzmadec_LDFLAGS = -static
+lzmadec_LDADD = @top_builddir@/src/liblzma/liblzma.la
+
+if COND_GNULIB
+lzmadec_LDADD += @top_builddir@/lib/libgnu.a
+endif
diff --git a/src/lzmadec/lzmadec.c b/src/lzmadec/lzmadec.c
new file mode 100644
index 00000000..93eed090
--- /dev/null
+++ b/src/lzmadec/lzmadec.c
@@ -0,0 +1,515 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file lzmadec.c
+/// \brief Simple single-threaded tool to uncompress .lzma files
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program 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
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "sysdefs.h"
+
+#ifdef HAVE_ERRNO_H
+# include <errno.h>
+#else
+extern int errno;
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include "getopt.h"
+#include "physmem.h"
+
+
+enum return_code {
+ SUCCESS,
+ ERROR,
+ WARNING,
+};
+
+
+enum format_type {
+ FORMAT_AUTO,
+ FORMAT_NATIVE,
+ FORMAT_ALONE,
+};
+
+
+enum {
+ OPTION_FORMAT = INT_MIN,
+};
+
+
+/// Input buffer
+static uint8_t in_buf[BUFSIZ];
+
+/// Output buffer
+static uint8_t out_buf[BUFSIZ];
+
+/// Decoder
+static lzma_stream strm = LZMA_STREAM_INIT;
+
+/// Number of bytes to use memory at maximum
+static size_t mem_limit;
+
+/// Memory allocation hooks
+static lzma_allocator allocator = {
+ .alloc = (void *(*)(void *, size_t, size_t))(&lzma_memlimit_alloc),
+ .free = (void (*)(void *, void *))(&lzma_memlimit_free),
+ .opaque = NULL,
+};
+
+/// Program name to be shown in error messages
+static const char *argv0;
+
+/// File currently being processed
+static FILE *file;
+
+/// Name of the file currently being processed
+static const char *filename;
+
+static enum return_code exit_status = SUCCESS;
+
+static enum format_type format_type = FORMAT_AUTO;
+
+static bool force = false;
+
+
+static void lzma_attribute((noreturn))
+help(void)
+{
+ printf(
+"Usage: %s [OPTION]... [FILE]...\n"
+"Uncompress files in the .lzma format to the standard output.\n"
+"\n"
+" -c, --stdout (ignored)\n"
+" -d, --decompress (ignored)\n"
+" -k, --keep (ignored)\n"
+" -f, --force allow reading compressed data from a terminal\n"
+" -M, --memory=NUM use NUM bytes of memory at maximum; the suffixes\n"
+" k, M, G, Ki, Mi, and Gi are supported.\n"
+" --format=FMT accept only files in the given file format;\n"
+" possible FMTs are `auto', `native', `single',\n"
+" `multi', and `alone', of which `single' and `multi'\n"
+" are aliases for `native'\n"
+" -h, --help display this help and exit\n"
+" -V, --version display version and license information and exit\n"
+"\n"
+"With no FILE, or when FILE is -, read standard input.\n"
+"\n"
+"On this configuration, the tool will use about %zu MiB of memory at maximum.\n"
+"\n"
+"Report bugs to <" PACKAGE_BUGREPORT "> (in English or Finnish).\n",
+ argv0, (mem_limit + 512 * 1024) / (1024 * 1024));
+ exit(0);
+}
+
+
+static void lzma_attribute((noreturn))
+version(void)
+{
+ printf(
+"lzmadec (LZMA Utils) " PACKAGE_VERSION "\n"
+"\n"
+"Copyright (C) 1999-2006 Igor Pavlov\n"
+"Copyright (C) 2007 Lasse Collin\n"
+"\n"
+"This program is free software; you can redistribute it and/or\n"
+"modify it under the terms of the GNU Lesser General Public\n"
+"License as published by the Free Software Foundation; either\n"
+"version 2.1 of the License, or (at your option) any later version.\n"
+"\n"
+"This program is distributed in the hope that it will be useful,\n"
+"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n"
+"Lesser General Public License for more details.\n"
+"\n");
+ exit(0);
+}
+
+
+/// Finds out the amount of physical memory in the system, and sets
+/// a default memory usage limit.
+static void
+set_default_mem_limit(void)
+{
+ uint64_t mem = physmem();
+ if (mem != 0) {
+ mem /= 3;
+
+#if UINT64_MAX > SIZE_MAX
+ if (mem > SIZE_MAX)
+ mem = SIZE_MAX;
+#endif
+
+ mem_limit = mem / 3;
+ } else {
+ // Cannot autodetect, use 10 MiB as the default limit.
+ mem_limit = (1U << 23) + (1U << 21);
+ }
+
+ return;
+}
+
+
+/// \brief Converts a string to size_t
+///
+/// This is rudely copied from src/lzma/util.c and modified a little. :-(
+///
+static size_t
+str_to_size(const char *value)
+{
+ size_t result = 0;
+
+ if (*value < '0' || *value > '9') {
+ fprintf(stderr, "%s: %s: Not a number", argv0, value);
+ exit(ERROR);
+ }
+
+ do {
+ // Don't overflow.
+ if (result > (SIZE_MAX - 9) / 10)
+ return SIZE_MAX;
+
+ result *= 10;
+ result += *value - '0';
+ ++value;
+ } while (*value >= '0' && *value <= '9');
+
+ if (*value != '\0') {
+ // Look for suffix.
+ static const struct {
+ const char *name;
+ size_t multiplier;
+ } suffixes[] = {
+ { "k", 1000 },
+ { "M", 1000000 },
+ { "G", 1000000000 },
+ { "Ki", 1024 },
+ { "Mi", 1048576 },
+ { "Gi", 1073741824 },
+ { NULL, 0 }
+ };
+
+ size_t multiplier = 0;
+ for (size_t i = 0; suffixes[i].name != NULL; ++i) {
+ if (strcmp(value, suffixes[i].name) == 0) {
+ multiplier = suffixes[i].multiplier;
+ break;
+ }
+ }
+
+ if (multiplier == 0) {
+ fprintf(stderr, "%s: %s: Invalid suffix",
+ argv0, value);
+ exit(ERROR);
+ }
+
+ // Don't overflow here either.
+ if (result > SIZE_MAX / multiplier)
+ return SIZE_MAX;
+
+ result *= multiplier;
+ }
+
+ return result;
+}
+
+
+/// Parses command line options.
+static void
+parse_options(int argc, char **argv)
+{
+ static const char short_opts[] = "cdkfM:hV";
+ static const struct option long_opts[] = {
+ { "stdout", no_argument, NULL, 'c' },
+ { "to-stdout", no_argument, NULL, 'c' },
+ { "decompress", no_argument, NULL, 'd' },
+ { "uncompress", no_argument, NULL, 'd' },
+ { "force", no_argument, NULL, 'f' },
+ { "keep", no_argument, NULL, 'k' },
+ { "memory", required_argument, NULL, 'M' },
+ { "format", required_argument, NULL, OPTION_FORMAT },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ int c;
+
+ while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL))
+ != -1) {
+ switch (c) {
+ case 'c':
+ case 'd':
+ case 'k':
+ break;
+
+ case 'f':
+ force = true;
+ break;
+
+ case 'M':
+ mem_limit = str_to_size(optarg);
+ break;
+
+ case 'h':
+ help();
+
+ case 'V':
+ version();
+
+ case OPTION_FORMAT: {
+ if (strcmp("auto", optarg) == 0) {
+ format_type = FORMAT_AUTO;
+ } else if (strcmp("native", optarg) == 0
+ || strcmp("single", optarg) == 0
+ || strcmp("multi", optarg) == 0) {
+ format_type = FORMAT_NATIVE;
+ } else if (strcmp("alone", optarg) == 0) {
+ format_type = FORMAT_ALONE;
+ } else {
+ fprintf(stderr, "%s: %s: Unknown file format "
+ "name\n", argv0, optarg);
+ exit(ERROR);
+ }
+ break;
+ }
+
+ default:
+ exit(ERROR);
+ }
+ }
+
+ return;
+}
+
+
+/// Initializes lzma_stream structure for decoding of a new Stream.
+static void
+init(void)
+{
+ lzma_ret ret;
+
+ switch (format_type) {
+ case FORMAT_AUTO:
+ ret = lzma_auto_decoder(&strm, NULL, NULL);
+ break;
+
+ case FORMAT_NATIVE:
+ ret = lzma_stream_decoder(&strm, NULL, NULL);
+ break;
+
+ case FORMAT_ALONE:
+ ret = lzma_alone_decoder(&strm);
+ break;
+
+ default:
+ assert(0);
+ ret = LZMA_PROG_ERROR;
+ }
+
+ if (ret != LZMA_OK) {
+ fprintf(stderr, "%s: ", argv0);
+
+ if (ret == LZMA_MEM_ERROR)
+ fprintf(stderr, "%s\n", strerror(ENOMEM));
+ else
+ fprintf(stderr, "Internal program error (bug)\n");
+
+ exit(ERROR);
+ }
+
+ return;
+}
+
+
+static void
+read_input(void)
+{
+ strm.next_in = in_buf;
+ strm.avail_in = fread(in_buf, 1, BUFSIZ, file);
+
+ if (ferror(file)) {
+ // POSIX says that fread() sets errno if an error occurred.
+ // ferror() doesn't touch errno.
+ fprintf(stderr, "%s: %s: Error reading input file: %s\n",
+ argv0, filename, strerror(errno));
+ exit(ERROR);
+ }
+
+ return;
+}
+
+
+static bool
+skip_padding(void)
+{
+ // Handle concatenated Streams. There can be arbitrary number of
+ // nul-byte padding between the Streams, which must be ignored.
+ //
+ // NOTE: Concatenating LZMA_Alone files works only if at least
+ // one of lc, lp, and pb is non-zero. Using the concatenation
+ // on LZMA_Alone files is strongly discouraged.
+ while (true) {
+ while (strm.avail_in > 0) {
+ if (*strm.next_in != '\0')
+ return true;
+
+ ++strm.next_in;
+ --strm.avail_in;
+ }
+
+ if (feof(file))
+ return false;
+
+ read_input();
+ }
+}
+
+
+static void
+uncompress(void)
+{
+ if (file == stdin && !force && isatty(STDIN_FILENO)) {
+ fprintf(stderr, "%s: Compressed data not read from "
+ "a terminal.\n%s: Use `-f' to force reading "
+ "from a terminal, or `-h' for help.\n",
+ argv0, argv0);
+ exit(ERROR);
+ }
+
+ init();
+ strm.avail_in = 0;
+
+ while (true) {
+ if (strm.avail_in == 0)
+ read_input();
+
+ strm.next_out = out_buf;
+ strm.avail_out = BUFSIZ;
+
+ const lzma_ret ret = lzma_code(&strm, LZMA_RUN);
+
+ // Write and check write error before checking decoder error.
+ // This way as much data as possible gets written to output
+ // even if decoder detected an error. Checking write error
+ // needs to be done before checking decoder error due to
+ // how concatenated Streams are handled a few lines later.
+ const size_t write_size = BUFSIZ - strm.avail_out;
+ if (fwrite(out_buf, 1, write_size, stdout) != write_size) {
+ // Wouldn't be a surprise if writing to stderr would
+ // fail too but at least try to show an error message.
+ fprintf(stderr, "%s: Cannot write to "
+ "standard output: %s\n", argv0,
+ strerror(errno));
+ exit(ERROR);
+ }
+
+ if (ret != LZMA_OK) {
+ if (ret == LZMA_STREAM_END) {
+ if (skip_padding()) {
+ init();
+ continue;
+ }
+
+ return;
+ }
+
+ fprintf(stderr, "%s: %s: ", argv0, filename);
+
+ switch (ret) {
+ case LZMA_DATA_ERROR:
+ fprintf(stderr, "File is corrupt\n");
+ exit(ERROR);
+
+ case LZMA_HEADER_ERROR:
+ fprintf(stderr, "Unsupported file "
+ "format or filters\n");
+ exit(ERROR);
+
+ case LZMA_MEM_ERROR:
+ fprintf(stderr, "%s\n", strerror(ENOMEM));
+ exit(ERROR);
+
+ case LZMA_BUF_ERROR:
+ fprintf(stderr, "Unexpected end of input\n");
+ exit(ERROR);
+
+ case LZMA_UNSUPPORTED_CHECK:
+ fprintf(stderr, "Unsupported type of "
+ "integrity check; not "
+ "verifying file integrity\n");
+ exit_status = WARNING;
+ break;
+
+ case LZMA_PROG_ERROR:
+ default:
+ fprintf(stderr, "Internal program "
+ "error (bug)\n");
+ exit(ERROR);
+ }
+ }
+ }
+}
+
+
+int
+main(int argc, char **argv)
+{
+ argv0 = argv[0];
+
+ set_default_mem_limit();
+
+ parse_options(argc, argv);
+
+ lzma_init_decoder();
+
+ lzma_memlimit *mem_limitter = lzma_memlimit_create(mem_limit);
+ if (mem_limitter == NULL) {
+ fprintf(stderr, "%s: %s\n", argv0, strerror(ENOMEM));
+ exit(ERROR);
+ }
+
+ allocator.opaque = mem_limitter;
+ strm.allocator = &allocator;
+
+ if (optind == argc) {
+ file = stdin;
+ filename = "(stdin)";
+ uncompress();
+ } else {
+ do {
+ if (strcmp(argv[optind], "-") == 0) {
+ file = stdin;
+ filename = "(stdin)";
+ uncompress();
+ } else {
+ filename = argv[optind];
+ file = fopen(filename, "rb");
+ if (file == NULL) {
+ fprintf(stderr, "%s: %s: %s\n",
+ argv0, filename,
+ strerror(errno));
+ exit(ERROR);
+ }
+
+ uncompress();
+ fclose(file);
+ }
+ } while (++optind < argc);
+ }
+
+ return exit_status;
+}