1 ///////////////////////////////////////////////////////////////////////////////
4 /// \brief Simple single-threaded tool to uncompress .xz or .lzma files
6 // Author: Lasse Collin
8 // This file has been put into the public domain.
9 // You can do whatever you want with this file.
11 ///////////////////////////////////////////////////////////////////////////////
22 #include "tuklib_progname.h"
23 #include "tuklib_exit.h"
24 #include "tuklib_physmem.h"
33 # define TOOL_FORMAT "lzma"
35 # define TOOL_FORMAT "xz"
39 /// Number of bytes to use memory at maximum
40 static uint64_t memlimit;
42 /// Error messages are suppressed if this is zero, which is the case when
43 /// --quiet has been given at least twice.
44 static unsigned int display_errors = 2;
47 static void lzma_attribute((format(printf, 1, 2)))
48 my_errorf(const char *fmt, ...)
54 fprintf(stderr, "%s: ", progname);
55 vfprintf(stderr, fmt, ap);
56 fprintf(stderr, "\n");
64 static void lzma_attribute((noreturn))
68 "Usage: %s [OPTION]... [FILE]...\n"
69 "Uncompress files in the ." TOOL_FORMAT " format to the standard output.\n"
71 " -c, --stdout (ignored)\n"
72 " -d, --decompress (ignored)\n"
73 " -k, --keep (ignored)\n"
74 " -M, --memory=NUM use NUM bytes of memory at maximum (0 means default)\n"
75 " -q, --quiet specify *twice* to suppress errors\n"
76 " -Q, --no-warn (ignored)\n"
77 " -h, --help display this help and exit\n"
78 " -V, --version display the version number and exit\n"
80 "With no FILE, or when FILE is -, read standard input.\n"
82 "On this system and configuration, this program will use a maximum of roughly\n"
83 "%" PRIu64 " MiB RAM.\n"
85 "Report bugs to <" PACKAGE_BUGREPORT "> (in English or Finnish).\n"
86 PACKAGE_NAME " home page: <" PACKAGE_HOMEPAGE ">\n",
87 progname, memlimit / (1024 * 1024));
88 tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, display_errors);
92 static void lzma_attribute((noreturn))
95 printf(TOOL_FORMAT "dec (" PACKAGE_NAME ") " LZMA_VERSION_STRING "\n"
96 "liblzma %s\n", lzma_version_string());
98 tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, display_errors);
102 /// Find out the amount of physical memory (RAM) in the system, and set
103 /// the memory usage limit to the given percentage of RAM.
105 memlimit_set_percentage(uint32_t percentage)
107 uint64_t mem = tuklib_physmem();
109 // If we cannot determine the amount of RAM, use the assumption
110 // set by the configure script.
112 mem = (uint64_t)(ASSUME_RAM) * 1024 * 1024;
114 memlimit = percentage * mem / 100;
119 /// Set the memory usage limit to give number of bytes. Zero is a special
120 /// value to indicate the default limit.
122 memlimit_set(uint64_t new_memlimit)
124 if (new_memlimit == 0)
125 memlimit_set_percentage(40);
127 memlimit = new_memlimit;
133 /// \brief Convert a string to uint64_t
135 /// This is rudely copied from src/xz/util.c and modified a little. :-(
137 /// \param max Return value when the string "max" was specified.
140 str_to_uint64(const char *value, uint64_t max)
144 // Accept special value "max".
145 if (strcmp(value, "max") == 0)
148 if (*value < '0' || *value > '9') {
149 my_errorf("%s: Value is not a non-negative decimal integer",
156 if (result > (UINT64_MAX - 9) / 10)
160 result += *value - '0';
162 } while (*value >= '0' && *value <= '9');
164 if (*value != '\0') {
166 static const struct {
175 { "GB", 1000000000 },
180 { "Gi", 1073741824 },
181 { "GiB", 1073741824 }
184 uint32_t multiplier = 0;
185 for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++i) {
186 if (strcmp(value, suffixes[i].name) == 0) {
187 multiplier = suffixes[i].multiplier;
192 if (multiplier == 0) {
193 my_errorf("%s: Invalid suffix", value);
197 // Don't overflow here either.
198 if (result > UINT64_MAX / multiplier)
201 result *= multiplier;
208 /// Parses command line options.
210 parse_options(int argc, char **argv)
212 static const char short_opts[] = "cdkM:hqQV";
213 static const struct option long_opts[] = {
214 { "stdout", no_argument, NULL, 'c' },
215 { "to-stdout", no_argument, NULL, 'c' },
216 { "decompress", no_argument, NULL, 'd' },
217 { "uncompress", no_argument, NULL, 'd' },
218 { "keep", no_argument, NULL, 'k' },
219 { "memory", required_argument, NULL, 'M' },
220 { "quiet", no_argument, NULL, 'q' },
221 { "no-warn", no_argument, NULL, 'Q' },
222 { "help", no_argument, NULL, 'h' },
223 { "version", no_argument, NULL, 'V' },
229 while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL))
239 // Support specifying the limit as a percentage of
240 // installed physical RAM.
241 const size_t len = strlen(optarg);
242 if (len > 0 && optarg[len - 1] == '%') {
243 // Memory limit is a percentage of total
245 optarg[len - 1] = '\0';
246 const uint64_t percentage
247 = str_to_uint64(optarg, 100);
248 if (percentage < 1 || percentage > 100) {
249 my_errorf("Percentage must be in "
250 "the range [1, 100]");
254 memlimit_set_percentage(percentage);
256 memlimit_set(str_to_uint64(
257 optarg, UINT64_MAX));
264 if (display_errors > 0)
285 uncompress(lzma_stream *strm, FILE *file, const char *filename)
289 // Initialize the decoder
291 ret = lzma_alone_decoder(strm, memlimit);
293 ret = lzma_stream_decoder(strm, memlimit, LZMA_CONCATENATED);
296 // The only reasonable error here is LZMA_MEM_ERROR.
297 // FIXME: Maybe also LZMA_MEMLIMIT_ERROR in future?
298 if (ret != LZMA_OK) {
299 my_errorf("%s", ret == LZMA_MEM_ERROR ? strerror(ENOMEM)
300 : "Internal error (bug)");
304 // Input and output buffers
305 uint8_t in_buf[BUFSIZ];
306 uint8_t out_buf[BUFSIZ];
309 strm->next_out = out_buf;
310 strm->avail_out = BUFSIZ;
312 lzma_action action = LZMA_RUN;
315 if (strm->avail_in == 0) {
316 strm->next_in = in_buf;
317 strm->avail_in = fread(in_buf, 1, BUFSIZ, file);
320 // POSIX says that fread() sets errno if
321 // an error occurred. ferror() doesn't
323 my_errorf("%s: Error reading input file: %s",
324 filename, strerror(errno));
329 // When using LZMA_CONCATENATED, we need to tell
330 // liblzma when it has got all the input.
332 action = LZMA_FINISH;
336 ret = lzma_code(strm, action);
338 // Write and check write error before checking decoder error.
339 // This way as much data as possible gets written to output
340 // even if decoder detected an error.
341 if (strm->avail_out == 0 || ret != LZMA_OK) {
342 const size_t write_size = BUFSIZ - strm->avail_out;
344 if (fwrite(out_buf, 1, write_size, stdout)
346 // Wouldn't be a surprise if writing to stderr
347 // would fail too but at least try to show an
349 my_errorf("Cannot write to standard output: "
350 "%s", strerror(errno));
354 strm->next_out = out_buf;
355 strm->avail_out = BUFSIZ;
358 if (ret != LZMA_OK) {
359 if (ret == LZMA_STREAM_END) {
361 // Check that there's no trailing garbage.
362 if (strm->avail_in != 0
363 || fread(in_buf, 1, 1, file)
366 ret = LZMA_DATA_ERROR;
370 // lzma_stream_decoder() already guarantees
371 // that there's no trailing garbage.
372 assert(strm->avail_in == 0);
373 assert(action == LZMA_FINISH);
382 msg = strerror(ENOMEM);
385 case LZMA_MEMLIMIT_ERROR:
386 msg = "Memory usage limit reached";
389 case LZMA_FORMAT_ERROR:
390 msg = "File format not recognized";
393 case LZMA_OPTIONS_ERROR:
394 // FIXME: Better message?
395 msg = "Unsupported compression options";
398 case LZMA_DATA_ERROR:
399 msg = "File is corrupt";
403 msg = "Unexpected end of input";
407 msg = "Internal error (bug)";
411 my_errorf("%s: %s", filename, msg);
419 main(int argc, char **argv)
421 // Initialize progname which we will be used in error messages.
422 tuklib_progname_init(argv);
424 // Set the default memory usage limit. This is needed before parsing
425 // the command line arguments.
428 // Parse the command line options.
429 parse_options(argc, argv);
431 // The same lzma_stream is used for all files that we decode. This way
432 // we don't need to reallocate memory for every file if they use same
433 // compression settings.
434 lzma_stream strm = LZMA_STREAM_INIT;
436 // Some systems require setting stdin and stdout to binary mode.
437 #ifdef TUKLIB_DOSLIKE
438 setmode(fileno(stdin), O_BINARY);
439 setmode(fileno(stdout), O_BINARY);
442 if (optind == argc) {
443 // No filenames given, decode from stdin.
444 uncompress(&strm, stdin, "(stdin)");
446 // Loop through the filenames given on the command line.
448 // "-" indicates stdin.
449 if (strcmp(argv[optind], "-") == 0) {
450 uncompress(&strm, stdin, "(stdin)");
452 FILE *file = fopen(argv[optind], "rb");
454 my_errorf("%s: %s", argv[optind],
459 uncompress(&strm, file, argv[optind]);
462 } while (++optind < argc);
466 // Free the memory only when debugging. Freeing wastes some time,
467 // but allows detecting possible memory leaks with Valgrind.
471 tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, display_errors);