]> icculus.org git repositories - icculus/xz.git/blob - src/xzdec/xzdec.c
Added support for --quiet and --no-warn to xzdec.
[icculus/xz.git] / src / xzdec / xzdec.c
1 ///////////////////////////////////////////////////////////////////////////////
2 //
3 /// \file       xzdec.c
4 /// \brief      Simple single-threaded tool to uncompress .xz or .lzma files
5 //
6 //  Author:     Lasse Collin
7 //
8 //  This file has been put into the public domain.
9 //  You can do whatever you want with this file.
10 //
11 ///////////////////////////////////////////////////////////////////////////////
12
13 #include "sysdefs.h"
14 #include "lzma.h"
15
16 #include <stdarg.h>
17 #include <errno.h>
18 #include <stdio.h>
19 #include <unistd.h>
20
21 #ifdef DOSLIKE
22 #       include <fcntl.h>
23 #       include <io.h>
24 #endif
25
26 #include "getopt.h"
27 #include "physmem.h"
28
29
30 #ifdef LZMADEC
31 #       define TOOL_FORMAT "lzma"
32 #else
33 #       define TOOL_FORMAT "xz"
34 #endif
35
36
37 /// Number of bytes to use memory at maximum
38 static uint64_t memlimit;
39
40 /// Error messages are suppressed if this is zero, which is the case when
41 /// --quiet has been given at least twice.
42 static unsigned int display_errors = 2;
43
44 /// Program name to be shown in error messages
45 static const char *argv0;
46
47
48 static void lzma_attribute((format(printf, 1, 2)))
49 my_errorf(const char *fmt, ...)
50 {
51         va_list ap;
52         va_start(ap, fmt);
53
54         if (display_errors) {
55                 fprintf(stderr, "%s: ", argv0);
56                 vfprintf(stderr, fmt, ap);
57                 fprintf(stderr, "\n");
58         }
59
60         va_end(ap);
61         return;
62 }
63
64
65 static void lzma_attribute((noreturn))
66 my_exit(void)
67 {
68         int status = EXIT_SUCCESS;
69
70         // Close stdout. We don't care about stderr, because we write to it
71         // only when an error has already occurred.
72         const int ferror_err = ferror(stdout);
73         const int fclose_err = fclose(stdout);
74
75         if (ferror_err || fclose_err) {
76                 // If it was fclose() that failed, we have the reason
77                 // in errno. If only ferror() indicated an error,
78                 // we have no idea what the reason was.
79                 my_errorf("Cannot write to standard output: %s", fclose_err
80                                 ? strerror(errno) : "Unknown error");
81                 status = EXIT_FAILURE;
82         }
83
84         exit(status);
85 }
86
87
88 static void lzma_attribute((noreturn))
89 help(void)
90 {
91         printf(
92 "Usage: %s [OPTION]... [FILE]...\n"
93 "Uncompress files in the ." TOOL_FORMAT " format to the standard output.\n"
94 "\n"
95 "  -c, --stdout       (ignored)\n"
96 "  -d, --decompress   (ignored)\n"
97 "  -k, --keep         (ignored)\n"
98 "  -f, --force        (ignored)\n"
99 "  -M, --memory=NUM   use NUM bytes of memory at maximum (0 means default);\n"
100 "                     the suffixes k, M, G, Ki, Mi, and Gi are supported.\n"
101 "  -q, --quiet        specify *twice* to suppress errors\n"
102 "  -Q, --no-warn      (ignored)\n"
103 "  -h, --help         display this help and exit\n"
104 "  -V, --version      display the version number and exit\n"
105 "\n"
106 "With no FILE, or when FILE is -, read standard input.\n"
107 "\n"
108 "On this system and configuration, this program will use at maximum of roughly\n"
109 "%" PRIu64 " MiB RAM.\n"
110 "\n"
111 "Report bugs to <" PACKAGE_BUGREPORT "> (in English or Finnish).\n"
112 "XZ Utils home page: <http://tukaani.org/xz/>\n",
113                 argv0, memlimit / (1024 * 1024));
114         my_exit();
115 }
116
117
118 static void lzma_attribute((noreturn))
119 version(void)
120 {
121         printf(TOOL_FORMAT "dec " LZMA_VERSION_STRING "\n"
122                         "liblzma %s\n", lzma_version_string());
123
124         my_exit();
125 }
126
127
128 /// Finds out the amount of physical memory in the system, and sets
129 /// a default memory usage limit.
130 static void
131 set_default_memlimit(void)
132 {
133         const uint64_t mem = physmem();
134
135         if (mem == 0)
136                 // Cannot autodetect, use 10 MiB as the default limit.
137                 memlimit = UINT64_C(10) * 1024 * 1024;
138         else
139                 // Limit is 40 % of RAM.
140                 memlimit = mem * 2 / 5;
141
142         return;
143 }
144
145
146 /// \brief      Converts a string to uint64_t
147 ///
148 /// This is rudely copied from src/xz/util.c and modified a little. :-(
149 ///
150 static uint64_t
151 str_to_uint64(const char *value)
152 {
153         uint64_t result = 0;
154
155         // Accept special value "max".
156         if (strcmp(value, "max") == 0)
157                 return UINT64_MAX;
158
159         if (*value < '0' || *value > '9') {
160                 my_errorf("%s: Not a number", value);
161                 exit(EXIT_FAILURE);
162         }
163
164         do {
165                 // Don't overflow.
166                 if (result > (UINT64_MAX - 9) / 10)
167                         return UINT64_MAX;
168
169                 result *= 10;
170                 result += *value - '0';
171                 ++value;
172         } while (*value >= '0' && *value <= '9');
173
174         if (*value != '\0') {
175                 // Look for suffix.
176                 static const struct {
177                         const char name[4];
178                         uint32_t multiplier;
179                 } suffixes[] = {
180                         { "k",   1000 },
181                         { "kB",  1000 },
182                         { "M",   1000000 },
183                         { "MB",  1000000 },
184                         { "G",   1000000000 },
185                         { "GB",  1000000000 },
186                         { "Ki",  1024 },
187                         { "KiB", 1024 },
188                         { "Mi",  1048576 },
189                         { "MiB", 1048576 },
190                         { "Gi",  1073741824 },
191                         { "GiB", 1073741824 }
192                 };
193
194                 uint32_t multiplier = 0;
195                 for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++i) {
196                         if (strcmp(value, suffixes[i].name) == 0) {
197                                 multiplier = suffixes[i].multiplier;
198                                 break;
199                         }
200                 }
201
202                 if (multiplier == 0) {
203                         my_errorf("%s: Invalid suffix", value);
204                         exit(EXIT_FAILURE);
205                 }
206
207                 // Don't overflow here either.
208                 if (result > UINT64_MAX / multiplier)
209                         result = UINT64_MAX;
210                 else
211                         result *= multiplier;
212         }
213
214         return result;
215 }
216
217
218 /// Parses command line options.
219 static void
220 parse_options(int argc, char **argv)
221 {
222         static const char short_opts[] = "cdkfM:hqQV";
223         static const struct option long_opts[] = {
224                 { "stdout",       no_argument,         NULL, 'c' },
225                 { "to-stdout",    no_argument,         NULL, 'c' },
226                 { "decompress",   no_argument,         NULL, 'd' },
227                 { "uncompress",   no_argument,         NULL, 'd' },
228                 { "force",        no_argument,         NULL, 'f' },
229                 { "keep",         no_argument,         NULL, 'k' },
230                 { "memory",       required_argument,   NULL, 'M' },
231                 { "quiet",        no_argument,         NULL, 'q' },
232                 { "no-warn",      no_argument,         NULL, 'Q' },
233                 { "help",         no_argument,         NULL, 'h' },
234                 { "version",      no_argument,         NULL, 'V' },
235                 { NULL,           0,                   NULL, 0   }
236         };
237
238         int c;
239
240         while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL))
241                         != -1) {
242                 switch (c) {
243                 case 'c':
244                 case 'd':
245                 case 'f':
246                 case 'k':
247                 case 'Q':
248                         break;
249
250                 case 'M':
251                         memlimit = str_to_uint64(optarg);
252                         if (memlimit == 0)
253                                 set_default_memlimit();
254
255                         break;
256
257                 case 'q':
258                         if (display_errors > 0)
259                                 --display_errors;
260
261                         break;
262
263                 case 'h':
264                         help();
265
266                 case 'V':
267                         version();
268
269                 default:
270                         exit(EXIT_FAILURE);
271                 }
272         }
273
274         return;
275 }
276
277
278 static void
279 uncompress(lzma_stream *strm, FILE *file, const char *filename)
280 {
281         lzma_ret ret;
282
283         // Initialize the decoder
284 #ifdef LZMADEC
285         ret = lzma_alone_decoder(strm, memlimit);
286 #else
287         ret = lzma_stream_decoder(strm, memlimit, LZMA_CONCATENATED);
288 #endif
289
290         // The only reasonable error here is LZMA_MEM_ERROR.
291         // FIXME: Maybe also LZMA_MEMLIMIT_ERROR in future?
292         if (ret != LZMA_OK) {
293                 my_errorf("%s", ret == LZMA_MEM_ERROR ? strerror(ENOMEM)
294                                 : "Internal program error (bug)");
295                 exit(EXIT_FAILURE);
296         }
297
298         // Input and output buffers
299         uint8_t in_buf[BUFSIZ];
300         uint8_t out_buf[BUFSIZ];
301
302         strm->avail_in = 0;
303         strm->next_out = out_buf;
304         strm->avail_out = BUFSIZ;
305
306         lzma_action action = LZMA_RUN;
307
308         while (true) {
309                 if (strm->avail_in == 0) {
310                         strm->next_in = in_buf;
311                         strm->avail_in = fread(in_buf, 1, BUFSIZ, file);
312
313                         if (ferror(file)) {
314                                 // POSIX says that fread() sets errno if
315                                 // an error occurred. ferror() doesn't
316                                 // touch errno.
317                                 my_errorf("%s: Error reading input file: %s",
318                                                 filename, strerror(errno));
319                                 exit(EXIT_FAILURE);
320                         }
321
322 #ifndef LZMADEC
323                         // When using LZMA_CONCATENATED, we need to tell
324                         // liblzma when it has got all the input.
325                         if (feof(file))
326                                 action = LZMA_FINISH;
327 #endif
328                 }
329
330                 ret = lzma_code(strm, action);
331
332                 // Write and check write error before checking decoder error.
333                 // This way as much data as possible gets written to output
334                 // even if decoder detected an error.
335                 if (strm->avail_out == 0 || ret != LZMA_OK) {
336                         const size_t write_size = BUFSIZ - strm->avail_out;
337
338                         if (fwrite(out_buf, 1, write_size, stdout)
339                                         != write_size) {
340                                 // Wouldn't be a surprise if writing to stderr
341                                 // would fail too but at least try to show an
342                                 // error message.
343                                 my_errorf("Cannot write to standard output: "
344                                                 "%s", strerror(errno));
345                                 exit(EXIT_FAILURE);
346                         }
347
348                         strm->next_out = out_buf;
349                         strm->avail_out = BUFSIZ;
350                 }
351
352                 if (ret != LZMA_OK) {
353                         if (ret == LZMA_STREAM_END) {
354 #ifdef LZMADEC
355                                 // Check that there's no trailing garbage.
356                                 if (strm->avail_in != 0
357                                                 || fread(in_buf, 1, 1, file)
358                                                         != 0
359                                                 || !feof(file))
360                                         ret = LZMA_DATA_ERROR;
361                                 else
362                                         return;
363 #else
364                                 // lzma_stream_decoder() already guarantees
365                                 // that there's no trailing garbage.
366                                 assert(strm->avail_in == 0);
367                                 assert(action == LZMA_FINISH);
368                                 assert(feof(file));
369                                 return;
370 #endif
371                         }
372
373                         const char *msg;
374                         switch (ret) {
375                         case LZMA_MEM_ERROR:
376                                 msg = strerror(ENOMEM);
377                                 break;
378
379                         case LZMA_MEMLIMIT_ERROR:
380                                 msg = "Memory usage limit reached";
381                                 break;
382
383                         case LZMA_FORMAT_ERROR:
384                                 msg = "File format not recognized";
385                                 break;
386
387                         case LZMA_OPTIONS_ERROR:
388                                 // FIXME: Better message?
389                                 msg = "Unsupported compression options";
390                                 break;
391
392                         case LZMA_DATA_ERROR:
393                                 msg = "File is corrupt";
394                                 break;
395
396                         case LZMA_BUF_ERROR:
397                                 msg = "Unexpected end of input";
398                                 break;
399
400                         default:
401                                 msg = "Internal program error (bug)";
402                                 break;
403                         }
404
405                         my_errorf("%s: %s", filename, msg);
406                         exit(EXIT_FAILURE);
407                 }
408         }
409 }
410
411
412 int
413 main(int argc, char **argv)
414 {
415         // Set the argv0 global so that we can print the command name in
416         // error and help messages.
417         argv0 = argv[0];
418
419         // Detect amount of installed RAM and set the memory usage limit.
420         // This is needed before parsing the command line arguments.
421         set_default_memlimit();
422
423         // Parse the command line options.
424         parse_options(argc, argv);
425
426         // The same lzma_stream is used for all files that we decode. This way
427         // we don't need to reallocate memory for every file if they use same
428         // compression settings.
429         lzma_stream strm = LZMA_STREAM_INIT;
430
431         // Some systems require setting stdin and stdout to binary mode.
432 #ifdef DOSLIKE
433         setmode(fileno(stdin), O_BINARY);
434         setmode(fileno(stdout), O_BINARY);
435 #endif
436
437         if (optind == argc) {
438                 // No filenames given, decode from stdin.
439                 uncompress(&strm, stdin, "(stdin)");
440         } else {
441                 // Loop through the filenames given on the command line.
442                 do {
443                         // "-" indicates stdin.
444                         if (strcmp(argv[optind], "-") == 0) {
445                                 uncompress(&strm, stdin, "(stdin)");
446                         } else {
447                                 FILE *file = fopen(argv[optind], "rb");
448                                 if (file == NULL) {
449                                         my_errorf("%s: %s", argv[optind],
450                                                         strerror(errno));
451                                         exit(EXIT_FAILURE);
452                                 }
453
454                                 uncompress(&strm, file, argv[optind]);
455                                 fclose(file);
456                         }
457                 } while (++optind < argc);
458         }
459
460 #ifndef NDEBUG
461         // Free the memory only when debugging. Freeing wastes some time,
462         // but allows detecting possible memory leaks with Valgrind.
463         lzma_end(&strm);
464 #endif
465
466         my_exit();
467 }