Notcurses 3.0.17
a blingful library for TUIs and character graphics
Loading...
Searching...
No Matches
unixsig.c
Go to the documentation of this file.
1#include <stdio.h>
2#include <signal.h>
3#include <stdatomic.h>
4#include "internal.h"
5
6// primarily drive ownership off an atomic, safely used within a signal handler
7static void* _Atomic signal_nc;
8
9#ifdef __MINGW32__
10int block_signals(sigset_t* old_blocked_signals){
11 (void)old_blocked_signals;
12 return 0;
13}
14
15int unblock_signals(const sigset_t* old_blocked_signals){
16 (void)old_blocked_signals;
17 return 0;
18}
19
20int drop_signals(void* nc, void** altstack){
21 void* expected = nc;
22 if(!altstack){
23 return 0;
24 }
25 if(!atomic_compare_exchange_strong(&signal_nc, &expected, NULL)){
26 return -1;
27 }
28 return 0;
29}
30
31// this both sets up our signal handlers (unless that behavior has been
32// inhibited), and ensures that only one notcurses/ncdirect context is active
33// at any given time.
34int setup_signals(void* vnc, bool no_quit_sigs, bool no_winch_sigs,
35 int(*handler)(void*, void**, int)){
36 (void)no_quit_sigs;
37 (void)no_winch_sigs;
38 (void)handler;
39 void* expected = NULL;
40 // don't register ourselves if we don't intend to set up signal handlers
41 // we expect NULL (nothing registered), and want to register nc
42 if(!atomic_compare_exchange_strong(&signal_nc, &expected, vnc)){
43 logpanic("%p is already registered for signals (provided %p)", expected, vnc);
44 return -1;
45 }
46 return 0;
47}
48
49void setup_alt_sig_stack(void){}
50#else
51// only one notcurses object can be the target of signal handlers, due to their
52// process-wide nature. hold this lock over any of the shared data below.
53static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
54
55static bool handling_winch;
56static bool handling_fatals;
57
58// alternate signal stack (per-thread; call setup_alt_sig_stack() to use)
59static stack_t alt_signal_stack;
60
61struct atermsig {
62 int sig;
63 struct sigaction oldfxn;
64};
65
66static struct atermsig fatal_signals[] = {
67 { SIGABRT, {}, },
68 { SIGBUS, {}, },
69 { SIGFPE, {}, },
70 { SIGILL, {}, },
71 { SIGINT, {}, },
72 { SIGQUIT, {}, },
73 { SIGSEGV, {}, },
74 { SIGTERM, {}, },
75};
76// saved non-fatal signal actions, restored in drop_signals()
77static struct sigaction old_winch;
78static struct sigaction old_cont;
79
80// Signals we block when we start writing out a frame, so as not to be
81// interrupted in media res (interrupting an escape can lock up a terminal).
82// Prepared in setup_signals(), and never changes across our lifetime.
83static sigset_t wblock_signals;
84
85// fatal handler callback, takes notcurses struct, pointer to altstack,
86// and return value override.
87static int(*fatal_callback)(void*, void**, int);
88
89int block_signals(sigset_t* old_blocked_signals){
90 if(pthread_sigmask(SIG_BLOCK, &wblock_signals, old_blocked_signals)){
91 return -1;
92 }
93 return 0;
94}
95
96int unblock_signals(const sigset_t* old_blocked_signals){
97 if(pthread_sigmask(SIG_SETMASK, old_blocked_signals, NULL)){
98 return -1;
99 }
100 return 0;
101}
102
103// we don't want to run our signal handlers (especially those for fatal
104// signals) once library shutdown has started. we need to leave any
105// alternate signal stack around, however, because musl rather dubiously
106// interprets POSIX's sigaltstack() definition to imply our alternate stack
107// a wildfire suitable for dousing with the hosting libc's adolescent seed.
108// see https://github.com/dankamongmen/notcurses/issues/2828. our child
109// threads still have the alternate stack configured, so we must leave it
110// up. callers ought thus free *altstack at the very end of things.
111int drop_signals(void* nc, void** altstack){
112 int ret = -1;
113 void* expected = nc;
114 if(!altstack){ // came in via signal handler
115 return 0;
116 }
117 *altstack = NULL;
118 pthread_mutex_lock(&lock);
119 if(atomic_compare_exchange_strong(&signal_nc, &expected, nc)){
120 if(handling_winch){
121 sigaction(SIGWINCH, &old_winch, NULL);
122 sigaction(SIGCONT, &old_cont, NULL);
123 handling_winch = false;
124 }
125 if(handling_fatals){
126 for(unsigned i = 0 ; i < sizeof(fatal_signals) / sizeof(*fatal_signals) ; ++i){
127 sigaction(fatal_signals[i].sig, &fatal_signals[i].oldfxn, NULL);
128 }
129 handling_fatals = false;
130 }
131 if(alt_signal_stack.ss_sp){
132 alt_signal_stack.ss_flags = SS_DISABLE;
133 if(sigaltstack(&alt_signal_stack, NULL)){
134 if(errno != EPERM){
135 fprintf(stderr, "couldn't remove alternate signal stack (%s)", strerror(errno));
136 }
137 }
138 }
139 *altstack = alt_signal_stack.ss_sp;
140 alt_signal_stack.ss_sp = NULL;
141 ret = !atomic_compare_exchange_strong(&signal_nc, &expected, NULL);
142 }
143 pthread_mutex_unlock(&lock);
144 if(ret){
145 fprintf(stderr, "signals weren't registered for %p (had %p)", nc, expected);
146 }
147 // we might not have established any handlers in setup_signals(); always
148 // return 0 here, for now...
149 return 0;
150}
151
152static void
153invoke_old(const struct sigaction* old, int signo, siginfo_t* sinfo, void* v){
154 if(old->sa_sigaction){
155 old->sa_sigaction(signo, sinfo, v);
156 }else if(old->sa_handler){
157 old->sa_handler(signo);
158 }
159}
160
161// this wildly unsafe handler will attempt to restore the screen upon receipt
162// of SIG{ILL, INT, SEGV, ABRT, QUIT, TERM}. godspeed you, black emperor!
163static void
164fatal_handler(int signo, siginfo_t* siginfo, void* v){
165 notcurses* nc = atomic_load(&signal_nc);
166 if(nc){
167 fatal_callback(nc, NULL, signo); // fuck the alt stack save yourselves
168 for(unsigned i = 0 ; i < sizeof(fatal_signals) / sizeof(*fatal_signals) ; ++i){
169 if(signo == fatal_signals[i].sig){
170 invoke_old(&fatal_signals[i].oldfxn, signo, siginfo, v);
171 }
172 }
173 raise(signo); // FIXME does this invoke twice? hrmmm
174 }
175}
176
177// the alternate signal stack is a thread property; any other threads we
178// create ought go ahead and install the same alternate signal stack.
180 pthread_mutex_lock(&lock);
181 if(alt_signal_stack.ss_sp){
182 if(sigaltstack(&alt_signal_stack, NULL)){
183 logerror("error installing alternate signal stack (%s)", strerror(errno));
184 }
185 }
186 pthread_mutex_unlock(&lock);
187}
188
189// this both sets up our signal handlers (unless that behavior has been
190// inhibited), and ensures that only one notcurses/ncdirect context is active
191// at any given time.
192int setup_signals(void* vnc, bool no_quit_sigs, bool no_winch_sigs,
193 int(*handler)(void*, void**, int)){
194 notcurses* nc = vnc;
195 void* expected = NULL;
196 struct sigaction sa;
197 // don't register ourselves if we don't intend to set up signal handlers
198 // we expect NULL (nothing registered), and want to register nc
199 if(!atomic_compare_exchange_strong(&signal_nc, &expected, nc)){
200 fprintf(stderr, "%p is already registered for signals (provided %p)" NL, expected, nc);
201 return -1;
202 }
203 pthread_mutex_lock(&lock);
204 if(!no_winch_sigs){
205 memset(&sa, 0, sizeof(sa));
206 sa.sa_handler = sigwinch_handler;
207 sigaddset(&sa.sa_mask, SIGWINCH);
208 sigaddset(&sa.sa_mask, SIGCONT);
209 int ret = 0;
210 ret |= sigaction(SIGWINCH, &sa, &old_winch);
211 ret |= sigaction(SIGCONT, &sa, &old_cont);
212 if(ret){
213 atomic_store(&signal_nc, NULL);
214 pthread_mutex_unlock(&lock);
215 fprintf(stderr, "error installing term signal handler (%s)" NL, strerror(errno));
216 return -1;
217 }
218 // we're not going to be restoring the old mask at exit, as who knows,
219 // they might have masked more things afterwards.
220 pthread_sigmask(SIG_BLOCK, &sa.sa_mask, NULL);
221 handling_winch = true;
222 }
223 if(!no_quit_sigs){
224 memset(&sa, 0, sizeof(sa));
225// AddressSanitizer doesn't want us to use sigaltstack(). we could force everyone
226// to export ASAN_OPTIONS=use_sigaltstack=0, or just not fuck with the alternate
227// signal stack when built with ASAN.
228#ifndef USE_ASAN
229#ifdef _SC_SIGSTKSZ
230 long minstksz = sysconf(_SC_SIGSTKSZ);
231#else
232 long minstksz = 0;
233#endif
234 if(minstksz <= 0){
235 minstksz = SIGSTKSZ * 4;
236 }
237 alt_signal_stack.ss_sp = malloc(minstksz);
238 if(alt_signal_stack.ss_sp == NULL){
239 fprintf(stderr, "warning: couldn't create %ldB alternate signal stack (%s)" NL, minstksz, strerror(errno));
240 }else{
241 alt_signal_stack.ss_size = minstksz;
242 if(sigaltstack(&alt_signal_stack, NULL)){
243 fprintf(stderr, "warning: couldn't set up %ldB alternate signal stack (%s)" NL, minstksz, strerror(errno));
244 free(alt_signal_stack.ss_sp);
245 alt_signal_stack.ss_sp = NULL;
246 alt_signal_stack.ss_size = 0;
247 }else{
248 sa.sa_flags = SA_ONSTACK;
249 }
250 }
251#endif
252 fatal_callback = handler;
253 sa.sa_sigaction = fatal_handler;
254 for(unsigned i = 0 ; i < sizeof(fatal_signals) / sizeof(*fatal_signals) ; ++i){
255 sigaddset(&sa.sa_mask, fatal_signals[i].sig);
256 }
257 // don't try to handle fatal signals twice, and use our alternative stack
258 sa.sa_flags |= SA_SIGINFO | SA_RESETHAND;
259 int ret = 0;
260 for(unsigned i = 0 ; i < sizeof(fatal_signals) / sizeof(*fatal_signals) ; ++i){
261 ret |= sigaction(fatal_signals[i].sig, &sa, &fatal_signals[i].oldfxn);
262 }
263 if(ret){
264 atomic_store(&signal_nc, NULL);
265 pthread_mutex_unlock(&lock);
266 fprintf(stderr, "error installing fatal signal handlers (%s)" NL, strerror(errno));
267 return -1;
268 }
269 handling_fatals = true;
270 }
271 // we don't really want to go blocking SIGSEGV etc while we write, but
272 // we *do* temporarily block user-initiated signals.
273 sigaddset(&wblock_signals, SIGINT);
274 sigaddset(&wblock_signals, SIGTERM);
275 sigaddset(&wblock_signals, SIGQUIT);
276 pthread_mutex_unlock(&lock);
277 return 0;
278}
279#endif
void sigwinch_handler(int signo)
Definition in.c:31
#define logerror(fmt,...)
Definition logging.h:32
#define logpanic(fmt,...)
Definition logging.h:22
struct ncvisual_options v
Definition notcurses.h:3501
int sig
Definition unixsig.c:62
struct sigaction oldfxn
Definition unixsig.c:63
return NULL
Definition termdesc.h:228
int block_signals(sigset_t *old_blocked_signals)
Definition unixsig.c:89
int setup_signals(void *vnc, bool no_quit_sigs, bool no_winch_sigs, int(*handler)(void *, void **, int))
Definition unixsig.c:192
int unblock_signals(const sigset_t *old_blocked_signals)
Definition unixsig.c:96
int drop_signals(void *nc, void **altstack)
Definition unixsig.c:111
void setup_alt_sig_stack(void)
Definition unixsig.c:179