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 208207753Smm // coder_run() handles compression, decompression, and testing. 209207753Smm // list_file() is for --list. 210207753Smm void (*run)(const char *filename) = opt_mode == MODE_LIST 211207753Smm ? &list_file : &coder_run; 212207753Smm 213207753Smm // Process the files given on the command line. Note that if no names 214207753Smm // were given, args_parse() gave us a fake "-" filename. 215207753Smm for (size_t i = 0; i < args.arg_count && !user_abort; ++i) { 216207753Smm if (strcmp("-", args.arg_names[i]) == 0) { 217207753Smm // Processing from stdin to stdout. Check that we 218207753Smm // aren't writing compressed data to a terminal or 219207753Smm // reading it from a terminal. 220207753Smm if (opt_mode == MODE_COMPRESS) { 221207753Smm if (is_tty_stdout()) 222207753Smm continue; 223207753Smm } else if (is_tty_stdin()) { 224207753Smm continue; 225207753Smm } 226207753Smm 227207753Smm // It doesn't make sense to compress data from stdin 228207753Smm // if we are supposed to read filenames from stdin 229207753Smm // too (enabled with --files or --files0). 230207753Smm if (args.files_name == stdin_filename) { 231207753Smm message_error(_("Cannot read data from " 232207753Smm "standard input when " 233207753Smm "reading filenames " 234207753Smm "from standard input")); 235207753Smm continue; 236207753Smm } 237207753Smm 238207753Smm // Replace the "-" with a special pointer, which is 239207753Smm // recognized by coder_run() and other things. 240207753Smm // This way error messages get a proper filename 241207753Smm // string and the code still knows that it is 242207753Smm // handling the special case of stdin. 243207753Smm args.arg_names[i] = (char *)stdin_filename; 244207753Smm } 245207753Smm 246207753Smm // Do the actual compression or decompression. 247207753Smm run(args.arg_names[i]); 248207753Smm } 249207753Smm 250207753Smm // If --files or --files0 was used, process the filenames from the 251207753Smm // given file or stdin. Note that here we don't consider "-" to 252207753Smm // indicate stdin like we do with the command line arguments. 253207753Smm if (args.files_name != NULL) { 254207753Smm // read_name() checks for user_abort so we don't need to 255207753Smm // check it as loop termination condition. 256207753Smm while (true) { 257207753Smm const char *name = read_name(&args); 258207753Smm if (name == NULL) 259207753Smm break; 260207753Smm 261207753Smm // read_name() doesn't return empty names. 262207753Smm assert(name[0] != '\0'); 263207753Smm run(name); 264207753Smm } 265207753Smm 266207753Smm if (args.files_name != stdin_filename) 267207753Smm (void)fclose(args.files_file); 268207753Smm } 269207753Smm 270207753Smm // All files have now been handled. If in --list mode, display 271207753Smm // the totals before exiting. We don't have signal handlers 272207753Smm // enabled in --list mode, so we don't need to check user_abort. 273207753Smm if (opt_mode == MODE_LIST) { 274207753Smm assert(!user_abort); 275207753Smm list_totals(); 276207753Smm } 277207753Smm 278207753Smm // If we have got a signal, raise it to kill the program instead 279207753Smm // of calling tuklib_exit(). 280207753Smm signals_exit(); 281207753Smm 282213700Smm // Make a local copy of exit_status to keep the Windows code 283213700Smm // thread safe. At this point it is fine if we miss the user 284213700Smm // pressing C-c and don't set the exit_status to E_ERROR on 285213700Smm // Windows. 286213700Smm#if defined(_WIN32) && !defined(__CYGWIN__) 287213700Smm EnterCriticalSection(&exit_status_cs); 288213700Smm#endif 289213700Smm 290213700Smm enum exit_status_type es = exit_status; 291213700Smm 292213700Smm#if defined(_WIN32) && !defined(__CYGWIN__) 293213700Smm LeaveCriticalSection(&exit_status_cs); 294213700Smm#endif 295213700Smm 296207753Smm // Suppress the exit status indicating a warning if --no-warn 297207753Smm // was specified. 298213700Smm if (es == E_WARNING && no_warn) 299213700Smm es = E_SUCCESS; 300207753Smm 301213700Smm tuklib_exit(es, E_ERROR, message_verbosity_get() != V_SILENT); 302207753Smm} 303