Build xzdec and lzmadec from xzdec.c. xzdec supports only .xz

files and lzmadec only .lzma files.
This commit is contained in:
Lasse Collin 2008-11-20 18:05:52 +02:00
parent 86a0ed8f01
commit f901a290ee
2 changed files with 142 additions and 180 deletions

View File

@ -12,7 +12,7 @@
## Lesser General Public License for more details. ## Lesser General Public License for more details.
## ##
bin_PROGRAMS = xzdec bin_PROGRAMS = xzdec lzmadec
xzdec_SOURCES = xzdec.c xzdec_SOURCES = xzdec.c
xzdec_CPPFLAGS = \ xzdec_CPPFLAGS = \
@ -27,3 +27,8 @@ xzdec_LDADD = \
if COND_GNULIB if COND_GNULIB
xzdec_LDADD += @top_builddir@/lib/libgnu.a xzdec_LDADD += @top_builddir@/lib/libgnu.a
endif endif
lzmadec_SOURCES = $(xzdec_SOURCES)
lzmadec_CPPFLAGS = $(xzdec_CPPFLAGS) -DLZMADEC
lzmadec_LDFLAGS = $(xzdec_LDFLAGS)
lzmadec_LDADD = $(xzdec_LDADD)

View File

@ -1,7 +1,7 @@
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// //
/// \file xzdec.c /// \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 // Copyright (C) 2007 Lasse Collin
// //
@ -31,51 +31,42 @@
#include "physmem.h" #include "physmem.h"
enum return_code { #ifdef LZMADEC
SUCCESS, # define TOOL_FORMAT "lzma"
ERROR, #else
WARNING, # 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 /// Number of bytes to use memory at maximum
static uint64_t memlimit; static uint64_t memlimit;
/// Program name to be shown in error messages /// Program name to be shown in error messages
static const char *argv0; static const char *argv0;
/// File currently being processed
static FILE *file;
/// Name of the file currently being processed static void lzma_attribute((noreturn))
static const char *filename; 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)) static void lzma_attribute((noreturn))
@ -83,16 +74,14 @@ help(void)
{ {
printf( printf(
"Usage: %s [OPTION]... [FILE]...\n" "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" "\n"
" -c, --stdout (ignored)\n" " -c, --stdout (ignored)\n"
" -d, --decompress (ignored)\n" " -d, --decompress (ignored)\n"
" -k, --keep (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" " -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" " 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" " -h, --help display this help and exit\n"
" -V, --version display version and license information and exit\n" " -V, --version display version and license information and exit\n"
"\n" "\n"
@ -102,32 +91,18 @@ help(void)
" MiB of memory at maximum.\n" " MiB of memory at maximum.\n"
"\n" "\n"
"Report bugs to <" PACKAGE_BUGREPORT "> (in English or Finnish).\n", "Report bugs to <" PACKAGE_BUGREPORT "> (in English or Finnish).\n",
argv0, ((uint64_t)(memlimit) + 512 * 1024) / (1024 * 1024)); argv0, (memlimit + 512 * 1024) / (1024 * 1024));
// Using PRIu64 above instead of %zu to support pre-C99 libc. my_exit();
exit(0);
} }
static void lzma_attribute((noreturn)) static void lzma_attribute((noreturn))
version(void) version(void)
{ {
printf( printf(TOOL_FORMAT "dec " PACKAGE_VERSION "\n"
"xzdec " PACKAGE_VERSION "\n" "liblzma %s\n", lzma_version_string());
"\n"
"Copyright (C) 1999-2006 Igor Pavlov\n" my_exit();
"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);
} }
@ -160,7 +135,7 @@ str_to_uint64(const char *value)
if (*value < '0' || *value > '9') { if (*value < '0' || *value > '9') {
fprintf(stderr, "%s: %s: Not a number", argv0, value); fprintf(stderr, "%s: %s: Not a number", argv0, value);
exit(ERROR); exit(EXIT_FAILURE);
} }
do { do {
@ -204,7 +179,7 @@ str_to_uint64(const char *value)
if (multiplier == 0) { if (multiplier == 0) {
fprintf(stderr, "%s: %s: Invalid suffix", fprintf(stderr, "%s: %s: Invalid suffix",
argv0, value); argv0, value);
exit(ERROR); exit(EXIT_FAILURE);
} }
// Don't overflow here either. // Don't overflow here either.
@ -231,7 +206,6 @@ parse_options(int argc, char **argv)
{ "force", no_argument, NULL, 'f' }, { "force", no_argument, NULL, 'f' },
{ "keep", no_argument, NULL, 'k' }, { "keep", no_argument, NULL, 'k' },
{ "memory", required_argument, NULL, 'M' }, { "memory", required_argument, NULL, 'M' },
{ "format", required_argument, NULL, OPTION_FORMAT },
{ "help", no_argument, NULL, 'h' }, { "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' }, { "version", no_argument, NULL, 'V' },
{ NULL, 0, NULL, 0 } { NULL, 0, NULL, 0 }
@ -244,11 +218,8 @@ parse_options(int argc, char **argv)
switch (c) { switch (c) {
case 'c': case 'c':
case 'd': case 'd':
case 'k':
break;
case 'f': case 'f':
force = true; case 'k':
break; break;
case 'M': case 'M':
@ -264,23 +235,8 @@ parse_options(int argc, char **argv)
case 'V': case 'V':
version(); 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: 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 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; lzma_ret ret;
switch (format_type) { // Initialize the decoder
case FORMAT_AUTO: #ifdef LZMADEC
ret = lzma_auto_decoder(&strm, memlimit, flags); ret = lzma_alone_decoder(strm, memlimit);
break; #else
ret = lzma_stream_decoder(strm, memlimit, LZMA_CONCATENATED);
case FORMAT_NATIVE: #endif
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;
}
// The only reasonable error here is LZMA_MEM_ERROR.
// FIXME: Maybe also LZMA_MEMLIMIT_ERROR in future?
if (ret != LZMA_OK) { if (ret != LZMA_OK) {
fprintf(stderr, "%s: ", argv0); fprintf(stderr, "%s: ", argv0);
@ -321,36 +266,23 @@ init(void)
else else
fprintf(stderr, "Internal program error (bug)\n"); 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];
strm->avail_in = 0;
static void strm->next_out = out_buf;
uncompress(void) strm->avail_out = BUFSIZ;
{
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; lzma_action action = LZMA_RUN;
while (true) { while (true) {
if (strm.avail_in == 0) { if (strm->avail_in == 0) {
strm.next_in = in_buf; strm->next_in = in_buf;
strm.avail_in = fread(in_buf, 1, BUFSIZ, file); strm->avail_in = fread(in_buf, 1, BUFSIZ, file);
if (ferror(file)) { if (ferror(file)) {
// POSIX says that fread() sets errno if // POSIX says that fread() sets errno if
@ -360,20 +292,24 @@ uncompress(void)
"input file: %s\n", "input file: %s\n",
argv0, filename, argv0, filename,
strerror(errno)); 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)) if (feof(file))
action = LZMA_FINISH; 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. // Write and check write error before checking decoder error.
// This way as much data as possible gets written to output // This way as much data as possible gets written to output
// even if decoder detected an error. // even if decoder detected an error.
if (strm.avail_out == 0 || ret != LZMA_OK) { if (strm->avail_out == 0 || ret != LZMA_OK) {
const size_t write_size = BUFSIZ - strm.avail_out; const size_t write_size = BUFSIZ - strm->avail_out;
if (fwrite(out_buf, 1, write_size, stdout) if (fwrite(out_buf, 1, write_size, stdout)
!= write_size) { != write_size) {
@ -383,58 +319,69 @@ uncompress(void)
fprintf(stderr, "%s: Cannot write to " fprintf(stderr, "%s: Cannot write to "
"standard output: %s\n", argv0, "standard output: %s\n", argv0,
strerror(errno)); strerror(errno));
exit(ERROR); exit(EXIT_FAILURE);
} }
strm.next_out = out_buf; strm->next_out = out_buf;
strm.avail_out = BUFSIZ; strm->avail_out = BUFSIZ;
} }
if (ret != LZMA_OK) { if (ret != LZMA_OK) {
// FIXME !!! Doesn't work with LZMA_Alone for the if (ret == LZMA_STREAM_END) {
// same reason as in process.c. #ifdef LZMADEC
if (ret == LZMA_STREAM_END) // 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; return;
#endif
}
fprintf(stderr, "%s: %s: ", argv0, filename); const char *msg;
// FIXME Add LZMA_*_CHECK and LZMA_FORMAT_ERROR.
switch (ret) { 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: case LZMA_MEM_ERROR:
fprintf(stderr, "%s\n", strerror(ENOMEM)); msg = strerror(ENOMEM);
exit(ERROR); break;
case LZMA_MEMLIMIT_ERROR: case LZMA_MEMLIMIT_ERROR:
fprintf(stderr, "Memory usage limit " msg = "Memory usage limit reached";
"reached\n"); break;
exit(ERROR);
case LZMA_FORMAT_ERROR:
case LZMA_BUF_ERROR: msg = "File format not recognized";
fprintf(stderr, "Unexpected end of input\n"); break;
exit(ERROR);
case LZMA_OPTIONS_ERROR:
case LZMA_UNSUPPORTED_CHECK: // FIXME: Better message?
fprintf(stderr, "Unsupported type of " msg = "Unsupported compression options";
"integrity check; not " break;
"verifying file integrity\n");
exit_status = WARNING; case LZMA_DATA_ERROR:
msg = "File is corrupt";
break;
case LZMA_BUF_ERROR:
msg = "Unexpected end of input";
break; break;
case LZMA_PROG_ERROR:
default: default:
fprintf(stderr, "Internal program " msg = "Internal program error (bug)";
"error (bug)\n"); break;
exit(ERROR);
} }
fprintf(stderr, "%s: %s: %s", argv0, filename, msg);
exit(EXIT_FAILURE);
} }
} }
} }
@ -443,40 +390,50 @@ uncompress(void)
int int
main(int argc, char **argv) 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]; 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(); set_default_memlimit();
// Parse the command line options.
parse_options(argc, argv); parse_options(argc, argv);
// Initialize liblzma internals.
lzma_init_decoder(); 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 #ifdef WIN32
setmode(fileno(stdin), O_BINARY); setmode(fileno(stdin), O_BINARY);
setmode(fileno(stdout), O_BINARY); setmode(fileno(stdout), O_BINARY);
#endif #endif
if (optind == argc) { if (optind == argc) {
file = stdin; // No filenames given, decode from stdin.
filename = "(stdin)"; uncompress(&strm, stdin, "(stdin)");
uncompress();
} else { } else {
// Loop through the filenames given on the command line.
do { do {
// "-" indicates stdin.
if (strcmp(argv[optind], "-") == 0) { if (strcmp(argv[optind], "-") == 0) {
file = stdin; uncompress(&strm, stdin, "(stdin)");
filename = "(stdin)";
uncompress();
} else { } else {
filename = argv[optind]; FILE *file = fopen(argv[optind], "rb");
file = fopen(filename, "rb");
if (file == NULL) { if (file == NULL) {
fprintf(stderr, "%s: %s: %s\n", fprintf(stderr, "%s: %s: %s\n",
argv0, filename, argv0, argv[optind],
strerror(errno)); strerror(errno));
exit(ERROR); exit(EXIT_FAILURE);
} }
uncompress(); uncompress(&strm, file, argv[optind]);
fclose(file); fclose(file);
} }
} while (++optind < argc); } while (++optind < argc);
@ -488,5 +445,5 @@ main(int argc, char **argv)
lzma_end(&strm); lzma_end(&strm);
#endif #endif
return exit_status; my_exit();
} }