xref: /dflybsd-src/contrib/xz/src/xzdec/xzdec.c (revision b5feb3da7c498482b19d14ac6f2b1901005f7d94)
12940b44dSPeter Avalos ///////////////////////////////////////////////////////////////////////////////
22940b44dSPeter Avalos //
32940b44dSPeter Avalos /// \file       xzdec.c
42940b44dSPeter Avalos /// \brief      Simple single-threaded tool to uncompress .xz or .lzma files
52940b44dSPeter Avalos //
62940b44dSPeter Avalos //  Author:     Lasse Collin
72940b44dSPeter Avalos //
82940b44dSPeter Avalos //  This file has been put into the public domain.
92940b44dSPeter Avalos //  You can do whatever you want with this file.
102940b44dSPeter Avalos //
112940b44dSPeter Avalos ///////////////////////////////////////////////////////////////////////////////
122940b44dSPeter Avalos 
132940b44dSPeter Avalos #include "sysdefs.h"
142940b44dSPeter Avalos #include "lzma.h"
152940b44dSPeter Avalos 
162940b44dSPeter Avalos #include <stdarg.h>
172940b44dSPeter Avalos #include <errno.h>
182940b44dSPeter Avalos #include <stdio.h>
192940b44dSPeter Avalos #include <unistd.h>
202940b44dSPeter Avalos 
212940b44dSPeter Avalos #include "getopt.h"
222940b44dSPeter Avalos #include "tuklib_progname.h"
232940b44dSPeter Avalos #include "tuklib_exit.h"
242940b44dSPeter Avalos 
252940b44dSPeter Avalos #ifdef TUKLIB_DOSLIKE
262940b44dSPeter Avalos #	include <fcntl.h>
272940b44dSPeter Avalos #	include <io.h>
282940b44dSPeter Avalos #endif
292940b44dSPeter Avalos 
302940b44dSPeter Avalos 
312940b44dSPeter Avalos #ifdef LZMADEC
322940b44dSPeter Avalos #	define TOOL_FORMAT "lzma"
332940b44dSPeter Avalos #else
342940b44dSPeter Avalos #	define TOOL_FORMAT "xz"
352940b44dSPeter Avalos #endif
362940b44dSPeter Avalos 
372940b44dSPeter Avalos 
382940b44dSPeter Avalos /// Error messages are suppressed if this is zero, which is the case when
392940b44dSPeter Avalos /// --quiet has been given at least twice.
40*e151908bSDaniel Fojt static int display_errors = 2;
412940b44dSPeter Avalos 
422940b44dSPeter Avalos 
43114db65bSPeter Avalos static void lzma_attribute((__format__(__printf__, 1, 2)))
my_errorf(const char * fmt,...)442940b44dSPeter Avalos my_errorf(const char *fmt, ...)
452940b44dSPeter Avalos {
462940b44dSPeter Avalos 	va_list ap;
472940b44dSPeter Avalos 	va_start(ap, fmt);
482940b44dSPeter Avalos 
492940b44dSPeter Avalos 	if (display_errors) {
502940b44dSPeter Avalos 		fprintf(stderr, "%s: ", progname);
512940b44dSPeter Avalos 		vfprintf(stderr, fmt, ap);
522940b44dSPeter Avalos 		fprintf(stderr, "\n");
532940b44dSPeter Avalos 	}
542940b44dSPeter Avalos 
552940b44dSPeter Avalos 	va_end(ap);
562940b44dSPeter Avalos 	return;
572940b44dSPeter Avalos }
582940b44dSPeter Avalos 
592940b44dSPeter Avalos 
60114db65bSPeter Avalos static void lzma_attribute((__noreturn__))
help(void)612940b44dSPeter Avalos help(void)
622940b44dSPeter Avalos {
632940b44dSPeter Avalos 	printf(
642940b44dSPeter Avalos "Usage: %s [OPTION]... [FILE]...\n"
65a530a267SJohn Marino "Decompress files in the ." TOOL_FORMAT " format to standard output.\n"
662940b44dSPeter Avalos "\n"
67a530a267SJohn Marino "  -d, --decompress   (ignored, only decompression is supported)\n"
68a530a267SJohn Marino "  -k, --keep         (ignored, files are never deleted)\n"
69a530a267SJohn Marino "  -c, --stdout       (ignored, output is always written to standard output)\n"
702940b44dSPeter Avalos "  -q, --quiet        specify *twice* to suppress errors\n"
71a530a267SJohn Marino "  -Q, --no-warn      (ignored, the exit status 2 is never used)\n"
722940b44dSPeter Avalos "  -h, --help         display this help and exit\n"
732940b44dSPeter Avalos "  -V, --version      display the version number and exit\n"
742940b44dSPeter Avalos "\n"
752940b44dSPeter Avalos "With no FILE, or when FILE is -, read standard input.\n"
762940b44dSPeter Avalos "\n"
772940b44dSPeter Avalos "Report bugs to <" PACKAGE_BUGREPORT "> (in English or Finnish).\n"
782940b44dSPeter Avalos PACKAGE_NAME " home page: <" PACKAGE_URL ">\n", progname);
792940b44dSPeter Avalos 
802940b44dSPeter Avalos 	tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, display_errors);
812940b44dSPeter Avalos }
822940b44dSPeter Avalos 
832940b44dSPeter Avalos 
84114db65bSPeter Avalos static void lzma_attribute((__noreturn__))
version(void)852940b44dSPeter Avalos version(void)
862940b44dSPeter Avalos {
872940b44dSPeter Avalos 	printf(TOOL_FORMAT "dec (" PACKAGE_NAME ") " LZMA_VERSION_STRING "\n"
882940b44dSPeter Avalos 			"liblzma %s\n", lzma_version_string());
892940b44dSPeter Avalos 
902940b44dSPeter Avalos 	tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, display_errors);
912940b44dSPeter Avalos }
922940b44dSPeter Avalos 
932940b44dSPeter Avalos 
942940b44dSPeter Avalos /// Parses command line options.
952940b44dSPeter Avalos static void
parse_options(int argc,char ** argv)962940b44dSPeter Avalos parse_options(int argc, char **argv)
972940b44dSPeter Avalos {
982940b44dSPeter Avalos 	static const char short_opts[] = "cdkM:hqQV";
992940b44dSPeter Avalos 	static const struct option long_opts[] = {
1002940b44dSPeter Avalos 		{ "stdout",       no_argument,         NULL, 'c' },
1012940b44dSPeter Avalos 		{ "to-stdout",    no_argument,         NULL, 'c' },
1022940b44dSPeter Avalos 		{ "decompress",   no_argument,         NULL, 'd' },
1032940b44dSPeter Avalos 		{ "uncompress",   no_argument,         NULL, 'd' },
1042940b44dSPeter Avalos 		{ "keep",         no_argument,         NULL, 'k' },
1052940b44dSPeter Avalos 		{ "quiet",        no_argument,         NULL, 'q' },
1062940b44dSPeter Avalos 		{ "no-warn",      no_argument,         NULL, 'Q' },
1072940b44dSPeter Avalos 		{ "help",         no_argument,         NULL, 'h' },
1082940b44dSPeter Avalos 		{ "version",      no_argument,         NULL, 'V' },
1092940b44dSPeter Avalos 		{ NULL,           0,                   NULL, 0   }
1102940b44dSPeter Avalos 	};
1112940b44dSPeter Avalos 
1122940b44dSPeter Avalos 	int c;
1132940b44dSPeter Avalos 
1142940b44dSPeter Avalos 	while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL))
1152940b44dSPeter Avalos 			!= -1) {
1162940b44dSPeter Avalos 		switch (c) {
1172940b44dSPeter Avalos 		case 'c':
1182940b44dSPeter Avalos 		case 'd':
1192940b44dSPeter Avalos 		case 'k':
1202940b44dSPeter Avalos 		case 'Q':
1212940b44dSPeter Avalos 			break;
1222940b44dSPeter Avalos 
1232940b44dSPeter Avalos 		case 'q':
1242940b44dSPeter Avalos 			if (display_errors > 0)
1252940b44dSPeter Avalos 				--display_errors;
1262940b44dSPeter Avalos 
1272940b44dSPeter Avalos 			break;
1282940b44dSPeter Avalos 
1292940b44dSPeter Avalos 		case 'h':
1302940b44dSPeter Avalos 			help();
1312940b44dSPeter Avalos 
1322940b44dSPeter Avalos 		case 'V':
1332940b44dSPeter Avalos 			version();
1342940b44dSPeter Avalos 
1352940b44dSPeter Avalos 		default:
1362940b44dSPeter Avalos 			exit(EXIT_FAILURE);
1372940b44dSPeter Avalos 		}
1382940b44dSPeter Avalos 	}
1392940b44dSPeter Avalos 
1402940b44dSPeter Avalos 	return;
1412940b44dSPeter Avalos }
1422940b44dSPeter Avalos 
1432940b44dSPeter Avalos 
1442940b44dSPeter Avalos static void
uncompress(lzma_stream * strm,FILE * file,const char * filename)1452940b44dSPeter Avalos uncompress(lzma_stream *strm, FILE *file, const char *filename)
1462940b44dSPeter Avalos {
1472940b44dSPeter Avalos 	lzma_ret ret;
1482940b44dSPeter Avalos 
1492940b44dSPeter Avalos 	// Initialize the decoder
1502940b44dSPeter Avalos #ifdef LZMADEC
1512940b44dSPeter Avalos 	ret = lzma_alone_decoder(strm, UINT64_MAX);
1522940b44dSPeter Avalos #else
1532940b44dSPeter Avalos 	ret = lzma_stream_decoder(strm, UINT64_MAX, LZMA_CONCATENATED);
1542940b44dSPeter Avalos #endif
1552940b44dSPeter Avalos 
1562940b44dSPeter Avalos 	// The only reasonable error here is LZMA_MEM_ERROR.
1572940b44dSPeter Avalos 	if (ret != LZMA_OK) {
1582940b44dSPeter Avalos 		my_errorf("%s", ret == LZMA_MEM_ERROR ? strerror(ENOMEM)
1592940b44dSPeter Avalos 				: "Internal error (bug)");
1602940b44dSPeter Avalos 		exit(EXIT_FAILURE);
1612940b44dSPeter Avalos 	}
1622940b44dSPeter Avalos 
1632940b44dSPeter Avalos 	// Input and output buffers
1642940b44dSPeter Avalos 	uint8_t in_buf[BUFSIZ];
1652940b44dSPeter Avalos 	uint8_t out_buf[BUFSIZ];
1662940b44dSPeter Avalos 
1672940b44dSPeter Avalos 	strm->avail_in = 0;
1682940b44dSPeter Avalos 	strm->next_out = out_buf;
1692940b44dSPeter Avalos 	strm->avail_out = BUFSIZ;
1702940b44dSPeter Avalos 
1712940b44dSPeter Avalos 	lzma_action action = LZMA_RUN;
1722940b44dSPeter Avalos 
1732940b44dSPeter Avalos 	while (true) {
1742940b44dSPeter Avalos 		if (strm->avail_in == 0) {
1752940b44dSPeter Avalos 			strm->next_in = in_buf;
1762940b44dSPeter Avalos 			strm->avail_in = fread(in_buf, 1, BUFSIZ, file);
1772940b44dSPeter Avalos 
1782940b44dSPeter Avalos 			if (ferror(file)) {
1792940b44dSPeter Avalos 				// POSIX says that fread() sets errno if
1802940b44dSPeter Avalos 				// an error occurred. ferror() doesn't
1812940b44dSPeter Avalos 				// touch errno.
1822940b44dSPeter Avalos 				my_errorf("%s: Error reading input file: %s",
1832940b44dSPeter Avalos 						filename, strerror(errno));
1842940b44dSPeter Avalos 				exit(EXIT_FAILURE);
1852940b44dSPeter Avalos 			}
1862940b44dSPeter Avalos 
1872940b44dSPeter Avalos #ifndef LZMADEC
1882940b44dSPeter Avalos 			// When using LZMA_CONCATENATED, we need to tell
1892940b44dSPeter Avalos 			// liblzma when it has got all the input.
1902940b44dSPeter Avalos 			if (feof(file))
1912940b44dSPeter Avalos 				action = LZMA_FINISH;
1922940b44dSPeter Avalos #endif
1932940b44dSPeter Avalos 		}
1942940b44dSPeter Avalos 
1952940b44dSPeter Avalos 		ret = lzma_code(strm, action);
1962940b44dSPeter Avalos 
1972940b44dSPeter Avalos 		// Write and check write error before checking decoder error.
1982940b44dSPeter Avalos 		// This way as much data as possible gets written to output
1992940b44dSPeter Avalos 		// even if decoder detected an error.
2002940b44dSPeter Avalos 		if (strm->avail_out == 0 || ret != LZMA_OK) {
2012940b44dSPeter Avalos 			const size_t write_size = BUFSIZ - strm->avail_out;
2022940b44dSPeter Avalos 
2032940b44dSPeter Avalos 			if (fwrite(out_buf, 1, write_size, stdout)
2042940b44dSPeter Avalos 					!= write_size) {
2052940b44dSPeter Avalos 				// Wouldn't be a surprise if writing to stderr
2062940b44dSPeter Avalos 				// would fail too but at least try to show an
2072940b44dSPeter Avalos 				// error message.
2082940b44dSPeter Avalos 				my_errorf("Cannot write to standard output: "
2092940b44dSPeter Avalos 						"%s", strerror(errno));
2102940b44dSPeter Avalos 				exit(EXIT_FAILURE);
2112940b44dSPeter Avalos 			}
2122940b44dSPeter Avalos 
2132940b44dSPeter Avalos 			strm->next_out = out_buf;
2142940b44dSPeter Avalos 			strm->avail_out = BUFSIZ;
2152940b44dSPeter Avalos 		}
2162940b44dSPeter Avalos 
2172940b44dSPeter Avalos 		if (ret != LZMA_OK) {
2182940b44dSPeter Avalos 			if (ret == LZMA_STREAM_END) {
2192940b44dSPeter Avalos #ifdef LZMADEC
2202940b44dSPeter Avalos 				// Check that there's no trailing garbage.
2212940b44dSPeter Avalos 				if (strm->avail_in != 0
2222940b44dSPeter Avalos 						|| fread(in_buf, 1, 1, file)
2232940b44dSPeter Avalos 							!= 0
2242940b44dSPeter Avalos 						|| !feof(file))
2252940b44dSPeter Avalos 					ret = LZMA_DATA_ERROR;
2262940b44dSPeter Avalos 				else
2272940b44dSPeter Avalos 					return;
2282940b44dSPeter Avalos #else
2292940b44dSPeter Avalos 				// lzma_stream_decoder() already guarantees
2302940b44dSPeter Avalos 				// that there's no trailing garbage.
2312940b44dSPeter Avalos 				assert(strm->avail_in == 0);
2322940b44dSPeter Avalos 				assert(action == LZMA_FINISH);
2332940b44dSPeter Avalos 				assert(feof(file));
2342940b44dSPeter Avalos 				return;
2352940b44dSPeter Avalos #endif
2362940b44dSPeter Avalos 			}
2372940b44dSPeter Avalos 
2382940b44dSPeter Avalos 			const char *msg;
2392940b44dSPeter Avalos 			switch (ret) {
2402940b44dSPeter Avalos 			case LZMA_MEM_ERROR:
2412940b44dSPeter Avalos 				msg = strerror(ENOMEM);
2422940b44dSPeter Avalos 				break;
2432940b44dSPeter Avalos 
2442940b44dSPeter Avalos 			case LZMA_FORMAT_ERROR:
2452940b44dSPeter Avalos 				msg = "File format not recognized";
2462940b44dSPeter Avalos 				break;
2472940b44dSPeter Avalos 
2482940b44dSPeter Avalos 			case LZMA_OPTIONS_ERROR:
2492940b44dSPeter Avalos 				// FIXME: Better message?
2502940b44dSPeter Avalos 				msg = "Unsupported compression options";
2512940b44dSPeter Avalos 				break;
2522940b44dSPeter Avalos 
2532940b44dSPeter Avalos 			case LZMA_DATA_ERROR:
2542940b44dSPeter Avalos 				msg = "File is corrupt";
2552940b44dSPeter Avalos 				break;
2562940b44dSPeter Avalos 
2572940b44dSPeter Avalos 			case LZMA_BUF_ERROR:
2582940b44dSPeter Avalos 				msg = "Unexpected end of input";
2592940b44dSPeter Avalos 				break;
2602940b44dSPeter Avalos 
2612940b44dSPeter Avalos 			default:
2622940b44dSPeter Avalos 				msg = "Internal error (bug)";
2632940b44dSPeter Avalos 				break;
2642940b44dSPeter Avalos 			}
2652940b44dSPeter Avalos 
2662940b44dSPeter Avalos 			my_errorf("%s: %s", filename, msg);
2672940b44dSPeter Avalos 			exit(EXIT_FAILURE);
2682940b44dSPeter Avalos 		}
2692940b44dSPeter Avalos 	}
2702940b44dSPeter Avalos }
2712940b44dSPeter Avalos 
2722940b44dSPeter Avalos 
2732940b44dSPeter Avalos int
main(int argc,char ** argv)2742940b44dSPeter Avalos main(int argc, char **argv)
2752940b44dSPeter Avalos {
2762940b44dSPeter Avalos 	// Initialize progname which we will be used in error messages.
2772940b44dSPeter Avalos 	tuklib_progname_init(argv);
2782940b44dSPeter Avalos 
2792940b44dSPeter Avalos 	// Parse the command line options.
2802940b44dSPeter Avalos 	parse_options(argc, argv);
2812940b44dSPeter Avalos 
2822940b44dSPeter Avalos 	// The same lzma_stream is used for all files that we decode. This way
2832940b44dSPeter Avalos 	// we don't need to reallocate memory for every file if they use same
2842940b44dSPeter Avalos 	// compression settings.
2852940b44dSPeter Avalos 	lzma_stream strm = LZMA_STREAM_INIT;
2862940b44dSPeter Avalos 
2872940b44dSPeter Avalos 	// Some systems require setting stdin and stdout to binary mode.
2882940b44dSPeter Avalos #ifdef TUKLIB_DOSLIKE
2892940b44dSPeter Avalos 	setmode(fileno(stdin), O_BINARY);
2902940b44dSPeter Avalos 	setmode(fileno(stdout), O_BINARY);
2912940b44dSPeter Avalos #endif
2922940b44dSPeter Avalos 
2932940b44dSPeter Avalos 	if (optind == argc) {
2942940b44dSPeter Avalos 		// No filenames given, decode from stdin.
2952940b44dSPeter Avalos 		uncompress(&strm, stdin, "(stdin)");
2962940b44dSPeter Avalos 	} else {
2972940b44dSPeter Avalos 		// Loop through the filenames given on the command line.
2982940b44dSPeter Avalos 		do {
2992940b44dSPeter Avalos 			// "-" indicates stdin.
3002940b44dSPeter Avalos 			if (strcmp(argv[optind], "-") == 0) {
3012940b44dSPeter Avalos 				uncompress(&strm, stdin, "(stdin)");
3022940b44dSPeter Avalos 			} else {
3032940b44dSPeter Avalos 				FILE *file = fopen(argv[optind], "rb");
3042940b44dSPeter Avalos 				if (file == NULL) {
3052940b44dSPeter Avalos 					my_errorf("%s: %s", argv[optind],
3062940b44dSPeter Avalos 							strerror(errno));
3072940b44dSPeter Avalos 					exit(EXIT_FAILURE);
3082940b44dSPeter Avalos 				}
3092940b44dSPeter Avalos 
3102940b44dSPeter Avalos 				uncompress(&strm, file, argv[optind]);
3112940b44dSPeter Avalos 				fclose(file);
3122940b44dSPeter Avalos 			}
3132940b44dSPeter Avalos 		} while (++optind < argc);
3142940b44dSPeter Avalos 	}
3152940b44dSPeter Avalos 
3162940b44dSPeter Avalos #ifndef NDEBUG
3172940b44dSPeter Avalos 	// Free the memory only when debugging. Freeing wastes some time,
3182940b44dSPeter Avalos 	// but allows detecting possible memory leaks with Valgrind.
3192940b44dSPeter Avalos 	lzma_end(&strm);
3202940b44dSPeter Avalos #endif
3212940b44dSPeter Avalos 
3222940b44dSPeter Avalos 	tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, display_errors);
3232940b44dSPeter Avalos }
324