(root)/
xz-5.4.5/
src/
xzdec/
xzdec.c
       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  }