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