/////////////////////////////////////////////////////////////////////////////// // /// \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 #else extern int errno; #endif #include #include #ifdef WIN32 # include #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 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 %" PRIu64 " MiB of memory at maximum.\n" "\n" "Report bugs to <" PACKAGE_BUGREPORT "> (in English or Finnish).\n", argv0, ((uint64_t)(mem_limit) + 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_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[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': 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) { 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); break; case FORMAT_NATIVE: ret = lzma_stream_decoder(&strm); 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_limiter = lzma_memlimit_create(mem_limit); if (mem_limiter == NULL) { fprintf(stderr, "%s: %s\n", argv0, strerror(ENOMEM)); exit(ERROR); } assert(lzma_memlimit_count(mem_limiter) == 0); allocator.opaque = mem_limiter; strm.allocator = &allocator; #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); assert(lzma_memlimit_count(mem_limiter) == 0); lzma_memlimit_end(mem_limiter, false); #endif return exit_status; }