xref: /dragonfly/contrib/xz/src/xz/main.c (revision 0db87cb7)
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 	// coder_run() handles compression, decompression, and testing.
209 	// list_file() is for --list.
210 	void (*run)(const char *filename) = opt_mode == MODE_LIST
211 			 ? &list_file : &coder_run;
212 
213 	// Process the files given on the command line. Note that if no names
214 	// were given, args_parse() gave us a fake "-" filename.
215 	for (size_t i = 0; i < args.arg_count && !user_abort; ++i) {
216 		if (strcmp("-", args.arg_names[i]) == 0) {
217 			// Processing from stdin to stdout. Check that we
218 			// aren't writing compressed data to a terminal or
219 			// reading it from a terminal.
220 			if (opt_mode == MODE_COMPRESS) {
221 				if (is_tty_stdout())
222 					continue;
223 			} else if (is_tty_stdin()) {
224 				continue;
225 			}
226 
227 			// It doesn't make sense to compress data from stdin
228 			// if we are supposed to read filenames from stdin
229 			// too (enabled with --files or --files0).
230 			if (args.files_name == stdin_filename) {
231 				message_error(_("Cannot read data from "
232 						"standard input when "
233 						"reading filenames "
234 						"from standard input"));
235 				continue;
236 			}
237 
238 			// Replace the "-" with a special pointer, which is
239 			// recognized by coder_run() and other things.
240 			// This way error messages get a proper filename
241 			// string and the code still knows that it is
242 			// handling the special case of stdin.
243 			args.arg_names[i] = (char *)stdin_filename;
244 		}
245 
246 		// Do the actual compression or decompression.
247 		run(args.arg_names[i]);
248 	}
249 
250 	// If --files or --files0 was used, process the filenames from the
251 	// given file or stdin. Note that here we don't consider "-" to
252 	// indicate stdin like we do with the command line arguments.
253 	if (args.files_name != NULL) {
254 		// read_name() checks for user_abort so we don't need to
255 		// check it as loop termination condition.
256 		while (true) {
257 			const char *name = read_name(&args);
258 			if (name == NULL)
259 				break;
260 
261 			// read_name() doesn't return empty names.
262 			assert(name[0] != '\0');
263 			run(name);
264 		}
265 
266 		if (args.files_name != stdin_filename)
267 			(void)fclose(args.files_file);
268 	}
269 
270 	// All files have now been handled. If in --list mode, display
271 	// the totals before exiting. We don't have signal handlers
272 	// enabled in --list mode, so we don't need to check user_abort.
273 	if (opt_mode == MODE_LIST) {
274 		assert(!user_abort);
275 		list_totals();
276 	}
277 
278 	// If we have got a signal, raise it to kill the program instead
279 	// of calling tuklib_exit().
280 	signals_exit();
281 
282 	// Make a local copy of exit_status to keep the Windows code
283 	// thread safe. At this point it is fine if we miss the user
284 	// pressing C-c and don't set the exit_status to E_ERROR on
285 	// Windows.
286 #if defined(_WIN32) && !defined(__CYGWIN__)
287 	EnterCriticalSection(&exit_status_cs);
288 #endif
289 
290 	enum exit_status_type es = exit_status;
291 
292 #if defined(_WIN32) && !defined(__CYGWIN__)
293 	LeaveCriticalSection(&exit_status_cs);
294 #endif
295 
296 	// Suppress the exit status indicating a warning if --no-warn
297 	// was specified.
298 	if (es == E_WARNING && no_warn)
299 		es = E_SUCCESS;
300 
301 	tuklib_exit(es, E_ERROR, message_verbosity_get() != V_SILENT);
302 }
303