root/src/xz/main.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. set_exit_status
  2. set_exit_no_warn
  3. read_name
  4. main

///////////////////////////////////////////////////////////////////////////////
//
/// \file       main.c
/// \brief      main()
//
//  Author:     Lasse Collin
//
//  This file has been put into the public domain.
//  You can do whatever you want with this file.
//
///////////////////////////////////////////////////////////////////////////////

#include "private.h"
#include <ctype.h>

/// Exit status to use. This can be changed with set_exit_status().
static enum exit_status_type exit_status = E_SUCCESS;

#if defined(_WIN32) && !defined(__CYGWIN__)
/// exit_status has to be protected with a critical section due to
/// how "signal handling" is done on Windows. See signals.c for details.
static CRITICAL_SECTION exit_status_cs;
#endif

/// True if --no-warn is specified. When this is true, we don't set
/// the exit status to E_WARNING when something worth a warning happens.
static bool no_warn = false;


extern void
set_exit_status(enum exit_status_type new_status)
{
        assert(new_status == E_WARNING || new_status == E_ERROR);

#if defined(_WIN32) && !defined(__CYGWIN__)
        EnterCriticalSection(&exit_status_cs);
#endif

        if (exit_status != E_ERROR)
                exit_status = new_status;

#if defined(_WIN32) && !defined(__CYGWIN__)
        LeaveCriticalSection(&exit_status_cs);
#endif

        return;
}


extern void
set_exit_no_warn(void)
{
        no_warn = true;
        return;
}


static const char *
read_name(const args_info *args)
{
        // FIXME: Maybe we should have some kind of memory usage limit here
        // like the tool has for the actual compression and decompression.
        // Giving some huge text file with --files0 makes us to read the
        // whole file in RAM.
        static char *name = NULL;
        static size_t size = 256;

        // Allocate the initial buffer. This is never freed, since after it
        // is no longer needed, the program exits very soon. It is safe to
        // use xmalloc() and xrealloc() in this function, because while
        // executing this function, no files are open for writing, and thus
        // there's no need to cleanup anything before exiting.
        if (name == NULL)
                name = xmalloc(size);

        // Write position in name
        size_t pos = 0;

        // Read one character at a time into name.
        while (!user_abort) {
                const int c = fgetc(args->files_file);

                if (ferror(args->files_file)) {
                        // Take care of EINTR since we have established
                        // the signal handlers already.
                        if (errno == EINTR)
                                continue;

                        message_error(_("%s: Error reading filenames: %s"),
                                        args->files_name, strerror(errno));
                        return NULL;
                }

                if (feof(args->files_file)) {
                        if (pos != 0)
                                message_error(_("%s: Unexpected end of input "
                                                "when reading filenames"),
                                                args->files_name);

                        return NULL;
                }

                if (c == args->files_delim) {
                        // We allow consecutive newline (--files) or '\0'
                        // characters (--files0), and ignore such empty
                        // filenames.
                        if (pos == 0)
                                continue;

                        // A non-empty name was read. Terminate it with '\0'
                        // and return it.
                        name[pos] = '\0';
                        return name;
                }

                if (c == '\0') {
                        // A null character was found when using --files,
                        // which expects plain text input separated with
                        // newlines.
                        message_error(_("%s: Null character found when "
                                        "reading filenames; maybe you meant "
                                        "to use `--files0' instead "
                                        "of `--files'?"), args->files_name);
                        return NULL;
                }

                name[pos++] = c;

                // Allocate more memory if needed. There must always be space
                // at least for one character to allow terminating the string
                // with '\0'.
                if (pos == size) {
                        size *= 2;
                        name = xrealloc(name, size);
                }
        }

        return NULL;
}


int
main(int argc, char **argv)
{
#if defined(_WIN32) && !defined(__CYGWIN__)
        InitializeCriticalSection(&exit_status_cs);
#endif

        // Set up the progname variable.
        tuklib_progname_init(argv);

        // Initialize the file I/O. This makes sure that
        // stdin, stdout, and stderr are something valid.
        io_init();

        // Set up the locale and message translations.
        tuklib_gettext_init(PACKAGE, LOCALEDIR);

        // Initialize handling of error/warning/other messages.
        message_init();

        // Set hardware-dependent default values. These can be overriden
        // on the command line, thus this must be done before args_parse().
        hardware_init();

        // Parse the command line arguments and get an array of filenames.
        // This doesn't return if something is wrong with the command line
        // arguments. If there are no arguments, one filename ("-") is still
        // returned to indicate stdin.
        args_info args;
        args_parse(&args, argc, argv);

        if (opt_mode != MODE_LIST && opt_robot)
                message_fatal(_("Compression and decompression with --robot "
                        "are not supported yet."));

        // Tell the message handling code how many input files there are if
        // we know it. This way the progress indicator can show it.
        if (args.files_name != NULL)
                message_set_files(0);
        else
                message_set_files(args.arg_count);

        // Refuse to write compressed data to standard output if it is
        // a terminal.
        if (opt_mode == MODE_COMPRESS) {
                if (opt_stdout || (args.arg_count == 1
                                && strcmp(args.arg_names[0], "-") == 0)) {
                        if (is_tty_stdout()) {
                                message_try_help();
                                tuklib_exit(E_ERROR, E_ERROR, false);
                        }
                }
        }

        // Set up the signal handlers. We don't need these before we
        // start the actual action and not in --list mode, so this is
        // done after parsing the command line arguments.
        //
        // It's good to keep signal handlers in normal compression and
        // decompression modes even when only writing to stdout, because
        // we might need to restore O_APPEND flag on stdout before exiting.
        // In --test mode, signal handlers aren't really needed, but let's
        // keep them there for consistency with normal decompression.
        if (opt_mode != MODE_LIST)
                signals_init();

        // coder_run() handles compression, decompression, and testing.
        // list_file() is for --list.
        void (*run)(const char *filename) = opt_mode == MODE_LIST
                         ? &list_file : &coder_run;

        // Process the files given on the command line. Note that if no names
        // were given, args_parse() gave us a fake "-" filename.
        for (size_t i = 0; i < args.arg_count && !user_abort; ++i) {
                if (strcmp("-", args.arg_names[i]) == 0) {
                        // Processing from stdin to stdout. Check that we
                        // aren't writing compressed data to a terminal or
                        // reading it from a terminal.
                        if (opt_mode == MODE_COMPRESS) {
                                if (is_tty_stdout())
                                        continue;
                        } else if (is_tty_stdin()) {
                                continue;
                        }

                        // It doesn't make sense to compress data from stdin
                        // if we are supposed to read filenames from stdin
                        // too (enabled with --files or --files0).
                        if (args.files_name == stdin_filename) {
                                message_error(_("Cannot read data from "
                                                "standard input when "
                                                "reading filenames "
                                                "from standard input"));
                                continue;
                        }

                        // Replace the "-" with a special pointer, which is
                        // recognized by coder_run() and other things.
                        // This way error messages get a proper filename
                        // string and the code still knows that it is
                        // handling the special case of stdin.
                        args.arg_names[i] = (char *)stdin_filename;
                }

                // Do the actual compression or decompression.
                run(args.arg_names[i]);
        }

        // If --files or --files0 was used, process the filenames from the
        // given file or stdin. Note that here we don't consider "-" to
        // indicate stdin like we do with the command line arguments.
        if (args.files_name != NULL) {
                // read_name() checks for user_abort so we don't need to
                // check it as loop termination condition.
                while (true) {
                        const char *name = read_name(&args);
                        if (name == NULL)
                                break;

                        // read_name() doesn't return empty names.
                        assert(name[0] != '\0');
                        run(name);
                }

                if (args.files_name != stdin_filename)
                        (void)fclose(args.files_file);
        }

        // All files have now been handled. If in --list mode, display
        // the totals before exiting. We don't have signal handlers
        // enabled in --list mode, so we don't need to check user_abort.
        if (opt_mode == MODE_LIST) {
                assert(!user_abort);
                list_totals();
        }

        // If we have got a signal, raise it to kill the program instead
        // of calling tuklib_exit().
        signals_exit();

        // Make a local copy of exit_status to keep the Windows code
        // thread safe. At this point it is fine if we miss the user
        // pressing C-c and don't set the exit_status to E_ERROR on
        // Windows.
#if defined(_WIN32) && !defined(__CYGWIN__)
        EnterCriticalSection(&exit_status_cs);
#endif

        enum exit_status_type es = exit_status;

#if defined(_WIN32) && !defined(__CYGWIN__)
        LeaveCriticalSection(&exit_status_cs);
#endif

        // Suppress the exit status indicating a warning if --no-warn
        // was specified.
        if (es == E_WARNING && no_warn)
                es = E_SUCCESS;

        tuklib_exit(es, E_ERROR, message_verbosity_get() != V_SILENT);
}

/* [<][>][^][v][top][bottom][index][help] */