| Index: xz/src/xzdec/xzdec.c
|
| ===================================================================
|
| --- xz/src/xzdec/xzdec.c (revision 0)
|
| +++ xz/src/xzdec/xzdec.c (revision 0)
|
| @@ -0,0 +1,491 @@
|
| +///////////////////////////////////////////////////////////////////////////////
|
| +//
|
| +/// \file xzdec.c
|
| +/// \brief Simple single-threaded tool to uncompress .xz or .lzma files
|
| +//
|
| +// Author: Lasse Collin
|
| +//
|
| +// This file has been put into the public domain.
|
| +// You can do whatever you want with this file.
|
| +//
|
| +///////////////////////////////////////////////////////////////////////////////
|
| +
|
| +#include "sysdefs.h"
|
| +#include "lzma.h"
|
| +
|
| +#include <stdarg.h>
|
| +#include <errno.h>
|
| +#include <stdio.h>
|
| +#include <unistd.h>
|
| +
|
| +#include "getopt.h"
|
| +#include "tuklib_progname.h"
|
| +#include "tuklib_exit.h"
|
| +
|
| +#ifdef TUKLIB_DOSLIKE
|
| +# include <fcntl.h>
|
| +# include <io.h>
|
| +#endif
|
| +
|
| +
|
| +#ifdef LZMADEC
|
| +# define TOOL_FORMAT "lzma"
|
| +#else
|
| +# define TOOL_FORMAT "xz"
|
| +#endif
|
| +
|
| +
|
| +/// Number of bytes to use memory at maximum
|
| +static uint64_t memlimit;
|
| +
|
| +/// Total amount of physical RAM
|
| +static uint64_t total_ram;
|
| +
|
| +/// Error messages are suppressed if this is zero, which is the case when
|
| +/// --quiet has been given at least twice.
|
| +static unsigned int display_errors = 2;
|
| +
|
| +
|
| +static void lzma_attribute((format(printf, 1, 2)))
|
| +my_errorf(const char *fmt, ...)
|
| +{
|
| + va_list ap;
|
| + va_start(ap, fmt);
|
| +
|
| + if (display_errors) {
|
| + fprintf(stderr, "%s: ", progname);
|
| + vfprintf(stderr, fmt, ap);
|
| + fprintf(stderr, "\n");
|
| + }
|
| +
|
| + va_end(ap);
|
| + return;
|
| +}
|
| +
|
| +
|
| +static void lzma_attribute((noreturn))
|
| +help(void)
|
| +{
|
| + // Round up to the next MiB and do it correctly also with UINT64_MAX.
|
| + const uint64_t mem_mib = (memlimit >> 20)
|
| + + ((memlimit & ((UINT32_C(1) << 20) - 1)) != 0);
|
| +
|
| + printf(
|
| +"Usage: %s [OPTION]... [FILE]...\n"
|
| +"Uncompress files in the ." TOOL_FORMAT " format to the standard output.\n"
|
| +"\n"
|
| +" -c, --stdout (ignored)\n"
|
| +" -d, --decompress (ignored)\n"
|
| +" -k, --keep (ignored)\n"
|
| +" -M, --memory=NUM use NUM bytes of memory at maximum (0 means default)\n"
|
| +" -q, --quiet specify *twice* to suppress errors\n"
|
| +" -Q, --no-warn (ignored)\n"
|
| +" -h, --help display this help and exit\n"
|
| +" -V, --version display the version number and exit\n"
|
| +"\n"
|
| +"With no FILE, or when FILE is -, read standard input.\n"
|
| +"\n"
|
| +"On this system and configuration, this program will use a maximum of roughly\n"
|
| +"%" PRIu64 " MiB RAM.\n"
|
| +"\n"
|
| +"Report bugs to <" PACKAGE_BUGREPORT "> (in English or Finnish).\n"
|
| +PACKAGE_NAME " home page: <" PACKAGE_URL ">\n", progname, mem_mib);
|
| + tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, display_errors);
|
| +}
|
| +
|
| +
|
| +static void lzma_attribute((noreturn))
|
| +version(void)
|
| +{
|
| + printf(TOOL_FORMAT "dec (" PACKAGE_NAME ") " LZMA_VERSION_STRING "\n"
|
| + "liblzma %s\n", lzma_version_string());
|
| +
|
| + tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, display_errors);
|
| +}
|
| +
|
| +
|
| +/// Find out the amount of physical memory (RAM) in the system, and set
|
| +/// the memory usage limit to the given percentage of RAM.
|
| +static void
|
| +memlimit_set_percentage(uint32_t percentage)
|
| +{
|
| + memlimit = percentage * total_ram / 100;
|
| + return;
|
| +}
|
| +
|
| +
|
| +/// Set the memory usage limit to give number of bytes. Zero is a special
|
| +/// value to indicate the default limit.
|
| +static void
|
| +memlimit_set(uint64_t new_memlimit)
|
| +{
|
| + if (new_memlimit != 0) {
|
| + memlimit = new_memlimit;
|
| + } else {
|
| + memlimit = 40 * total_ram / 100;
|
| + if (memlimit < UINT64_C(80) * 1024 * 1024) {
|
| + memlimit = 80 * total_ram / 100;
|
| + if (memlimit > UINT64_C(80) * 1024 * 1024)
|
| + memlimit = UINT64_C(80) * 1024 * 1024;
|
| + }
|
| + }
|
| +
|
| + return;
|
| +}
|
| +
|
| +
|
| +/// Get the total amount of physical RAM and set the memory usage limit
|
| +/// to the default value.
|
| +static void
|
| +memlimit_init(void)
|
| +{
|
| + // If we cannot determine the amount of RAM, use the assumption
|
| + // defined by the configure script.
|
| + total_ram = lzma_physmem();
|
| + if (total_ram == 0)
|
| + total_ram = (uint64_t)(ASSUME_RAM) * 1024 * 1024;
|
| +
|
| + memlimit_set(0);
|
| + return;
|
| +}
|
| +
|
| +
|
| +/// \brief Convert a string to uint64_t
|
| +///
|
| +/// This is rudely copied from src/xz/util.c and modified a little. :-(
|
| +/// Since this function is used only for parsing the memory usage limit,
|
| +/// this cheats a little and saturates too big values to UINT64_MAX instead
|
| +/// of giving an error.
|
| +///
|
| +/// \param max Return value when the string "max" was specified.
|
| +///
|
| +static uint64_t
|
| +str_to_uint64(const char *value, uint64_t max)
|
| +{
|
| + uint64_t result = 0;
|
| +
|
| + // Accept special value "max".
|
| + if (strcmp(value, "max") == 0)
|
| + return max;
|
| +
|
| + if (*value < '0' || *value > '9') {
|
| + my_errorf("%s: Value is not a non-negative decimal integer",
|
| + value);
|
| + exit(EXIT_FAILURE);
|
| + }
|
| +
|
| + do {
|
| + // Don't overflow.
|
| + if (result > UINT64_MAX / 10)
|
| + return UINT64_MAX;
|
| +
|
| + result *= 10;
|
| +
|
| + // Another overflow check
|
| + const uint32_t add = *value - '0';
|
| + if (UINT64_MAX - add < result)
|
| + return UINT64_MAX;
|
| +
|
| + result += add;
|
| + ++value;
|
| + } while (*value >= '0' && *value <= '9');
|
| +
|
| + if (*value != '\0') {
|
| + // Look for suffix.
|
| + uint64_t multiplier = 0;
|
| + if (*value == 'k' || *value == 'K')
|
| + multiplier = UINT64_C(1) << 10;
|
| + else if (*value == 'm' || *value == 'M')
|
| + multiplier = UINT64_C(1) << 20;
|
| + else if (*value == 'g' || *value == 'G')
|
| + multiplier = UINT64_C(1) << 30;
|
| +
|
| + ++value;
|
| +
|
| + // Allow also e.g. Ki, KiB, and KB.
|
| + if (*value != '\0' && strcmp(value, "i") != 0
|
| + && strcmp(value, "iB") != 0
|
| + && strcmp(value, "B") != 0)
|
| + multiplier = 0;
|
| +
|
| + if (multiplier == 0) {
|
| + my_errorf("%s: Invalid suffix", value - 1);
|
| + exit(EXIT_FAILURE);
|
| + }
|
| +
|
| + // Don't overflow here either.
|
| + if (result > UINT64_MAX / multiplier)
|
| + result = UINT64_MAX;
|
| + else
|
| + result *= multiplier;
|
| + }
|
| +
|
| + return result;
|
| +}
|
| +
|
| +
|
| +/// Parses command line options.
|
| +static void
|
| +parse_options(int argc, char **argv)
|
| +{
|
| + static const char short_opts[] = "cdkM:hqQV";
|
| + 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' },
|
| + { "keep", no_argument, NULL, 'k' },
|
| + { "memory", required_argument, NULL, 'M' },
|
| + { "quiet", no_argument, NULL, 'q' },
|
| + { "no-warn", no_argument, NULL, 'Q' },
|
| + { "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':
|
| + case 'Q':
|
| + break;
|
| +
|
| + case 'M': {
|
| + // Support specifying the limit as a percentage of
|
| + // installed physical RAM.
|
| + const size_t len = strlen(optarg);
|
| + if (len > 0 && optarg[len - 1] == '%') {
|
| + // Memory limit is a percentage of total
|
| + // installed RAM.
|
| + optarg[len - 1] = '\0';
|
| + const uint64_t percentage
|
| + = str_to_uint64(optarg, 100);
|
| + if (percentage < 1 || percentage > 100) {
|
| + my_errorf("Percentage must be in "
|
| + "the range [1, 100]");
|
| + exit(EXIT_FAILURE);
|
| + }
|
| +
|
| + memlimit_set_percentage(percentage);
|
| + } else {
|
| + memlimit_set(str_to_uint64(
|
| + optarg, UINT64_MAX));
|
| + }
|
| +
|
| + break;
|
| + }
|
| +
|
| + case 'q':
|
| + if (display_errors > 0)
|
| + --display_errors;
|
| +
|
| + break;
|
| +
|
| + case 'h':
|
| + help();
|
| +
|
| + case 'V':
|
| + version();
|
| +
|
| + default:
|
| + exit(EXIT_FAILURE);
|
| + }
|
| + }
|
| +
|
| + return;
|
| +}
|
| +
|
| +
|
| +static void
|
| +uncompress(lzma_stream *strm, FILE *file, const char *filename)
|
| +{
|
| + lzma_ret ret;
|
| +
|
| + // Initialize the decoder
|
| +#ifdef LZMADEC
|
| + ret = lzma_alone_decoder(strm, memlimit);
|
| +#else
|
| + ret = lzma_stream_decoder(strm, memlimit, LZMA_CONCATENATED);
|
| +#endif
|
| +
|
| + // The only reasonable error here is LZMA_MEM_ERROR.
|
| + // FIXME: Maybe also LZMA_MEMLIMIT_ERROR in future?
|
| + if (ret != LZMA_OK) {
|
| + my_errorf("%s", ret == LZMA_MEM_ERROR ? strerror(ENOMEM)
|
| + : "Internal error (bug)");
|
| + exit(EXIT_FAILURE);
|
| + }
|
| +
|
| + // Input and output buffers
|
| + uint8_t in_buf[BUFSIZ];
|
| + uint8_t out_buf[BUFSIZ];
|
| +
|
| + 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.
|
| + my_errorf("%s: Error reading input file: %s",
|
| + filename, strerror(errno));
|
| + exit(EXIT_FAILURE);
|
| + }
|
| +
|
| +#ifndef LZMADEC
|
| + // When using LZMA_CONCATENATED, we need to tell
|
| + // liblzma when it has got all the input.
|
| + if (feof(file))
|
| + action = LZMA_FINISH;
|
| +#endif
|
| + }
|
| +
|
| + 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.
|
| + my_errorf("Cannot write to standard output: "
|
| + "%s", strerror(errno));
|
| + exit(EXIT_FAILURE);
|
| + }
|
| +
|
| + strm->next_out = out_buf;
|
| + strm->avail_out = BUFSIZ;
|
| + }
|
| +
|
| + if (ret != LZMA_OK) {
|
| + if (ret == LZMA_STREAM_END) {
|
| +#ifdef LZMADEC
|
| + // Check that there's no trailing garbage.
|
| + if (strm->avail_in != 0
|
| + || fread(in_buf, 1, 1, file)
|
| + != 0
|
| + || !feof(file))
|
| + ret = LZMA_DATA_ERROR;
|
| + else
|
| + return;
|
| +#else
|
| + // lzma_stream_decoder() already guarantees
|
| + // that there's no trailing garbage.
|
| + assert(strm->avail_in == 0);
|
| + assert(action == LZMA_FINISH);
|
| + assert(feof(file));
|
| + return;
|
| +#endif
|
| + }
|
| +
|
| + const char *msg;
|
| + switch (ret) {
|
| + case LZMA_MEM_ERROR:
|
| + msg = strerror(ENOMEM);
|
| + break;
|
| +
|
| + case LZMA_MEMLIMIT_ERROR:
|
| + msg = "Memory usage limit reached";
|
| + break;
|
| +
|
| + case LZMA_FORMAT_ERROR:
|
| + msg = "File format not recognized";
|
| + break;
|
| +
|
| + case LZMA_OPTIONS_ERROR:
|
| + // FIXME: Better message?
|
| + msg = "Unsupported compression options";
|
| + break;
|
| +
|
| + case LZMA_DATA_ERROR:
|
| + msg = "File is corrupt";
|
| + break;
|
| +
|
| + case LZMA_BUF_ERROR:
|
| + msg = "Unexpected end of input";
|
| + break;
|
| +
|
| + default:
|
| + msg = "Internal error (bug)";
|
| + break;
|
| + }
|
| +
|
| + my_errorf("%s: %s", filename, msg);
|
| + exit(EXIT_FAILURE);
|
| + }
|
| + }
|
| +}
|
| +
|
| +
|
| +int
|
| +main(int argc, char **argv)
|
| +{
|
| + // Initialize progname which we will be used in error messages.
|
| + tuklib_progname_init(argv);
|
| +
|
| + // Set the default memory usage limit. This is needed before parsing
|
| + // the command line arguments.
|
| + memlimit_init();
|
| +
|
| + // Parse the command line options.
|
| + parse_options(argc, argv);
|
| +
|
| + // The same lzma_stream is used for all files that we decode. This way
|
| + // we don't need to reallocate memory for every file if they use same
|
| + // compression settings.
|
| + lzma_stream strm = LZMA_STREAM_INIT;
|
| +
|
| + // Some systems require setting stdin and stdout to binary mode.
|
| +#ifdef TUKLIB_DOSLIKE
|
| + setmode(fileno(stdin), O_BINARY);
|
| + setmode(fileno(stdout), O_BINARY);
|
| +#endif
|
| +
|
| + if (optind == argc) {
|
| + // No filenames given, decode from stdin.
|
| + uncompress(&strm, stdin, "(stdin)");
|
| + } else {
|
| + // Loop through the filenames given on the command line.
|
| + do {
|
| + // "-" indicates stdin.
|
| + if (strcmp(argv[optind], "-") == 0) {
|
| + uncompress(&strm, stdin, "(stdin)");
|
| + } else {
|
| + FILE *file = fopen(argv[optind], "rb");
|
| + if (file == NULL) {
|
| + my_errorf("%s: %s", argv[optind],
|
| + strerror(errno));
|
| + exit(EXIT_FAILURE);
|
| + }
|
| +
|
| + uncompress(&strm, file, argv[optind]);
|
| + 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
|
| +
|
| + tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, display_errors);
|
| +}
|
|
|
| Property changes on: xz/src/xzdec/xzdec.c
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|