(root)/
xz-5.4.5/
src/
xz/
main.c
       1  ///////////////////////////////////////////////////////////////////////////////
       2  //
       3  /// \file       main.c
       4  /// \brief      main()
       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 "private.h"
      14  #include <ctype.h>
      15  
      16  /// Exit status to use. This can be changed with set_exit_status().
      17  static enum exit_status_type exit_status = E_SUCCESS;
      18  
      19  #if defined(_WIN32) && !defined(__CYGWIN__)
      20  /// exit_status has to be protected with a critical section due to
      21  /// how "signal handling" is done on Windows. See signals.c for details.
      22  static CRITICAL_SECTION exit_status_cs;
      23  #endif
      24  
      25  /// True if --no-warn is specified. When this is true, we don't set
      26  /// the exit status to E_WARNING when something worth a warning happens.
      27  static bool no_warn = false;
      28  
      29  
      30  extern void
      31  set_exit_status(enum exit_status_type new_status)
      32  {
      33  	assert(new_status == E_WARNING || new_status == E_ERROR);
      34  
      35  #if defined(_WIN32) && !defined(__CYGWIN__)
      36  	EnterCriticalSection(&exit_status_cs);
      37  #endif
      38  
      39  	if (exit_status != E_ERROR)
      40  		exit_status = new_status;
      41  
      42  #if defined(_WIN32) && !defined(__CYGWIN__)
      43  	LeaveCriticalSection(&exit_status_cs);
      44  #endif
      45  
      46  	return;
      47  }
      48  
      49  
      50  extern void
      51  set_exit_no_warn(void)
      52  {
      53  	no_warn = true;
      54  	return;
      55  }
      56  
      57  
      58  static const char *
      59  read_name(const args_info *args)
      60  {
      61  	// FIXME: Maybe we should have some kind of memory usage limit here
      62  	// like the tool has for the actual compression and decompression.
      63  	// Giving some huge text file with --files0 makes us to read the
      64  	// whole file in RAM.
      65  	static char *name = NULL;
      66  	static size_t size = 256;
      67  
      68  	// Allocate the initial buffer. This is never freed, since after it
      69  	// is no longer needed, the program exits very soon. It is safe to
      70  	// use xmalloc() and xrealloc() in this function, because while
      71  	// executing this function, no files are open for writing, and thus
      72  	// there's no need to cleanup anything before exiting.
      73  	if (name == NULL)
      74  		name = xmalloc(size);
      75  
      76  	// Write position in name
      77  	size_t pos = 0;
      78  
      79  	// Read one character at a time into name.
      80  	while (!user_abort) {
      81  		const int c = fgetc(args->files_file);
      82  
      83  		if (ferror(args->files_file)) {
      84  			// Take care of EINTR since we have established
      85  			// the signal handlers already.
      86  			if (errno == EINTR)
      87  				continue;
      88  
      89  			message_error(_("%s: Error reading filenames: %s"),
      90  					args->files_name, strerror(errno));
      91  			return NULL;
      92  		}
      93  
      94  		if (feof(args->files_file)) {
      95  			if (pos != 0)
      96  				message_error(_("%s: Unexpected end of input "
      97  						"when reading filenames"),
      98  						args->files_name);
      99  
     100  			return NULL;
     101  		}
     102  
     103  		if (c == args->files_delim) {
     104  			// We allow consecutive newline (--files) or '\0'
     105  			// characters (--files0), and ignore such empty
     106  			// filenames.
     107  			if (pos == 0)
     108  				continue;
     109  
     110  			// A non-empty name was read. Terminate it with '\0'
     111  			// and return it.
     112  			name[pos] = '\0';
     113  			return name;
     114  		}
     115  
     116  		if (c == '\0') {
     117  			// A null character was found when using --files,
     118  			// which expects plain text input separated with
     119  			// newlines.
     120  			message_error(_("%s: Null character found when "
     121  					"reading filenames; maybe you meant "
     122  					"to use `--files0' instead "
     123  					"of `--files'?"), args->files_name);
     124  			return NULL;
     125  		}
     126  
     127  		name[pos++] = c;
     128  
     129  		// Allocate more memory if needed. There must always be space
     130  		// at least for one character to allow terminating the string
     131  		// with '\0'.
     132  		if (pos == size) {
     133  			size *= 2;
     134  			name = xrealloc(name, size);
     135  		}
     136  	}
     137  
     138  	return NULL;
     139  }
     140  
     141  
     142  int
     143  main(int argc, char **argv)
     144  {
     145  #ifdef HAVE_PLEDGE
     146  	// OpenBSD's pledge(2) sandbox
     147  	//
     148  	// Unconditionally enable sandboxing with fairly relaxed promises.
     149  	// This is still way better than having no sandbox at all. :-)
     150  	// More strict promises will be made later in file_io.c if possible.
     151  	if (pledge("stdio rpath wpath cpath fattr", "")) {
     152  		// Don't translate the string or use message_fatal() as
     153  		// those haven't been initialized yet.
     154  		fprintf(stderr, "%s: Failed to enable the sandbox\n", argv[0]);
     155  		return E_ERROR;
     156  	}
     157  #endif
     158  
     159  #if defined(_WIN32) && !defined(__CYGWIN__)
     160  	InitializeCriticalSection(&exit_status_cs);
     161  #endif
     162  
     163  	// Set up the progname variable.
     164  	tuklib_progname_init(argv);
     165  
     166  	// Initialize the file I/O. This makes sure that
     167  	// stdin, stdout, and stderr are something valid.
     168  	io_init();
     169  
     170  	// Set up the locale and message translations.
     171  	tuklib_gettext_init(PACKAGE, LOCALEDIR);
     172  
     173  	// Initialize handling of error/warning/other messages.
     174  	message_init();
     175  
     176  	// Set hardware-dependent default values. These can be overridden
     177  	// on the command line, thus this must be done before args_parse().
     178  	hardware_init();
     179  
     180  	// Parse the command line arguments and get an array of filenames.
     181  	// This doesn't return if something is wrong with the command line
     182  	// arguments. If there are no arguments, one filename ("-") is still
     183  	// returned to indicate stdin.
     184  	args_info args;
     185  	args_parse(&args, argc, argv);
     186  
     187  	if (opt_mode != MODE_LIST && opt_robot)
     188  		message_fatal(_("Compression and decompression with --robot "
     189  			"are not supported yet."));
     190  
     191  	// Tell the message handling code how many input files there are if
     192  	// we know it. This way the progress indicator can show it.
     193  	if (args.files_name != NULL)
     194  		message_set_files(0);
     195  	else
     196  		message_set_files(args.arg_count);
     197  
     198  	// Refuse to write compressed data to standard output if it is
     199  	// a terminal.
     200  	if (opt_mode == MODE_COMPRESS) {
     201  		if (opt_stdout || (args.arg_count == 1
     202  				&& strcmp(args.arg_names[0], "-") == 0)) {
     203  			if (is_tty_stdout()) {
     204  				message_try_help();
     205  				tuklib_exit(E_ERROR, E_ERROR, false);
     206  			}
     207  		}
     208  	}
     209  
     210  	// Set up the signal handlers. We don't need these before we
     211  	// start the actual action and not in --list mode, so this is
     212  	// done after parsing the command line arguments.
     213  	//
     214  	// It's good to keep signal handlers in normal compression and
     215  	// decompression modes even when only writing to stdout, because
     216  	// we might need to restore O_APPEND flag on stdout before exiting.
     217  	// In --test mode, signal handlers aren't really needed, but let's
     218  	// keep them there for consistency with normal decompression.
     219  	if (opt_mode != MODE_LIST)
     220  		signals_init();
     221  
     222  #ifdef ENABLE_SANDBOX
     223  	// Set a flag that sandboxing is allowed if all these are true:
     224  	//   - --files or --files0 wasn't used.
     225  	//   - There is exactly one input file or we are reading from stdin.
     226  	//   - We won't create any files: output goes to stdout or --test
     227  	//     or --list was used. Note that --test implies opt_stdout = true
     228  	//     but --list doesn't.
     229  	//
     230  	// This is obviously not ideal but it was easy to implement and
     231  	// it covers the most common use cases.
     232  	//
     233  	// TODO: Make sandboxing work for other situations too.
     234  	if (args.files_name == NULL && args.arg_count == 1
     235  			&& (opt_stdout || strcmp("-", args.arg_names[0]) == 0
     236  				|| opt_mode == MODE_LIST))
     237  		io_allow_sandbox();
     238  #endif
     239  
     240  	// coder_run() handles compression, decompression, and testing.
     241  	// list_file() is for --list.
     242  	void (*run)(const char *filename) = &coder_run;
     243  #ifdef HAVE_DECODERS
     244  	if (opt_mode == MODE_LIST)
     245  		run = &list_file;
     246  #endif
     247  
     248  	// Process the files given on the command line. Note that if no names
     249  	// were given, args_parse() gave us a fake "-" filename.
     250  	for (unsigned i = 0; i < args.arg_count && !user_abort; ++i) {
     251  		if (strcmp("-", args.arg_names[i]) == 0) {
     252  			// Processing from stdin to stdout. Check that we
     253  			// aren't writing compressed data to a terminal or
     254  			// reading it from a terminal.
     255  			if (opt_mode == MODE_COMPRESS) {
     256  				if (is_tty_stdout())
     257  					continue;
     258  			} else if (is_tty_stdin()) {
     259  				continue;
     260  			}
     261  
     262  			// It doesn't make sense to compress data from stdin
     263  			// if we are supposed to read filenames from stdin
     264  			// too (enabled with --files or --files0).
     265  			if (args.files_name == stdin_filename) {
     266  				message_error(_("Cannot read data from "
     267  						"standard input when "
     268  						"reading filenames "
     269  						"from standard input"));
     270  				continue;
     271  			}
     272  
     273  			// Replace the "-" with a special pointer, which is
     274  			// recognized by coder_run() and other things.
     275  			// This way error messages get a proper filename
     276  			// string and the code still knows that it is
     277  			// handling the special case of stdin.
     278  			args.arg_names[i] = (char *)stdin_filename;
     279  		}
     280  
     281  		// Do the actual compression or decompression.
     282  		run(args.arg_names[i]);
     283  	}
     284  
     285  	// If --files or --files0 was used, process the filenames from the
     286  	// given file or stdin. Note that here we don't consider "-" to
     287  	// indicate stdin like we do with the command line arguments.
     288  	if (args.files_name != NULL) {
     289  		// read_name() checks for user_abort so we don't need to
     290  		// check it as loop termination condition.
     291  		while (true) {
     292  			const char *name = read_name(&args);
     293  			if (name == NULL)
     294  				break;
     295  
     296  			// read_name() doesn't return empty names.
     297  			assert(name[0] != '\0');
     298  			run(name);
     299  		}
     300  
     301  		if (args.files_name != stdin_filename)
     302  			(void)fclose(args.files_file);
     303  	}
     304  
     305  #ifdef HAVE_DECODERS
     306  	// All files have now been handled. If in --list mode, display
     307  	// the totals before exiting. We don't have signal handlers
     308  	// enabled in --list mode, so we don't need to check user_abort.
     309  	if (opt_mode == MODE_LIST) {
     310  		assert(!user_abort);
     311  		list_totals();
     312  	}
     313  #endif
     314  
     315  #ifndef NDEBUG
     316  	coder_free();
     317  	args_free();
     318  #endif
     319  
     320  	// If we have got a signal, raise it to kill the program instead
     321  	// of calling tuklib_exit().
     322  	signals_exit();
     323  
     324  	// Make a local copy of exit_status to keep the Windows code
     325  	// thread safe. At this point it is fine if we miss the user
     326  	// pressing C-c and don't set the exit_status to E_ERROR on
     327  	// Windows.
     328  #if defined(_WIN32) && !defined(__CYGWIN__)
     329  	EnterCriticalSection(&exit_status_cs);
     330  #endif
     331  
     332  	enum exit_status_type es = exit_status;
     333  
     334  #if defined(_WIN32) && !defined(__CYGWIN__)
     335  	LeaveCriticalSection(&exit_status_cs);
     336  #endif
     337  
     338  	// Suppress the exit status indicating a warning if --no-warn
     339  	// was specified.
     340  	if (es == E_WARNING && no_warn)
     341  		es = E_SUCCESS;
     342  
     343  	tuklib_exit((int)es, E_ERROR, message_verbosity_get() != V_SILENT);
     344  }