]> icculus.org git repositories - icculus/xz.git/blob - src/xz/main.c
Make "xz --force" to write to terminal as the error
[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         // Set up the locale.
168         setlocale(LC_ALL, "");
169
170 #ifdef ENABLE_NLS
171         // Set up the message translations too.
172         bindtextdomain(PACKAGE, LOCALEDIR);
173         textdomain(PACKAGE);
174 #endif
175
176         // Set the program invocation name used in various messages, and
177         // do other message handling related initializations.
178         message_init(argv[0]);
179
180         // Set hardware-dependent default values. These can be overriden
181         // on the command line, thus this must be done before parse_args().
182         hardware_init();
183
184         // Parse the command line arguments and get an array of filenames.
185         // This doesn't return if something is wrong with the command line
186         // arguments. If there are no arguments, one filename ("-") is still
187         // returned to indicate stdin.
188         args_info args;
189         args_parse(&args, argc, argv);
190
191         // Tell the message handling code how many input files there are if
192         // we know it. This way the progress indicator can show it.
193         if (args.files_name != NULL)
194                 message_set_files(0);
195         else
196                 message_set_files(args.arg_count);
197
198         // Refuse to write compressed data to standard output if it is
199         // a terminal and --force wasn't used.
200         if (opt_mode == MODE_COMPRESS && !opt_force) {
201                 if (opt_stdout || (args.arg_count == 1
202                                 && strcmp(args.arg_names[0], "-") == 0)) {
203                         if (is_tty_stdout()) {
204                                 message_try_help();
205                                 my_exit(E_ERROR);
206                         }
207                 }
208         }
209
210         if (opt_mode == MODE_LIST) {
211                 message_fatal("--list is not implemented yet.");
212         }
213
214         // Hook the signal handlers. We don't need these before we start
215         // the actual action, so this is done after parsing the command
216         // line arguments.
217         signals_init();
218
219         // Process the files given on the command line. Note that if no names
220         // were given, parse_args() gave us a fake "-" filename.
221         for (size_t i = 0; i < args.arg_count && !user_abort; ++i) {
222                 if (strcmp("-", args.arg_names[i]) == 0) {
223                         // Processing from stdin to stdout. Unless --force
224                         // was used, check that we aren't writing compressed
225                         // data to a terminal or reading it from terminal.
226                         if (!opt_force) {
227                                 if (opt_mode == MODE_COMPRESS) {
228                                         if (is_tty_stdout())
229                                                 continue;
230                                 } else if (is_tty_stdin()) {
231                                         continue;
232                                 }
233                         }
234
235                         // It doesn't make sense to compress data from stdin
236                         // if we are supposed to read filenames from stdin
237                         // too (enabled with --files or --files0).
238                         if (args.files_name == stdin_filename) {
239                                 message_error(_("Cannot read data from "
240                                                 "standard input when "
241                                                 "reading filenames "
242                                                 "from standard input"));
243                                 continue;
244                         }
245
246                         // Replace the "-" with a special pointer, which is
247                         // recognized by process_file() and other things.
248                         // This way error messages get a proper filename
249                         // string and the code still knows that it is
250                         // handling the special case of stdin.
251                         args.arg_names[i] = (char *)stdin_filename;
252                 }
253
254                 // Do the actual compression or uncompression.
255                 process_file(args.arg_names[i]);
256         }
257
258         // If --files or --files0 was used, process the filenames from the
259         // given file or stdin. Note that here we don't consider "-" to
260         // indicate stdin like we do with the command line arguments.
261         if (args.files_name != NULL) {
262                 // read_name() checks for user_abort so we don't need to
263                 // check it as loop termination condition.
264                 while (true) {
265                         const char *name = read_name(&args);
266                         if (name == NULL)
267                                 break;
268
269                         // read_name() doesn't return empty names.
270                         assert(name[0] != '\0');
271                         process_file(name);
272                 }
273
274                 if (args.files_name != stdin_filename)
275                         (void)fclose(args.files_file);
276         }
277
278         my_exit(exit_status);
279 }