1 ///////////////////////////////////////////////////////////////////////////////
4 /// \brief Simple single-threaded tool to uncompress .xz or .lzma files
6 // Copyright (C) 2007 Lasse Collin
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU Lesser General Public
10 // License as published by the Free Software Foundation; either
11 // version 2.1 of the License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // Lesser General Public License for more details.
18 ///////////////////////////////////////////////////////////////////////////////
36 # define TOOL_FORMAT "lzma"
38 # define TOOL_FORMAT "xz"
42 /// Number of bytes to use memory at maximum
43 static uint64_t memlimit;
45 /// Program name to be shown in error messages
46 static const char *argv0;
49 static void lzma_attribute((noreturn))
52 int status = EXIT_SUCCESS;
54 // Close stdout. We don't care about stderr, because we write to it
55 // only when an error has already occurred.
56 const int ferror_err = ferror(stdout);
57 const int fclose_err = fclose(stdout);
59 if (ferror_err || fclose_err) {
60 // If it was fclose() that failed, we have the reason
61 // in errno. If only ferror() indicated an error,
62 // we have no idea what the reason was.
63 fprintf(stderr, "%s: Cannot write to standard output: %s\n",
65 ? strerror(errno) : "Unknown error");
66 status = EXIT_FAILURE;
73 static void lzma_attribute((noreturn))
77 "Usage: %s [OPTION]... [FILE]...\n"
78 "Uncompress files in the ." TOOL_FORMAT " format to the standard output.\n"
80 " -c, --stdout (ignored)\n"
81 " -d, --decompress (ignored)\n"
82 " -k, --keep (ignored)\n"
83 " -f, --force (ignored)\n"
84 " -M, --memory=NUM use NUM bytes of memory at maximum (0 means default);\n"
85 " the suffixes k, M, G, Ki, Mi, and Gi are supported.\n"
86 " -h, --help display this help and exit\n"
87 " -V, --version display version and license information and exit\n"
89 "With no FILE, or when FILE is -, read standard input.\n"
91 "On this configuration, the tool will use about %" PRIu64
92 " MiB of memory at maximum.\n"
94 "Report bugs to <" PACKAGE_BUGREPORT "> (in English or Finnish).\n",
95 argv0, (memlimit + 512 * 1024) / (1024 * 1024));
100 static void lzma_attribute((noreturn))
103 printf(TOOL_FORMAT "dec " PACKAGE_VERSION "\n"
104 "liblzma %s\n", lzma_version_string());
110 /// Finds out the amount of physical memory in the system, and sets
111 /// a default memory usage limit.
113 set_default_memlimit(void)
115 const uint64_t mem = physmem();
118 // Cannot autodetect, use 10 MiB as the default limit.
119 memlimit = (1U << 23) + (1U << 21);
121 // Limit is 33 % of RAM.
128 /// \brief Converts a string to uint64_t
130 /// This is rudely copied from src/xz/util.c and modified a little. :-(
133 str_to_uint64(const char *value)
137 if (*value < '0' || *value > '9') {
138 fprintf(stderr, "%s: %s: Not a number\n", argv0, value);
144 if (result > (UINT64_MAX - 9) / 10)
148 result += *value - '0';
150 } while (*value >= '0' && *value <= '9');
152 if (*value != '\0') {
154 static const struct {
163 { "GB", 1000000000 },
168 { "Gi", 1073741824 },
169 { "GiB", 1073741824 }
172 uint32_t multiplier = 0;
173 for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++i) {
174 if (strcmp(value, suffixes[i].name) == 0) {
175 multiplier = suffixes[i].multiplier;
180 if (multiplier == 0) {
181 fprintf(stderr, "%s: %s: Invalid suffix\n",
186 // Don't overflow here either.
187 if (result > UINT64_MAX / multiplier)
190 result *= multiplier;
197 /// Parses command line options.
199 parse_options(int argc, char **argv)
201 static const char short_opts[] = "cdkfM:hV";
202 static const struct option long_opts[] = {
203 { "stdout", no_argument, NULL, 'c' },
204 { "to-stdout", no_argument, NULL, 'c' },
205 { "decompress", no_argument, NULL, 'd' },
206 { "uncompress", no_argument, NULL, 'd' },
207 { "force", no_argument, NULL, 'f' },
208 { "keep", no_argument, NULL, 'k' },
209 { "memory", required_argument, NULL, 'M' },
210 { "help", no_argument, NULL, 'h' },
211 { "version", no_argument, NULL, 'V' },
217 while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL))
227 memlimit = str_to_uint64(optarg);
229 set_default_memlimit();
249 uncompress(lzma_stream *strm, FILE *file, const char *filename)
253 // Initialize the decoder
255 ret = lzma_alone_decoder(strm, memlimit);
257 ret = lzma_stream_decoder(strm, memlimit, LZMA_CONCATENATED);
260 // The only reasonable error here is LZMA_MEM_ERROR.
261 // FIXME: Maybe also LZMA_MEMLIMIT_ERROR in future?
262 if (ret != LZMA_OK) {
263 fprintf(stderr, "%s: ", argv0);
265 if (ret == LZMA_MEM_ERROR)
266 fprintf(stderr, "%s\n", strerror(ENOMEM));
268 fprintf(stderr, "Internal program error (bug)\n");
273 // Input and output buffers
274 uint8_t in_buf[BUFSIZ];
275 uint8_t out_buf[BUFSIZ];
278 strm->next_out = out_buf;
279 strm->avail_out = BUFSIZ;
281 lzma_action action = LZMA_RUN;
284 if (strm->avail_in == 0) {
285 strm->next_in = in_buf;
286 strm->avail_in = fread(in_buf, 1, BUFSIZ, file);
289 // POSIX says that fread() sets errno if
290 // an error occurred. ferror() doesn't
292 fprintf(stderr, "%s: %s: Error reading "
300 // When using LZMA_CONCATENATED, we need to tell
301 // liblzma when it has got all the input.
303 action = LZMA_FINISH;
307 ret = lzma_code(strm, action);
309 // Write and check write error before checking decoder error.
310 // This way as much data as possible gets written to output
311 // even if decoder detected an error.
312 if (strm->avail_out == 0 || ret != LZMA_OK) {
313 const size_t write_size = BUFSIZ - strm->avail_out;
315 if (fwrite(out_buf, 1, write_size, stdout)
317 // Wouldn't be a surprise if writing to stderr
318 // would fail too but at least try to show an
320 fprintf(stderr, "%s: Cannot write to "
321 "standard output: %s\n", argv0,
326 strm->next_out = out_buf;
327 strm->avail_out = BUFSIZ;
330 if (ret != LZMA_OK) {
331 if (ret == LZMA_STREAM_END) {
333 // Check that there's no trailing garbage.
334 if (strm->avail_in != 0
335 || fread(in_buf, 1, 1, file)
338 ret = LZMA_DATA_ERROR;
342 // lzma_stream_decoder() already guarantees
343 // that there's no trailing garbage.
344 assert(strm->avail_in == 0);
345 assert(action == LZMA_FINISH);
354 msg = strerror(ENOMEM);
357 case LZMA_MEMLIMIT_ERROR:
358 msg = "Memory usage limit reached";
361 case LZMA_FORMAT_ERROR:
362 msg = "File format not recognized";
365 case LZMA_OPTIONS_ERROR:
366 // FIXME: Better message?
367 msg = "Unsupported compression options";
370 case LZMA_DATA_ERROR:
371 msg = "File is corrupt";
375 msg = "Unexpected end of input";
379 msg = "Internal program error (bug)";
383 fprintf(stderr, "%s: %s: %s\n", argv0, filename, msg);
392 main(int argc, char **argv)
394 // Set the argv0 global so that we can print the command name in
395 // error and help messages.
398 // Detect amount of installed RAM and set the memory usage limit.
399 // This is needed before parsing the command line arguments.
400 set_default_memlimit();
402 // Parse the command line options.
403 parse_options(argc, argv);
405 // The same lzma_stream is used for all files that we decode. This way
406 // we don't need to reallocate memory for every file if they use same
407 // compression settings.
408 lzma_stream strm = LZMA_STREAM_INIT;
410 // Some systems require setting stdin and stdout to binary mode.
412 setmode(fileno(stdin), O_BINARY);
413 setmode(fileno(stdout), O_BINARY);
416 if (optind == argc) {
417 // No filenames given, decode from stdin.
418 uncompress(&strm, stdin, "(stdin)");
420 // Loop through the filenames given on the command line.
422 // "-" indicates stdin.
423 if (strcmp(argv[optind], "-") == 0) {
424 uncompress(&strm, stdin, "(stdin)");
426 FILE *file = fopen(argv[optind], "rb");
428 fprintf(stderr, "%s: %s: %s\n",
434 uncompress(&strm, file, argv[optind]);
437 } while (++optind < argc);
441 // Free the memory only when debugging. Freeing wastes some time,
442 // but allows detecting possible memory leaks with Valgrind.