1235911Smav// SPDX-License-Identifier: 0BSD 2235911Smav 3235911Smav/////////////////////////////////////////////////////////////////////////////// 4235911Smav// 5235911Smav/// \file main.c 6235911Smav/// \brief main() 7235911Smav// 8235911Smav// Author: Lasse Collin 9235911Smav// 10235911Smav/////////////////////////////////////////////////////////////////////////////// 11235911Smav 12235911Smav#include "private.h" 13235911Smav#include <ctype.h> 14235911Smav 15235911Smav 16235911Smav/// Exit status to use. This can be changed with set_exit_status(). 17235911Smavstatic enum exit_status_type exit_status = E_SUCCESS; 18235911Smav 19235911Smav#if defined(_WIN32) && !defined(__CYGWIN__) 20235911Smav/// exit_status has to be protected with a critical section due to 21235911Smav/// how "signal handling" is done on Windows. See signals.c for details. 22235911Smavstatic CRITICAL_SECTION exit_status_cs; 23235911Smav#endif 24235911Smav 25235911Smav/// True if --no-warn is specified. When this is true, we don't set 26235911Smav/// the exit status to E_WARNING when something worth a warning happens. 27235911Smavstatic bool no_warn = false; 28235911Smav 29235911Smav 30235911Smavextern void 31235911Smavset_exit_status(enum exit_status_type new_status) 32235911Smav{ 33235911Smav assert(new_status == E_WARNING || new_status == E_ERROR); 34235911Smav 35235911Smav#if defined(_WIN32) && !defined(__CYGWIN__) 36235911Smav EnterCriticalSection(&exit_status_cs); 37235911Smav#endif 38235911Smav 39245891Sjh if (exit_status != E_ERROR) 40235911Smav exit_status = new_status; 41235911Smav 42235911Smav#if defined(_WIN32) && !defined(__CYGWIN__) 43235911Smav LeaveCriticalSection(&exit_status_cs); 44235911Smav#endif 45235911Smav 46235911Smav return; 47235911Smav} 48235911Smav 49235911Smav 50235911Smavextern void 51235911Smavset_exit_no_warn(void) 52235911Smav{ 53235911Smav no_warn = true; 54235911Smav return; 55235911Smav} 56235911Smav 57235911Smav 58235911Smavstatic const char * 59235911Smavread_name(const args_info *args) 60235911Smav{ 61235911Smav // FIXME: Maybe we should have some kind of memory usage limit here 62235911Smav // like the tool has for the actual compression and decompression. 63235911Smav // Giving some huge text file with --files0 makes us to read the 64235911Smav // whole file in RAM. 65235911Smav static char *name = NULL; 66235911Smav static size_t size = 256; 67235911Smav 68235911Smav // Allocate the initial buffer. This is never freed, since after it 69235911Smav // is no longer needed, the program exits very soon. It is safe to 70235911Smav // use xmalloc() and xrealloc() in this function, because while 71235911Smav // executing this function, no files are open for writing, and thus 72235911Smav // there's no need to cleanup anything before exiting. 73235911Smav if (name == NULL) 74235911Smav name = xmalloc(size); 75235911Smav 76235911Smav // Write position in name 77235911Smav size_t pos = 0; 78235911Smav 79235911Smav // Read one character at a time into name. 80235911Smav while (!user_abort) { 81235911Smav const int c = fgetc(args->files_file); 82235911Smav 83235911Smav if (ferror(args->files_file)) { 84235911Smav // Take care of EINTR since we have established 85235911Smav // the signal handlers already. 86235911Smav if (errno == EINTR) 87235911Smav continue; 88235911Smav 89235911Smav message_error(_("%s: Error reading filenames: %s"), 90235911Smav args->files_name, strerror(errno)); 91235911Smav return NULL; 92235911Smav } 93235911Smav 94235911Smav if (feof(args->files_file)) { 95235911Smav if (pos != 0) 96235911Smav message_error(_("%s: Unexpected end of input " 97235911Smav "when reading filenames"), 98235911Smav args->files_name); 99235911Smav 100235911Smav return NULL; 101235911Smav } 102235911Smav 103235911Smav if (c == args->files_delim) { 104235911Smav // We allow consecutive newline (--files) or '\0' 105235911Smav // characters (--files0), and ignore such empty 106235911Smav // filenames. 107235911Smav if (pos == 0) 108235911Smav continue; 109235911Smav 110235911Smav // A non-empty name was read. Terminate it with '\0' 111235911Smav // and return it. 112235911Smav name[pos] = '\0'; 113235911Smav return name; 114235911Smav } 115235911Smav 116235911Smav if (c == '\0') { 117235911Smav // A null character was found when using --files, 118235911Smav // which expects plain text input separated with 119235911Smav // newlines. 120235911Smav message_error(_("%s: Null character found when " 121235911Smav "reading filenames; maybe you meant " 122235911Smav "to use '--files0' instead " 123235911Smav "of '--files'?"), args->files_name); 124235911Smav return NULL; 125235911Smav } 126235911Smav 127235911Smav name[pos++] = c; 128235911Smav 129235911Smav // Allocate more memory if needed. There must always be space 130235911Smav // at least for one character to allow terminating the string 131235911Smav // with '\0'. 132235911Smav if (pos == size) { 133235911Smav size *= 2; 134235911Smav name = xrealloc(name, size); 135235911Smav } 136235911Smav } 137235911Smav 138235911Smav return NULL; 139235911Smav} 140235911Smav 141235911Smav 142235911Smavint 143235911Smavmain(int argc, char **argv) 144235911Smav{ 145235911Smav#if defined(_WIN32) && !defined(__CYGWIN__) 146235911Smav InitializeCriticalSection(&exit_status_cs); 147235911Smav#endif 148235911Smav 149235911Smav // Set up the progname variable needed for messages. 150235911Smav tuklib_progname_init(argv); 151235911Smav 152235911Smav // Initialize the file I/O. This makes sure that 153235911Smav // stdin, stdout, and stderr are something valid. 154235911Smav // This must be done before we might open any files 155235911Smav // even indirectly like locale and gettext initializations. 156235911Smav io_init(); 157235911Smav 158235911Smav#ifdef ENABLE_SANDBOX 159235911Smav // Enable such sandboxing that can always be enabled. 160235911Smav // This requires that progname has been set up. 161235911Smav // It's also good that io_init() has been called because it 162235911Smav // might need to do things that the initial sandbox won't allow. 163235911Smav // Otherwise this should be called as early as possible. 164235911Smav // 165235911Smav // NOTE: Calling this before tuklib_gettext_init() means that 166235911Smav // translated error message won't be available if sandbox 167235911Smav // initialization fails. However, sandbox_init() shouldn't 168235911Smav // fail and this order simply feels better. 169235911Smav sandbox_init(); 170235911Smav#endif 171235911Smav 172235911Smav // Set up the locale and message translations. 173235911Smav tuklib_gettext_init(PACKAGE, LOCALEDIR); 174235911Smav 175235911Smav // Initialize progress message handling. It's not always needed 176235911Smav // but it's simpler to do this unconditionally. 177235911Smav message_init(); 178235911Smav 179235911Smav // Set hardware-dependent default values. These can be overridden 180235911Smav // on the command line, thus this must be done before args_parse(). 181235911Smav hardware_init(); 182235911Smav 183235911Smav // Parse the command line arguments and get an array of filenames. 184235911Smav // This doesn't return if something is wrong with the command line 185235911Smav // arguments. If there are no arguments, one filename ("-") is still 186235911Smav // returned to indicate stdin. 187235911Smav args_info args; 188235911Smav args_parse(&args, argc, argv); 189235911Smav 190235911Smav if (opt_mode != MODE_LIST && opt_robot) 191235911Smav message_fatal(_("Compression and decompression with --robot " 192235911Smav "are not supported yet.")); 193235911Smav 194235911Smav // Tell the message handling code how many input files there are if 195235911Smav // we know it. This way the progress indicator can show it. 196235911Smav if (args.files_name != NULL) 197235911Smav message_set_files(0); 198235911Smav else 199235911Smav message_set_files(args.arg_count); 200235911Smav 201235911Smav // Refuse to write compressed data to standard output if it is 202235911Smav // a terminal. 203235911Smav if (opt_mode == MODE_COMPRESS) { 204235911Smav if (opt_stdout || (args.arg_count == 1 205235911Smav && strcmp(args.arg_names[0], "-") == 0)) { 206235911Smav if (is_tty_stdout()) { 207235911Smav message_try_help(); 208235911Smav tuklib_exit(E_ERROR, E_ERROR, false); 209235911Smav } 210235911Smav } 211235911Smav } 212235911Smav 213235911Smav // Set up the signal handlers. We don't need these before we 214235911Smav // start the actual action and not in --list mode, so this is 215235911Smav // done after parsing the command line arguments. 216235911Smav // 217235911Smav // It's good to keep signal handlers in normal compression and 218235911Smav // decompression modes even when only writing to stdout, because 219235911Smav // we might need to restore O_APPEND flag on stdout before exiting. 220235911Smav // In --test mode, signal handlers aren't really needed, but let's 221235911Smav // keep them there for consistency with normal decompression. 222235911Smav if (opt_mode != MODE_LIST) 223235911Smav signals_init(); 224235911Smav 225235911Smav#ifdef ENABLE_SANDBOX 226235911Smav // Read-only sandbox can be enabled if we won't create or delete 227235911Smav // any files: 228235911Smav // 229235911Smav // - --stdout, --test, or --list was used. Note that --test 230235911Smav // implies opt_stdout = true but --list doesn't. 231235911Smav // 232235911Smav // - Output goes to stdout because --files or --files0 wasn't used 233235911Smav // and no arguments were given on the command line or the 234235911Smav // arguments are all "-" (indicating standard input). 235235911Smav bool to_stdout_only = opt_stdout || opt_mode == MODE_LIST; 236235911Smav if (!to_stdout_only && args.files_name == NULL) { 237235911Smav // If all of the filenames provided are "-" (more than one 238235911Smav // "-" could be specified), then we are only going to be 239235911Smav // writing to standard output. Note that if no filename args 240235911Smav // were provided, args.c puts a single "-" in arg_names[0]. 241235911Smav to_stdout_only = true; 242235911Smav 243235911Smav for (unsigned i = 0; i < args.arg_count; ++i) { 244235911Smav if (strcmp("-", args.arg_names[i]) != 0) { 245235911Smav to_stdout_only = false; 246235911Smav break; 247235911Smav } 248235911Smav } 249235911Smav } 250235911Smav 251235911Smav if (to_stdout_only) { 252235911Smav sandbox_enable_read_only(); 253235911Smav 254235911Smav // Allow strict sandboxing if we are processing exactly one 255235911Smav // file to standard output. This requires that --files or 256235911Smav // --files0 wasn't specified (an unknown number of filenames 257235911Smav // could be provided that way). 258235911Smav if (args.files_name == NULL && args.arg_count == 1) 259235911Smav sandbox_allow_strict(); 260235911Smav } 261235911Smav#endif 262235911Smav 263235911Smav // coder_run() handles compression, decompression, and testing. 264235911Smav // list_file() is for --list. 265235911Smav void (*run)(const char *filename) = &coder_run; 266235911Smav#ifdef HAVE_DECODERS 267235911Smav if (opt_mode == MODE_LIST) 268235911Smav run = &list_file; 269235911Smav#endif 270235911Smav 271235911Smav // Process the files given on the command line. Note that if no names 272235911Smav // were given, args_parse() gave us a fake "-" filename. 273235911Smav for (unsigned i = 0; i < args.arg_count && !user_abort; ++i) { 274235911Smav if (strcmp("-", args.arg_names[i]) == 0) { 275235911Smav // Processing from stdin to stdout. Check that we 276235911Smav // aren't writing compressed data to a terminal or 277235911Smav // reading it from a terminal. 278235911Smav if (opt_mode == MODE_COMPRESS) { 279235911Smav if (is_tty_stdout()) 280235911Smav continue; 281235911Smav } else if (is_tty_stdin()) { 282235911Smav continue; 283235911Smav } 284235911Smav 285235911Smav // It doesn't make sense to compress data from stdin 286235911Smav // if we are supposed to read filenames from stdin 287235911Smav // too (enabled with --files or --files0). 288235911Smav if (args.files_name == stdin_filename) { 289235911Smav message_error(_("Cannot read data from " 290235911Smav "standard input when " 291235911Smav "reading filenames " 292235911Smav "from standard input")); 293235911Smav continue; 294235911Smav } 295235911Smav 296235911Smav // Replace the "-" with a special pointer, which is 297235911Smav // recognized by coder_run() and other things. 298235911Smav // This way error messages get a proper filename 299235911Smav // string and the code still knows that it is 300235911Smav // handling the special case of stdin. 301235911Smav args.arg_names[i] = (char *)stdin_filename; 302235911Smav } 303235911Smav 304235911Smav // Do the actual compression or decompression. 305235911Smav run(args.arg_names[i]); 306235911Smav } 307235911Smav 308235911Smav // If --files or --files0 was used, process the filenames from the 309235911Smav // given file or stdin. Note that here we don't consider "-" to 310235911Smav // indicate stdin like we do with the command line arguments. 311235911Smav if (args.files_name != NULL) { 312235911Smav // read_name() checks for user_abort so we don't need to 313235911Smav // check it as loop termination condition. 314235911Smav while (true) { 315235911Smav const char *name = read_name(&args); 316235911Smav if (name == NULL) 317235911Smav break; 318235911Smav 319235911Smav // read_name() doesn't return empty names. 320235911Smav assert(name[0] != '\0'); 321235911Smav run(name); 322235911Smav } 323235911Smav 324235911Smav if (args.files_name != stdin_filename) 325235911Smav (void)fclose(args.files_file); 326235911Smav } 327235911Smav 328235911Smav#ifdef HAVE_DECODERS 329235911Smav // All files have now been handled. If in --list mode, display 330235911Smav // the totals before exiting. We don't have signal handlers 331235911Smav // enabled in --list mode, so we don't need to check user_abort. 332235911Smav if (opt_mode == MODE_LIST) { 333235911Smav assert(!user_abort); 334235911Smav list_totals(); 335235911Smav } 336235911Smav#endif 337235911Smav 338235911Smav#ifndef NDEBUG 339235911Smav coder_free(); 340235911Smav args_free(); 341235911Smav#endif 342235911Smav 343235911Smav // If we have got a signal, raise it to kill the program instead 344235911Smav // of calling tuklib_exit(). 345235911Smav signals_exit(); 346235911Smav 347235911Smav // Make a local copy of exit_status to keep the Windows code 348235911Smav // thread safe. At this point it is fine if we miss the user 349235911Smav // pressing C-c and don't set the exit_status to E_ERROR on 350235911Smav // Windows. 351235911Smav#if defined(_WIN32) && !defined(__CYGWIN__) 352235911Smav EnterCriticalSection(&exit_status_cs); 353235911Smav#endif 354235911Smav 355235911Smav enum exit_status_type es = exit_status; 356235911Smav 357235911Smav#if defined(_WIN32) && !defined(__CYGWIN__) 358235911Smav LeaveCriticalSection(&exit_status_cs); 359235911Smav#endif 360235911Smav 361235911Smav // Suppress the exit status indicating a warning if --no-warn 362235911Smav // was specified. 363235911Smav if (es == E_WARNING && no_warn) 364235911Smav es = E_SUCCESS; 365235911Smav 366235911Smav tuklib_exit((int)es, E_ERROR, message_verbosity_get() != V_SILENT); 367242720Smav} 368235911Smav