xref: /freebsd/contrib/xz/src/xzdec/xzdec.c (revision 2e3507c25e42292b45a5482e116d278f5515d04d)
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 }
327