diff options
Diffstat (limited to 'src/signals.c')
-rw-r--r-- | src/signals.c | 560 |
1 files changed, 560 insertions, 0 deletions
diff --git a/src/signals.c b/src/signals.c new file mode 100644 index 0000000..02cbb65 --- /dev/null +++ b/src/signals.c @@ -0,0 +1,560 @@ +#include "signals.h" +#include "daemon.h" + +#include "port/string.h" /* for memset */ + +#include <unistd.h> /* for getpid, pipe, write, read */ +#include <errno.h> /* for errno */ +#include <sys/types.h> /* for ssize_t */ +#include <sys/select.h> /* for FD_XX and select */ + +#include "errors.h" +#include "log.h" + +#include "port/unused.h" + +const char *signal_get_short_name( int sig ) { + switch( sig ) { + case SIGHUP: return "SIGHUP"; + case SIGINT: return "SIGINT"; + case SIGQUIT: return "SIGQUIT"; + case SIGILL: return "SIGILL"; + case SIGTRAP: return "SIGTRAP"; + case SIGABRT: return "SIGABRT"; +#if defined( SIGIOT ) +#if SIGIOT != SIGABRT + case SIGIOT: return "SIGIOT"; +#endif +#endif +#if defined( SIGEMT ) + case SIGEMT: return "SIGEMT"; +#endif + case SIGBUS: return "SIGBUS"; + case SIGFPE: return "SIGFPE"; + case SIGKILL: return "SIGKILL"; + case SIGUSR1: return "SIGUSR1"; + case SIGSEGV: return "SIGSEGV"; + case SIGUSR2: return "SIGUSR2"; + case SIGPIPE: return "SIGPIPE"; + case SIGALRM: return "SIGALRM"; + case SIGTERM: return "SIGTERM"; +#if defined( SIGSTKFLT ) + case SIGSTKFLT: return "SIGSTKFLT"; +#endif +#if defined( SIGCLD ) +#if SIGCLD != SIGCHLD + /* the SysV version */ + case SIGCLD: return "SIGCLD"; +#endif +#endif + /* the POSIX version */ + case SIGCHLD: return "SIGCHLD"; + case SIGCONT: return "SIGCONT"; + case SIGSTOP: return "SIGSTOP"; + case SIGTSTP: return "SIGTSTP"; + case SIGTTIN: return "SIGTTIN"; + case SIGTTOU: return "SIGTTOU"; + case SIGURG: return "SIGURG"; + case SIGXCPU: return "SIGXCPU"; + case SIGXFSZ: return "SIGXFSZ"; + case SIGVTALRM: return "SIGVTALRM"; + case SIGPROF: return "SIGPROF"; +#if defined( SIGWINCH ) + case SIGWINCH: return "SIGWINCH"; +#endif +#if defined( SIGPOLL ) +#if SIGPOLL != SIGIO + /* SysV version of SIGIO */ + case SIGPOLL: return "SIGPOLL"; +#endif +#endif +#if defined( SIGIO ) + case SIGIO: return "SIGIO"; +#endif +#if defined( SIGPWR ) + case SIGPWR: return "SIGPWR"; +#endif +#if defined( SIGSYS ) + case SIGSYS: return "SIGSYS"; +#endif + + default: return "<unknown>"; + } +} + +const char *signal_get_long_name( int sig ) { + /* we don't trust strsignal, needs _GNU_SOURCE, + * naming comes from bits/signum.h on Linux 2.6 + */ + switch( sig ) { + case SIGHUP: return "Hangup"; + case SIGINT: return "Interrupt"; + case SIGQUIT: return "Quit"; + case SIGILL: return "Illegal instruction"; + case SIGTRAP: return "Trace trap"; + case SIGABRT: return "Abort"; +#if defined( SIGIOT ) +#if SIGIOT != SIGABRT + case SIGIOT: return "IOT trap"; +#endif +#endif +#if defined( SIGEMT ) + case SIGEMT: return "EMT instruction"; +#endif + case SIGBUS: return "BUS error"; + case SIGFPE: return "Floating-point exception"; + case SIGKILL: return "Kill"; + case SIGUSR1: return "User-defined signal 1"; + case SIGSEGV: return "Segmentation violation"; + case SIGUSR2: return "User-defined signal 2"; + case SIGPIPE: return "Broken pipe"; + case SIGALRM: return "Alarm clock"; + case SIGTERM: return "Termination"; +#if defined( SIGSTKFLT ) + case SIGSTKFLT: return "Stack fault"; +#endif +#if defined( SIGCLD ) +#if SIGCLD != SIGCHLD + case SIGCLD: return "Child status has changed"; +#endif +#endif + case SIGCHLD: return "Child status has changed"; + case SIGCONT: return "Continue"; + case SIGSTOP: return "Stop"; + case SIGTSTP: return "Keyboard stop"; + case SIGTTIN: return "Background read from tty"; + case SIGTTOU: return "Background write to tty"; + case SIGURG: return "Urgent condition on socket"; + case SIGXCPU: return "CPU limit exceeded"; + case SIGXFSZ: return "File size limit exceeded"; + case SIGVTALRM: return "Virtual alarm clock"; + case SIGPROF: return "Profiling alarm clock"; +#if defined( SIGWINCH ) + case SIGWINCH: return "Window size change"; +#endif +#if defined( SIGPOLL ) +#if SIGPOLL != SIGIO + case SIGPOLL: return "Pollable event occurred"; +#endif +#endif +#if defined( SIGIO ) + case SIGIO: return "I/O now possible"; +#endif +#if defined( SIGPWR ) + case SIGPWR: return "Power failure restart"; +#endif +#if defined( SIGSYS ) + case SIGSYS: return "Bad system call"; +#endif + + default: return "<unknown signal>"; + } +} + +static error_t vsignal_install_ignore( int sig, va_list ap ) { + struct sigaction sa; + char errbuf[1024]; + +install_ignore_again: + memset( &sa, 0, sizeof( struct sigaction ) ); + sa.sa_handler = SIG_IGN; + + if( sigaction( sig, &sa, NULL ) < 0 ) { + (void)strerror_r( errno, errbuf, 1024 ); + LOG( LOG_CRIT, "Can't ignore signal handler for signal '%s' (%s, %d): %s (errno: %d)", + signal_get_long_name( sig ), + signal_get_short_name( sig ), + sig, + errbuf, + errno ); + return ERR_PROGRAMMING; + } + LOG( LOG_DEBUG, "Ignoring signal handler for signal '%s' (%s)", + signal_get_long_name( sig ), + signal_get_short_name( sig ) ); + sig = va_arg( ap, int ); + if( sig > 0 ) goto install_ignore_again; + + return OK; +} + +/* some signals we should not ignore, like SIGPIPE or SIGC(H)LD, because + * results of functions like write and some process data gets lost in + * forked children and waitpid. + */ +static void empty_handler( int sig ) { + UNUSED( sig ); +} + +static error_t vsignal_install_empty( int sig, va_list ap ) { + struct sigaction sa; + char errbuf[1024]; + +install_empty_again: + memset( &sa, 0, sizeof( struct sigaction ) ); + sa.sa_handler = empty_handler; + + if( sigaction( sig, &sa, NULL ) < 0 ) { + (void)strerror_r( errno, errbuf, 1024 ); + LOG( LOG_CRIT, "Can't install empty signal handler for signal '%s' (%s, %d): %s (errno: %d)", + signal_get_long_name( sig ), + signal_get_short_name( sig ), + sig, + errbuf, + errno ); + return ERR_PROGRAMMING; + } + LOG( LOG_DEBUG, "Installed empty signal handler for signal '%s' (%s)", + signal_get_long_name( sig ), + signal_get_short_name( sig ) ); + sig = va_arg( ap, int ); + if( sig > 0 ) goto install_empty_again; + + return OK; +} + +static void fatal_handler( int sig ) { + struct sigaction sa; + + /* reset default behaviour of the signal */ + memset( &sa, 0, sizeof( struct sigaction ) ); + sa.sa_handler = SIG_DFL; + (void)sigaction( sig, &sa, NULL ); + + /* log what happened */ + LOG( LOG_ALERT, "Got signal '%s' (%s)", + signal_get_long_name( sig ), + signal_get_short_name( sig ) ); + + /* we are in one thread when we got the signal, now inform + * all other threads too (see Apache mpm_common.c for this + * trick) + */ + kill( getpid( ), sig ); +} + +static error_t vsignal_install_func( signal_func_t func, int sig, va_list ap ) { + struct sigaction sa; + char errbuf[1024]; + +install_func_again: + memset( &sa, 0, sizeof( struct sigaction ) ); + sa.sa_handler = func; + sa.sa_flags = SA_RESTART; + + if( sigaction( sig, &sa, NULL ) < 0 ) { + (void)strerror_r( errno, errbuf, 1024 ); + LOG( LOG_CRIT, "Can't install signal handler for signal '%s' (%s, %d): %s (errno: %d)", + signal_get_long_name( sig ), + signal_get_short_name( sig ), + sig, + errbuf, + errno ); + return ERR_PROGRAMMING; + } + LOG( LOG_DEBUG, "Installed signal handler for signal '%s' (%s)", + signal_get_long_name( sig ), + signal_get_short_name( sig ) ); + sig = va_arg( ap, int ); + if( sig > 0 ) goto install_func_again; + + return OK; +} + +error_t signal_install_func( signal_func_t func, int sig, ... ) { + va_list ap; + error_t error; + va_start( ap, sig ); + error = vsignal_install_func( func, sig, ap ); + va_end( ap ); + return error; +} + +#define INSTALL_SIGNAL( MODE ) \ +error_t signal_install_##MODE( int sig, ... ) { \ + va_list ap; \ + error_t error; \ + va_start( ap, sig ); \ + error = vsignal_install_##MODE( sig, ap ); \ + va_end( ap ); \ + return error; \ +} + +INSTALL_SIGNAL( ignore ) +INSTALL_SIGNAL( empty ) + +#define INSTALL_SIGNAL_FUNC( MODE, FUNC ) \ +error_t signal_install_##MODE( int sig, ... ) { \ + va_list ap; \ + error_t error; \ + va_start( ap, sig ); \ + error = vsignal_install_func( FUNC, sig, ap ); \ + va_end( ap ); \ + return error; \ +} + +INSTALL_SIGNAL_FUNC( fatal, fatal_handler ) + +int daemon_signal_pipe[2] = { -1, -1 }; + +static void notify_parent_handler( int sig ) { + if( daemon_parent_pipe[1] != -1 ) + (void)write( daemon_parent_pipe[1], &sig, sizeof( int ) ); +} + +static void notify_handler( int sig ) { + if( daemon_signal_pipe[1] != -1 ) + (void)write( daemon_signal_pipe[1], &sig, sizeof( int ) ); +} + +INSTALL_SIGNAL_FUNC( notify, notify_handler ) +INSTALL_SIGNAL_FUNC( notify_parent, notify_parent_handler ) + +error_t signal_initialize( void ) { + int res; + char errbuf[1024]; + + if( daemon_signal_pipe[0] != -1 || + daemon_signal_pipe[1] != -1 ) { + return ERR_INVALID_STATE; + } + + res = pipe( daemon_signal_pipe ); + if( res < 0 ) { + (void)strerror_r( errno, errbuf, 1024 ); + LOG( LOG_EMERG, "Unable to create signal pipe: %s (%d)", + errbuf, errno ); + return ERR_INTERNAL; + } + LOG( LOG_DEBUG, "Created signal pipe (%d,%d)", daemon_signal_pipe[0], daemon_signal_pipe[1] ); + + return OK; +} + +void signal_terminate( void ) { + (void)close( daemon_signal_pipe[0] ); + (void)close( daemon_signal_pipe[1] ); +} + +int signal_suspend( int timeout, error_t *error ) { + struct timeval tv; + fd_set fds; + ssize_t res; + int sig; + char errbuf[1024]; + + if( daemon_signal_pipe[0] == -1 ) { + *error = ERR_INVALID_STATE; + return -1; + } + + tv.tv_sec = timeout; + tv.tv_usec = 0; + + FD_ZERO( &fds ); + FD_SET( daemon_signal_pipe[0], &fds ); + +signal_select_again: + res = select( daemon_signal_pipe[0] + 1, &fds, 0, 0, &tv ); + switch( res ) { + case 1: /* ready on pipe */ + break; + + case 0: /* timeout */ + *error = ERR_TIMEOUT; + return 0; + + case -1: /* error */ + if( errno == EINTR ) goto signal_select_again; + /* gdb no-brainer when pressing Ctrl-C (at least Linux) */ + if( errno == 514 ) goto signal_select_again; + (void)strerror_r( errno, errbuf, 1024 ); + LOG( LOG_EMERG, "Error in select when waiting for signal from pipe: %s (%d)", + errbuf, errno ); + *error = ERR_INTERNAL; + return -1; + } + + res = read( daemon_signal_pipe[0], &sig, sizeof( int ) ); + switch( res ) { + case sizeof( int ): /* got a signal */ + break; + + case -1: /* error */ + (void)strerror_r( errno, errbuf, 1024 ); + LOG( LOG_EMERG, "Error while reading a signal from the pipe: %s (%d)", + errbuf, errno ); + *error = ERR_INTERNAL; + return -1; + + default: /* unexpected result on atomic read */ + LOG( LOG_EMERG, "Unexpected error in read: result is %d", res ); + *error = ERR_PROGRAMMING; + return -1; + } + + *error = OK; + return sig; +} + +error_t signal_install_handlers_parent( void ) { + error_t error; + + /* signals to ignore */ + if( ( error = signal_install_ignore( + SIGPIPE, + SIGTSTP, + SIGTTIN, + SIGTTOU, + SIGURG, + SIGXCPU, /* later: throthling */ + SIGXFSZ, /* later: close connections, reduce channels */ + SIGVTALRM, + SIGPROF, +#if defined( SIGWINCH ) + SIGWINCH, +#endif +#if defined( SIGPOLL ) + SIGPOLL, +#endif +#if defined( SIGIO ) +#if SIGIO != SIGPOLL + SIGIO, +#endif +#endif +#if defined( SIGPWR ) + SIGPWR, +#endif + SIGTERM, + SIGINT, + SIGHUP, + SIGUSR1, + SIGUSR2, + 0 ) ) != OK ) return error; + + /* signals for empty handlers */ + if( ( error = signal_install_empty( +#if defined( SIGCHLD ) && defined( SIGCLD ) +#if SIGCLD != SIGCHLD + SIGCHLD, +#else +#if defined( SIGCHLD ) + SIGCHLD, +#endif +#if defined( SIGCLD ) + SIGCLD, +#endif +#endif +#endif + 0 ) ) != OK ) return error; + + /* fatal signal handlers, make sure the parent can read the + * signal and clean up + */ + if( ( error = signal_install_notify_parent( + SIGILL, + SIGABRT, +#if defined( SIGIOT ) +#if SIGIOT != SIGABRT + SIGIOT, +#endif +#endif + SIGBUS, + SIGFPE, + SIGSEGV, + SIGVTALRM, +#if defined( SIGSTKFLT ) + SIGSTKFLT, +#endif +#if defined( SIGSYS ) + SIGSYS, +#endif + SIGALRM, /* we don't use it, but maybe a plugin */ + 0 ) ) != OK ) return error; + + return OK; +} + + +error_t signal_install_handlers_daemon( void ) { + error_t error; + + /* signals to ignore */ + if( ( error = signal_install_ignore( + SIGPIPE, + SIGTSTP, + SIGTTIN, + SIGTTOU, + SIGURG, + SIGXCPU, /* later: throthling */ + SIGXFSZ, /* later: close connections, reduce channels */ + SIGVTALRM, + SIGPROF, +#if defined( SIGWINCH ) + SIGWINCH, +#endif +#if defined( SIGPOLL ) + SIGPOLL, +#endif +#if defined SIGIO +#if SIGIO != SIGPOLL + SIGIO, +#endif +#endif +#if defined( SIGPWR ) + SIGPWR, +#endif + 0 ) ) != OK ) return error; + + /* signals for empty handlers */ + if( ( error = signal_install_empty( +#if defined( SIGCHLD ) && defined( SIGCLD ) +#if SIGCLD != SIGCHLD + SIGCHLD, +#else +#if defined( SIGCHLD ) + SIGCHLD, +#endif +#if defined( SIGCLD ) + SIGCLD, +#endif +#endif +#endif + 0 ) ) != OK ) return error; + + /* fatal signal handlers, log the exception and continue with + * default behaviour of the system + */ + if( ( error = signal_install_fatal( + SIGILL, + SIGABRT, +#if defined( SIGIOT ) +#if SIGIOT != SIGABRT + SIGIOT, +#endif +#endif + SIGBUS, + SIGFPE, + SIGSEGV, + SIGVTALRM, +#if defined( SIGSTKFLT ) + SIGSTKFLT, +#endif +#if defined( SIGSYS ) + SIGSYS, +#endif + SIGALRM, /* we don't use it, but maybe a plugin */ + 0 ) ) != OK ) return error; + + /* notify the following signals to the main loop */ + if( ( error = signal_install_notify( + SIGTERM, + SIGINT, + SIGHUP, + SIGUSR1, + SIGUSR2, + 0 ) ) != OK ) return error; + + return OK; +} + |