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