]> icculus.org git repositories - icculus/xz.git/blob - src/lzma/main.c
Oh well, big messy commit again. Some highlights:
[icculus/xz.git] / src / lzma / 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 "open_stdxxx.h"
22 #include <ctype.h>
23
24
25 volatile sig_atomic_t user_abort = false;
26
27 /// Exit status to use. This can be changed with set_exit_status().
28 static enum exit_status_type exit_status = E_SUCCESS;
29
30 /// If we were interrupted by a signal, we store the signal number so that
31 /// we can raise that signal to kill the program when all cleanups have
32 /// been done.
33 static volatile sig_atomic_t exit_signal = 0;
34
35 /// Mask of signals for which have have established a signal handler to set
36 /// user_abort to true.
37 static sigset_t hooked_signals;
38
39 /// signals_block() and signals_unblock() can be called recursively.
40 static size_t signals_block_count = 0;
41
42
43 static void
44 signal_handler(int sig)
45 {
46         exit_signal = sig;
47         user_abort = true;
48         return;
49 }
50
51
52 static void
53 establish_signal_handlers(void)
54 {
55         // List of signals for which we establish the signal handler.
56         static const int sigs[] = {
57                 SIGINT,
58                 SIGTERM,
59 #ifdef SIGHUP
60                 SIGHUP,
61 #endif
62 #ifdef SIGPIPE
63                 SIGPIPE,
64 #endif
65 #ifdef SIGXCPU
66                 SIGXCPU,
67 #endif
68 #ifdef SIGXFSZ
69                 SIGXFSZ,
70 #endif
71         };
72
73         // Mask of the signals for which we have established a signal handler.
74         sigemptyset(&hooked_signals);
75         for (size_t i = 0; i < ARRAY_SIZE(sigs); ++i)
76                 sigaddset(&hooked_signals, sigs[i]);
77
78         struct sigaction sa;
79
80         // All the signals that we handle we also blocked while the signal
81         // handler runs.
82         sa.sa_mask = hooked_signals;
83
84         // Don't set SA_RESTART, because we want EINTR so that we can check
85         // for user_abort and cleanup before exiting. We block the signals
86         // for which we have established a handler when we don't want EINTR.
87         sa.sa_flags = 0;
88         sa.sa_handler = &signal_handler;
89
90         for (size_t i = 0; i < ARRAY_SIZE(sigs); ++i) {
91                 // If the parent process has left some signals ignored,
92                 // we don't unignore them.
93                 struct sigaction old;
94                 if (sigaction(sigs[i], NULL, &old) == 0
95                                 && old.sa_handler == SIG_IGN)
96                         continue;
97
98                 // Establish the signal handler.
99                 if (sigaction(sigs[i], &sa, NULL))
100                         message_signal_handler();
101         }
102
103         return;
104 }
105
106
107 extern void
108 signals_block(void)
109 {
110         if (signals_block_count++ == 0) {
111                 const int saved_errno = errno;
112                 sigprocmask(SIG_BLOCK, &hooked_signals, NULL);
113                 errno = saved_errno;
114         }
115
116         return;
117 }
118
119
120 extern void
121 signals_unblock(void)
122 {
123         assert(signals_block_count > 0);
124
125         if (--signals_block_count == 0) {
126                 const int saved_errno = errno;
127                 sigprocmask(SIG_UNBLOCK, &hooked_signals, NULL);
128                 errno = saved_errno;
129         }
130
131         return;
132 }
133
134
135 extern void
136 set_exit_status(enum exit_status_type new_status)
137 {
138         assert(new_status == E_WARNING || new_status == E_ERROR);
139
140         if (exit_status != E_ERROR)
141                 exit_status = new_status;
142
143         return;
144 }
145
146
147 extern void
148 my_exit(enum exit_status_type status)
149 {
150         // Close stdout. If something goes wrong, print an error message
151         // to stderr.
152         {
153                 const int ferror_err = ferror(stdout);
154                 const int fclose_err = fclose(stdout);
155                 if (ferror_err || fclose_err) {
156                         // If it was fclose() that failed, we have the reason
157                         // in errno. If only ferror() indicated an error,
158                         // we have no idea what the reason was.
159                         message(V_ERROR, _("Writing to standard output "
160                                         "failed: %s"),
161                                         fclose_err ? strerror(errno)
162                                                 : _("Unknown error"));
163                         status = E_ERROR;
164                 }
165         }
166
167         // Close stderr. If something goes wrong, there's nothing where we
168         // could print an error message. Just set the exit status.
169         {
170                 const int ferror_err = ferror(stderr);
171                 const int fclose_err = fclose(stderr);
172                 if (fclose_err || ferror_err)
173                         status = E_ERROR;
174         }
175
176         // If we have got a signal, raise it to kill the program.
177         const int sig = exit_signal;
178         if (sig != 0) {
179                 struct sigaction sa;
180                 sa.sa_handler = SIG_DFL;
181                 sigfillset(&sa.sa_mask);
182                 sa.sa_flags = 0;
183                 sigaction(sig, &sa, NULL);
184                 raise(exit_signal);
185
186                 // If, for some weird reason, the signal doesn't kill us,
187                 // we safely fall to the exit below.
188         }
189
190         exit(status);
191 }
192
193
194 static const char *
195 read_name(const args_info *args)
196 {
197         // FIXME: Maybe we should have some kind of memory usage limit here
198         // like the tool has for the actual compression and uncompression.
199         // Giving some huge text file with --files0 makes us to read the
200         // whole file in RAM.
201         static char *name = NULL;
202         static size_t size = 256;
203
204         // Allocate the initial buffer. This is never freed, since after it
205         // is no longer needed, the program exits very soon. It is safe to
206         // use xmalloc() and xrealloc() in this function, because while
207         // executing this function, no files are open for writing, and thus
208         // there's no need to cleanup anything before exiting.
209         if (name == NULL)
210                 name = xmalloc(size);
211
212         // Write position in name
213         size_t pos = 0;
214
215         // Read one character at a time into name.
216         while (!user_abort) {
217                 const int c = fgetc(args->files_file);
218
219                 if (ferror(args->files_file)) {
220                         // Take care of EINTR since we have established
221                         // the signal handlers already.
222                         if (errno == EINTR)
223                                 continue;
224
225                         message_error(_("%s: Error reading filenames: %s"),
226                                         args->files_name, strerror(errno));
227                         return NULL;
228                 }
229
230                 if (feof(args->files_file)) {
231                         if (pos != 0)
232                                 message_error(_("%s: Unexpected end of input "
233                                                 "when reading filenames"),
234                                                 args->files_name);
235
236                         return NULL;
237                 }
238
239                 if (c == args->files_delim) {
240                         // We allow consecutive newline (--files) or '\0'
241                         // characters (--files0), and ignore such empty
242                         // filenames.
243                         if (pos == 0)
244                                 continue;
245
246                         // A non-empty name was read. Terminate it with '\0'
247                         // and return it.
248                         name[pos] = '\0';
249                         return name;
250                 }
251
252                 if (c == '\0') {
253                         // A null character was found when using --files,
254                         // which expects plain text input separated with
255                         // newlines.
256                         message_error(_("%s: Null character found when "
257                                         "reading filenames; maybe you meant "
258                                         "to use `--files0' instead "
259                                         "of `--files'?"), args->files_name);
260                         return NULL;
261                 }
262
263                 name[pos++] = c;
264
265                 // Allocate more memory if needed. There must always be space
266                 // at least for one character to allow terminating the string
267                 // with '\0'.
268                 if (pos == size) {
269                         size *= 2;
270                         name = xrealloc(name, size);
271                 }
272         }
273
274         return NULL;
275 }
276
277
278 int
279 main(int argc, char **argv)
280 {
281         // Make sure that stdin, stdout, and and stderr are connected to
282         // a valid file descriptor. Exit immediatelly with exit code ERROR
283         // if we cannot make the file descriptors valid. Maybe we should
284         // print an error message, but our stderr could be screwed anyway.
285         open_stdxxx(E_ERROR);
286
287         // This has to be done before calling any liblzma functions.
288         lzma_init();
289
290         // Set up the locale.
291         setlocale(LC_ALL, "");
292
293 #ifdef ENABLE_NLS
294         // Set up the message translations too.
295         bindtextdomain(PACKAGE, LOCALEDIR);
296         textdomain(PACKAGE);
297 #endif
298
299         // Set the program invocation name used in various messages, and
300         // do other message handling related initializations.
301         message_init(argv[0]);
302
303         // Set hardware-dependent default values. These can be overriden
304         // on the command line, thus this must be done before parse_args().
305         hardware_init();
306
307         // Parse the command line arguments and get an array of filenames.
308         // This doesn't return if something is wrong with the command line
309         // arguments. If there are no arguments, one filename ("-") is still
310         // returned to indicate stdin.
311         args_info args;
312         args_parse(&args, argc, argv);
313
314         // Tell the message handling code how many input files there are if
315         // we know it. This way the progress indicator can show it.
316         if (args.files_name != NULL)
317                 message_set_files(0);
318         else
319                 message_set_files(args.arg_count);
320
321         // Refuse to write compressed data to standard output if it is
322         // a terminal and --force wasn't used.
323         if (opt_mode == MODE_COMPRESS) {
324                 if (opt_stdout || (args.arg_count == 1
325                                 && strcmp(args.arg_names[0], "-") == 0)) {
326                         if (is_tty_stdout()) {
327                                 message_try_help();
328                                 my_exit(E_ERROR);
329                         }
330                 }
331         }
332
333         if (opt_mode == MODE_LIST) {
334                 message_fatal("--list is not implemented yet.");
335         }
336
337         // Hook the signal handlers. We don't need these before we start
338         // the actual action, so this is done after parsing the command
339         // line arguments.
340         establish_signal_handlers();
341
342         // Process the files given on the command line. Note that if no names
343         // were given, parse_args() gave us a fake "-" filename.
344         for (size_t i = 0; i < args.arg_count && !user_abort; ++i) {
345                 if (strcmp("-", args.arg_names[i]) == 0) {
346                         // Processing from stdin to stdout. Unless --force
347                         // was used, check that we aren't writing compressed
348                         // data to a terminal or reading it from terminal.
349                         if (!opt_force) {
350                                 if (opt_mode == MODE_COMPRESS) {
351                                         if (is_tty_stdout())
352                                                 continue;
353                                 } else if (is_tty_stdin()) {
354                                         continue;
355                                 }
356                         }
357
358                         // It doesn't make sense to compress data from stdin
359                         // if we are supposed to read filenames from stdin
360                         // too (enabled with --files or --files0).
361                         if (args.files_name == stdin_filename) {
362                                 message_error(_("Cannot read data from "
363                                                 "standard input when "
364                                                 "reading filenames "
365                                                 "from standard input"));
366                                 continue;
367                         }
368
369                         // Replace the "-" with a special pointer, which is
370                         // recognized by process_file() and other things.
371                         // This way error messages get a proper filename
372                         // string and the code still knows that it is
373                         // handling the special case of stdin.
374                         args.arg_names[i] = (char *)stdin_filename;
375                 }
376
377                 // Do the actual compression or uncompression.
378                 process_file(args.arg_names[i]);
379         }
380
381         // If --files or --files0 was used, process the filenames from the
382         // given file or stdin. Note that here we don't consider "-" to
383         // indicate stdin like we do with the command line arguments.
384         if (args.files_name != NULL) {
385                 // read_name() checks for user_abort so we don't need to
386                 // check it as loop termination condition.
387                 while (true) {
388                         const char *name = read_name(&args);
389                         if (name == NULL)
390                                 break;
391
392                         // read_name() doesn't return empty names.
393                         assert(name[0] != '\0');
394                         process_file(name);
395                 }
396
397                 if (args.files_name != stdin_filename)
398                         (void)fclose(args.files_file);
399         }
400
401         my_exit(exit_status);
402 }