From f901a290eef67b8ea4720ccdf5f46edf775ed9d7 Mon Sep 17 00:00:00 2001 From: Lasse Collin Date: Thu, 20 Nov 2008 18:05:52 +0200 Subject: [PATCH] Build xzdec and lzmadec from xzdec.c. xzdec supports only .xz files and lzmadec only .lzma files. --- src/xzdec/Makefile.am | 7 +- src/xzdec/xzdec.c | 315 ++++++++++++++++++------------------------ 2 files changed, 142 insertions(+), 180 deletions(-) diff --git a/src/xzdec/Makefile.am b/src/xzdec/Makefile.am index 8c8cae80..84183fc0 100644 --- a/src/xzdec/Makefile.am +++ b/src/xzdec/Makefile.am @@ -12,7 +12,7 @@ ## Lesser General Public License for more details. ## -bin_PROGRAMS = xzdec +bin_PROGRAMS = xzdec lzmadec xzdec_SOURCES = xzdec.c xzdec_CPPFLAGS = \ @@ -27,3 +27,8 @@ xzdec_LDADD = \ if COND_GNULIB xzdec_LDADD += @top_builddir@/lib/libgnu.a endif + +lzmadec_SOURCES = $(xzdec_SOURCES) +lzmadec_CPPFLAGS = $(xzdec_CPPFLAGS) -DLZMADEC +lzmadec_LDFLAGS = $(xzdec_LDFLAGS) +lzmadec_LDADD = $(xzdec_LDADD) diff --git a/src/xzdec/xzdec.c b/src/xzdec/xzdec.c index d9cd5457..1660cddd 100644 --- a/src/xzdec/xzdec.c +++ b/src/xzdec/xzdec.c @@ -1,7 +1,7 @@ /////////////////////////////////////////////////////////////////////////////// // /// \file xzdec.c -/// \brief Simple single-threaded tool to uncompress .lzma files +/// \brief Simple single-threaded tool to uncompress .xz or .lzma files // // Copyright (C) 2007 Lasse Collin // @@ -31,51 +31,42 @@ #include "physmem.h" -enum return_code { - SUCCESS, - ERROR, - WARNING, -}; +#ifdef LZMADEC +# define TOOL_FORMAT "lzma" +#else +# define TOOL_FORMAT "xz" +#endif -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 void lzma_attribute((noreturn)) +my_exit(void) +{ + int status = EXIT_SUCCESS; -static enum return_code exit_status = SUCCESS; + // Close stdout. We don't care about stderr, because we write to it + // only when an error has already occurred. + const int ferror_err = ferror(stdout); + const int fclose_err = fclose(stdout); -static enum format_type format_type = FORMAT_AUTO; + if (ferror_err || fclose_err) { + // If it was fclose() that failed, we have the reason + // in errno. If only ferror() indicated an error, + // we have no idea what the reason was. + fprintf(stderr, "%s: Cannot write to standard output: %s\n", + argv0, fclose_err + ? strerror(errno) : "Unknown error"); + status = EXIT_FAILURE; + } -static bool force = false; + exit(status); +} static void lzma_attribute((noreturn)) @@ -83,16 +74,14 @@ help(void) { printf( "Usage: %s [OPTION]... [FILE]...\n" -"Uncompress files in the .lzma format to the standard output.\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" -" -f, --force allow reading compressed data from a terminal\n" +" -f, --force (ignored)\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" @@ -102,32 +91,18 @@ help(void) " 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); + argv0, (memlimit + 512 * 1024) / (1024 * 1024)); + my_exit(); } static void lzma_attribute((noreturn)) version(void) { - printf( -"xzdec " 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); + printf(TOOL_FORMAT "dec " PACKAGE_VERSION "\n" + "liblzma %s\n", lzma_version_string()); + + my_exit(); } @@ -160,7 +135,7 @@ str_to_uint64(const char *value) if (*value < '0' || *value > '9') { fprintf(stderr, "%s: %s: Not a number", argv0, value); - exit(ERROR); + exit(EXIT_FAILURE); } do { @@ -204,7 +179,7 @@ str_to_uint64(const char *value) if (multiplier == 0) { fprintf(stderr, "%s: %s: Invalid suffix", argv0, value); - exit(ERROR); + exit(EXIT_FAILURE); } // Don't overflow here either. @@ -231,7 +206,6 @@ parse_options(int argc, char **argv) { "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 } @@ -244,11 +218,8 @@ parse_options(int argc, char **argv) switch (c) { case 'c': case 'd': - case 'k': - break; - case 'f': - force = true; + case 'k': break; case 'M': @@ -264,23 +235,8 @@ parse_options(int argc, char **argv) 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); + exit(EXIT_FAILURE); } } @@ -288,31 +244,20 @@ parse_options(int argc, char **argv) } -/// Initializes lzma_stream structure for decoding of a new Stream. static void -init(void) +uncompress(lzma_stream *strm, FILE *file, const char *filename) { - 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; - } + // 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) { fprintf(stderr, "%s: ", argv0); @@ -321,36 +266,23 @@ init(void) else fprintf(stderr, "Internal program error (bug)\n"); - exit(ERROR); + exit(EXIT_FAILURE); } - return; -} + // Input and output buffers + uint8_t in_buf[BUFSIZ]; + uint8_t out_buf[BUFSIZ]; - -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; + 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 (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 @@ -360,20 +292,24 @@ uncompress(void) "input file: %s\n", argv0, filename, strerror(errno)); - exit(ERROR); + 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 } - const lzma_ret ret = lzma_code(&strm, action); + 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 (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) { @@ -383,58 +319,69 @@ uncompress(void) fprintf(stderr, "%s: Cannot write to " "standard output: %s\n", argv0, strerror(errno)); - exit(ERROR); + exit(EXIT_FAILURE); } - strm.next_out = out_buf; - strm.avail_out = BUFSIZ; + 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) + 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 + } - fprintf(stderr, "%s: %s: ", argv0, filename); - - // FIXME Add LZMA_*_CHECK and LZMA_FORMAT_ERROR. + const char *msg; 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; + 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; - case LZMA_PROG_ERROR: default: - fprintf(stderr, "Internal program " - "error (bug)\n"); - exit(ERROR); + msg = "Internal program error (bug)"; + break; } + + fprintf(stderr, "%s: %s: %s", argv0, filename, msg); + + exit(EXIT_FAILURE); } } } @@ -443,40 +390,50 @@ uncompress(void) int main(int argc, char **argv) { + // Set the argv0 global so that we can print the command name in + // error and help messages. argv0 = argv[0]; + // Detect amount of installed RAM and set the memory usage limit. + // This is needed before parsing the command line arguments. set_default_memlimit(); + // Parse the command line options. parse_options(argc, argv); + // Initialize liblzma internals. lzma_init_decoder(); + // 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 WIN32 setmode(fileno(stdin), O_BINARY); setmode(fileno(stdout), O_BINARY); #endif if (optind == argc) { - file = stdin; - filename = "(stdin)"; - uncompress(); + // 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) { - file = stdin; - filename = "(stdin)"; - uncompress(); + uncompress(&strm, stdin, "(stdin)"); } else { - filename = argv[optind]; - file = fopen(filename, "rb"); + FILE *file = fopen(argv[optind], "rb"); if (file == NULL) { fprintf(stderr, "%s: %s: %s\n", - argv0, filename, + argv0, argv[optind], strerror(errno)); - exit(ERROR); + exit(EXIT_FAILURE); } - uncompress(); + uncompress(&strm, file, argv[optind]); fclose(file); } } while (++optind < argc); @@ -488,5 +445,5 @@ main(int argc, char **argv) lzma_end(&strm); #endif - return exit_status; + my_exit(); }