OLD | NEW |
(Empty) | |
| 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 #if defined(_WIN32) && !defined(__CYGWIN__) |
| 146 InitializeCriticalSection(&exit_status_cs); |
| 147 #endif |
| 148 |
| 149 // Set up the progname variable. |
| 150 tuklib_progname_init(argv); |
| 151 |
| 152 // Initialize the file I/O. This makes sure that |
| 153 // stdin, stdout, and stderr are something valid. |
| 154 io_init(); |
| 155 |
| 156 // Set up the locale and message translations. |
| 157 tuklib_gettext_init(PACKAGE, LOCALEDIR); |
| 158 |
| 159 // Initialize handling of error/warning/other messages. |
| 160 message_init(); |
| 161 |
| 162 // Set hardware-dependent default values. These can be overriden |
| 163 // on the command line, thus this must be done before args_parse(). |
| 164 hardware_init(); |
| 165 |
| 166 // Parse the command line arguments and get an array of filenames. |
| 167 // This doesn't return if something is wrong with the command line |
| 168 // arguments. If there are no arguments, one filename ("-") is still |
| 169 // returned to indicate stdin. |
| 170 args_info args; |
| 171 args_parse(&args, argc, argv); |
| 172 |
| 173 if (opt_mode != MODE_LIST && opt_robot) |
| 174 message_fatal(_("Compression and decompression with --robot " |
| 175 "are not supported yet.")); |
| 176 |
| 177 // Tell the message handling code how many input files there are if |
| 178 // we know it. This way the progress indicator can show it. |
| 179 if (args.files_name != NULL) |
| 180 message_set_files(0); |
| 181 else |
| 182 message_set_files(args.arg_count); |
| 183 |
| 184 // Refuse to write compressed data to standard output if it is |
| 185 // a terminal. |
| 186 if (opt_mode == MODE_COMPRESS) { |
| 187 if (opt_stdout || (args.arg_count == 1 |
| 188 && strcmp(args.arg_names[0], "-") == 0)) { |
| 189 if (is_tty_stdout()) { |
| 190 message_try_help(); |
| 191 tuklib_exit(E_ERROR, E_ERROR, false); |
| 192 } |
| 193 } |
| 194 } |
| 195 |
| 196 // Set up the signal handlers. We don't need these before we |
| 197 // start the actual action and not in --list mode, so this is |
| 198 // done after parsing the command line arguments. |
| 199 // |
| 200 // It's good to keep signal handlers in normal compression and |
| 201 // decompression modes even when only writing to stdout, because |
| 202 // we might need to restore O_APPEND flag on stdout before exiting. |
| 203 // In --test mode, signal handlers aren't really needed, but let's |
| 204 // keep them there for consistency with normal decompression. |
| 205 if (opt_mode != MODE_LIST) |
| 206 signals_init(); |
| 207 |
| 208 // coder_run() handles compression, decompression, and testing. |
| 209 // list_file() is for --list. |
| 210 void (*run)(const char *filename) = opt_mode == MODE_LIST |
| 211 ? &list_file : &coder_run; |
| 212 |
| 213 // Process the files given on the command line. Note that if no names |
| 214 // were given, args_parse() gave us a fake "-" filename. |
| 215 for (size_t i = 0; i < args.arg_count && !user_abort; ++i) { |
| 216 if (strcmp("-", args.arg_names[i]) == 0) { |
| 217 // Processing from stdin to stdout. Check that we |
| 218 // aren't writing compressed data to a terminal or |
| 219 // reading it from a terminal. |
| 220 if (opt_mode == MODE_COMPRESS) { |
| 221 if (is_tty_stdout()) |
| 222 continue; |
| 223 } else if (is_tty_stdin()) { |
| 224 continue; |
| 225 } |
| 226 |
| 227 // It doesn't make sense to compress data from stdin |
| 228 // if we are supposed to read filenames from stdin |
| 229 // too (enabled with --files or --files0). |
| 230 if (args.files_name == stdin_filename) { |
| 231 message_error(_("Cannot read data from " |
| 232 "standard input when " |
| 233 "reading filenames " |
| 234 "from standard input")); |
| 235 continue; |
| 236 } |
| 237 |
| 238 // Replace the "-" with a special pointer, which is |
| 239 // recognized by coder_run() and other things. |
| 240 // This way error messages get a proper filename |
| 241 // string and the code still knows that it is |
| 242 // handling the special case of stdin. |
| 243 args.arg_names[i] = (char *)stdin_filename; |
| 244 } |
| 245 |
| 246 // Do the actual compression or decompression. |
| 247 run(args.arg_names[i]); |
| 248 } |
| 249 |
| 250 // If --files or --files0 was used, process the filenames from the |
| 251 // given file or stdin. Note that here we don't consider "-" to |
| 252 // indicate stdin like we do with the command line arguments. |
| 253 if (args.files_name != NULL) { |
| 254 // read_name() checks for user_abort so we don't need to |
| 255 // check it as loop termination condition. |
| 256 while (true) { |
| 257 const char *name = read_name(&args); |
| 258 if (name == NULL) |
| 259 break; |
| 260 |
| 261 // read_name() doesn't return empty names. |
| 262 assert(name[0] != '\0'); |
| 263 run(name); |
| 264 } |
| 265 |
| 266 if (args.files_name != stdin_filename) |
| 267 (void)fclose(args.files_file); |
| 268 } |
| 269 |
| 270 // All files have now been handled. If in --list mode, display |
| 271 // the totals before exiting. We don't have signal handlers |
| 272 // enabled in --list mode, so we don't need to check user_abort. |
| 273 if (opt_mode == MODE_LIST) { |
| 274 assert(!user_abort); |
| 275 list_totals(); |
| 276 } |
| 277 |
| 278 // If we have got a signal, raise it to kill the program instead |
| 279 // of calling tuklib_exit(). |
| 280 signals_exit(); |
| 281 |
| 282 // Make a local copy of exit_status to keep the Windows code |
| 283 // thread safe. At this point it is fine if we miss the user |
| 284 // pressing C-c and don't set the exit_status to E_ERROR on |
| 285 // Windows. |
| 286 #if defined(_WIN32) && !defined(__CYGWIN__) |
| 287 EnterCriticalSection(&exit_status_cs); |
| 288 #endif |
| 289 |
| 290 enum exit_status_type es = exit_status; |
| 291 |
| 292 #if defined(_WIN32) && !defined(__CYGWIN__) |
| 293 LeaveCriticalSection(&exit_status_cs); |
| 294 #endif |
| 295 |
| 296 // Suppress the exit status indicating a warning if --no-warn |
| 297 // was specified. |
| 298 if (es == E_WARNING && no_warn) |
| 299 es = E_SUCCESS; |
| 300 |
| 301 tuklib_exit(es, E_ERROR, message_verbosity_get() != V_SILENT); |
| 302 } |
OLD | NEW |