2007-12-08 23:42:33 +01:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
/// \file main.c
|
|
|
|
/// \brief main()
|
|
|
|
//
|
|
|
|
// Copyright (C) 2007 Lasse Collin
|
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU Lesser General Public
|
|
|
|
// License as published by the Free Software Foundation; either
|
|
|
|
// version 2.1 of the License, or (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
// Lesser General Public License for more details.
|
|
|
|
//
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
#include "private.h"
|
|
|
|
#include "open_stdxxx.h"
|
|
|
|
#include <ctype.h>
|
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
|
|
|
|
volatile sig_atomic_t user_abort = false;
|
|
|
|
|
|
|
|
/// Exit status to use. This can be changed with set_exit_status().
|
|
|
|
static enum exit_status_type exit_status = E_SUCCESS;
|
|
|
|
|
|
|
|
/// If we were interrupted by a signal, we store the signal number so that
|
|
|
|
/// we can raise that signal to kill the program when all cleanups have
|
|
|
|
/// been done.
|
|
|
|
static volatile sig_atomic_t exit_signal = 0;
|
|
|
|
|
|
|
|
/// Mask of signals for which have have established a signal handler to set
|
|
|
|
/// user_abort to true.
|
|
|
|
static sigset_t hooked_signals;
|
|
|
|
|
|
|
|
/// signals_block() and signals_unblock() can be called recursively.
|
|
|
|
static size_t signals_block_count = 0;
|
2007-12-08 23:42:33 +01:00
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
signal_handler(int sig)
|
|
|
|
{
|
|
|
|
exit_signal = sig;
|
2008-11-19 19:46:52 +01:00
|
|
|
user_abort = true;
|
2007-12-08 23:42:33 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
establish_signal_handlers(void)
|
|
|
|
{
|
2008-11-19 19:46:52 +01:00
|
|
|
// List of signals for which we establish the signal handler.
|
2007-12-08 23:42:33 +01:00
|
|
|
static const int sigs[] = {
|
|
|
|
SIGINT,
|
|
|
|
SIGTERM,
|
2008-11-19 19:46:52 +01:00
|
|
|
#ifdef SIGHUP
|
|
|
|
SIGHUP,
|
|
|
|
#endif
|
|
|
|
#ifdef SIGPIPE
|
|
|
|
SIGPIPE,
|
|
|
|
#endif
|
|
|
|
#ifdef SIGXCPU
|
2007-12-08 23:42:33 +01:00
|
|
|
SIGXCPU,
|
2008-11-19 19:46:52 +01:00
|
|
|
#endif
|
|
|
|
#ifdef SIGXFSZ
|
2007-12-08 23:42:33 +01:00
|
|
|
SIGXFSZ,
|
2008-11-19 19:46:52 +01:00
|
|
|
#endif
|
2007-12-08 23:42:33 +01:00
|
|
|
};
|
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
// Mask of the signals for which we have established a signal handler.
|
|
|
|
sigemptyset(&hooked_signals);
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(sigs); ++i)
|
|
|
|
sigaddset(&hooked_signals, sigs[i]);
|
|
|
|
|
|
|
|
struct sigaction sa;
|
|
|
|
|
|
|
|
// All the signals that we handle we also blocked while the signal
|
|
|
|
// handler runs.
|
|
|
|
sa.sa_mask = hooked_signals;
|
|
|
|
|
|
|
|
// Don't set SA_RESTART, because we want EINTR so that we can check
|
|
|
|
// for user_abort and cleanup before exiting. We block the signals
|
|
|
|
// for which we have established a handler when we don't want EINTR.
|
|
|
|
sa.sa_flags = 0;
|
|
|
|
sa.sa_handler = &signal_handler;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(sigs); ++i) {
|
|
|
|
// If the parent process has left some signals ignored,
|
|
|
|
// we don't unignore them.
|
|
|
|
struct sigaction old;
|
|
|
|
if (sigaction(sigs[i], NULL, &old) == 0
|
|
|
|
&& old.sa_handler == SIG_IGN)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Establish the signal handler.
|
|
|
|
if (sigaction(sigs[i], &sa, NULL))
|
|
|
|
message_signal_handler();
|
2007-12-08 23:42:33 +01:00
|
|
|
}
|
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
return;
|
2007-12-08 23:42:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
extern void
|
|
|
|
signals_block(void)
|
2007-12-08 23:42:33 +01:00
|
|
|
{
|
2008-11-19 19:46:52 +01:00
|
|
|
if (signals_block_count++ == 0) {
|
|
|
|
const int saved_errno = errno;
|
|
|
|
sigprocmask(SIG_BLOCK, &hooked_signals, NULL);
|
|
|
|
errno = saved_errno;
|
2007-12-08 23:42:33 +01:00
|
|
|
}
|
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
return;
|
2007-12-08 23:42:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
extern void
|
|
|
|
signals_unblock(void)
|
2007-12-08 23:42:33 +01:00
|
|
|
{
|
2008-11-19 19:46:52 +01:00
|
|
|
assert(signals_block_count > 0);
|
|
|
|
|
|
|
|
if (--signals_block_count == 0) {
|
|
|
|
const int saved_errno = errno;
|
|
|
|
sigprocmask(SIG_UNBLOCK, &hooked_signals, NULL);
|
|
|
|
errno = saved_errno;
|
2007-12-08 23:42:33 +01:00
|
|
|
}
|
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
return;
|
2007-12-08 23:42:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
extern void
|
|
|
|
set_exit_status(enum exit_status_type new_status)
|
2007-12-08 23:42:33 +01:00
|
|
|
{
|
2008-11-19 19:46:52 +01:00
|
|
|
assert(new_status == E_WARNING || new_status == E_ERROR);
|
|
|
|
|
|
|
|
if (exit_status != E_ERROR)
|
|
|
|
exit_status = new_status;
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
extern void
|
|
|
|
my_exit(enum exit_status_type status)
|
|
|
|
{
|
|
|
|
// Close stdout. If something goes wrong, print an error message
|
|
|
|
// to stderr.
|
|
|
|
{
|
|
|
|
const int ferror_err = ferror(stdout);
|
|
|
|
const int fclose_err = fclose(stdout);
|
|
|
|
if (ferror_err || fclose_err) {
|
|
|
|
// If it was fclose() that failed, we have the reason
|
|
|
|
// in errno. If only ferror() indicated an error,
|
|
|
|
// we have no idea what the reason was.
|
|
|
|
message(V_ERROR, _("Writing to standard output "
|
|
|
|
"failed: %s"),
|
|
|
|
fclose_err ? strerror(errno)
|
|
|
|
: _("Unknown error"));
|
|
|
|
status = E_ERROR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close stderr. If something goes wrong, there's nothing where we
|
|
|
|
// could print an error message. Just set the exit status.
|
|
|
|
{
|
|
|
|
const int ferror_err = ferror(stderr);
|
|
|
|
const int fclose_err = fclose(stderr);
|
|
|
|
if (fclose_err || ferror_err)
|
|
|
|
status = E_ERROR;
|
2007-12-08 23:42:33 +01:00
|
|
|
}
|
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
// If we have got a signal, raise it to kill the program.
|
|
|
|
const int sig = exit_signal;
|
|
|
|
if (sig != 0) {
|
|
|
|
struct sigaction sa;
|
|
|
|
sa.sa_handler = SIG_DFL;
|
|
|
|
sigfillset(&sa.sa_mask);
|
|
|
|
sa.sa_flags = 0;
|
|
|
|
sigaction(sig, &sa, NULL);
|
|
|
|
raise(exit_signal);
|
2007-12-08 23:42:33 +01:00
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
// If, for some weird reason, the signal doesn't kill us,
|
|
|
|
// we safely fall to the exit below.
|
|
|
|
}
|
|
|
|
|
|
|
|
exit(status);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 uncompression.
|
|
|
|
// 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));
|
2007-12-08 23:42:33 +01:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
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;
|
|
|
|
}
|
2007-12-08 23:42:33 +01:00
|
|
|
|
|
|
|
name[pos++] = c;
|
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
// Allocate more memory if needed. There must always be space
|
|
|
|
// at least for one character to allow terminating the string
|
|
|
|
// with '\0'.
|
2007-12-08 23:42:33 +01:00
|
|
|
if (pos == size) {
|
|
|
|
size *= 2;
|
2008-11-19 19:46:52 +01:00
|
|
|
name = xrealloc(name, size);
|
2007-12-08 23:42:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
return NULL;
|
2007-12-08 23:42:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
main(int argc, char **argv)
|
|
|
|
{
|
|
|
|
// Make sure that stdin, stdout, and and stderr are connected to
|
|
|
|
// a valid file descriptor. Exit immediatelly with exit code ERROR
|
|
|
|
// if we cannot make the file descriptors valid. Maybe we should
|
|
|
|
// print an error message, but our stderr could be screwed anyway.
|
2008-11-19 19:46:52 +01:00
|
|
|
open_stdxxx(E_ERROR);
|
2007-12-08 23:42:33 +01:00
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
// Set up the locale.
|
|
|
|
setlocale(LC_ALL, "");
|
|
|
|
|
|
|
|
#ifdef ENABLE_NLS
|
|
|
|
// Set up the message translations too.
|
2007-12-08 23:42:33 +01:00
|
|
|
bindtextdomain(PACKAGE, LOCALEDIR);
|
|
|
|
textdomain(PACKAGE);
|
2008-11-19 19:46:52 +01:00
|
|
|
#endif
|
|
|
|
|
|
|
|
// Set the program invocation name used in various messages, and
|
|
|
|
// do other message handling related initializations.
|
|
|
|
message_init(argv[0]);
|
2007-12-08 23:42:33 +01:00
|
|
|
|
|
|
|
// Set hardware-dependent default values. These can be overriden
|
|
|
|
// on the command line, thus this must be done before parse_args().
|
|
|
|
hardware_init();
|
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
// 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);
|
|
|
|
|
|
|
|
// 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);
|
2007-12-08 23:42:33 +01:00
|
|
|
else
|
2008-11-19 19:46:52 +01:00
|
|
|
message_set_files(args.arg_count);
|
|
|
|
|
|
|
|
// Refuse to write compressed data to standard output if it is
|
|
|
|
// a terminal and --force wasn't used.
|
|
|
|
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();
|
|
|
|
my_exit(E_ERROR);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2007-12-08 23:42:33 +01:00
|
|
|
|
|
|
|
if (opt_mode == MODE_LIST) {
|
2008-11-19 19:46:52 +01:00
|
|
|
message_fatal("--list is not implemented yet.");
|
2007-12-08 23:42:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Hook the signal handlers. We don't need these before we start
|
|
|
|
// the actual action, so this is done after parsing the command
|
|
|
|
// line arguments.
|
|
|
|
establish_signal_handlers();
|
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
// Process the files given on the command line. Note that if no names
|
|
|
|
// were given, parse_args() 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. Unless --force
|
|
|
|
// was used, check that we aren't writing compressed
|
|
|
|
// data to a terminal or reading it from terminal.
|
2007-12-08 23:42:33 +01:00
|
|
|
if (!opt_force) {
|
|
|
|
if (opt_mode == MODE_COMPRESS) {
|
2008-11-19 19:46:52 +01:00
|
|
|
if (is_tty_stdout())
|
2007-12-08 23:42:33 +01:00
|
|
|
continue;
|
|
|
|
} else if (is_tty_stdin()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
// 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 "
|
2007-12-08 23:42:33 +01:00
|
|
|
"standard input when "
|
|
|
|
"reading filenames "
|
|
|
|
"from standard input"));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
// Replace the "-" with a special pointer, which is
|
|
|
|
// recognized by process_file() 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;
|
2007-12-08 23:42:33 +01:00
|
|
|
}
|
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
// Do the actual compression or uncompression.
|
|
|
|
process_file(args.arg_names[i]);
|
2007-12-08 23:42:33 +01:00
|
|
|
}
|
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
// 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.
|
2007-12-08 23:42:33 +01:00
|
|
|
while (true) {
|
2008-11-19 19:46:52 +01:00
|
|
|
const char *name = read_name(&args);
|
2007-12-08 23:42:33 +01:00
|
|
|
if (name == NULL)
|
|
|
|
break;
|
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
// read_name() doesn't return empty names.
|
|
|
|
assert(name[0] != '\0');
|
|
|
|
process_file(name);
|
2007-12-08 23:42:33 +01:00
|
|
|
}
|
|
|
|
|
2008-11-19 19:46:52 +01:00
|
|
|
if (args.files_name != stdin_filename)
|
|
|
|
(void)fclose(args.files_file);
|
2007-12-08 23:42:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
my_exit(exit_status);
|
|
|
|
}
|