1 ///////////////////////////////////////////////////////////////////////////////
6 // Copyright (C) 2007 Lasse Collin
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU Lesser General Public
10 // License as published by the Free Software Foundation; either
11 // version 2.1 of the License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // Lesser General Public License for more details.
18 ///////////////////////////////////////////////////////////////////////////////
24 /// Exit status to use. This can be changed with set_exit_status().
25 static enum exit_status_type exit_status = E_SUCCESS;
29 set_exit_status(enum exit_status_type new_status)
31 assert(new_status == E_WARNING || new_status == E_ERROR);
33 if (exit_status != E_ERROR)
34 exit_status = new_status;
41 my_exit(enum exit_status_type status)
43 // Close stdout. If something goes wrong, print an error message
46 const int ferror_err = ferror(stdout);
47 const int fclose_err = fclose(stdout);
48 if (ferror_err || fclose_err) {
49 // If it was fclose() that failed, we have the reason
50 // in errno. If only ferror() indicated an error,
51 // we have no idea what the reason was.
52 message(V_ERROR, _("Writing to standard output "
54 fclose_err ? strerror(errno)
55 : _("Unknown error"));
60 // Close stderr. If something goes wrong, there's nothing where we
61 // could print an error message. Just set the exit status.
63 const int ferror_err = ferror(stderr);
64 const int fclose_err = fclose(stderr);
65 if (fclose_err || ferror_err)
69 // If we have got a signal, raise it to kill the program.
70 // Otherwise we just call exit().
77 read_name(const args_info *args)
79 // FIXME: Maybe we should have some kind of memory usage limit here
80 // like the tool has for the actual compression and uncompression.
81 // Giving some huge text file with --files0 makes us to read the
83 static char *name = NULL;
84 static size_t size = 256;
86 // Allocate the initial buffer. This is never freed, since after it
87 // is no longer needed, the program exits very soon. It is safe to
88 // use xmalloc() and xrealloc() in this function, because while
89 // executing this function, no files are open for writing, and thus
90 // there's no need to cleanup anything before exiting.
94 // Write position in name
97 // Read one character at a time into name.
99 const int c = fgetc(args->files_file);
101 if (ferror(args->files_file)) {
102 // Take care of EINTR since we have established
103 // the signal handlers already.
107 message_error(_("%s: Error reading filenames: %s"),
108 args->files_name, strerror(errno));
112 if (feof(args->files_file)) {
114 message_error(_("%s: Unexpected end of input "
115 "when reading filenames"),
121 if (c == args->files_delim) {
122 // We allow consecutive newline (--files) or '\0'
123 // characters (--files0), and ignore such empty
128 // A non-empty name was read. Terminate it with '\0'
135 // A null character was found when using --files,
136 // which expects plain text input separated with
138 message_error(_("%s: Null character found when "
139 "reading filenames; maybe you meant "
140 "to use `--files0' instead "
141 "of `--files'?"), args->files_name);
147 // Allocate more memory if needed. There must always be space
148 // at least for one character to allow terminating the string
152 name = xrealloc(name, size);
161 main(int argc, char **argv)
163 // Initialize the file I/O as the very first step. This makes sure
164 // that stdin, stdout, and stderr are something valid.
168 // Adjust argv[0] to make it look nicer in messages, and also to
169 // help the code in args.c.
171 // Strip the leading path.
172 char *p = argv[0] + strlen(argv[0]);
173 while (argv[0] < p && p[-1] != '/' && p[-1] != '\\')
178 // Strip the .exe suffix.
183 // Make it lowercase.
184 for (p = argv[0]; *p != '\0'; ++p)
185 if (*p >= 'A' && *p <= 'Z')
190 // Set up the locale.
191 setlocale(LC_ALL, "");
194 // Set up the message translations too.
195 bindtextdomain(PACKAGE, LOCALEDIR);
199 // Set the program invocation name used in various messages, and
200 // do other message handling related initializations.
201 message_init(argv[0]);
203 // Set hardware-dependent default values. These can be overriden
204 // on the command line, thus this must be done before parse_args().
207 // Parse the command line arguments and get an array of filenames.
208 // This doesn't return if something is wrong with the command line
209 // arguments. If there are no arguments, one filename ("-") is still
210 // returned to indicate stdin.
212 args_parse(&args, argc, argv);
214 // Tell the message handling code how many input files there are if
215 // we know it. This way the progress indicator can show it.
216 if (args.files_name != NULL)
217 message_set_files(0);
219 message_set_files(args.arg_count);
221 // Refuse to write compressed data to standard output if it is
222 // a terminal and --force wasn't used.
223 if (opt_mode == MODE_COMPRESS && !opt_force) {
224 if (opt_stdout || (args.arg_count == 1
225 && strcmp(args.arg_names[0], "-") == 0)) {
226 if (is_tty_stdout()) {
233 if (opt_mode == MODE_LIST) {
234 message_fatal("--list is not implemented yet.");
237 // Hook the signal handlers. We don't need these before we start
238 // the actual action, so this is done after parsing the command
242 // Process the files given on the command line. Note that if no names
243 // were given, parse_args() gave us a fake "-" filename.
244 for (size_t i = 0; i < args.arg_count && !user_abort; ++i) {
245 if (strcmp("-", args.arg_names[i]) == 0) {
246 // Processing from stdin to stdout. Unless --force
247 // was used, check that we aren't writing compressed
248 // data to a terminal or reading it from terminal.
250 if (opt_mode == MODE_COMPRESS) {
253 } else if (is_tty_stdin()) {
258 // It doesn't make sense to compress data from stdin
259 // if we are supposed to read filenames from stdin
260 // too (enabled with --files or --files0).
261 if (args.files_name == stdin_filename) {
262 message_error(_("Cannot read data from "
263 "standard input when "
265 "from standard input"));
269 // Replace the "-" with a special pointer, which is
270 // recognized by process_file() and other things.
271 // This way error messages get a proper filename
272 // string and the code still knows that it is
273 // handling the special case of stdin.
274 args.arg_names[i] = (char *)stdin_filename;
277 // Do the actual compression or uncompression.
278 process_file(args.arg_names[i]);
281 // If --files or --files0 was used, process the filenames from the
282 // given file or stdin. Note that here we don't consider "-" to
283 // indicate stdin like we do with the command line arguments.
284 if (args.files_name != NULL) {
285 // read_name() checks for user_abort so we don't need to
286 // check it as loop termination condition.
288 const char *name = read_name(&args);
292 // read_name() doesn't return empty names.
293 assert(name[0] != '\0');
297 if (args.files_name != stdin_filename)
298 (void)fclose(args.files_file);
301 my_exit(exit_status);