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 #include "getopt.h"
22 #include "tuklib_progname.h"
23 #include "tuklib_exit.h"
24
25 #ifdef TUKLIB_DOSLIKE
26 # include <fcntl.h>
27 # include <io.h>
28 #endif
29
30
31 #ifdef LZMADEC
32 # define TOOL_FORMAT "lzma"
33 #else
34 # define TOOL_FORMAT "xz"
35 #endif
36
37
38 /// Error messages are suppressed if this is zero, which is the case when
39 /// --quiet has been given at least twice.
40 static int display_errors = 2;
41
42
43 lzma_attribute((__format__(__printf__, 1, 2)))
44 static void
45 my_errorf(const char *fmt, ...)
46 {
47 va_list ap;
48 va_start(ap, fmt);
49
50 if (display_errors) {
51 fprintf(stderr, "%s: ", progname);
52 vfprintf(stderr, fmt, ap);
53 fprintf(stderr, "\n");
54 }
55
56 va_end(ap);
57 return;
58 }
59
60
61 tuklib_attr_noreturn
62 static void
63 help(void)
64 {
65 printf(
66 "Usage: %s [OPTION]... [FILE]...\n"
67 "Decompress files in the ." TOOL_FORMAT " format to standard output.\n"
68 "\n"
69 " -d, --decompress (ignored, only decompression is supported)\n"
70 " -k, --keep (ignored, files are never deleted)\n"
71 " -c, --stdout (ignored, output is always written to standard output)\n"
72 " -q, --quiet specify *twice* to suppress errors\n"
73 " -Q, --no-warn (ignored, the exit status 2 is never used)\n"
74 " -h, --help display this help and exit\n"
75 " -V, --version display the version number and exit\n"
76 "\n"
77 "With no FILE, or when FILE is -, read standard input.\n"
78 "\n"
79 "Report bugs to <" PACKAGE_BUGREPORT "> (in English or Finnish).\n"
80 PACKAGE_NAME " home page: <" PACKAGE_URL ">\n", progname);
81
82 tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, display_errors);
83 }
84
85
86 tuklib_attr_noreturn
87 static void
88 version(void)
89 {
90 printf(TOOL_FORMAT "dec (" PACKAGE_NAME ") " LZMA_VERSION_STRING "\n"
91 "liblzma %s\n", lzma_version_string());
92
93 tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, display_errors);
94 }
95
96
97 /// Parses command line options.
98 static void
99 parse_options(int argc, char **argv)
100 {
101 static const char short_opts[] = "cdkM:hqQV";
102 static const struct option long_opts[] = {
103 { "stdout", no_argument, NULL, 'c' },
104 { "to-stdout", no_argument, NULL, 'c' },
105 { "decompress", no_argument, NULL, 'd' },
106 { "uncompress", no_argument, NULL, 'd' },
107 { "keep", no_argument, NULL, 'k' },
108 { "quiet", no_argument, NULL, 'q' },
109 { "no-warn", no_argument, NULL, 'Q' },
110 { "help", no_argument, NULL, 'h' },
111 { "version", no_argument, NULL, 'V' },
112 { NULL, 0, NULL, 0 }
113 };
114
115 int c;
116
117 while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL))
118 != -1) {
119 switch (c) {
120 case 'c':
121 case 'd':
122 case 'k':
123 case 'Q':
124 break;
125
126 case 'q':
127 if (display_errors > 0)
128 --display_errors;
129
130 break;
131
132 case 'h':
133 help();
134
135 case 'V':
136 version();
137
138 default:
139 exit(EXIT_FAILURE);
140 }
141 }
142
143 return;
144 }
145
146
147 static void
148 uncompress(lzma_stream *strm, FILE *file, const char *filename)
149 {
150 lzma_ret ret;
151
152 // Initialize the decoder
153 #ifdef LZMADEC
154 ret = lzma_alone_decoder(strm, UINT64_MAX);
155 #else
156 ret = lzma_stream_decoder(strm, UINT64_MAX, LZMA_CONCATENATED);
157 #endif
158
159 // The only reasonable error here is LZMA_MEM_ERROR.
160 if (ret != LZMA_OK) {
161 my_errorf("%s", ret == LZMA_MEM_ERROR ? strerror(ENOMEM)
162 : "Internal error (bug)");
163 exit(EXIT_FAILURE);
164 }
165
166 // Input and output buffers
167 uint8_t in_buf[BUFSIZ];
168 uint8_t out_buf[BUFSIZ];
169
170 strm->avail_in = 0;
171 strm->next_out = out_buf;
172 strm->avail_out = BUFSIZ;
173
174 lzma_action action = LZMA_RUN;
175
176 while (true) {
177 if (strm->avail_in == 0) {
178 strm->next_in = in_buf;
179 strm->avail_in = fread(in_buf, 1, BUFSIZ, file);
180
181 if (ferror(file)) {
182 // POSIX says that fread() sets errno if
183 // an error occurred. ferror() doesn't
184 // touch errno.
185 my_errorf("%s: Error reading input file: %s",
186 filename, strerror(errno));
187 exit(EXIT_FAILURE);
188 }
189
190 #ifndef LZMADEC
191 // When using LZMA_CONCATENATED, we need to tell
192 // liblzma when it has got all the input.
193 if (feof(file))
194 action = LZMA_FINISH;
195 #endif
196 }
197
198 ret = lzma_code(strm, action);
199
200 // Write and check write error before checking decoder error.
201 // This way as much data as possible gets written to output
202 // even if decoder detected an error.
203 if (strm->avail_out == 0 || ret != LZMA_OK) {
204 const size_t write_size = BUFSIZ - strm->avail_out;
205
206 if (fwrite(out_buf, 1, write_size, stdout)
207 != write_size) {
208 // Wouldn't be a surprise if writing to stderr
209 // would fail too but at least try to show an
210 // error message.
211 my_errorf("Cannot write to standard output: "
212 "%s", strerror(errno));
213 exit(EXIT_FAILURE);
214 }
215
216 strm->next_out = out_buf;
217 strm->avail_out = BUFSIZ;
218 }
219
220 if (ret != LZMA_OK) {
221 if (ret == LZMA_STREAM_END) {
222 #ifdef LZMADEC
223 // Check that there's no trailing garbage.
224 if (strm->avail_in != 0
225 || fread(in_buf, 1, 1, file)
226 != 0
227 || !feof(file))
228 ret = LZMA_DATA_ERROR;
229 else
230 return;
231 #else
232 // lzma_stream_decoder() already guarantees
233 // that there's no trailing garbage.
234 assert(strm->avail_in == 0);
235 assert(action == LZMA_FINISH);
236 assert(feof(file));
237 return;
238 #endif
239 }
240
241 const char *msg;
242 switch (ret) {
243 case LZMA_MEM_ERROR:
244 msg = strerror(ENOMEM);
245 break;
246
247 case LZMA_FORMAT_ERROR:
248 msg = "File format not recognized";
249 break;
250
251 case LZMA_OPTIONS_ERROR:
252 // FIXME: Better message?
253 msg = "Unsupported compression options";
254 break;
255
256 case LZMA_DATA_ERROR:
257 msg = "File is corrupt";
258 break;
259
260 case LZMA_BUF_ERROR:
261 msg = "Unexpected end of input";
262 break;
263
264 default:
265 msg = "Internal error (bug)";
266 break;
267 }
268
269 my_errorf("%s: %s", filename, msg);
270 exit(EXIT_FAILURE);
271 }
272 }
273 }
274
275
276 int
277 main(int argc, char **argv)
278 {
279 // Initialize progname which we will be used in error messages.
280 tuklib_progname_init(argv);
281
282 // Parse the command line options.
283 parse_options(argc, argv);
284
285 // The same lzma_stream is used for all files that we decode. This way
286 // we don't need to reallocate memory for every file if they use same
287 // compression settings.
288 lzma_stream strm = LZMA_STREAM_INIT;
289
290 // Some systems require setting stdin and stdout to binary mode.
291 #ifdef TUKLIB_DOSLIKE
292 setmode(fileno(stdin), O_BINARY);
293 setmode(fileno(stdout), O_BINARY);
294 #endif
295
296 if (optind == argc) {
297 // No filenames given, decode from stdin.
298 uncompress(&strm, stdin, "(stdin)");
299 } else {
300 // Loop through the filenames given on the command line.
301 do {
302 // "-" indicates stdin.
303 if (strcmp(argv[optind], "-") == 0) {
304 uncompress(&strm, stdin, "(stdin)");
305 } else {
306 FILE *file = fopen(argv[optind], "rb");
307 if (file == NULL) {
308 my_errorf("%s: %s", argv[optind],
309 strerror(errno));
310 exit(EXIT_FAILURE);
311 }
312
313 uncompress(&strm, file, argv[optind]);
314 fclose(file);
315 }
316 } while (++optind < argc);
317 }
318
319 #ifndef NDEBUG
320 // Free the memory only when debugging. Freeing wastes some time,
321 // but allows detecting possible memory leaks with Valgrind.
322 lzma_end(&strm);
323 #endif
324
325 tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, display_errors);
326 }