aboutsummaryrefslogtreecommitdiff
path: root/src/xzdec
diff options
context:
space:
mode:
authorLasse Collin <lasse.collin@tukaani.org>2008-11-19 23:52:24 +0200
committerLasse Collin <lasse.collin@tukaani.org>2008-11-19 23:52:24 +0200
commit1880a3927b23f265f63b2adb86fbdb81ea09eb06 (patch)
tree2fe1b65d21f81b28f46eb707378d97f553e99ee1 /src/xzdec
parentOh well, big messy commit again. Some highlights: (diff)
downloadxz-1880a3927b23f265f63b2adb86fbdb81ea09eb06.tar.xz
Renamed lzma to xz and lzmadec to xzdec. We create symlinks
lzma, unlzma, and lzcat in "make install" for backwards compatibility with LZMA Utils 4.32.x; I'm not sure if this should be the default though.
Diffstat (limited to 'src/xzdec')
-rw-r--r--src/xzdec/Makefile.am29
-rw-r--r--src/xzdec/xzdec.c492
2 files changed, 521 insertions, 0 deletions
diff --git a/src/xzdec/Makefile.am b/src/xzdec/Makefile.am
new file mode 100644
index 00000000..8c8cae80
--- /dev/null
+++ b/src/xzdec/Makefile.am
@@ -0,0 +1,29 @@
+##
+## 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 = xzdec
+
+xzdec_SOURCES = xzdec.c
+xzdec_CPPFLAGS = \
+ -I@top_srcdir@/src/common \
+ -I@top_srcdir@/src/liblzma/api \
+ -I@top_builddir@/lib
+xzdec_LDFLAGS = -static
+xzdec_LDADD = \
+ @top_builddir@/src/liblzma/liblzma.la \
+ @LTLIBINTL@
+
+if COND_GNULIB
+xzdec_LDADD += @top_builddir@/lib/libgnu.a
+endif
diff --git a/src/xzdec/xzdec.c b/src/xzdec/xzdec.c
new file mode 100644
index 00000000..e100d049
--- /dev/null
+++ b/src/xzdec/xzdec.c
@@ -0,0 +1,492 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file xzdec.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"
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#ifdef WIN32
+# include <fcntl.h>
+#endif
+
+#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 uint64_t memlimit;
+
+/// 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 (0 means default);\n"
+" the suffixes 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', and alone',\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 %" PRIu64
+ " MiB of memory at maximum.\n"
+"\n"
+"Report bugs to <" PACKAGE_BUGREPORT "> (in English or Finnish).\n",
+ argv0, ((uint64_t)(memlimit) + 512 * 1024) / (1024 * 1024));
+ // Using PRIu64 above instead of %zu to support pre-C99 libc.
+ 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_memlimit(void)
+{
+ const uint64_t mem = physmem();
+
+ if (mem == 0)
+ // Cannot autodetect, use 10 MiB as the default limit.
+ memlimit = (1U << 23) + (1U << 21);
+ else
+ // Limit is 33 % of RAM.
+ memlimit = mem / 3;
+
+ return;
+}
+
+
+/// \brief Converts a string to size_t
+///
+/// This is rudely copied from src/xz/util.c and modified a little. :-(
+///
+static size_t
+str_to_uint64(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[4];
+ size_t multiplier;
+ } suffixes[] = {
+ { "k", 1000 },
+ { "kB", 1000 },
+ { "M", 1000000 },
+ { "MB", 1000000 },
+ { "G", 1000000000 },
+ { "GB", 1000000000 },
+ { "Ki", 1024 },
+ { "KiB", 1024 },
+ { "Mi", 1048576 },
+ { "MiB", 1048576 },
+ { "Gi", 1073741824 },
+ { "GiB", 1073741824 }
+ };
+
+ size_t multiplier = 0;
+ for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++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)
+ result = SIZE_MAX;
+ else
+ 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':
+ memlimit = str_to_uint64(optarg);
+ if (memlimit == 0)
+ set_default_memlimit();
+
+ 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) {
+ 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)
+{
+ const uint32_t flags = LZMA_TELL_UNSUPPORTED_CHECK | LZMA_CONCATENATED;
+ lzma_ret ret;
+
+ switch (format_type) {
+ case FORMAT_AUTO:
+ ret = lzma_auto_decoder(&strm, memlimit, flags);
+ break;
+
+ case FORMAT_NATIVE:
+ ret = lzma_stream_decoder(&strm, memlimit, flags);
+ break;
+
+ case FORMAT_ALONE:
+ ret = lzma_alone_decoder(&strm, memlimit);
+ 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
+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;
+ strm.next_out = out_buf;
+ strm.avail_out = BUFSIZ;
+
+ lzma_action action = LZMA_RUN;
+
+ while (true) {
+ if (strm.avail_in == 0) {
+ 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);
+ }
+
+ if (feof(file))
+ action = LZMA_FINISH;
+ }
+
+ const lzma_ret ret = lzma_code(&strm, action);
+
+ // 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.
+ if (strm.avail_out == 0 || ret != LZMA_OK) {
+ 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);
+ }
+
+ strm.next_out = out_buf;
+ strm.avail_out = BUFSIZ;
+ }
+
+ if (ret != LZMA_OK) {
+ // FIXME !!! Doesn't work with LZMA_Alone for the
+ // same reason as in process.c.
+ if (ret == LZMA_STREAM_END)
+ return;
+
+ fprintf(stderr, "%s: %s: ", argv0, filename);
+
+ // FIXME Add LZMA_*_CHECK and LZMA_FORMAT_ERROR.
+ switch (ret) {
+ case LZMA_DATA_ERROR:
+ fprintf(stderr, "File is corrupt\n");
+ exit(ERROR);
+
+ case LZMA_OPTIONS_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_MEMLIMIT_ERROR:
+ fprintf(stderr, "Memory usage limit "
+ "reached\n");
+ 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_memlimit();
+
+ parse_options(argc, argv);
+
+ lzma_init_decoder();
+
+#ifdef WIN32
+ setmode(fileno(stdin), O_BINARY);
+ setmode(fileno(stdout), O_BINARY);
+#endif
+
+ 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);
+ }
+
+#ifndef NDEBUG
+ // Free the memory only when debugging. Freeing wastes some time,
+ // but allows detecting possible memory leaks with Valgrind.
+ lzma_end(&strm);
+#endif
+
+ return exit_status;
+}