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 ///////////////////////////////////////////////////////////////////////////////
37 # define TOOL_FORMAT "lzma"
39 # define TOOL_FORMAT "xz"
43 /// Number of bytes to use memory at maximum
44 static uint64_t memlimit;
46 /// Program name to be shown in error messages
47 static const char *argv0;
50 static void lzma_attribute((noreturn))
53 int status = EXIT_SUCCESS;
55 // Close stdout. We don't care about stderr, because we write to it
56 // only when an error has already occurred.
57 const int ferror_err = ferror(stdout);
58 const int fclose_err = fclose(stdout);
60 if (ferror_err || fclose_err) {
61 // If it was fclose() that failed, we have the reason
62 // in errno. If only ferror() indicated an error,
63 // we have no idea what the reason was.
64 fprintf(stderr, "%s: Cannot write to standard output: %s\n",
66 ? strerror(errno) : "Unknown error");
67 status = EXIT_FAILURE;
74 static void lzma_attribute((noreturn))
78 "Usage: %s [OPTION]... [FILE]...\n"
79 "Uncompress files in the ." TOOL_FORMAT " format to the standard output.\n"
81 " -c, --stdout (ignored)\n"
82 " -d, --decompress (ignored)\n"
83 " -k, --keep (ignored)\n"
84 " -f, --force (ignored)\n"
85 " -M, --memory=NUM use NUM bytes of memory at maximum (0 means default);\n"
86 " the suffixes k, M, G, Ki, Mi, and Gi are supported.\n"
87 " -h, --help display this help and exit\n"
88 " -V, --version display version and license information and exit\n"
90 "With no FILE, or when FILE is -, read standard input.\n"
92 "On this configuration, the tool will use about %" PRIu64
93 " MiB of memory at maximum.\n"
95 "Report bugs to <" PACKAGE_BUGREPORT "> (in English or Finnish).\n",
96 argv0, (memlimit + 512 * 1024) / (1024 * 1024));
101 static void lzma_attribute((noreturn))
104 printf(TOOL_FORMAT "dec " LZMA_VERSION_STRING "\n"
105 "liblzma %s\n", lzma_version_string());
111 /// Finds out the amount of physical memory in the system, and sets
112 /// a default memory usage limit.
114 set_default_memlimit(void)
116 const uint64_t mem = physmem();
119 // Cannot autodetect, use 10 MiB as the default limit.
120 memlimit = (1U << 23) + (1U << 21);
122 // Limit is 33 % of RAM.
129 /// \brief Converts a string to uint64_t
131 /// This is rudely copied from src/xz/util.c and modified a little. :-(
134 str_to_uint64(const char *value)
138 if (*value < '0' || *value > '9') {
139 fprintf(stderr, "%s: %s: Not a number\n", argv0, value);
145 if (result > (UINT64_MAX - 9) / 10)
149 result += *value - '0';
151 } while (*value >= '0' && *value <= '9');
153 if (*value != '\0') {
155 static const struct {
164 { "GB", 1000000000 },
169 { "Gi", 1073741824 },
170 { "GiB", 1073741824 }
173 uint32_t multiplier = 0;
174 for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++i) {
175 if (strcmp(value, suffixes[i].name) == 0) {
176 multiplier = suffixes[i].multiplier;
181 if (multiplier == 0) {
182 fprintf(stderr, "%s: %s: Invalid suffix\n",
187 // Don't overflow here either.
188 if (result > UINT64_MAX / multiplier)
191 result *= multiplier;
198 /// Parses command line options.
200 parse_options(int argc, char **argv)
202 static const char short_opts[] = "cdkfM:hV";
203 static const struct option long_opts[] = {
204 { "stdout", no_argument, NULL, 'c' },
205 { "to-stdout", no_argument, NULL, 'c' },
206 { "decompress", no_argument, NULL, 'd' },
207 { "uncompress", no_argument, NULL, 'd' },
208 { "force", no_argument, NULL, 'f' },
209 { "keep", no_argument, NULL, 'k' },
210 { "memory", required_argument, NULL, 'M' },
211 { "help", no_argument, NULL, 'h' },
212 { "version", no_argument, NULL, 'V' },
218 while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL))
228 memlimit = str_to_uint64(optarg);
230 set_default_memlimit();
250 uncompress(lzma_stream *strm, FILE *file, const char *filename)
254 // Initialize the decoder
256 ret = lzma_alone_decoder(strm, memlimit);
258 ret = lzma_stream_decoder(strm, memlimit, LZMA_CONCATENATED);
261 // The only reasonable error here is LZMA_MEM_ERROR.
262 // FIXME: Maybe also LZMA_MEMLIMIT_ERROR in future?
263 if (ret != LZMA_OK) {
264 fprintf(stderr, "%s: ", argv0);
266 if (ret == LZMA_MEM_ERROR)
267 fprintf(stderr, "%s\n", strerror(ENOMEM));
269 fprintf(stderr, "Internal program error (bug)\n");
274 // Input and output buffers
275 uint8_t in_buf[BUFSIZ];
276 uint8_t out_buf[BUFSIZ];
279 strm->next_out = out_buf;
280 strm->avail_out = BUFSIZ;
282 lzma_action action = LZMA_RUN;
285 if (strm->avail_in == 0) {
286 strm->next_in = in_buf;
287 strm->avail_in = fread(in_buf, 1, BUFSIZ, file);
290 // POSIX says that fread() sets errno if
291 // an error occurred. ferror() doesn't
293 fprintf(stderr, "%s: %s: Error reading "
301 // When using LZMA_CONCATENATED, we need to tell
302 // liblzma when it has got all the input.
304 action = LZMA_FINISH;
308 ret = lzma_code(strm, action);
310 // Write and check write error before checking decoder error.
311 // This way as much data as possible gets written to output
312 // even if decoder detected an error.
313 if (strm->avail_out == 0 || ret != LZMA_OK) {
314 const size_t write_size = BUFSIZ - strm->avail_out;
316 if (fwrite(out_buf, 1, write_size, stdout)
318 // Wouldn't be a surprise if writing to stderr
319 // would fail too but at least try to show an
321 fprintf(stderr, "%s: Cannot write to "
322 "standard output: %s\n", argv0,
327 strm->next_out = out_buf;
328 strm->avail_out = BUFSIZ;
331 if (ret != LZMA_OK) {
332 if (ret == LZMA_STREAM_END) {
334 // Check that there's no trailing garbage.
335 if (strm->avail_in != 0
336 || fread(in_buf, 1, 1, file)
339 ret = LZMA_DATA_ERROR;
343 // lzma_stream_decoder() already guarantees
344 // that there's no trailing garbage.
345 assert(strm->avail_in == 0);
346 assert(action == LZMA_FINISH);
355 msg = strerror(ENOMEM);
358 case LZMA_MEMLIMIT_ERROR:
359 msg = "Memory usage limit reached";
362 case LZMA_FORMAT_ERROR:
363 msg = "File format not recognized";
366 case LZMA_OPTIONS_ERROR:
367 // FIXME: Better message?
368 msg = "Unsupported compression options";
371 case LZMA_DATA_ERROR:
372 msg = "File is corrupt";
376 msg = "Unexpected end of input";
380 msg = "Internal program error (bug)";
384 fprintf(stderr, "%s: %s: %s\n", argv0, filename, msg);
393 main(int argc, char **argv)
395 // Set the argv0 global so that we can print the command name in
396 // error and help messages.
399 // Detect amount of installed RAM and set the memory usage limit.
400 // This is needed before parsing the command line arguments.
401 set_default_memlimit();
403 // Parse the command line options.
404 parse_options(argc, argv);
406 // The same lzma_stream is used for all files that we decode. This way
407 // we don't need to reallocate memory for every file if they use same
408 // compression settings.
409 lzma_stream strm = LZMA_STREAM_INIT;
411 // Some systems require setting stdin and stdout to binary mode.
413 setmode(fileno(stdin), O_BINARY);
414 setmode(fileno(stdout), O_BINARY);
417 if (optind == argc) {
418 // No filenames given, decode from stdin.
419 uncompress(&strm, stdin, "(stdin)");
421 // Loop through the filenames given on the command line.
423 // "-" indicates stdin.
424 if (strcmp(argv[optind], "-") == 0) {
425 uncompress(&strm, stdin, "(stdin)");
427 FILE *file = fopen(argv[optind], "rb");
429 fprintf(stderr, "%s: %s: %s\n",
435 uncompress(&strm, file, argv[optind]);
438 } while (++optind < argc);
442 // Free the memory only when debugging. Freeing wastes some time,
443 // but allows detecting possible memory leaks with Valgrind.