1207753Smm///////////////////////////////////////////////////////////////////////////////
2207753Smm//
3207753Smm/// \file       main.c
4207753Smm/// \brief      main()
5207753Smm//
6207753Smm//  Author:     Lasse Collin
7207753Smm//
8207753Smm//  This file has been put into the public domain.
9207753Smm//  You can do whatever you want with this file.
10207753Smm//
11207753Smm///////////////////////////////////////////////////////////////////////////////
12207753Smm
13207753Smm#include "private.h"
14207753Smm#include <ctype.h>
15207753Smm
16207753Smm/// Exit status to use. This can be changed with set_exit_status().
17207753Smmstatic enum exit_status_type exit_status = E_SUCCESS;
18207753Smm
19213700Smm#if defined(_WIN32) && !defined(__CYGWIN__)
20213700Smm/// exit_status has to be protected with a critical section due to
21213700Smm/// how "signal handling" is done on Windows. See signals.c for details.
22213700Smmstatic CRITICAL_SECTION exit_status_cs;
23213700Smm#endif
24213700Smm
25207753Smm/// True if --no-warn is specified. When this is true, we don't set
26207753Smm/// the exit status to E_WARNING when something worth a warning happens.
27207753Smmstatic bool no_warn = false;
28207753Smm
29207753Smm
30207753Smmextern void
31207753Smmset_exit_status(enum exit_status_type new_status)
32207753Smm{
33207753Smm	assert(new_status == E_WARNING || new_status == E_ERROR);
34207753Smm
35213700Smm#if defined(_WIN32) && !defined(__CYGWIN__)
36213700Smm	EnterCriticalSection(&exit_status_cs);
37213700Smm#endif
38213700Smm
39207753Smm	if (exit_status != E_ERROR)
40207753Smm		exit_status = new_status;
41207753Smm
42213700Smm#if defined(_WIN32) && !defined(__CYGWIN__)
43213700Smm	LeaveCriticalSection(&exit_status_cs);
44213700Smm#endif
45213700Smm
46207753Smm	return;
47207753Smm}
48207753Smm
49207753Smm
50207753Smmextern void
51207753Smmset_exit_no_warn(void)
52207753Smm{
53207753Smm	no_warn = true;
54207753Smm	return;
55207753Smm}
56207753Smm
57207753Smm
58207753Smmstatic const char *
59207753Smmread_name(const args_info *args)
60207753Smm{
61207753Smm	// FIXME: Maybe we should have some kind of memory usage limit here
62207753Smm	// like the tool has for the actual compression and decompression.
63207753Smm	// Giving some huge text file with --files0 makes us to read the
64207753Smm	// whole file in RAM.
65207753Smm	static char *name = NULL;
66207753Smm	static size_t size = 256;
67207753Smm
68207753Smm	// Allocate the initial buffer. This is never freed, since after it
69207753Smm	// is no longer needed, the program exits very soon. It is safe to
70207753Smm	// use xmalloc() and xrealloc() in this function, because while
71207753Smm	// executing this function, no files are open for writing, and thus
72207753Smm	// there's no need to cleanup anything before exiting.
73207753Smm	if (name == NULL)
74207753Smm		name = xmalloc(size);
75207753Smm
76207753Smm	// Write position in name
77207753Smm	size_t pos = 0;
78207753Smm
79207753Smm	// Read one character at a time into name.
80207753Smm	while (!user_abort) {
81207753Smm		const int c = fgetc(args->files_file);
82207753Smm
83207753Smm		if (ferror(args->files_file)) {
84207753Smm			// Take care of EINTR since we have established
85207753Smm			// the signal handlers already.
86207753Smm			if (errno == EINTR)
87207753Smm				continue;
88207753Smm
89207753Smm			message_error(_("%s: Error reading filenames: %s"),
90207753Smm					args->files_name, strerror(errno));
91207753Smm			return NULL;
92207753Smm		}
93207753Smm
94207753Smm		if (feof(args->files_file)) {
95207753Smm			if (pos != 0)
96207753Smm				message_error(_("%s: Unexpected end of input "
97207753Smm						"when reading filenames"),
98207753Smm						args->files_name);
99207753Smm
100207753Smm			return NULL;
101207753Smm		}
102207753Smm
103207753Smm		if (c == args->files_delim) {
104207753Smm			// We allow consecutive newline (--files) or '\0'
105207753Smm			// characters (--files0), and ignore such empty
106207753Smm			// filenames.
107207753Smm			if (pos == 0)
108207753Smm				continue;
109207753Smm
110207753Smm			// A non-empty name was read. Terminate it with '\0'
111207753Smm			// and return it.
112207753Smm			name[pos] = '\0';
113207753Smm			return name;
114207753Smm		}
115207753Smm
116207753Smm		if (c == '\0') {
117207753Smm			// A null character was found when using --files,
118207753Smm			// which expects plain text input separated with
119207753Smm			// newlines.
120207753Smm			message_error(_("%s: Null character found when "
121207753Smm					"reading filenames; maybe you meant "
122207753Smm					"to use `--files0' instead "
123207753Smm					"of `--files'?"), args->files_name);
124207753Smm			return NULL;
125207753Smm		}
126207753Smm
127207753Smm		name[pos++] = c;
128207753Smm
129207753Smm		// Allocate more memory if needed. There must always be space
130207753Smm		// at least for one character to allow terminating the string
131207753Smm		// with '\0'.
132207753Smm		if (pos == size) {
133207753Smm			size *= 2;
134207753Smm			name = xrealloc(name, size);
135207753Smm		}
136207753Smm	}
137207753Smm
138207753Smm	return NULL;
139207753Smm}
140207753Smm
141207753Smm
142207753Smmint
143207753Smmmain(int argc, char **argv)
144207753Smm{
145213700Smm#if defined(_WIN32) && !defined(__CYGWIN__)
146213700Smm	InitializeCriticalSection(&exit_status_cs);
147213700Smm#endif
148213700Smm
149207753Smm	// Set up the progname variable.
150207753Smm	tuklib_progname_init(argv);
151207753Smm
152207753Smm	// Initialize the file I/O. This makes sure that
153207753Smm	// stdin, stdout, and stderr are something valid.
154207753Smm	io_init();
155207753Smm
156207753Smm	// Set up the locale and message translations.
157207753Smm	tuklib_gettext_init(PACKAGE, LOCALEDIR);
158207753Smm
159207753Smm	// Initialize handling of error/warning/other messages.
160207753Smm	message_init();
161207753Smm
162207753Smm	// Set hardware-dependent default values. These can be overriden
163207753Smm	// on the command line, thus this must be done before args_parse().
164207753Smm	hardware_init();
165207753Smm
166207753Smm	// Parse the command line arguments and get an array of filenames.
167207753Smm	// This doesn't return if something is wrong with the command line
168207753Smm	// arguments. If there are no arguments, one filename ("-") is still
169207753Smm	// returned to indicate stdin.
170207753Smm	args_info args;
171207753Smm	args_parse(&args, argc, argv);
172207753Smm
173207753Smm	if (opt_mode != MODE_LIST && opt_robot)
174207753Smm		message_fatal(_("Compression and decompression with --robot "
175207753Smm			"are not supported yet."));
176207753Smm
177207753Smm	// Tell the message handling code how many input files there are if
178207753Smm	// we know it. This way the progress indicator can show it.
179207753Smm	if (args.files_name != NULL)
180207753Smm		message_set_files(0);
181207753Smm	else
182207753Smm		message_set_files(args.arg_count);
183207753Smm
184207753Smm	// Refuse to write compressed data to standard output if it is
185207753Smm	// a terminal.
186207753Smm	if (opt_mode == MODE_COMPRESS) {
187207753Smm		if (opt_stdout || (args.arg_count == 1
188207753Smm				&& strcmp(args.arg_names[0], "-") == 0)) {
189207753Smm			if (is_tty_stdout()) {
190207753Smm				message_try_help();
191207753Smm				tuklib_exit(E_ERROR, E_ERROR, false);
192207753Smm			}
193207753Smm		}
194207753Smm	}
195207753Smm
196207753Smm	// Set up the signal handlers. We don't need these before we
197207753Smm	// start the actual action and not in --list mode, so this is
198207753Smm	// done after parsing the command line arguments.
199207753Smm	//
200207753Smm	// It's good to keep signal handlers in normal compression and
201207753Smm	// decompression modes even when only writing to stdout, because
202207753Smm	// we might need to restore O_APPEND flag on stdout before exiting.
203207753Smm	// In --test mode, signal handlers aren't really needed, but let's
204207753Smm	// keep them there for consistency with normal decompression.
205207753Smm	if (opt_mode != MODE_LIST)
206207753Smm		signals_init();
207207753Smm
208312518Sdelphij#ifdef ENABLE_SANDBOX
209312518Sdelphij	// Set a flag that sandboxing is allowed if all these are true:
210312518Sdelphij	//   - --files or --files0 wasn't used.
211312518Sdelphij	//   - There is exactly one input file or we are reading from stdin.
212312518Sdelphij	//   - We won't create any files: output goes to stdout or --test
213312518Sdelphij	//     or --list was used. Note that --test implies opt_stdout = true
214312518Sdelphij	//     but --list doesn't.
215312518Sdelphij	//
216312518Sdelphij	// This is obviously not ideal but it was easy to implement and
217312518Sdelphij	// it covers the most common use cases.
218312518Sdelphij	//
219312518Sdelphij	// TODO: Make sandboxing work for other situations too.
220312518Sdelphij	if (args.files_name == NULL && args.arg_count == 1
221312518Sdelphij			&& (opt_stdout || strcmp("-", args.arg_names[0]) == 0
222312518Sdelphij				|| opt_mode == MODE_LIST))
223312518Sdelphij		io_allow_sandbox();
224312518Sdelphij#endif
225312518Sdelphij
226207753Smm	// coder_run() handles compression, decompression, and testing.
227207753Smm	// list_file() is for --list.
228312518Sdelphij	void (*run)(const char *filename) = &coder_run;
229312518Sdelphij#ifdef HAVE_DECODERS
230312518Sdelphij	if (opt_mode == MODE_LIST)
231312518Sdelphij		run = &list_file;
232312518Sdelphij#endif
233207753Smm
234207753Smm	// Process the files given on the command line. Note that if no names
235207753Smm	// were given, args_parse() gave us a fake "-" filename.
236292588Sdelphij	for (unsigned i = 0; i < args.arg_count && !user_abort; ++i) {
237207753Smm		if (strcmp("-", args.arg_names[i]) == 0) {
238207753Smm			// Processing from stdin to stdout. Check that we
239207753Smm			// aren't writing compressed data to a terminal or
240207753Smm			// reading it from a terminal.
241207753Smm			if (opt_mode == MODE_COMPRESS) {
242207753Smm				if (is_tty_stdout())
243207753Smm					continue;
244207753Smm			} else if (is_tty_stdin()) {
245207753Smm				continue;
246207753Smm			}
247207753Smm
248207753Smm			// It doesn't make sense to compress data from stdin
249207753Smm			// if we are supposed to read filenames from stdin
250207753Smm			// too (enabled with --files or --files0).
251207753Smm			if (args.files_name == stdin_filename) {
252207753Smm				message_error(_("Cannot read data from "
253207753Smm						"standard input when "
254207753Smm						"reading filenames "
255207753Smm						"from standard input"));
256207753Smm				continue;
257207753Smm			}
258207753Smm
259207753Smm			// Replace the "-" with a special pointer, which is
260207753Smm			// recognized by coder_run() and other things.
261207753Smm			// This way error messages get a proper filename
262207753Smm			// string and the code still knows that it is
263207753Smm			// handling the special case of stdin.
264207753Smm			args.arg_names[i] = (char *)stdin_filename;
265207753Smm		}
266207753Smm
267207753Smm		// Do the actual compression or decompression.
268207753Smm		run(args.arg_names[i]);
269207753Smm	}
270207753Smm
271207753Smm	// If --files or --files0 was used, process the filenames from the
272207753Smm	// given file or stdin. Note that here we don't consider "-" to
273207753Smm	// indicate stdin like we do with the command line arguments.
274207753Smm	if (args.files_name != NULL) {
275207753Smm		// read_name() checks for user_abort so we don't need to
276207753Smm		// check it as loop termination condition.
277207753Smm		while (true) {
278207753Smm			const char *name = read_name(&args);
279207753Smm			if (name == NULL)
280207753Smm				break;
281207753Smm
282207753Smm			// read_name() doesn't return empty names.
283207753Smm			assert(name[0] != '\0');
284207753Smm			run(name);
285207753Smm		}
286207753Smm
287207753Smm		if (args.files_name != stdin_filename)
288207753Smm			(void)fclose(args.files_file);
289207753Smm	}
290207753Smm
291312518Sdelphij#ifdef HAVE_DECODERS
292207753Smm	// All files have now been handled. If in --list mode, display
293207753Smm	// the totals before exiting. We don't have signal handlers
294207753Smm	// enabled in --list mode, so we don't need to check user_abort.
295207753Smm	if (opt_mode == MODE_LIST) {
296207753Smm		assert(!user_abort);
297207753Smm		list_totals();
298207753Smm	}
299312518Sdelphij#endif
300207753Smm
301292588Sdelphij#ifndef NDEBUG
302292588Sdelphij	coder_free();
303292588Sdelphij	args_free();
304292588Sdelphij#endif
305292588Sdelphij
306207753Smm	// If we have got a signal, raise it to kill the program instead
307207753Smm	// of calling tuklib_exit().
308207753Smm	signals_exit();
309207753Smm
310213700Smm	// Make a local copy of exit_status to keep the Windows code
311213700Smm	// thread safe. At this point it is fine if we miss the user
312213700Smm	// pressing C-c and don't set the exit_status to E_ERROR on
313213700Smm	// Windows.
314213700Smm#if defined(_WIN32) && !defined(__CYGWIN__)
315213700Smm	EnterCriticalSection(&exit_status_cs);
316213700Smm#endif
317213700Smm
318213700Smm	enum exit_status_type es = exit_status;
319213700Smm
320213700Smm#if defined(_WIN32) && !defined(__CYGWIN__)
321213700Smm	LeaveCriticalSection(&exit_status_cs);
322213700Smm#endif
323213700Smm
324207753Smm	// Suppress the exit status indicating a warning if --no-warn
325207753Smm	// was specified.
326213700Smm	if (es == E_WARNING && no_warn)
327213700Smm		es = E_SUCCESS;
328207753Smm
329213700Smm	tuklib_exit(es, E_ERROR, message_verbosity_get() != V_SILENT);
330207753Smm}
331