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