summaryrefslogtreecommitdiff
path: root/examples/dte/signals.c
blob: e1a7155440397888954649b48ce63764f91b77b1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
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);
}