]> icculus.org git repositories - icculus/xz.git/blob - src/xz/main.c
Improve support for DOS-like systems.
[icculus/xz.git] / src / xz / main.c
1 ///////////////////////////////////////////////////////////////////////////////
2 //
3 /// \file       main.c
4 /// \brief      main()
5 //
6 //  Copyright (C) 2007 Lasse Collin
7 //
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.
12 //
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.
17 //
18 ///////////////////////////////////////////////////////////////////////////////
19
20 #include "private.h"
21 #include <ctype.h>
22
23
24 /// Exit status to use. This can be changed with set_exit_status().
25 static enum exit_status_type exit_status = E_SUCCESS;
26
27
28 extern void
29 set_exit_status(enum exit_status_type new_status)
30 {
31         assert(new_status == E_WARNING || new_status == E_ERROR);
32
33         if (exit_status != E_ERROR)
34                 exit_status = new_status;
35
36         return;
37 }
38
39
40 extern void
41 my_exit(enum exit_status_type status)
42 {
43         // Close stdout. If something goes wrong, print an error message
44         // to stderr.
45         {
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 "
53                                         "failed: %s"),
54                                         fclose_err ? strerror(errno)
55                                                 : _("Unknown error"));
56                         status = E_ERROR;
57                 }
58         }
59
60         // Close stderr. If something goes wrong, there's nothing where we
61         // could print an error message. Just set the exit status.
62         {
63                 const int ferror_err = ferror(stderr);
64                 const int fclose_err = fclose(stderr);
65                 if (fclose_err || ferror_err)
66                         status = E_ERROR;
67         }
68
69         // If we have got a signal, raise it to kill the program.
70         // Otherwise we just call exit().
71         signals_exit();
72         exit(status);
73 }
74
75
76 static const char *
77 read_name(const args_info *args)
78 {
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
82         // whole file in RAM.
83         static char *name = NULL;
84         static size_t size = 256;
85
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.
91         if (name == NULL)
92                 name = xmalloc(size);
93
94         // Write position in name
95         size_t pos = 0;
96
97         // Read one character at a time into name.
98         while (!user_abort) {
99                 const int c = fgetc(args->files_file);
100
101                 if (ferror(args->files_file)) {
102                         // Take care of EINTR since we have established
103                         // the signal handlers already.
104                         if (errno == EINTR)
105                                 continue;
106
107                         message_error(_("%s: Error reading filenames: %s"),
108                                         args->files_name, strerror(errno));
109                         return NULL;
110                 }
111
112                 if (feof(args->files_file)) {
113                         if (pos != 0)
114                                 message_error(_("%s: Unexpected end of input "
115                                                 "when reading filenames"),
116                                                 args->files_name);
117
118                         return NULL;
119                 }
120
121                 if (c == args->files_delim) {
122                         // We allow consecutive newline (--files) or '\0'
123                         // characters (--files0), and ignore such empty
124                         // filenames.
125                         if (pos == 0)
126                                 continue;
127
128                         // A non-empty name was read. Terminate it with '\0'
129                         // and return it.
130                         name[pos] = '\0';
131                         return name;
132                 }
133
134                 if (c == '\0') {
135                         // A null character was found when using --files,
136                         // which expects plain text input separated with
137                         // newlines.
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);
142                         return NULL;
143                 }
144
145                 name[pos++] = c;
146
147                 // Allocate more memory if needed. There must always be space
148                 // at least for one character to allow terminating the string
149                 // with '\0'.
150                 if (pos == size) {
151                         size *= 2;
152                         name = xrealloc(name, size);
153                 }
154         }
155
156         return NULL;
157 }
158
159
160 int
161 main(int argc, char **argv)
162 {
163         // Initialize the file I/O as the very first step. This makes sure
164         // that stdin, stdout, and stderr are something valid.
165         io_init();
166
167 #ifdef DOSLIKE
168         // Adjust argv[0] to make it look nicer in messages, and also to
169         // help the code in args.c.
170         {
171                 // Strip the leading path.
172                 char *p = argv[0] + strlen(argv[0]);
173                 while (argv[0] < p && p[-1] != '/' && p[-1] != '\\')
174                         --p;
175
176                 argv[0] = p;
177
178                 // Strip the .exe suffix.
179                 p = strrchr(p, '.');
180                 if (p != NULL)
181                         *p = '\0';
182
183                 // Make it lowercase.
184                 for (p = argv[0]; *p != '\0'; ++p)
185                         if (*p >= 'A' && *p <= 'Z')
186                                 *p = *p - 'A' + 'a';
187         }
188 #endif
189
190         // Set up the locale.
191         setlocale(LC_ALL, "");
192
193 #ifdef ENABLE_NLS
194         // Set up the message translations too.
195         bindtextdomain(PACKAGE, LOCALEDIR);
196         textdomain(PACKAGE);
197 #endif
198
199         // Set the program invocation name used in various messages, and
200         // do other message handling related initializations.
201         message_init(argv[0]);
202
203         // Set hardware-dependent default values. These can be overriden
204         // on the command line, thus this must be done before parse_args().
205         hardware_init();
206
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.
211         args_info args;
212         args_parse(&args, argc, argv);
213
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);
218         else
219                 message_set_files(args.arg_count);
220
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()) {
227                                 message_try_help();
228                                 my_exit(E_ERROR);
229                         }
230                 }
231         }
232
233         if (opt_mode == MODE_LIST) {
234                 message_fatal("--list is not implemented yet.");
235         }
236
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
239         // line arguments.
240         signals_init();
241
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.
249                         if (!opt_force) {
250                                 if (opt_mode == MODE_COMPRESS) {
251                                         if (is_tty_stdout())
252                                                 continue;
253                                 } else if (is_tty_stdin()) {
254                                         continue;
255                                 }
256                         }
257
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 "
264                                                 "reading filenames "
265                                                 "from standard input"));
266                                 continue;
267                         }
268
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;
275                 }
276
277                 // Do the actual compression or uncompression.
278                 process_file(args.arg_names[i]);
279         }
280
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.
287                 while (true) {
288                         const char *name = read_name(&args);
289                         if (name == NULL)
290                                 break;
291
292                         // read_name() doesn't return empty names.
293                         assert(name[0] != '\0');
294                         process_file(name);
295                 }
296
297                 if (args.files_name != stdin_filename)
298                         (void)fclose(args.files_file);
299         }
300
301         my_exit(exit_status);
302 }