]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/xz/src/xz/main.c
Merge ^/vendor/lldb/dist up to its last change, and resolve conflicts.
[FreeBSD/FreeBSD.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 /// Exit status to use. This can be changed with set_exit_status().
17 static enum exit_status_type exit_status = E_SUCCESS;
18
19 #if defined(_WIN32) && !defined(__CYGWIN__)
20 /// exit_status has to be protected with a critical section due to
21 /// how "signal handling" is done on Windows. See signals.c for details.
22 static CRITICAL_SECTION exit_status_cs;
23 #endif
24
25 /// True if --no-warn is specified. When this is true, we don't set
26 /// the exit status to E_WARNING when something worth a warning happens.
27 static bool no_warn = false;
28
29
30 extern void
31 set_exit_status(enum exit_status_type new_status)
32 {
33         assert(new_status == E_WARNING || new_status == E_ERROR);
34
35 #if defined(_WIN32) && !defined(__CYGWIN__)
36         EnterCriticalSection(&exit_status_cs);
37 #endif
38
39         if (exit_status != E_ERROR)
40                 exit_status = new_status;
41
42 #if defined(_WIN32) && !defined(__CYGWIN__)
43         LeaveCriticalSection(&exit_status_cs);
44 #endif
45
46         return;
47 }
48
49
50 extern void
51 set_exit_no_warn(void)
52 {
53         no_warn = true;
54         return;
55 }
56
57
58 static const char *
59 read_name(const args_info *args)
60 {
61         // FIXME: Maybe we should have some kind of memory usage limit here
62         // like the tool has for the actual compression and decompression.
63         // Giving some huge text file with --files0 makes us to read the
64         // whole file in RAM.
65         static char *name = NULL;
66         static size_t size = 256;
67
68         // Allocate the initial buffer. This is never freed, since after it
69         // is no longer needed, the program exits very soon. It is safe to
70         // use xmalloc() and xrealloc() in this function, because while
71         // executing this function, no files are open for writing, and thus
72         // there's no need to cleanup anything before exiting.
73         if (name == NULL)
74                 name = xmalloc(size);
75
76         // Write position in name
77         size_t pos = 0;
78
79         // Read one character at a time into name.
80         while (!user_abort) {
81                 const int c = fgetc(args->files_file);
82
83                 if (ferror(args->files_file)) {
84                         // Take care of EINTR since we have established
85                         // the signal handlers already.
86                         if (errno == EINTR)
87                                 continue;
88
89                         message_error(_("%s: Error reading filenames: %s"),
90                                         args->files_name, strerror(errno));
91                         return NULL;
92                 }
93
94                 if (feof(args->files_file)) {
95                         if (pos != 0)
96                                 message_error(_("%s: Unexpected end of input "
97                                                 "when reading filenames"),
98                                                 args->files_name);
99
100                         return NULL;
101                 }
102
103                 if (c == args->files_delim) {
104                         // We allow consecutive newline (--files) or '\0'
105                         // characters (--files0), and ignore such empty
106                         // filenames.
107                         if (pos == 0)
108                                 continue;
109
110                         // A non-empty name was read. Terminate it with '\0'
111                         // and return it.
112                         name[pos] = '\0';
113                         return name;
114                 }
115
116                 if (c == '\0') {
117                         // A null character was found when using --files,
118                         // which expects plain text input separated with
119                         // newlines.
120                         message_error(_("%s: Null character found when "
121                                         "reading filenames; maybe you meant "
122                                         "to use `--files0' instead "
123                                         "of `--files'?"), args->files_name);
124                         return NULL;
125                 }
126
127                 name[pos++] = c;
128
129                 // Allocate more memory if needed. There must always be space
130                 // at least for one character to allow terminating the string
131                 // with '\0'.
132                 if (pos == size) {
133                         size *= 2;
134                         name = xrealloc(name, size);
135                 }
136         }
137
138         return NULL;
139 }
140
141
142 int
143 main(int argc, char **argv)
144 {
145 #if defined(_WIN32) && !defined(__CYGWIN__)
146         InitializeCriticalSection(&exit_status_cs);
147 #endif
148
149         // Set up the progname variable.
150         tuklib_progname_init(argv);
151
152         // Initialize the file I/O. This makes sure that
153         // stdin, stdout, and stderr are something valid.
154         io_init();
155
156         // Set up the locale and message translations.
157         tuklib_gettext_init(PACKAGE, LOCALEDIR);
158
159         // Initialize handling of error/warning/other messages.
160         message_init();
161
162         // Set hardware-dependent default values. These can be overriden
163         // on the command line, thus this must be done before args_parse().
164         hardware_init();
165
166         // Parse the command line arguments and get an array of filenames.
167         // This doesn't return if something is wrong with the command line
168         // arguments. If there are no arguments, one filename ("-") is still
169         // returned to indicate stdin.
170         args_info args;
171         args_parse(&args, argc, argv);
172
173         if (opt_mode != MODE_LIST && opt_robot)
174                 message_fatal(_("Compression and decompression with --robot "
175                         "are not supported yet."));
176
177         // Tell the message handling code how many input files there are if
178         // we know it. This way the progress indicator can show it.
179         if (args.files_name != NULL)
180                 message_set_files(0);
181         else
182                 message_set_files(args.arg_count);
183
184         // Refuse to write compressed data to standard output if it is
185         // a terminal.
186         if (opt_mode == MODE_COMPRESS) {
187                 if (opt_stdout || (args.arg_count == 1
188                                 && strcmp(args.arg_names[0], "-") == 0)) {
189                         if (is_tty_stdout()) {
190                                 message_try_help();
191                                 tuklib_exit(E_ERROR, E_ERROR, false);
192                         }
193                 }
194         }
195
196         // Set up the signal handlers. We don't need these before we
197         // start the actual action and not in --list mode, so this is
198         // done after parsing the command line arguments.
199         //
200         // It's good to keep signal handlers in normal compression and
201         // decompression modes even when only writing to stdout, because
202         // we might need to restore O_APPEND flag on stdout before exiting.
203         // In --test mode, signal handlers aren't really needed, but let's
204         // keep them there for consistency with normal decompression.
205         if (opt_mode != MODE_LIST)
206                 signals_init();
207
208 #ifdef ENABLE_SANDBOX
209         // Set a flag that sandboxing is allowed if all these are true:
210         //   - --files or --files0 wasn't used.
211         //   - There is exactly one input file or we are reading from stdin.
212         //   - We won't create any files: output goes to stdout or --test
213         //     or --list was used. Note that --test implies opt_stdout = true
214         //     but --list doesn't.
215         //
216         // This is obviously not ideal but it was easy to implement and
217         // it covers the most common use cases.
218         //
219         // TODO: Make sandboxing work for other situations too.
220         if (args.files_name == NULL && args.arg_count == 1
221                         && (opt_stdout || strcmp("-", args.arg_names[0]) == 0
222                                 || opt_mode == MODE_LIST))
223                 io_allow_sandbox();
224 #endif
225
226         // coder_run() handles compression, decompression, and testing.
227         // list_file() is for --list.
228         void (*run)(const char *filename) = &coder_run;
229 #ifdef HAVE_DECODERS
230         if (opt_mode == MODE_LIST)
231                 run = &list_file;
232 #endif
233
234         // Process the files given on the command line. Note that if no names
235         // were given, args_parse() gave us a fake "-" filename.
236         for (unsigned i = 0; i < args.arg_count && !user_abort; ++i) {
237                 if (strcmp("-", args.arg_names[i]) == 0) {
238                         // Processing from stdin to stdout. Check that we
239                         // aren't writing compressed data to a terminal or
240                         // reading it from a terminal.
241                         if (opt_mode == MODE_COMPRESS) {
242                                 if (is_tty_stdout())
243                                         continue;
244                         } else if (is_tty_stdin()) {
245                                 continue;
246                         }
247
248                         // It doesn't make sense to compress data from stdin
249                         // if we are supposed to read filenames from stdin
250                         // too (enabled with --files or --files0).
251                         if (args.files_name == stdin_filename) {
252                                 message_error(_("Cannot read data from "
253                                                 "standard input when "
254                                                 "reading filenames "
255                                                 "from standard input"));
256                                 continue;
257                         }
258
259                         // Replace the "-" with a special pointer, which is
260                         // recognized by coder_run() and other things.
261                         // This way error messages get a proper filename
262                         // string and the code still knows that it is
263                         // handling the special case of stdin.
264                         args.arg_names[i] = (char *)stdin_filename;
265                 }
266
267                 // Do the actual compression or decompression.
268                 run(args.arg_names[i]);
269         }
270
271         // If --files or --files0 was used, process the filenames from the
272         // given file or stdin. Note that here we don't consider "-" to
273         // indicate stdin like we do with the command line arguments.
274         if (args.files_name != NULL) {
275                 // read_name() checks for user_abort so we don't need to
276                 // check it as loop termination condition.
277                 while (true) {
278                         const char *name = read_name(&args);
279                         if (name == NULL)
280                                 break;
281
282                         // read_name() doesn't return empty names.
283                         assert(name[0] != '\0');
284                         run(name);
285                 }
286
287                 if (args.files_name != stdin_filename)
288                         (void)fclose(args.files_file);
289         }
290
291 #ifdef HAVE_DECODERS
292         // All files have now been handled. If in --list mode, display
293         // the totals before exiting. We don't have signal handlers
294         // enabled in --list mode, so we don't need to check user_abort.
295         if (opt_mode == MODE_LIST) {
296                 assert(!user_abort);
297                 list_totals();
298         }
299 #endif
300
301 #ifndef NDEBUG
302         coder_free();
303         args_free();
304 #endif
305
306         // If we have got a signal, raise it to kill the program instead
307         // of calling tuklib_exit().
308         signals_exit();
309
310         // Make a local copy of exit_status to keep the Windows code
311         // thread safe. At this point it is fine if we miss the user
312         // pressing C-c and don't set the exit_status to E_ERROR on
313         // Windows.
314 #if defined(_WIN32) && !defined(__CYGWIN__)
315         EnterCriticalSection(&exit_status_cs);
316 #endif
317
318         enum exit_status_type es = exit_status;
319
320 #if defined(_WIN32) && !defined(__CYGWIN__)
321         LeaveCriticalSection(&exit_status_cs);
322 #endif
323
324         // Suppress the exit status indicating a warning if --no-warn
325         // was specified.
326         if (es == E_WARNING && no_warn)
327                 es = E_SUCCESS;
328
329         tuklib_exit(es, E_ERROR, message_verbosity_get() != V_SILENT);
330 }