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);
}
|