1207753Smm/////////////////////////////////////////////////////////////////////////////// 2207753Smm// 3207753Smm/// \file main.c 4207753Smm/// \brief main() 5207753Smm// 6207753Smm// Author: Lasse Collin 7207753Smm// 8207753Smm// This file has been put into the public domain. 9207753Smm// You can do whatever you want with this file. 10207753Smm// 11207753Smm/////////////////////////////////////////////////////////////////////////////// 12207753Smm 13207753Smm#include "private.h" 14207753Smm#include <ctype.h> 15207753Smm 16207753Smm/// Exit status to use. This can be changed with set_exit_status(). 17207753Smmstatic enum exit_status_type exit_status = E_SUCCESS; 18207753Smm 19213700Smm#if defined(_WIN32) && !defined(__CYGWIN__) 20213700Smm/// exit_status has to be protected with a critical section due to 21213700Smm/// how "signal handling" is done on Windows. See signals.c for details. 22213700Smmstatic CRITICAL_SECTION exit_status_cs; 23213700Smm#endif 24213700Smm 25207753Smm/// True if --no-warn is specified. When this is true, we don't set 26207753Smm/// the exit status to E_WARNING when something worth a warning happens. 27207753Smmstatic bool no_warn = false; 28207753Smm 29207753Smm 30207753Smmextern void 31207753Smmset_exit_status(enum exit_status_type new_status) 32207753Smm{ 33207753Smm assert(new_status == E_WARNING || new_status == E_ERROR); 34207753Smm 35213700Smm#if defined(_WIN32) && !defined(__CYGWIN__) 36213700Smm EnterCriticalSection(&exit_status_cs); 37213700Smm#endif 38213700Smm 39207753Smm if (exit_status != E_ERROR) 40207753Smm exit_status = new_status; 41207753Smm 42213700Smm#if defined(_WIN32) && !defined(__CYGWIN__) 43213700Smm LeaveCriticalSection(&exit_status_cs); 44213700Smm#endif 45213700Smm 46207753Smm return; 47207753Smm} 48207753Smm 49207753Smm 50207753Smmextern void 51207753Smmset_exit_no_warn(void) 52207753Smm{ 53207753Smm no_warn = true; 54207753Smm return; 55207753Smm} 56207753Smm 57207753Smm 58207753Smmstatic const char * 59207753Smmread_name(const args_info *args) 60207753Smm{ 61207753Smm // FIXME: Maybe we should have some kind of memory usage limit here 62207753Smm // like the tool has for the actual compression and decompression. 63207753Smm // Giving some huge text file with --files0 makes us to read the 64207753Smm // whole file in RAM. 65207753Smm static char *name = NULL; 66207753Smm static size_t size = 256; 67207753Smm 68207753Smm // Allocate the initial buffer. This is never freed, since after it 69207753Smm // is no longer needed, the program exits very soon. It is safe to 70207753Smm // use xmalloc() and xrealloc() in this function, because while 71207753Smm // executing this function, no files are open for writing, and thus 72207753Smm // there's no need to cleanup anything before exiting. 73207753Smm if (name == NULL) 74207753Smm name = xmalloc(size); 75207753Smm 76207753Smm // Write position in name 77207753Smm size_t pos = 0; 78207753Smm 79207753Smm // Read one character at a time into name. 80207753Smm while (!user_abort) { 81207753Smm const int c = fgetc(args->files_file); 82207753Smm 83207753Smm if (ferror(args->files_file)) { 84207753Smm // Take care of EINTR since we have established 85207753Smm // the signal handlers already. 86207753Smm if (errno == EINTR) 87207753Smm continue; 88207753Smm 89207753Smm message_error(_("%s: Error reading filenames: %s"), 90207753Smm args->files_name, strerror(errno)); 91207753Smm return NULL; 92207753Smm } 93207753Smm 94207753Smm if (feof(args->files_file)) { 95207753Smm if (pos != 0) 96207753Smm message_error(_("%s: Unexpected end of input " 97207753Smm "when reading filenames"), 98207753Smm args->files_name); 99207753Smm 100207753Smm return NULL; 101207753Smm } 102207753Smm 103207753Smm if (c == args->files_delim) { 104207753Smm // We allow consecutive newline (--files) or '\0' 105207753Smm // characters (--files0), and ignore such empty 106207753Smm // filenames. 107207753Smm if (pos == 0) 108207753Smm continue; 109207753Smm 110207753Smm // A non-empty name was read. Terminate it with '\0' 111207753Smm // and return it. 112207753Smm name[pos] = '\0'; 113207753Smm return name; 114207753Smm } 115207753Smm 116207753Smm if (c == '\0') { 117207753Smm // A null character was found when using --files, 118207753Smm // which expects plain text input separated with 119207753Smm // newlines. 120207753Smm message_error(_("%s: Null character found when " 121207753Smm "reading filenames; maybe you meant " 122207753Smm "to use `--files0' instead " 123207753Smm "of `--files'?"), args->files_name); 124207753Smm return NULL; 125207753Smm } 126207753Smm 127207753Smm name[pos++] = c; 128207753Smm 129207753Smm // Allocate more memory if needed. There must always be space 130207753Smm // at least for one character to allow terminating the string 131207753Smm // with '\0'. 132207753Smm if (pos == size) { 133207753Smm size *= 2; 134207753Smm name = xrealloc(name, size); 135207753Smm } 136207753Smm } 137207753Smm 138207753Smm return NULL; 139207753Smm} 140207753Smm 141207753Smm 142207753Smmint 143207753Smmmain(int argc, char **argv) 144207753Smm{ 145213700Smm#if defined(_WIN32) && !defined(__CYGWIN__) 146213700Smm InitializeCriticalSection(&exit_status_cs); 147213700Smm#endif 148213700Smm 149207753Smm // Set up the progname variable. 150207753Smm tuklib_progname_init(argv); 151207753Smm 152207753Smm // Initialize the file I/O. This makes sure that 153207753Smm // stdin, stdout, and stderr are something valid. 154207753Smm io_init(); 155207753Smm 156207753Smm // Set up the locale and message translations. 157207753Smm tuklib_gettext_init(PACKAGE, LOCALEDIR); 158207753Smm 159207753Smm // Initialize handling of error/warning/other messages. 160207753Smm message_init(); 161207753Smm 162207753Smm // Set hardware-dependent default values. These can be overriden 163207753Smm // on the command line, thus this must be done before args_parse(). 164207753Smm hardware_init(); 165207753Smm 166207753Smm // Parse the command line arguments and get an array of filenames. 167207753Smm // This doesn't return if something is wrong with the command line 168207753Smm // arguments. If there are no arguments, one filename ("-") is still 169207753Smm // returned to indicate stdin. 170207753Smm args_info args; 171207753Smm args_parse(&args, argc, argv); 172207753Smm 173207753Smm if (opt_mode != MODE_LIST && opt_robot) 174207753Smm message_fatal(_("Compression and decompression with --robot " 175207753Smm "are not supported yet.")); 176207753Smm 177207753Smm // Tell the message handling code how many input files there are if 178207753Smm // we know it. This way the progress indicator can show it. 179207753Smm if (args.files_name != NULL) 180207753Smm message_set_files(0); 181207753Smm else 182207753Smm message_set_files(args.arg_count); 183207753Smm 184207753Smm // Refuse to write compressed data to standard output if it is 185207753Smm // a terminal. 186207753Smm if (opt_mode == MODE_COMPRESS) { 187207753Smm if (opt_stdout || (args.arg_count == 1 188207753Smm && strcmp(args.arg_names[0], "-") == 0)) { 189207753Smm if (is_tty_stdout()) { 190207753Smm message_try_help(); 191207753Smm tuklib_exit(E_ERROR, E_ERROR, false); 192207753Smm } 193207753Smm } 194207753Smm } 195207753Smm 196207753Smm // Set up the signal handlers. We don't need these before we 197207753Smm // start the actual action and not in --list mode, so this is 198207753Smm // done after parsing the command line arguments. 199207753Smm // 200207753Smm // It's good to keep signal handlers in normal compression and 201207753Smm // decompression modes even when only writing to stdout, because 202207753Smm // we might need to restore O_APPEND flag on stdout before exiting. 203207753Smm // In --test mode, signal handlers aren't really needed, but let's 204207753Smm // keep them there for consistency with normal decompression. 205207753Smm if (opt_mode != MODE_LIST) 206207753Smm signals_init(); 207207753Smm 208312518Sdelphij#ifdef ENABLE_SANDBOX 209312518Sdelphij // Set a flag that sandboxing is allowed if all these are true: 210312518Sdelphij // - --files or --files0 wasn't used. 211312518Sdelphij // - There is exactly one input file or we are reading from stdin. 212312518Sdelphij // - We won't create any files: output goes to stdout or --test 213312518Sdelphij // or --list was used. Note that --test implies opt_stdout = true 214312518Sdelphij // but --list doesn't. 215312518Sdelphij // 216312518Sdelphij // This is obviously not ideal but it was easy to implement and 217312518Sdelphij // it covers the most common use cases. 218312518Sdelphij // 219312518Sdelphij // TODO: Make sandboxing work for other situations too. 220312518Sdelphij if (args.files_name == NULL && args.arg_count == 1 221312518Sdelphij && (opt_stdout || strcmp("-", args.arg_names[0]) == 0 222312518Sdelphij || opt_mode == MODE_LIST)) 223312518Sdelphij io_allow_sandbox(); 224312518Sdelphij#endif 225312518Sdelphij 226207753Smm // coder_run() handles compression, decompression, and testing. 227207753Smm // list_file() is for --list. 228312518Sdelphij void (*run)(const char *filename) = &coder_run; 229312518Sdelphij#ifdef HAVE_DECODERS 230312518Sdelphij if (opt_mode == MODE_LIST) 231312518Sdelphij run = &list_file; 232312518Sdelphij#endif 233207753Smm 234207753Smm // Process the files given on the command line. Note that if no names 235207753Smm // were given, args_parse() gave us a fake "-" filename. 236292588Sdelphij for (unsigned i = 0; i < args.arg_count && !user_abort; ++i) { 237207753Smm if (strcmp("-", args.arg_names[i]) == 0) { 238207753Smm // Processing from stdin to stdout. Check that we 239207753Smm // aren't writing compressed data to a terminal or 240207753Smm // reading it from a terminal. 241207753Smm if (opt_mode == MODE_COMPRESS) { 242207753Smm if (is_tty_stdout()) 243207753Smm continue; 244207753Smm } else if (is_tty_stdin()) { 245207753Smm continue; 246207753Smm } 247207753Smm 248207753Smm // It doesn't make sense to compress data from stdin 249207753Smm // if we are supposed to read filenames from stdin 250207753Smm // too (enabled with --files or --files0). 251207753Smm if (args.files_name == stdin_filename) { 252207753Smm message_error(_("Cannot read data from " 253207753Smm "standard input when " 254207753Smm "reading filenames " 255207753Smm "from standard input")); 256207753Smm continue; 257207753Smm } 258207753Smm 259207753Smm // Replace the "-" with a special pointer, which is 260207753Smm // recognized by coder_run() and other things. 261207753Smm // This way error messages get a proper filename 262207753Smm // string and the code still knows that it is 263207753Smm // handling the special case of stdin. 264207753Smm args.arg_names[i] = (char *)stdin_filename; 265207753Smm } 266207753Smm 267207753Smm // Do the actual compression or decompression. 268207753Smm run(args.arg_names[i]); 269207753Smm } 270207753Smm 271207753Smm // If --files or --files0 was used, process the filenames from the 272207753Smm // given file or stdin. Note that here we don't consider "-" to 273207753Smm // indicate stdin like we do with the command line arguments. 274207753Smm if (args.files_name != NULL) { 275207753Smm // read_name() checks for user_abort so we don't need to 276207753Smm // check it as loop termination condition. 277207753Smm while (true) { 278207753Smm const char *name = read_name(&args); 279207753Smm if (name == NULL) 280207753Smm break; 281207753Smm 282207753Smm // read_name() doesn't return empty names. 283207753Smm assert(name[0] != '\0'); 284207753Smm run(name); 285207753Smm } 286207753Smm 287207753Smm if (args.files_name != stdin_filename) 288207753Smm (void)fclose(args.files_file); 289207753Smm } 290207753Smm 291312518Sdelphij#ifdef HAVE_DECODERS 292207753Smm // All files have now been handled. If in --list mode, display 293207753Smm // the totals before exiting. We don't have signal handlers 294207753Smm // enabled in --list mode, so we don't need to check user_abort. 295207753Smm if (opt_mode == MODE_LIST) { 296207753Smm assert(!user_abort); 297207753Smm list_totals(); 298207753Smm } 299312518Sdelphij#endif 300207753Smm 301292588Sdelphij#ifndef NDEBUG 302292588Sdelphij coder_free(); 303292588Sdelphij args_free(); 304292588Sdelphij#endif 305292588Sdelphij 306207753Smm // If we have got a signal, raise it to kill the program instead 307207753Smm // of calling tuklib_exit(). 308207753Smm signals_exit(); 309207753Smm 310213700Smm // Make a local copy of exit_status to keep the Windows code 311213700Smm // thread safe. At this point it is fine if we miss the user 312213700Smm // pressing C-c and don't set the exit_status to E_ERROR on 313213700Smm // Windows. 314213700Smm#if defined(_WIN32) && !defined(__CYGWIN__) 315213700Smm EnterCriticalSection(&exit_status_cs); 316213700Smm#endif 317213700Smm 318213700Smm enum exit_status_type es = exit_status; 319213700Smm 320213700Smm#if defined(_WIN32) && !defined(__CYGWIN__) 321213700Smm LeaveCriticalSection(&exit_status_cs); 322213700Smm#endif 323213700Smm 324207753Smm // Suppress the exit status indicating a warning if --no-warn 325207753Smm // was specified. 326213700Smm if (es == E_WARNING && no_warn) 327213700Smm es = E_SUCCESS; 328207753Smm 329213700Smm tuklib_exit(es, E_ERROR, message_verbosity_get() != V_SILENT); 330207753Smm} 331