1
0
Fork 0
mirror of https://git.tukaani.org/xz.git synced 2024-04-04 12:36:23 +02:00
xz-archive/src/xz/signals.c
Lasse Collin 9dc319eabb xz: Use the self-pipe trick to avoid a race condition with signals.
It is possible that a signal to set user_abort arrives right
before a blocking system call is made. In this case the call
may block until another signal arrives, while the wanted
behavior is to make xz clean up and exit as soon as possible.

After this commit, the race condition is avoided with the
input side which already uses non-blocking I/O. The output
side still uses blocking I/O and thus has the race condition.
2013-06-28 23:48:05 +03:00

207 lines
4.7 KiB
C

///////////////////////////////////////////////////////////////////////////////
//
/// \file signals.c
/// \brief Handling signals to abort operation
//
// Author: Lasse Collin
//
// This file has been put into the public domain.
// You can do whatever you want with this file.
//
///////////////////////////////////////////////////////////////////////////////
#include "private.h"
volatile sig_atomic_t user_abort = false;
#if !(defined(_WIN32) && !defined(__CYGWIN__))
/// 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;
/// True once signals_init() has finished. This is used to skip blocking
/// signals (with uninitialized hooked_signals) if signals_block() and
/// signals_unblock() are called before signals_init() has been called.
static bool signals_are_initialized = false;
/// signals_block() and signals_unblock() can be called recursively.
static size_t signals_block_count = 0;
static void
signal_handler(int sig)
{
exit_signal = sig;
user_abort = true;
#ifndef TUKLIB_DOSLIKE
io_write_to_user_abort_pipe();
#endif
return;
}
extern void
signals_init(void)
{
// List of signals for which we establish the signal handler.
static const int sigs[] = {
SIGINT,
SIGTERM,
#ifdef SIGHUP
SIGHUP,
#endif
#ifdef SIGPIPE
SIGPIPE,
#endif
#ifdef SIGXCPU
SIGXCPU,
#endif
#ifdef SIGXFSZ
SIGXFSZ,
#endif
};
// 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]);
#ifdef SIGALRM
// Add also the signals from message.c to hooked_signals.
for (size_t i = 0; message_progress_sigs[i] != 0; ++i)
sigaddset(&hooked_signals, message_progress_sigs[i]);
#endif
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();
}
signals_are_initialized = true;
return;
}
#ifndef __VMS
extern void
signals_block(void)
{
if (signals_are_initialized) {
if (signals_block_count++ == 0) {
const int saved_errno = errno;
mythread_sigmask(SIG_BLOCK, &hooked_signals, NULL);
errno = saved_errno;
}
}
return;
}
extern void
signals_unblock(void)
{
if (signals_are_initialized) {
assert(signals_block_count > 0);
if (--signals_block_count == 0) {
const int saved_errno = errno;
mythread_sigmask(SIG_UNBLOCK, &hooked_signals, NULL);
errno = saved_errno;
}
}
return;
}
#endif
extern void
signals_exit(void)
{
const int sig = exit_signal;
if (sig != 0) {
#if defined(TUKLIB_DOSLIKE) || defined(__VMS)
// Don't raise(), set only exit status. This avoids
// printing unwanted message about SIGINT when the user
// presses C-c.
set_exit_status(E_ERROR);
#else
struct sigaction sa;
sa.sa_handler = SIG_DFL;
sigfillset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(sig, &sa, NULL);
raise(exit_signal);
#endif
}
return;
}
#else
// While Windows has some very basic signal handling functions as required
// by C89, they are not really used, and e.g. SIGINT doesn't work exactly
// the way it does on POSIX (Windows creates a new thread for the signal
// handler). Instead, we use SetConsoleCtrlHandler() to catch user
// pressing C-c, because that seems to be the recommended way to do it.
//
// NOTE: This doesn't work under MSYS. Trying with SIGINT doesn't work
// either even if it appeared to work at first. So test using Windows
// console window.
static BOOL WINAPI
signal_handler(DWORD type lzma_attribute((__unused__)))
{
// Since we don't get a signal number which we could raise() at
// signals_exit() like on POSIX, just set the exit status to
// indicate an error, so that we cannot return with zero exit status.
set_exit_status(E_ERROR);
user_abort = true;
return TRUE;
}
extern void
signals_init(void)
{
if (!SetConsoleCtrlHandler(&signal_handler, TRUE))
message_signal_handler();
return;
}
#endif