diff options
Diffstat (limited to 'src/lib/lib-signals.c')
-rw-r--r-- | src/lib/lib-signals.c | 702 |
1 files changed, 702 insertions, 0 deletions
diff --git a/src/lib/lib-signals.c b/src/lib/lib-signals.c new file mode 100644 index 0000000..6ac657a --- /dev/null +++ b/src/lib/lib-signals.c @@ -0,0 +1,702 @@ +/* Copyright (c) 2001-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "ioloop.h" +#include "write-full.h" +#include "llist.h" +#include "lib-signals.h" + +#include <stdio.h> +#include <signal.h> +#include <unistd.h> + +#define MAX_SIGNAL_VALUE 63 + +#define SIGNAL_IS_TERMINAL(signo) \ + ((signo) == SIGINT || (signo) == SIGQUIT || (signo) == SIGTERM) + +#if !defined(SA_SIGINFO) && !defined(SI_NOINFO) +/* without SA_SIGINFO we don't know what the real code is. we need SI_NOINFO + to make sure lib_signal_code_to_str() returns "". */ +# define SI_NOINFO -1 +#endif + +struct signal_ioloop { + struct signal_ioloop *prev, *next; + + int refcount; + struct ioloop *ioloop; + struct io *io; +}; + +struct signal_handler { + signal_handler_t *handler; + void *context; + + enum libsig_flags flags; + struct signal_handler *next; + struct signal_ioloop *sig_ioloop; + + bool expected:1; + bool shadowed:1; +}; + +volatile unsigned int signal_term_counter = 0; + +/* Remember that these are accessed inside signal handler which may be called + even while we're initializing/deinitializing. Try hard to keep everything + in consistent state. */ +static struct signal_handler *signal_handlers[MAX_SIGNAL_VALUE+1] = { NULL, }; +static int sig_pipe_fd[2] = { -1, -1 }; + +static bool signals_initialized = FALSE; +static unsigned int signals_expected = 0; +static struct signal_ioloop *signal_ioloops = NULL; + +static siginfo_t pending_signals[MAX_SIGNAL_VALUE+1]; +static ARRAY(siginfo_t) pending_shadowed_signals; +static bool have_pending_signals = FALSE; +static bool have_missing_ioloops = FALSE; +static bool ioloop_switched = FALSE; + +static void signal_read(void *context); + +const char *lib_signal_code_to_str(int signo, int sicode) +{ + /* common */ + switch (sicode) { +#ifdef SI_NOINFO + case SI_NOINFO: + return ""; +#endif + case SI_USER: + return "kill"; +#ifdef SI_KERNEL + case SI_KERNEL: + return "kernel"; +#endif + case SI_TIMER: + return "timer"; + } + + /* If SEGV_MAPERR is supported, the rest of them must be too. + FreeBSD 6 at least doesn't support these. */ +#ifdef SEGV_MAPERR + switch (signo) { + case SIGSEGV: + switch (sicode) { + case SEGV_MAPERR: + return "address not mapped"; + case SEGV_ACCERR: + return "invalid permissions"; + } + break; + case SIGBUS: + switch (sicode) { + case BUS_ADRALN: + return "invalid address alignment"; +#ifdef BUS_ADRERR /* for OSX 10.3 */ + case BUS_ADRERR: + return "nonexistent physical address"; +#endif +#ifdef BUS_OBJERR /* for OSX 10.3 */ + case BUS_OBJERR: + return "object-specific hardware error"; +#endif + } + } +#endif + return t_strdup_printf("unknown %d", sicode); +} + +#ifdef SA_SIGINFO +static void sig_handler(int signo, siginfo_t *si, void *context ATTR_UNUSED) +#else +static void sig_handler(int signo) +#endif +{ + struct signal_handler *h; + int saved_errno; + char c = 0; + +#if defined(SI_NOINFO) || !defined(SA_SIGINFO) +#ifndef SA_SIGINFO + siginfo_t *si = NULL; +#endif + siginfo_t tmp_si; + + if (si == NULL) { + /* Solaris can leave this to NULL */ + i_zero(&tmp_si); + tmp_si.si_signo = signo; + tmp_si.si_code = SI_NOINFO; + si = &tmp_si; + } +#endif + + if (signo < 0 || signo > MAX_SIGNAL_VALUE) + return; + + if (SIGNAL_IS_TERMINAL(signo)) + signal_term_counter++; + + /* remember that we're inside a signal handler which might have been + called at any time. don't do anything that's unsafe. we might also + get interrupted by another signal while inside this handler. */ + saved_errno = errno; + for (h = signal_handlers[signo]; h != NULL; h = h->next) { + if ((h->flags & LIBSIG_FLAG_DELAYED) == 0) + h->handler(si, h->context); + else if (pending_signals[signo].si_signo == 0) { + pending_signals[signo] = *si; + if (!have_pending_signals) { + if (write(sig_pipe_fd[1], &c, 1) != 1) { + lib_signals_syscall_error( + "signal: write(sigpipe) failed: "); + } + have_pending_signals = TRUE; + } + } + } + errno = saved_errno; +} + +#ifdef SA_SIGINFO +static void sig_ignore(int signo ATTR_UNUSED, siginfo_t *si ATTR_UNUSED, + void *context ATTR_UNUSED) +#else +static void sig_ignore(int signo ATTR_UNUSED) +#endif +{ + /* if we used SIG_IGN instead of this function, + the system call might be restarted */ +} + +static struct signal_ioloop * +lib_signals_ioloop_find(struct ioloop *ioloop) +{ + struct signal_ioloop *l; + + for (l = signal_ioloops; l != NULL; l = l->next) { + if (l->ioloop == ioloop) + break; + } + return l; +} + +static void lib_signals_init_io(struct signal_ioloop *l) +{ + i_assert(sig_pipe_fd[0] != -1); + + l->io = io_add_to(l->ioloop, sig_pipe_fd[0], IO_READ, signal_read, NULL); + io_set_never_wait_alone(l->io, signals_expected == 0); +} + +static struct signal_ioloop * +lib_signals_ioloop_ref(struct ioloop *ioloop) +{ + struct signal_ioloop *l; + + l = lib_signals_ioloop_find(ioloop); + if (l == NULL) { + l = i_new(struct signal_ioloop, 1); + l->ioloop = ioloop; + lib_signals_init_io(l); + DLLIST_PREPEND(&signal_ioloops, l); + } + l->refcount++; + return l; +} + +static void lib_signals_ioloop_unref(struct signal_ioloop **_sig_ioloop) +{ + struct signal_ioloop *sig_ioloop = *_sig_ioloop; + + *_sig_ioloop = NULL; + + if (sig_ioloop == NULL) + return; + i_assert(sig_ioloop->refcount > 0); + if (--sig_ioloop->refcount > 0) + return; + io_remove(&sig_ioloop->io); + DLLIST_REMOVE(&signal_ioloops, sig_ioloop); + i_free(sig_ioloop); +} + +static void signal_handler_switch_ioloop(struct signal_handler *h) +{ + lib_signals_ioloop_unref(&h->sig_ioloop); + if (current_ioloop != NULL) + h->sig_ioloop = lib_signals_ioloop_ref(current_ioloop); + else + have_missing_ioloops = TRUE; +} + +static void signal_handler_free(struct signal_handler *h) +{ + lib_signals_ioloop_unref(&h->sig_ioloop); + i_free(h); +} + +static void signal_handle_shadowed(void) +{ + const siginfo_t *sis; + unsigned int count, i; + + if (!array_is_created(&pending_shadowed_signals) || + array_count(&pending_shadowed_signals) == 0) + return; + + sis = array_get(&pending_shadowed_signals, &count); + for (i = 0; i < count; i++) { + struct signal_handler *h; + bool shadowed = FALSE; + + i_assert(sis[i].si_signo > 0); + for (h = signal_handlers[sis[i].si_signo]; h != NULL; + h = h->next) { + i_assert(h->sig_ioloop != NULL); + if ((h->flags & LIBSIG_FLAG_DELAYED) == 0 || + (h->flags & LIBSIG_FLAG_IOLOOP_AUTOMOVE) != 0) + continue; + if (h->shadowed && + h->sig_ioloop->ioloop != current_ioloop) { + shadowed = TRUE; + continue; + } + /* handler can be called now */ + h->shadowed = FALSE; + h->handler(&sis[i], h->context); + } + if (!shadowed) { + /* no handlers are shadowed anymore; delete the signal + info */ + array_delete(&pending_shadowed_signals, i, 1); + sis = array_get(&pending_shadowed_signals, &count); + } + } +} + +static void signal_check_shadowed(void) +{ + struct signal_ioloop *sig_ioloop; + + if (!array_is_created(&pending_shadowed_signals) || + array_count(&pending_shadowed_signals) == 0) + return; + + sig_ioloop = lib_signals_ioloop_find(current_ioloop); + if (sig_ioloop != NULL) + io_set_pending(sig_ioloop->io); +} + +static void signal_shadow(int signo, const siginfo_t *si) +{ + const siginfo_t *sis; + unsigned int count, i; + + /* remember last signal info for handlers that cannot run in + current ioloop */ + if (!array_is_created(&pending_shadowed_signals)) + i_array_init(&pending_shadowed_signals, 4); + sis = array_get(&pending_shadowed_signals, &count); + for (i = 0; i < count; i++) { + i_assert(sis[i].si_signo != 0); + if (sis[i].si_signo == signo) + break; + } + array_idx_set(&pending_shadowed_signals, i, si); +} + +static void ATTR_NULL(1) signal_read(void *context ATTR_UNUSED) +{ + siginfo_t signals[MAX_SIGNAL_VALUE+1]; + sigset_t fullset, oldset; + struct signal_handler *h; + char buf[64]; + int signo; + ssize_t ret; + + if (ioloop_switched) { + ioloop_switched = FALSE; + /* handle any delayed signal handlers that emerged from the + shadow */ + signal_handle_shadowed(); + } + + if (sigfillset(&fullset) < 0) + i_fatal("sigfillset() failed: %m"); + if (sigprocmask(SIG_BLOCK, &fullset, &oldset) < 0) + i_fatal("sigprocmask() failed: %m"); + + /* typically we should read only a single byte, but if a signal is sent + while signal handler is running we might get more. */ + ret = read(sig_pipe_fd[0], buf, sizeof(buf)); + if (ret > 0) { + memcpy(signals, pending_signals, sizeof(signals)); + memset(pending_signals, 0, sizeof(pending_signals)); + have_pending_signals = FALSE; + } else if (ret < 0) { + if (errno != EAGAIN) + i_fatal("read(sigpipe) failed: %m"); + } else { + i_fatal("read(sigpipe) failed: EOF"); + } + if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0) + i_fatal("sigprocmask() failed: %m"); + + if (ret < 0) + return; + + /* call the delayed handlers after signals are copied and unblocked */ + for (signo = 0; signo < MAX_SIGNAL_VALUE; signo++) { + bool shadowed = FALSE; + + if (signals[signo].si_signo == 0) + continue; + + for (h = signal_handlers[signo]; h != NULL; h = h->next) { + i_assert(h->sig_ioloop != NULL); + if ((h->flags & LIBSIG_FLAG_DELAYED) == 0) { + /* handler already called immediately in signal + context */ + continue; + } + if ((h->flags & LIBSIG_FLAG_IOLOOP_AUTOMOVE) == 0 && + h->sig_ioloop->ioloop != current_ioloop) { + /* cannot run handler in current ioloop + (shadowed) */ + h->shadowed = TRUE; + shadowed = TRUE; + continue; + } + /* handler can be called now */ + h->handler(&signals[signo], h->context); + } + + if (shadowed) { + /* remember last signal info for handlers that cannot + run in current ioloop (shadowed) */ + signal_shadow(signo, &signals[signo]); + } + } +} + +static void lib_signals_update_expected_signals(bool expected) +{ + struct signal_ioloop *sig_ioloop; + + if (expected) + signals_expected++; + else { + i_assert(signals_expected > 0); + signals_expected--; + } + + sig_ioloop = signal_ioloops; + for (; sig_ioloop != NULL; sig_ioloop = sig_ioloop->next) { + if (sig_ioloop->io != NULL) { + io_set_never_wait_alone(sig_ioloop->io, + signals_expected == 0); + } + } +} + +static void lib_signals_ioloop_switch(void) +{ + struct signal_handler *h; + + if (current_ioloop == NULL || sig_pipe_fd[0] <= 0) + return; + + /* initialize current_ioloop for signal handlers created before the + first ioloop. */ + for (int signo = 0; signo < MAX_SIGNAL_VALUE; signo++) { + for (h = signal_handlers[signo]; h != NULL; h = h->next) { + if ((h->flags & LIBSIG_FLAG_IOLOOP_AUTOMOVE) != 0) + lib_signals_ioloop_unref(&h->sig_ioloop); + if (h->sig_ioloop == NULL) + h->sig_ioloop = lib_signals_ioloop_ref(current_ioloop); + } + } + have_missing_ioloops = FALSE; +} + +static void lib_signals_ioloop_switched(struct ioloop *prev_ioloop ATTR_UNUSED) +{ + ioloop_switched = TRUE; + + lib_signals_ioloop_switch(); + + /* check whether we can now handle any shadowed delayed signals */ + signal_check_shadowed(); +} + +static void lib_signals_ioloop_destroyed(struct ioloop *ioloop) +{ + struct signal_ioloop *sig_ioloop; + + sig_ioloop = lib_signals_ioloop_find(ioloop); + if (sig_ioloop != NULL) { + io_remove(&sig_ioloop->io); + sig_ioloop->ioloop = NULL; + } +} + +void lib_signals_ioloop_detach(void) +{ + struct signal_handler *h; + + for (int signo = 0; signo < MAX_SIGNAL_VALUE; signo++) { + for (h = signal_handlers[signo]; h != NULL; h = h->next) { + if (h->sig_ioloop != NULL) { + lib_signals_ioloop_unref(&h->sig_ioloop); + have_missing_ioloops = TRUE; + } + } + } +} + +void lib_signals_ioloop_attach(void) +{ + if (have_missing_ioloops) + lib_signals_ioloop_switch(); +} + +static void lib_signals_set(int signo, enum libsig_flags flags) +{ + struct sigaction act; + + if (sigemptyset(&act.sa_mask) < 0) + i_fatal("sigemptyset(): %m"); +#ifdef SA_SIGINFO + act.sa_flags = SA_SIGINFO; + act.sa_sigaction = sig_handler; +#else + act.sa_flags = 0; + act.sa_handler = sig_handler; +#endif + if ((flags & LIBSIG_FLAG_RESTART) != 0) + act.sa_flags |= SA_RESTART; + if (sigaction(signo, &act, NULL) < 0) + i_fatal("sigaction(%d): %m", signo); +} + +void lib_signals_set_handler(int signo, enum libsig_flags flags, + signal_handler_t *handler, void *context) +{ + struct signal_handler *h; + + i_assert(handler != NULL); + + if (signo < 0 || signo > MAX_SIGNAL_VALUE) { + i_panic("Trying to set signal %d handler, but max is %d", + signo, MAX_SIGNAL_VALUE); + } + + if (signal_handlers[signo] == NULL && signals_initialized) + lib_signals_set(signo, flags); + + h = i_new(struct signal_handler, 1); + h->handler = handler; + h->context = context; + h->flags = flags; + + /* atomically set to signal_handlers[] list */ + h->next = signal_handlers[signo]; + signal_handlers[signo] = h; + + if ((flags & LIBSIG_FLAG_DELAYED) != 0 && sig_pipe_fd[0] == -1) { + /* first delayed handler */ + if (pipe(sig_pipe_fd) < 0) + i_fatal("pipe() failed: %m"); + fd_set_nonblock(sig_pipe_fd[0], TRUE); + fd_set_nonblock(sig_pipe_fd[1], TRUE); + fd_close_on_exec(sig_pipe_fd[0], TRUE); + fd_close_on_exec(sig_pipe_fd[1], TRUE); + } + signal_handler_switch_ioloop(h); +} + +static void lib_signals_ignore_forced(int signo, bool restart_syscalls) +{ + struct sigaction act; + + if (sigemptyset(&act.sa_mask) < 0) + i_fatal("sigemptyset(): %m"); + if (restart_syscalls) { + act.sa_flags = SA_RESTART; + act.sa_handler = SIG_IGN; + } else { +#ifdef SA_SIGINFO + act.sa_flags = SA_SIGINFO; + act.sa_sigaction = sig_ignore; +#else + act.sa_flags = 0; + act.sa_handler = sig_ignore; +#endif + } + + if (sigaction(signo, &act, NULL) < 0) + i_fatal("sigaction(%d): %m", signo); +} + +void lib_signals_ignore(int signo, bool restart_syscalls) +{ + if (signo < 0 || signo > MAX_SIGNAL_VALUE) { + i_panic("Trying to ignore signal %d, but max is %d", + signo, MAX_SIGNAL_VALUE); + } + + i_assert(signal_handlers[signo] == NULL); + + lib_signals_ignore_forced(signo, restart_syscalls); +} + +void lib_signals_clear_handlers_and_ignore(int signo) +{ + struct signal_handler *h; + + if (signal_handlers[signo] == NULL) + return; + + lib_signals_ignore_forced(signo, TRUE); + + h = signal_handlers[signo]; + signal_handlers[signo] = NULL; + + while (h != NULL) { + struct signal_handler *h_next = h->next; + + if (h->expected) + signals_expected--; + signal_handler_free(h); + h = h_next; + } +} + +void lib_signals_unset_handler(int signo, signal_handler_t *handler, + void *context) +{ + struct signal_handler *h, **p; + + for (p = &signal_handlers[signo]; *p != NULL; p = &(*p)->next) { + if ((*p)->handler == handler && (*p)->context == context) { + if (p == &signal_handlers[signo] && + (*p)->next == NULL) { + /* last handler is to be removed */ + lib_signals_ignore_forced(signo, TRUE); + } + h = *p; + *p = h->next; + if (h->expected) + lib_signals_update_expected_signals(FALSE); + signal_handler_free(h); + return; + } + } + + i_panic("lib_signals_unset_handler(%d, %p, %p): handler not found", + signo, (void *)handler, context); +} + +void lib_signals_set_expected(int signo, bool expected, + signal_handler_t *handler, void *context) +{ + struct signal_handler *h; + + for (h = signal_handlers[signo]; h != NULL; h = h->next) { + if (h->handler == handler && h->context == context) { + if (h->expected == expected) + return; + h->expected = expected; + lib_signals_update_expected_signals(expected); + return; + } + } + + i_panic("lib_signals_set_expected(%d, %p, %p): handler not found", + signo, (void *)handler, context); +} + +void lib_signals_switch_ioloop(int signo, + signal_handler_t *handler, void *context) +{ + struct signal_handler *h; + + for (h = signal_handlers[signo]; h != NULL; h = h->next) { + if (h->handler == handler && h->context == context) { + i_assert((h->flags & LIBSIG_FLAG_DELAYED) != 0); + i_assert((h->flags & LIBSIG_FLAG_IOLOOP_AUTOMOVE) == 0); + signal_handler_switch_ioloop(h); + /* check whether we can now handle any shadowed delayed + signals */ + signal_check_shadowed(); + return; + } + } + + i_panic("lib_signals_switch_ioloop(%d, %p, %p): handler not found", + signo, (void *)handler, context); +} + +void lib_signals_syscall_error(const char *prefix) +{ + /* @UNSAFE: We're in a signal handler. It's very limited what is + allowed in here. Especially strerror() isn't at least officially + allowed. */ + char errno_buf[MAX_INT_STRLEN], *errno_str; + errno_str = dec2str_buf(errno_buf, errno); + + size_t prefix_len = strlen(prefix); + size_t errno_str_len = strlen(errno_str); + char buf[prefix_len + errno_str_len + 1]; + + memcpy(buf, prefix, prefix_len); + memcpy(buf + prefix_len, errno_str, errno_str_len); + buf[prefix_len + errno_str_len] = '\n'; + if (write_full(STDERR_FILENO, buf, + prefix_len + errno_str_len + 1) < 0) { + /* can't really do anything */ + } +} + +void lib_signals_init(void) +{ + int i; + + signals_initialized = TRUE; + io_loop_add_switch_callback(lib_signals_ioloop_switched); + io_loop_add_destroy_callback(lib_signals_ioloop_destroyed); + + /* add signals that were already registered */ + for (i = 0; i < MAX_SIGNAL_VALUE; i++) { + if (signal_handlers[i] != NULL) + lib_signals_set(i, signal_handlers[i]->flags); + } +} + +void lib_signals_deinit(void) +{ + int i; + + for (i = 0; i < MAX_SIGNAL_VALUE; i++) { + if (signal_handlers[i] != NULL) + lib_signals_clear_handlers_and_ignore(i); + } + i_assert(signals_expected == 0); + + if (sig_pipe_fd[0] != -1) { + if (close(sig_pipe_fd[0]) < 0) + i_error("close(sigpipe) failed: %m"); + if (close(sig_pipe_fd[1]) < 0) + i_error("close(sigpipe) failed: %m"); + sig_pipe_fd[0] = sig_pipe_fd[1] = -1; + } + + if (array_is_created(&pending_shadowed_signals)) + array_free(&pending_shadowed_signals); + i_assert(signal_ioloops == NULL); +} |