]> icculus.org git repositories - icculus/xz.git/blob - src/lzmadec/lzmadec.c
Oh well, big messy commit again. Some highlights:
[icculus/xz.git] / src / lzmadec / lzmadec.c
1 ///////////////////////////////////////////////////////////////////////////////
2 //
3 /// \file       lzmadec.c
4 /// \brief      Simple single-threaded tool to uncompress .lzma files
5 //
6 //  Copyright (C) 2007 Lasse Collin
7 //
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.
12 //
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.
17 //
18 ///////////////////////////////////////////////////////////////////////////////
19
20 #include "sysdefs.h"
21
22 #include <errno.h>
23 #include <stdio.h>
24 #include <unistd.h>
25
26 #ifdef WIN32
27 #       include <fcntl.h>
28 #endif
29
30 #include "getopt.h"
31 #include "physmem.h"
32
33
34 enum return_code {
35         SUCCESS,
36         ERROR,
37         WARNING,
38 };
39
40
41 enum format_type {
42         FORMAT_AUTO,
43         FORMAT_NATIVE,
44         FORMAT_ALONE,
45 };
46
47
48 enum {
49         OPTION_FORMAT = INT_MIN,
50 };
51
52
53 /// Input buffer
54 static uint8_t in_buf[BUFSIZ];
55
56 /// Output buffer
57 static uint8_t out_buf[BUFSIZ];
58
59 /// Decoder
60 static lzma_stream strm = LZMA_STREAM_INIT;
61
62 /// Number of bytes to use memory at maximum
63 static uint64_t memlimit;
64
65 /// Program name to be shown in error messages
66 static const char *argv0;
67
68 /// File currently being processed
69 static FILE *file;
70
71 /// Name of the file currently being processed
72 static const char *filename;
73
74 static enum return_code exit_status = SUCCESS;
75
76 static enum format_type format_type = FORMAT_AUTO;
77
78 static bool force = false;
79
80
81 static void lzma_attribute((noreturn))
82 help(void)
83 {
84         printf(
85 "Usage: %s [OPTION]... [FILE]...\n"
86 "Uncompress files in the .lzma format to the standard output.\n"
87 "\n"
88 "  -c, --stdout       (ignored)\n"
89 "  -d, --decompress   (ignored)\n"
90 "  -k, --keep         (ignored)\n"
91 "  -f, --force        allow reading compressed data from a terminal\n"
92 "  -M, --memory=NUM   use NUM bytes of memory at maximum (0 means default);\n"
93 "                     the suffixes k, M, G, Ki, Mi, and Gi are supported.\n"
94 "      --format=FMT   accept only files in the given file format;\n"
95 "                     possible FMTs are `auto', `native', and alone',\n"
96 "  -h, --help         display this help and exit\n"
97 "  -V, --version      display version and license information and exit\n"
98 "\n"
99 "With no FILE, or when FILE is -, read standard input.\n"
100 "\n"
101 "On this configuration, the tool will use about %" PRIu64
102                 " MiB of memory at maximum.\n"
103 "\n"
104 "Report bugs to <" PACKAGE_BUGREPORT "> (in English or Finnish).\n",
105                 argv0, ((uint64_t)(memlimit) + 512 * 1024) / (1024 * 1024));
106                 // Using PRIu64 above instead of %zu to support pre-C99 libc.
107         exit(0);
108 }
109
110
111 static void lzma_attribute((noreturn))
112 version(void)
113 {
114         printf(
115 "lzmadec (LZMA Utils) " PACKAGE_VERSION "\n"
116 "\n"
117 "Copyright (C) 1999-2006 Igor Pavlov\n"
118 "Copyright (C) 2007 Lasse Collin\n"
119 "\n"
120 "This program is free software; you can redistribute it and/or\n"
121 "modify it under the terms of the GNU Lesser General Public\n"
122 "License as published by the Free Software Foundation; either\n"
123 "version 2.1 of the License, or (at your option) any later version.\n"
124 "\n"
125 "This program is distributed in the hope that it will be useful,\n"
126 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
127 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n"
128 "Lesser General Public License for more details.\n"
129 "\n");
130         exit(0);
131 }
132
133
134 /// Finds out the amount of physical memory in the system, and sets
135 /// a default memory usage limit.
136 static void
137 set_default_memlimit(void)
138 {
139         const uint64_t mem = physmem();
140
141         if (mem == 0)
142                 // Cannot autodetect, use 10 MiB as the default limit.
143                 memlimit = (1U << 23) + (1U << 21);
144         else
145                 // Limit is 33 % of RAM.
146                 memlimit = mem / 3;
147
148         return;
149 }
150
151
152 /// \brief      Converts a string to size_t
153 ///
154 /// This is rudely copied from src/lzma/util.c and modified a little. :-(
155 ///
156 static size_t
157 str_to_uint64(const char *value)
158 {
159         size_t result = 0;
160
161         if (*value < '0' || *value > '9') {
162                 fprintf(stderr, "%s: %s: Not a number", argv0, value);
163                 exit(ERROR);
164         }
165
166         do {
167                 // Don't overflow.
168                 if (result > (SIZE_MAX - 9) / 10)
169                         return SIZE_MAX;
170
171                 result *= 10;
172                 result += *value - '0';
173                 ++value;
174         } while (*value >= '0' && *value <= '9');
175
176         if (*value != '\0') {
177                 // Look for suffix.
178                 static const struct {
179                         const char name[4];
180                         size_t multiplier;
181                 } suffixes[] = {
182                         { "k",   1000 },
183                         { "kB",  1000 },
184                         { "M",   1000000 },
185                         { "MB",  1000000 },
186                         { "G",   1000000000 },
187                         { "GB",  1000000000 },
188                         { "Ki",  1024 },
189                         { "KiB", 1024 },
190                         { "Mi",  1048576 },
191                         { "MiB", 1048576 },
192                         { "Gi",  1073741824 },
193                         { "GiB", 1073741824 }
194                 };
195
196                 size_t multiplier = 0;
197                 for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++i) {
198                         if (strcmp(value, suffixes[i].name) == 0) {
199                                 multiplier = suffixes[i].multiplier;
200                                 break;
201                         }
202                 }
203
204                 if (multiplier == 0) {
205                         fprintf(stderr, "%s: %s: Invalid suffix",
206                                         argv0, value);
207                         exit(ERROR);
208                 }
209
210                 // Don't overflow here either.
211                 if (result > SIZE_MAX / multiplier)
212                         result = SIZE_MAX;
213                 else
214                         result *= multiplier;
215         }
216
217         return result;
218 }
219
220
221 /// Parses command line options.
222 static void
223 parse_options(int argc, char **argv)
224 {
225         static const char short_opts[] = "cdkfM:hV";
226         static const struct option long_opts[] = {
227                 { "stdout",       no_argument,         NULL, 'c' },
228                 { "to-stdout",    no_argument,         NULL, 'c' },
229                 { "decompress",   no_argument,         NULL, 'd' },
230                 { "uncompress",   no_argument,         NULL, 'd' },
231                 { "force",        no_argument,         NULL, 'f' },
232                 { "keep",         no_argument,         NULL, 'k' },
233                 { "memory",       required_argument,   NULL, 'M' },
234                 { "format",       required_argument,   NULL, OPTION_FORMAT },
235                 { "help",         no_argument,         NULL, 'h' },
236                 { "version",      no_argument,         NULL, 'V' },
237                 { NULL,           0,                   NULL, 0   }
238         };
239
240         int c;
241
242         while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL))
243                         != -1) {
244                 switch (c) {
245                 case 'c':
246                 case 'd':
247                 case 'k':
248                         break;
249
250                 case 'f':
251                         force = true;
252                         break;
253
254                 case 'M':
255                         memlimit = str_to_uint64(optarg);
256                         if (memlimit == 0)
257                                 set_default_memlimit();
258
259                         break;
260
261                 case 'h':
262                         help();
263
264                 case 'V':
265                         version();
266
267                 case OPTION_FORMAT: {
268                         if (strcmp("auto", optarg) == 0) {
269                                 format_type = FORMAT_AUTO;
270                         } else if (strcmp("native", optarg) == 0) {
271                                 format_type = FORMAT_NATIVE;
272                         } else if (strcmp("alone", optarg) == 0) {
273                                 format_type = FORMAT_ALONE;
274                         } else {
275                                 fprintf(stderr, "%s: %s: Unknown file format "
276                                                 "name\n", argv0, optarg);
277                                 exit(ERROR);
278                         }
279                         break;
280                 }
281
282                 default:
283                         exit(ERROR);
284                 }
285         }
286
287         return;
288 }
289
290
291 /// Initializes lzma_stream structure for decoding of a new Stream.
292 static void
293 init(void)
294 {
295         const uint32_t flags = LZMA_TELL_UNSUPPORTED_CHECK | LZMA_CONCATENATED;
296         lzma_ret ret;
297
298         switch (format_type) {
299         case FORMAT_AUTO:
300                 ret = lzma_auto_decoder(&strm, memlimit, flags);
301                 break;
302
303         case FORMAT_NATIVE:
304                 ret = lzma_stream_decoder(&strm, memlimit, flags);
305                 break;
306
307         case FORMAT_ALONE:
308                 ret = lzma_alone_decoder(&strm, memlimit);
309                 break;
310
311         default:
312                 assert(0);
313                 ret = LZMA_PROG_ERROR;
314         }
315
316         if (ret != LZMA_OK) {
317                 fprintf(stderr, "%s: ", argv0);
318
319                 if (ret == LZMA_MEM_ERROR)
320                         fprintf(stderr, "%s\n", strerror(ENOMEM));
321                 else
322                         fprintf(stderr, "Internal program error (bug)\n");
323
324                 exit(ERROR);
325         }
326
327         return;
328 }
329
330
331 static void
332 uncompress(void)
333 {
334         if (file == stdin && !force && isatty(STDIN_FILENO)) {
335                 fprintf(stderr, "%s: Compressed data not read from "
336                                 "a terminal.\n%s: Use `-f' to force reading "
337                                 "from a terminal, or `-h' for help.\n",
338                                 argv0, argv0);
339                 exit(ERROR);
340         }
341
342         init();
343
344         strm.avail_in = 0;
345         strm.next_out = out_buf;
346         strm.avail_out = BUFSIZ;
347
348         lzma_action action = LZMA_RUN;
349
350         while (true) {
351                 if (strm.avail_in == 0) {
352                         strm.next_in = in_buf;
353                         strm.avail_in = fread(in_buf, 1, BUFSIZ, file);
354
355                         if (ferror(file)) {
356                                 // POSIX says that fread() sets errno if
357                                 // an error occurred. ferror() doesn't
358                                 // touch errno.
359                                 fprintf(stderr, "%s: %s: Error reading "
360                                                 "input file: %s\n",
361                                                 argv0, filename,
362                                                 strerror(errno));
363                                 exit(ERROR);
364                         }
365
366                         if (feof(file))
367                                 action = LZMA_FINISH;
368                 }
369
370                 const lzma_ret ret = lzma_code(&strm, action);
371
372                 // Write and check write error before checking decoder error.
373                 // This way as much data as possible gets written to output
374                 // even if decoder detected an error.
375                 if (strm.avail_out == 0 || ret != LZMA_OK) {
376                         const size_t write_size = BUFSIZ - strm.avail_out;
377
378                         if (fwrite(out_buf, 1, write_size, stdout)
379                                         != write_size) {
380                                 // Wouldn't be a surprise if writing to stderr
381                                 // would fail too but at least try to show an
382                                 // error message.
383                                 fprintf(stderr, "%s: Cannot write to "
384                                                 "standard output: %s\n", argv0,
385                                                 strerror(errno));
386                                 exit(ERROR);
387                         }
388
389                         strm.next_out = out_buf;
390                         strm.avail_out = BUFSIZ;
391                 }
392
393                 if (ret != LZMA_OK) {
394                         // FIXME !!! Doesn't work with LZMA_Alone for the
395                         // same reason as in process.c.
396                         if (ret == LZMA_STREAM_END)
397                                 return;
398
399                         fprintf(stderr, "%s: %s: ", argv0, filename);
400
401                         // FIXME Add LZMA_*_CHECK and LZMA_FORMAT_ERROR.
402                         switch (ret) {
403                         case LZMA_DATA_ERROR:
404                                 fprintf(stderr, "File is corrupt\n");
405                                 exit(ERROR);
406
407                         case LZMA_OPTIONS_ERROR:
408                                 fprintf(stderr, "Unsupported file "
409                                                 "format or filters\n");
410                                 exit(ERROR);
411
412                         case LZMA_MEM_ERROR:
413                                 fprintf(stderr, "%s\n", strerror(ENOMEM));
414                                 exit(ERROR);
415
416                         case LZMA_MEMLIMIT_ERROR:
417                                 fprintf(stderr, "Memory usage limit "
418                                                 "reached\n");
419                                 exit(ERROR);
420
421                         case LZMA_BUF_ERROR:
422                                 fprintf(stderr, "Unexpected end of input\n");
423                                 exit(ERROR);
424
425                         case LZMA_UNSUPPORTED_CHECK:
426                                 fprintf(stderr, "Unsupported type of "
427                                                 "integrity check; not "
428                                                 "verifying file integrity\n");
429                                 exit_status = WARNING;
430                                 break;
431
432                         case LZMA_PROG_ERROR:
433                         default:
434                                 fprintf(stderr, "Internal program "
435                                                 "error (bug)\n");
436                                 exit(ERROR);
437                         }
438                 }
439         }
440 }
441
442
443 int
444 main(int argc, char **argv)
445 {
446         argv0 = argv[0];
447
448         set_default_memlimit();
449
450         parse_options(argc, argv);
451
452         lzma_init_decoder();
453
454 #ifdef WIN32
455         setmode(fileno(stdin), O_BINARY);
456         setmode(fileno(stdout), O_BINARY);
457 #endif
458
459         if (optind == argc) {
460                 file = stdin;
461                 filename = "(stdin)";
462                 uncompress();
463         } else {
464                 do {
465                         if (strcmp(argv[optind], "-") == 0) {
466                                 file = stdin;
467                                 filename = "(stdin)";
468                                 uncompress();
469                         } else {
470                                 filename = argv[optind];
471                                 file = fopen(filename, "rb");
472                                 if (file == NULL) {
473                                         fprintf(stderr, "%s: %s: %s\n",
474                                                         argv0, filename,
475                                                         strerror(errno));
476                                         exit(ERROR);
477                                 }
478
479                                 uncompress();
480                                 fclose(file);
481                         }
482                 } while (++optind < argc);
483         }
484
485 #ifndef NDEBUG
486         // Free the memory only when debugging. Freeing wastes some time,
487         // but allows detecting possible memory leaks with Valgrind.
488         lzma_end(&strm);
489 #endif
490
491         return exit_status;
492 }