/////////////////////////////////////////////////////////////////////////////// // /// \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 /// Exit status to use. This can be changed with set_exit_status(). static enum exit_status_type exit_status = E_SUCCESS; /// 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 (exit_status != E_ERROR) exit_status = new_status; return; } extern void set_exit_no_warn(void) { no_warn = true; return; } extern void my_exit(enum exit_status_type status) { // Close stdout. If something goes wrong, print an error message // to stderr. { const int ferror_err = ferror(stdout); const int fclose_err = fclose(stdout); if (ferror_err || fclose_err) { // If it was fclose() that failed, we have the reason // in errno. If only ferror() indicated an error, // we have no idea what the reason was. message(V_ERROR, _("Writing to standard output " "failed: %s"), fclose_err ? strerror(errno) : _("Unknown error")); status = E_ERROR; } } // Close stderr. If something goes wrong, there's nothing where we // could print an error message. Just set the exit status. { const int ferror_err = ferror(stderr); const int fclose_err = fclose(stderr); if (fclose_err || ferror_err) status = E_ERROR; } // Suppress the exit status indicating a warning if --no-warn // was specified. if (status == E_WARNING && no_warn) status = E_SUCCESS; // If we have got a signal, raise it to kill the program. // Otherwise we just call exit(). signals_exit(); exit(status); } 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 uncompression. // 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) { // Initialize the file I/O as the very first step. This makes sure // that stdin, stdout, and stderr are something valid. io_init(); #ifdef DOSLIKE // Adjust argv[0] to make it look nicer in messages, and also to // help the code in args.c. { // Strip the leading path. char *p = argv[0] + strlen(argv[0]); while (argv[0] < p && p[-1] != '/' && p[-1] != '\\') --p; argv[0] = p; // Strip the .exe suffix. p = strrchr(p, '.'); if (p != NULL) *p = '\0'; // Make it lowercase. for (p = argv[0]; *p != '\0'; ++p) if (*p >= 'A' && *p <= 'Z') *p = *p - 'A' + 'a'; } #endif // Set up the locale. setlocale(LC_ALL, ""); #ifdef ENABLE_NLS // Set up the message translations too. bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); #endif // Set the program invocation name used in various messages, and // do other message handling related initializations. message_init(argv[0]); // Set hardware-dependent default values. These can be overriden // on the command line, thus this must be done before parse_args(). 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); // 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 and --force wasn't used. if (opt_mode == MODE_COMPRESS && !opt_force) { if (opt_stdout || (args.arg_count == 1 && strcmp(args.arg_names[0], "-") == 0)) { if (is_tty_stdout()) { message_try_help(); my_exit(E_ERROR); } } } if (opt_mode == MODE_LIST) { message_fatal("--list is not implemented yet."); } // Hook the signal handlers. We don't need these before we start // the actual action, so this is done after parsing the command // line arguments. signals_init(); // Process the files given on the command line. Note that if no names // were given, parse_args() 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. Unless --force // was used, check that we aren't writing compressed // data to a terminal or reading it from terminal. if (!opt_force) { 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 process_file() 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 uncompression. process_file(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'); process_file(name); } if (args.files_name != stdin_filename) (void)fclose(args.files_file); } my_exit(exit_status); }