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