]> icculus.org git repositories - icculus/xz.git/blob - src/xz/main.c
Remove --force from xzdec.
[icculus/xz.git] / src / xz / main.c
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
17 /// Exit status to use. This can be changed with set_exit_status().
18 static enum exit_status_type exit_status = E_SUCCESS;
19
20 /// True if --no-warn is specified. When this is true, we don't set
21 /// the exit status to E_WARNING when something worth a warning happens.
22 static bool no_warn = false;
23
24
25 extern void
26 set_exit_status(enum exit_status_type new_status)
27 {
28         assert(new_status == E_WARNING || new_status == E_ERROR);
29
30         if (exit_status != E_ERROR)
31                 exit_status = new_status;
32
33         return;
34 }
35
36
37 extern void
38 set_exit_no_warn(void)
39 {
40         no_warn = true;
41         return;
42 }
43
44
45 extern void
46 my_exit(enum exit_status_type status)
47 {
48         // Close stdout. If something goes wrong, print an error message
49         // to stderr.
50         {
51                 const int ferror_err = ferror(stdout);
52                 const int fclose_err = fclose(stdout);
53                 if (ferror_err || fclose_err) {
54                         // If it was fclose() that failed, we have the reason
55                         // in errno. If only ferror() indicated an error,
56                         // we have no idea what the reason was.
57                         message(V_ERROR, _("Writing to standard output "
58                                         "failed: %s"),
59                                         fclose_err ? strerror(errno)
60                                                 : _("Unknown error"));
61                         status = E_ERROR;
62                 }
63         }
64
65         // Close stderr. If something goes wrong, there's nothing where we
66         // could print an error message. Just set the exit status.
67         {
68                 const int ferror_err = ferror(stderr);
69                 const int fclose_err = fclose(stderr);
70                 if (fclose_err || ferror_err)
71                         status = E_ERROR;
72         }
73
74         // Suppress the exit status indicating a warning if --no-warn
75         // was specified.
76         if (status == E_WARNING && no_warn)
77                 status = E_SUCCESS;
78
79         // If we have got a signal, raise it to kill the program.
80         // Otherwise we just call exit().
81         signals_exit();
82         exit(status);
83 }
84
85
86 static const char *
87 read_name(const args_info *args)
88 {
89         // FIXME: Maybe we should have some kind of memory usage limit here
90         // like the tool has for the actual compression and uncompression.
91         // Giving some huge text file with --files0 makes us to read the
92         // whole file in RAM.
93         static char *name = NULL;
94         static size_t size = 256;
95
96         // Allocate the initial buffer. This is never freed, since after it
97         // is no longer needed, the program exits very soon. It is safe to
98         // use xmalloc() and xrealloc() in this function, because while
99         // executing this function, no files are open for writing, and thus
100         // there's no need to cleanup anything before exiting.
101         if (name == NULL)
102                 name = xmalloc(size);
103
104         // Write position in name
105         size_t pos = 0;
106
107         // Read one character at a time into name.
108         while (!user_abort) {
109                 const int c = fgetc(args->files_file);
110
111                 if (ferror(args->files_file)) {
112                         // Take care of EINTR since we have established
113                         // the signal handlers already.
114                         if (errno == EINTR)
115                                 continue;
116
117                         message_error(_("%s: Error reading filenames: %s"),
118                                         args->files_name, strerror(errno));
119                         return NULL;
120                 }
121
122                 if (feof(args->files_file)) {
123                         if (pos != 0)
124                                 message_error(_("%s: Unexpected end of input "
125                                                 "when reading filenames"),
126                                                 args->files_name);
127
128                         return NULL;
129                 }
130
131                 if (c == args->files_delim) {
132                         // We allow consecutive newline (--files) or '\0'
133                         // characters (--files0), and ignore such empty
134                         // filenames.
135                         if (pos == 0)
136                                 continue;
137
138                         // A non-empty name was read. Terminate it with '\0'
139                         // and return it.
140                         name[pos] = '\0';
141                         return name;
142                 }
143
144                 if (c == '\0') {
145                         // A null character was found when using --files,
146                         // which expects plain text input separated with
147                         // newlines.
148                         message_error(_("%s: Null character found when "
149                                         "reading filenames; maybe you meant "
150                                         "to use `--files0' instead "
151                                         "of `--files'?"), args->files_name);
152                         return NULL;
153                 }
154
155                 name[pos++] = c;
156
157                 // Allocate more memory if needed. There must always be space
158                 // at least for one character to allow terminating the string
159                 // with '\0'.
160                 if (pos == size) {
161                         size *= 2;
162                         name = xrealloc(name, size);
163                 }
164         }
165
166         return NULL;
167 }
168
169
170 int
171 main(int argc, char **argv)
172 {
173         // Initialize the file I/O as the very first step. This makes sure
174         // that stdin, stdout, and stderr are something valid.
175         io_init();
176
177 #ifdef DOSLIKE
178         // Adjust argv[0] to make it look nicer in messages, and also to
179         // help the code in args.c.
180         {
181                 // Strip the leading path.
182                 char *p = argv[0] + strlen(argv[0]);
183                 while (argv[0] < p && p[-1] != '/' && p[-1] != '\\')
184                         --p;
185
186                 argv[0] = p;
187
188                 // Strip the .exe suffix.
189                 p = strrchr(p, '.');
190                 if (p != NULL)
191                         *p = '\0';
192
193                 // Make it lowercase.
194                 for (p = argv[0]; *p != '\0'; ++p)
195                         if (*p >= 'A' && *p <= 'Z')
196                                 *p = *p - 'A' + 'a';
197         }
198 #endif
199
200         // Set up the locale.
201         setlocale(LC_ALL, "");
202
203 #ifdef ENABLE_NLS
204         // Set up the message translations too.
205         bindtextdomain(PACKAGE, LOCALEDIR);
206         textdomain(PACKAGE);
207 #endif
208
209         // Set the program invocation name used in various messages, and
210         // do other message handling related initializations.
211         message_init(argv[0]);
212
213         // Set hardware-dependent default values. These can be overriden
214         // on the command line, thus this must be done before parse_args().
215         hardware_init();
216
217         // Parse the command line arguments and get an array of filenames.
218         // This doesn't return if something is wrong with the command line
219         // arguments. If there are no arguments, one filename ("-") is still
220         // returned to indicate stdin.
221         args_info args;
222         args_parse(&args, argc, argv);
223
224         // Tell the message handling code how many input files there are if
225         // we know it. This way the progress indicator can show it.
226         if (args.files_name != NULL)
227                 message_set_files(0);
228         else
229                 message_set_files(args.arg_count);
230
231         // Refuse to write compressed data to standard output if it is
232         // a terminal and --force wasn't used.
233         if (opt_mode == MODE_COMPRESS && !opt_force) {
234                 if (opt_stdout || (args.arg_count == 1
235                                 && strcmp(args.arg_names[0], "-") == 0)) {
236                         if (is_tty_stdout()) {
237                                 message_try_help();
238                                 my_exit(E_ERROR);
239                         }
240                 }
241         }
242
243         if (opt_mode == MODE_LIST) {
244                 message_fatal("--list is not implemented yet.");
245         }
246
247         // Hook the signal handlers. We don't need these before we start
248         // the actual action, so this is done after parsing the command
249         // line arguments.
250         signals_init();
251
252         // Process the files given on the command line. Note that if no names
253         // were given, parse_args() gave us a fake "-" filename.
254         for (size_t i = 0; i < args.arg_count && !user_abort; ++i) {
255                 if (strcmp("-", args.arg_names[i]) == 0) {
256                         // Processing from stdin to stdout. Unless --force
257                         // was used, check that we aren't writing compressed
258                         // data to a terminal or reading it from terminal.
259                         if (!opt_force) {
260                                 if (opt_mode == MODE_COMPRESS) {
261                                         if (is_tty_stdout())
262                                                 continue;
263                                 } else if (is_tty_stdin()) {
264                                         continue;
265                                 }
266                         }
267
268                         // It doesn't make sense to compress data from stdin
269                         // if we are supposed to read filenames from stdin
270                         // too (enabled with --files or --files0).
271                         if (args.files_name == stdin_filename) {
272                                 message_error(_("Cannot read data from "
273                                                 "standard input when "
274                                                 "reading filenames "
275                                                 "from standard input"));
276                                 continue;
277                         }
278
279                         // Replace the "-" with a special pointer, which is
280                         // recognized by coder_run() and other things.
281                         // This way error messages get a proper filename
282                         // string and the code still knows that it is
283                         // handling the special case of stdin.
284                         args.arg_names[i] = (char *)stdin_filename;
285                 }
286
287                 // Do the actual compression or uncompression.
288                 coder_run(args.arg_names[i]);
289         }
290
291         // If --files or --files0 was used, process the filenames from the
292         // given file or stdin. Note that here we don't consider "-" to
293         // indicate stdin like we do with the command line arguments.
294         if (args.files_name != NULL) {
295                 // read_name() checks for user_abort so we don't need to
296                 // check it as loop termination condition.
297                 while (true) {
298                         const char *name = read_name(&args);
299                         if (name == NULL)
300                                 break;
301
302                         // read_name() doesn't return empty names.
303                         assert(name[0] != '\0');
304                         coder_run(name);
305                 }
306
307                 if (args.files_name != stdin_filename)
308                         (void)fclose(args.files_file);
309         }
310
311         my_exit(exit_status);
312 }