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