fish-shell/signal.cpp

701 lines
12 KiB
C++

/** \file signal.c
The library for various signal related issues
*/
#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#ifdef HAVE_SIGINFO_H
#include <siginfo.h>
#endif
#include "common.h"
#include "fallback.h"
#include "util.h"
#include "wutil.h"
#include "signal.h"
#include "event.h"
#include "reader.h"
#include "proc.h"
/**
Struct describing an entry for the lookup table used to convert
between signal names and signal ids, etc.
*/
struct lookup_entry
{
/**
Signal id
*/
int signal;
/**
Signal name
*/
const wchar_t *name;
/**
Signal description
*/
const wchar_t *desc;
};
/**
The number of signal blocks in place. Increased by signal_block, decreased by signal_unblock.
*/
static int block_count=0;
/**
Lookup table used to convert between signal names and signal ids,
etc.
*/
static const struct lookup_entry lookup[] =
{
#ifdef SIGHUP
{
SIGHUP,
L"SIGHUP",
N_(L"Terminal hung up")
}
,
#endif
#ifdef SIGINT
{
SIGINT,
L"SIGINT",
N_(L"Quit request from job control (^C)")
}
,
#endif
#ifdef SIGQUIT
{
SIGQUIT,
L"SIGQUIT",
N_(L"Quit request from job control with core dump (^\\)")
}
,
#endif
#ifdef SIGILL
{
SIGILL,
L"SIGILL",
N_(L"Illegal instruction")
}
,
#endif
#ifdef SIGTRAP
{
SIGTRAP,
L"SIGTRAP",
N_(L"Trace or breakpoint trap")
}
,
#endif
#ifdef SIGABRT
{
SIGABRT,
L"SIGABRT",
N_(L"Abort")
}
,
#endif
#ifdef SIGBUS
{
SIGBUS,
L"SIGBUS",
N_(L"Misaligned address error")
}
,
#endif
#ifdef SIGFPE
{
SIGFPE,
L"SIGFPE",
N_(L"Floating point exception")
}
,
#endif
#ifdef SIGKILL
{
SIGKILL,
L"SIGKILL",
N_(L"Forced quit")
}
,
#endif
#ifdef SIGUSR1
{
SIGUSR1,
L"SIGUSR1",
N_(L"User defined signal 1")
}
,
#endif
#ifdef SIGUSR2
{
SIGUSR2, L"SIGUSR2",
N_(L"User defined signal 2")
}
,
#endif
#ifdef SIGSEGV
{
SIGSEGV,
L"SIGSEGV",
N_(L"Address boundary error")
}
,
#endif
#ifdef SIGPIPE
{
SIGPIPE,
L"SIGPIPE",
N_(L"Broken pipe")
}
,
#endif
#ifdef SIGALRM
{
SIGALRM,
L"SIGALRM",
N_(L"Timer expired")
}
,
#endif
#ifdef SIGTERM
{
SIGTERM,
L"SIGTERM",
N_(L"Polite quit request")
}
,
#endif
#ifdef SIGCHLD
{
SIGCHLD,
L"SIGCHLD",
N_(L"Child process status changed")
}
,
#endif
#ifdef SIGCONT
{
SIGCONT,
L"SIGCONT",
N_(L"Continue previously stopped process")
}
,
#endif
#ifdef SIGSTOP
{
SIGSTOP,
L"SIGSTOP",
N_(L"Forced stop")
}
,
#endif
#ifdef SIGTSTP
{
SIGTSTP,
L"SIGTSTP",
N_(L"Stop request from job control (^Z)")
}
,
#endif
#ifdef SIGTTIN
{
SIGTTIN,
L"SIGTTIN",
N_(L"Stop from terminal input")
}
,
#endif
#ifdef SIGTTOU
{
SIGTTOU,
L"SIGTTOU",
N_(L"Stop from terminal output")
}
,
#endif
#ifdef SIGURG
{
SIGURG,
L"SIGURG",
N_(L"Urgent socket condition")
}
,
#endif
#ifdef SIGXCPU
{
SIGXCPU,
L"SIGXCPU",
N_(L"CPU time limit exceeded")
}
,
#endif
#ifdef SIGXFSZ
{
SIGXFSZ,
L"SIGXFSZ",
N_(L"File size limit exceeded")
}
,
#endif
#ifdef SIGVTALRM
{
SIGVTALRM,
L"SIGVTALRM",
N_(L"Virtual timer expired")
}
,
#endif
#ifdef SIGPROF
{
SIGPROF,
L"SIGPROF",
N_(L"Profiling timer expired")
}
,
#endif
#ifdef SIGWINCH
{
SIGWINCH,
L"SIGWINCH",
N_(L"Window size change")
}
,
#endif
#ifdef SIGWIND
{
SIGWIND,
L"SIGWIND",
N_(L"Window size change")
}
,
#endif
#ifdef SIGIO
{
SIGIO,
L"SIGIO",
N_(L"I/O on asynchronous file descriptor is possible")
}
,
#endif
#ifdef SIGPWR
{
SIGPWR,
L"SIGPWR",
N_(L"Power failure")
}
,
#endif
#ifdef SIGSYS
{
SIGSYS,
L"SIGSYS",
N_(L"Bad system call")
}
,
#endif
#ifdef SIGINFO
{
SIGINFO,
L"SIGINFO",
N_(L"Information request")
}
,
#endif
#ifdef SIGSTKFLT
{
SIGSTKFLT,
L"SISTKFLT",
N_(L"Stack fault")
}
,
#endif
#ifdef SIGEMT
{
SIGEMT,
L"SIGEMT",
N_(L"Emulator trap")
}
,
#endif
#ifdef SIGIOT
{
SIGIOT,
L"SIGIOT",
N_(L"Abort (Alias for SIGABRT)")
}
,
#endif
#ifdef SIGUNUSED
{
SIGUNUSED,
L"SIGUNUSED",
N_(L"Unused signal")
}
,
#endif
{
0,
0,
0
}
}
;
/**
Test if \c name is a string describing the signal named \c canonical.
*/
static int match_signal_name(const wchar_t *canonical,
const wchar_t *name)
{
if (wcsncasecmp(name, L"sig", 3)==0)
name +=3;
return wcscasecmp(canonical+3,name) == 0;
}
int wcs2sig(const wchar_t *str)
{
int i;
wchar_t *end=0;
for (i=0; lookup[i].desc ; i++)
{
if (match_signal_name(lookup[i].name, str))
{
return lookup[i].signal;
}
}
errno=0;
int res = fish_wcstoi(str, &end, 10);
if (!errno && res>=0 && !*end)
return res;
return -1;
}
const wchar_t *sig2wcs(int sig)
{
int i;
for (i=0; lookup[i].desc ; i++)
{
if (lookup[i].signal == sig)
{
return lookup[i].name;
}
}
return _(L"Unknown");
}
const wchar_t *signal_get_desc(int sig)
{
int i;
for (i=0; lookup[i].desc ; i++)
{
if (lookup[i].signal == sig)
{
return _(lookup[i].desc);
}
}
return _(L"Unknown");
}
/**
Standard signal handler
*/
static void default_handler(int signal, siginfo_t *info, void *context)
{
if (event_is_signal_observed(signal))
{
event_fire_signal(signal);
}
}
/**
Respond to a winch signal by checking the terminal size
*/
static void handle_winch(int sig, siginfo_t *info, void *context)
{
common_handle_winch(sig);
default_handler(sig, 0, 0);
}
/**
Respond to a hup signal by exiting, unless it is caught by a
shellscript function, in which case we do nothing.
*/
static void handle_hup(int sig, siginfo_t *info, void *context)
{
if (event_is_signal_observed(SIGHUP))
{
default_handler(sig, 0, 0);
}
else
{
reader_exit(1, 1);
}
}
/** Handle sigterm. The only thing we do is restore the front process ID, then die. */
static void handle_term(int sig, siginfo_t *info, void *context)
{
restore_term_foreground_process_group();
signal(SIGTERM, SIG_DFL);
raise(SIGTERM);
}
/**
Interactive mode ^C handler. Respond to int signal by setting
interrupted-flag and stopping all loops and conditionals.
*/
static void handle_int(int sig, siginfo_t *info, void *context)
{
reader_handle_int(sig);
default_handler(sig, info, context);
}
/**
sigchld handler. Does notification and calls the handler in proc.c
*/
static void handle_chld(int sig, siginfo_t *info, void *context)
{
job_handle_signal(sig, info, context);
default_handler(sig, info, context);
}
void signal_reset_handlers()
{
int i;
struct sigaction act;
sigemptyset(& act.sa_mask);
act.sa_flags=0;
act.sa_handler=SIG_DFL;
for (i=0; lookup[i].desc ; i++)
{
sigaction(lookup[i].signal, &act, 0);
}
}
/**
Sets appropriate signal handlers.
*/
void signal_set_handlers()
{
struct sigaction act;
if (get_is_interactive() == -1)
return;
sigemptyset(& act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction = &default_handler;
/*
First reset everything to a use default_handler, a function
whose sole action is to fire of an event
*/
sigaction(SIGINT, &act, 0);
sigaction(SIGQUIT, &act, 0);
sigaction(SIGTSTP, &act, 0);
sigaction(SIGTTIN, &act, 0);
sigaction(SIGTTOU, &act, 0);
sigaction(SIGCHLD, &act, 0);
/*
Ignore sigpipe, it is generated if fishd dies, but we can
recover.
*/
sigaction(SIGPIPE, &act, 0);
if (get_is_interactive())
{
/*
Interactive mode. Ignore interactive signals. We are a
shell, we know whats best for the user. ;-)
*/
act.sa_handler=SIG_IGN;
sigaction(SIGINT, &act, 0);
sigaction(SIGQUIT, &act, 0);
sigaction(SIGTSTP, &act, 0);
sigaction(SIGTTIN, &act, 0);
sigaction(SIGTTOU, &act, 0);
act.sa_sigaction = &handle_int;
act.sa_flags = SA_SIGINFO;
if (sigaction(SIGINT, &act, 0))
{
wperror(L"sigaction");
FATAL_EXIT();
}
act.sa_sigaction = &handle_chld;
act.sa_flags = SA_SIGINFO;
if (sigaction(SIGCHLD, &act, 0))
{
wperror(L"sigaction");
FATAL_EXIT();
}
#ifdef SIGWINCH
act.sa_flags = SA_SIGINFO;
act.sa_sigaction= &handle_winch;
if (sigaction(SIGWINCH, &act, 0))
{
wperror(L"sigaction");
FATAL_EXIT();
}
#endif
act.sa_flags = SA_SIGINFO;
act.sa_sigaction= &handle_hup;
if (sigaction(SIGHUP, &act, 0))
{
wperror(L"sigaction");
FATAL_EXIT();
}
// SIGTERM restores the terminal controlling process before dying
act.sa_flags = SA_SIGINFO;
act.sa_sigaction= &handle_term;
if (sigaction(SIGTERM, &act, 0))
{
wperror(L"sigaction");
FATAL_EXIT();
}
}
else
{
/*
Non-interactive. Ignore interrupt, check exit status of
processes to determine result instead.
*/
act.sa_handler=SIG_IGN;
sigaction(SIGINT, &act, 0);
sigaction(SIGQUIT, &act, 0);
act.sa_handler=SIG_DFL;
act.sa_sigaction = &handle_chld;
act.sa_flags = SA_SIGINFO;
if (sigaction(SIGCHLD, &act, 0))
{
wperror(L"sigaction");
exit_without_destructors(1);
}
}
}
void signal_handle(int sig, int do_handle)
{
struct sigaction act;
/*
These should always be handled
*/
if ((sig == SIGINT) ||
(sig == SIGQUIT) ||
(sig == SIGTSTP) ||
(sig == SIGTTIN) ||
(sig == SIGTTOU) ||
(sig == SIGCHLD))
return;
sigemptyset(&act.sa_mask);
if (do_handle)
{
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = &default_handler;
}
else
{
act.sa_flags = 0;
act.sa_handler = SIG_DFL;
}
sigaction(sig, &act, 0);
}
void get_signals_with_handlers(sigset_t *set)
{
sigemptyset(set);
for (int i=0; lookup[i].desc ; i++)
{
struct sigaction act = {};
sigaction(lookup[i].signal, NULL, &act);
if (act.sa_handler != SIG_DFL)
sigaddset(set, lookup[i].signal);
}
}
void signal_block()
{
ASSERT_IS_MAIN_THREAD();
sigset_t chldset;
if (!block_count)
{
sigfillset(&chldset);
VOMIT_ON_FAILURE(pthread_sigmask(SIG_BLOCK, &chldset, NULL));
}
block_count++;
// debug( 0, L"signal block level increased to %d", block_count );
}
void signal_unblock()
{
ASSERT_IS_MAIN_THREAD();
sigset_t chldset;
block_count--;
if (block_count < 0)
{
debug(0, _(L"Signal block mismatch"));
bugreport();
FATAL_EXIT();
}
if (!block_count)
{
sigfillset(&chldset);
VOMIT_ON_FAILURE(pthread_sigmask(SIG_UNBLOCK, &chldset, 0));
}
// debug( 0, L"signal block level decreased to %d", block_count );
}
bool signal_is_blocked()
{
return !!block_count;
}