summaryrefslogtreecommitdiff
path: root/src/signals.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/signals.c')
-rw-r--r--src/signals.c560
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;
+}
+