diff options
Diffstat (limited to 'examples/dte/signals.c')
| -rw-r--r-- | examples/dte/signals.c | 169 |
1 files changed, 169 insertions, 0 deletions
diff --git a/examples/dte/signals.c b/examples/dte/signals.c new file mode 100644 index 0000000..e1a7155 --- /dev/null +++ b/examples/dte/signals.c @@ -0,0 +1,169 @@ +#include "compat.h" +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include "signals.h" +#include "util/debug.h" +#include "util/exitcode.h" +#include "util/log.h" +#include "util/macros.h" + +volatile sig_atomic_t resized = 0; + +static const int ignored_signals[] = { + SIGINT, // Terminal interrupt (see: VINTR in termios(3)) + SIGQUIT, // Terminal quit (see: VQUIT in termios(3)) + SIGTSTP, // Terminal stop (see: VSUSP in termios(3)) + SIGXFSZ, // File size limit exceeded (see: RLIMIT_FSIZE in getrlimit(3)) + SIGPIPE, // Broken pipe (see: EPIPE error in write(3)) + SIGUSR1, // User signal 1 (terminates by default, for no good reason) + SIGUSR2, // User signal 2 (as above) +}; + +static const int default_signals[] = { + SIGABRT, // Terminate (cleanup already done) + SIGCHLD, // Ignore (see: wait(3)) + SIGURG, // Ignore + SIGTTIN, // Stop + SIGTTOU, // Stop + SIGCONT, // Continue +}; + +static const int fatal_signals[] = { + SIGBUS, + SIGFPE, + SIGILL, + SIGSEGV, + SIGSYS, + SIGTRAP, + SIGXCPU, + SIGALRM, + SIGVTALRM, + SIGHUP, + SIGTERM, +#ifdef SIGPROF + SIGPROF, +#endif +#ifdef SIGEMT + SIGEMT, +#endif +}; + +void handle_sigwinch(int UNUSED_ARG(signum)) +{ + resized = 1; +} + +static noreturn COLD void handle_fatal_signal(int signum) +{ + LOG_CRITICAL("received signal %d (%s)", signum, strsignal(signum)); + + // If `signum` is SIGHUP, there's no point in trying to clean up the + // state of the (disconnected) terminal + if (signum != SIGHUP) { + fatal_error_cleanup(); + } + + // Restore and unblock `signum` and then re-raise it, to ensure the + // termination status (as seen by e.g. waitpid(3) in the parent) is + // set appropriately + struct sigaction sa = {.sa_handler = SIG_DFL}; + if ( + sigemptyset(&sa.sa_mask) == 0 + && sigaction(signum, &sa, NULL) == 0 + && sigaddset(&sa.sa_mask, signum) == 0 + && sigprocmask(SIG_UNBLOCK, &sa.sa_mask, NULL) == 0 + ) { + raise(signum); + } + + // This is here just to make extra certain the handler never returns. + // If everything is working correctly, this code should be unreachable. + raise(SIGKILL); + _exit(EX_OSERR); +} + +// strsignal(3) is fine in situations where a signal is being reported +// as terminating a process, but it tends to be confusing in most other +// circumstances, where the signal name (not description) is usually +// clearer +static const char *signum_to_str(int signum) +{ +#if HAVE_SIG2STR + static char buf[SIG2STR_MAX + 3]; + if (sig2str(signum, buf + 3) == 0) { + return memcpy(buf, "SIG", 3); + } +#elif HAVE_SIGABBREV_NP + static char buf[16]; + const char *abbr = sigabbrev_np(signum); + if (abbr && memccpy(buf + 3, abbr, '\0', sizeof(buf) - 3)) { + return memcpy(buf, "SIG", 3); + } +#endif + + const char *str = strsignal(signum); + return likely(str) ? str : "??"; +} + +static void do_sigaction(int sig, const struct sigaction *action) +{ + struct sigaction old_action; + if (unlikely(sigaction(sig, action, &old_action) != 0)) { + const char *err = strerror(errno); + LOG_ERROR("failed to set disposition for signal %d: %s", sig, err); + return; + } + if (unlikely(old_action.sa_handler == SIG_IGN)) { + const char *str = signum_to_str(sig); + LOG_WARNING("ignored signal was inherited: %d (%s)", sig, str); + } +} + +/* + * "A program that uses these functions should be written to catch all + * signals and take other appropriate actions to ensure that when the + * program terminates, whether planned or not, the terminal device's + * state is restored to its original state." + * + * (https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetattr.html) + */ +void set_signal_handlers(void) +{ + struct sigaction action = {.sa_handler = handle_fatal_signal}; + sigfillset(&action.sa_mask); + for (size_t i = 0; i < ARRAYLEN(fatal_signals); i++) { + do_sigaction(fatal_signals[i], &action); + } + + // "The default actions for the realtime signals in the range SIGRTMIN + // to SIGRTMAX shall be to terminate the process abnormally." + // (POSIX.1-2017 ยง2.4.3) +#if defined(SIGRTMIN) && defined(SIGRTMAX) + for (int s = SIGRTMIN, max = SIGRTMAX; s <= max; s++) { + do_sigaction(s, &action); + } +#endif + + action.sa_handler = SIG_IGN; + for (size_t i = 0; i < ARRAYLEN(ignored_signals); i++) { + do_sigaction(ignored_signals[i], &action); + } + + action.sa_handler = SIG_DFL; + for (size_t i = 0; i < ARRAYLEN(default_signals); i++) { + do_sigaction(default_signals[i], &action); + } + +#if defined(SIGWINCH) + LOG_INFO("setting SIGWINCH handler"); + action.sa_handler = handle_sigwinch; + do_sigaction(SIGWINCH, &action); +#endif + + // Set signal mask explicitly, to avoid any possibility of + // inheriting blocked signals + sigset_t mask; + sigemptyset(&mask); + sigprocmask(SIG_SETMASK, &mask, NULL); +} |
