]> CyberLeo.Net >> Repos - FreeBSD/releng/8.1.git/blob - contrib/xz/src/xz/main.c
Copy stable/8 to releng/8.1 in preparation for 8.1-RC1.
[FreeBSD/releng/8.1.git] / contrib / xz / 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 static const char *
46 read_name(const args_info *args)
47 {
48         // FIXME: Maybe we should have some kind of memory usage limit here
49         // like the tool has for the actual compression and decompression.
50         // Giving some huge text file with --files0 makes us to read the
51         // whole file in RAM.
52         static char *name = NULL;
53         static size_t size = 256;
54
55         // Allocate the initial buffer. This is never freed, since after it
56         // is no longer needed, the program exits very soon. It is safe to
57         // use xmalloc() and xrealloc() in this function, because while
58         // executing this function, no files are open for writing, and thus
59         // there's no need to cleanup anything before exiting.
60         if (name == NULL)
61                 name = xmalloc(size);
62
63         // Write position in name
64         size_t pos = 0;
65
66         // Read one character at a time into name.
67         while (!user_abort) {
68                 const int c = fgetc(args->files_file);
69
70                 if (ferror(args->files_file)) {
71                         // Take care of EINTR since we have established
72                         // the signal handlers already.
73                         if (errno == EINTR)
74                                 continue;
75
76                         message_error(_("%s: Error reading filenames: %s"),
77                                         args->files_name, strerror(errno));
78                         return NULL;
79                 }
80
81                 if (feof(args->files_file)) {
82                         if (pos != 0)
83                                 message_error(_("%s: Unexpected end of input "
84                                                 "when reading filenames"),
85                                                 args->files_name);
86
87                         return NULL;
88                 }
89
90                 if (c == args->files_delim) {
91                         // We allow consecutive newline (--files) or '\0'
92                         // characters (--files0), and ignore such empty
93                         // filenames.
94                         if (pos == 0)
95                                 continue;
96
97                         // A non-empty name was read. Terminate it with '\0'
98                         // and return it.
99                         name[pos] = '\0';
100                         return name;
101                 }
102
103                 if (c == '\0') {
104                         // A null character was found when using --files,
105                         // which expects plain text input separated with
106                         // newlines.
107                         message_error(_("%s: Null character found when "
108                                         "reading filenames; maybe you meant "
109                                         "to use `--files0' instead "
110                                         "of `--files'?"), args->files_name);
111                         return NULL;
112                 }
113
114                 name[pos++] = c;
115
116                 // Allocate more memory if needed. There must always be space
117                 // at least for one character to allow terminating the string
118                 // with '\0'.
119                 if (pos == size) {
120                         size *= 2;
121                         name = xrealloc(name, size);
122                 }
123         }
124
125         return NULL;
126 }
127
128
129 int
130 main(int argc, char **argv)
131 {
132         // Set up the progname variable.
133         tuklib_progname_init(argv);
134
135         // Initialize the file I/O. This makes sure that
136         // stdin, stdout, and stderr are something valid.
137         io_init();
138
139         // Set up the locale and message translations.
140         tuklib_gettext_init(PACKAGE, LOCALEDIR);
141
142         // Initialize handling of error/warning/other messages.
143         message_init();
144
145         // Set hardware-dependent default values. These can be overriden
146         // on the command line, thus this must be done before args_parse().
147         hardware_init();
148
149         // Parse the command line arguments and get an array of filenames.
150         // This doesn't return if something is wrong with the command line
151         // arguments. If there are no arguments, one filename ("-") is still
152         // returned to indicate stdin.
153         args_info args;
154         args_parse(&args, argc, argv);
155
156         if (opt_mode != MODE_LIST && opt_robot)
157                 message_fatal(_("Compression and decompression with --robot "
158                         "are not supported yet."));
159
160         // Tell the message handling code how many input files there are if
161         // we know it. This way the progress indicator can show it.
162         if (args.files_name != NULL)
163                 message_set_files(0);
164         else
165                 message_set_files(args.arg_count);
166
167         // Refuse to write compressed data to standard output if it is
168         // a terminal.
169         if (opt_mode == MODE_COMPRESS) {
170                 if (opt_stdout || (args.arg_count == 1
171                                 && strcmp(args.arg_names[0], "-") == 0)) {
172                         if (is_tty_stdout()) {
173                                 message_try_help();
174                                 tuklib_exit(E_ERROR, E_ERROR, false);
175                         }
176                 }
177         }
178
179         // Set up the signal handlers. We don't need these before we
180         // start the actual action and not in --list mode, so this is
181         // done after parsing the command line arguments.
182         //
183         // It's good to keep signal handlers in normal compression and
184         // decompression modes even when only writing to stdout, because
185         // we might need to restore O_APPEND flag on stdout before exiting.
186         // In --test mode, signal handlers aren't really needed, but let's
187         // keep them there for consistency with normal decompression.
188         if (opt_mode != MODE_LIST)
189                 signals_init();
190
191         // coder_run() handles compression, decompression, and testing.
192         // list_file() is for --list.
193         void (*run)(const char *filename) = opt_mode == MODE_LIST
194                          ? &list_file : &coder_run;
195
196         // Process the files given on the command line. Note that if no names
197         // were given, args_parse() gave us a fake "-" filename.
198         for (size_t i = 0; i < args.arg_count && !user_abort; ++i) {
199                 if (strcmp("-", args.arg_names[i]) == 0) {
200                         // Processing from stdin to stdout. Check that we
201                         // aren't writing compressed data to a terminal or
202                         // reading it from a terminal.
203                         if (opt_mode == MODE_COMPRESS) {
204                                 if (is_tty_stdout())
205                                         continue;
206                         } else if (is_tty_stdin()) {
207                                 continue;
208                         }
209
210                         // It doesn't make sense to compress data from stdin
211                         // if we are supposed to read filenames from stdin
212                         // too (enabled with --files or --files0).
213                         if (args.files_name == stdin_filename) {
214                                 message_error(_("Cannot read data from "
215                                                 "standard input when "
216                                                 "reading filenames "
217                                                 "from standard input"));
218                                 continue;
219                         }
220
221                         // Replace the "-" with a special pointer, which is
222                         // recognized by coder_run() and other things.
223                         // This way error messages get a proper filename
224                         // string and the code still knows that it is
225                         // handling the special case of stdin.
226                         args.arg_names[i] = (char *)stdin_filename;
227                 }
228
229                 // Do the actual compression or decompression.
230                 run(args.arg_names[i]);
231         }
232
233         // If --files or --files0 was used, process the filenames from the
234         // given file or stdin. Note that here we don't consider "-" to
235         // indicate stdin like we do with the command line arguments.
236         if (args.files_name != NULL) {
237                 // read_name() checks for user_abort so we don't need to
238                 // check it as loop termination condition.
239                 while (true) {
240                         const char *name = read_name(&args);
241                         if (name == NULL)
242                                 break;
243
244                         // read_name() doesn't return empty names.
245                         assert(name[0] != '\0');
246                         run(name);
247                 }
248
249                 if (args.files_name != stdin_filename)
250                         (void)fclose(args.files_file);
251         }
252
253         // All files have now been handled. If in --list mode, display
254         // the totals before exiting. We don't have signal handlers
255         // enabled in --list mode, so we don't need to check user_abort.
256         if (opt_mode == MODE_LIST) {
257                 assert(!user_abort);
258                 list_totals();
259         }
260
261         // If we have got a signal, raise it to kill the program instead
262         // of calling tuklib_exit().
263         signals_exit();
264
265         // Suppress the exit status indicating a warning if --no-warn
266         // was specified.
267         if (exit_status == E_WARNING && no_warn)
268                 exit_status = E_SUCCESS;
269
270         tuklib_exit(exit_status, E_ERROR,
271                         message_verbosity_get() != V_SILENT);
272 }