Notcurses 3.0.16
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
61// saved signal actions, restored in drop_signals() FIXME make an array
62static struct sigaction old_winch;
63static struct sigaction old_cont;
64static struct sigaction old_abrt;
65static struct sigaction old_bus;
66static struct sigaction old_fpe;
67static struct sigaction old_ill;
68static struct sigaction old_int;
69static struct sigaction old_quit;
70static struct sigaction old_segv;
71static struct sigaction old_term;
72
73// Signals we block when we start writing out a frame, so as not to be
74// interrupted in media res (interrupting an escape can lock up a terminal).
75// Prepared in setup_signals(), and never changes across our lifetime.
76static sigset_t wblock_signals;
77
78// fatal handler callback, takes notcurses struct, pointer to altstack,
79// and return value override.
80static int(*fatal_callback)(void*, void**, int);
81
82int block_signals(sigset_t* old_blocked_signals){
83 if(pthread_sigmask(SIG_BLOCK, &wblock_signals, old_blocked_signals)){
84 return -1;
85 }
86 return 0;
87}
88
89int unblock_signals(const sigset_t* old_blocked_signals){
90 if(pthread_sigmask(SIG_SETMASK, old_blocked_signals, NULL)){
91 return -1;
92 }
93 return 0;
94}
95
96// we don't want to run our signal handlers (especially those for fatal
97// signals) once library shutdown has started. we need to leave any
98// alternate signal stack around, however, because musl rather dubiously
99// interprets POSIX's sigaltstack() definition to imply our alternate stack
100// a wildfire suitable for dousing with the hosting libc's adolescent seed.
101// see https://github.com/dankamongmen/notcurses/issues/2828. our child
102// threads still have the alternate stack configured, so we must leave it
103// up. callers ought thus free *altstack at the very end of things.
104int drop_signals(void* nc, void** altstack){
105 int ret = -1;
106 void* expected = nc;
107 if(!altstack){ // came in via signal handler
108 return 0;
109 }
110 *altstack = NULL;
111 pthread_mutex_lock(&lock);
112 if(atomic_compare_exchange_strong(&signal_nc, &expected, nc)){
113 if(handling_winch){
114 sigaction(SIGWINCH, &old_winch, NULL);
115 sigaction(SIGCONT, &old_cont, NULL);
116 handling_winch = false;
117 }
118 if(handling_fatals){
119 sigaction(SIGABRT, &old_abrt, NULL);
120 sigaction(SIGBUS, &old_bus, NULL);
121 sigaction(SIGFPE, &old_fpe, NULL);
122 sigaction(SIGILL, &old_ill, NULL);
123 sigaction(SIGINT, &old_int, NULL);
124 sigaction(SIGQUIT, &old_quit, NULL);
125 sigaction(SIGSEGV, &old_segv, NULL);
126 sigaction(SIGTERM, &old_term, NULL);
127 handling_fatals = false;
128 }
129 if(alt_signal_stack.ss_sp){
130 alt_signal_stack.ss_flags = SS_DISABLE;
131 if(sigaltstack(&alt_signal_stack, NULL)){
132 if(errno != EPERM){
133 fprintf(stderr, "couldn't remove alternate signal stack (%s)", strerror(errno));
134 }
135 }
136 }
137 *altstack = alt_signal_stack.ss_sp;
138 alt_signal_stack.ss_sp = NULL;
139 ret = !atomic_compare_exchange_strong(&signal_nc, &expected, NULL);
140 }
141 pthread_mutex_unlock(&lock);
142 if(ret){
143 fprintf(stderr, "signals weren't registered for %p (had %p)", nc, expected);
144 }
145 // we might not have established any handlers in setup_signals(); always
146 // return 0 here, for now...
147 return 0;
148}
149
150static void
151invoke_old(const struct sigaction* old, int signo, siginfo_t* sinfo, void* v){
152 if(old->sa_sigaction){
153 old->sa_sigaction(signo, sinfo, v);
154 }else if(old->sa_handler){
155 old->sa_handler(signo);
156 }
157}
158
159// this wildly unsafe handler will attempt to restore the screen upon receipt
160// of SIG{ILL, INT, SEGV, ABRT, QUIT, TERM}. godspeed you, black emperor!
161static void
162fatal_handler(int signo, siginfo_t* siginfo, void* v){
163 notcurses* nc = atomic_load(&signal_nc);
164 if(nc){
165 fatal_callback(nc, NULL, signo); // fuck the alt stack save yourselves
166 switch(signo){
167 case SIGTERM: invoke_old(&old_term, signo, siginfo, v); break;
168 case SIGSEGV: invoke_old(&old_segv, signo, siginfo, v); break;
169 case SIGQUIT: invoke_old(&old_quit, signo, siginfo, v); break;
170 case SIGINT: invoke_old(&old_int, signo, siginfo, v); break;
171 case SIGILL: invoke_old(&old_ill, signo, siginfo, v); break;
172 case SIGFPE: invoke_old(&old_fpe, signo, siginfo, v); break;
173 case SIGBUS: invoke_old(&old_bus, signo, siginfo, v); break;
174 case SIGABRT: invoke_old(&old_abrt, signo, siginfo, v); break;
175 }
176 raise(signo); // FIXME does this invoke twice? hrmmm
177 }
178}
179
180// the alternate signal stack is a thread property; any other threads we
181// create ought go ahead and install the same alternate signal stack.
183 pthread_mutex_lock(&lock);
184 if(alt_signal_stack.ss_sp){
185 if(sigaltstack(&alt_signal_stack, NULL)){
186 logerror("error installing alternate signal stack (%s)", strerror(errno));
187 }
188 }
189 pthread_mutex_unlock(&lock);
190}
191
192// this both sets up our signal handlers (unless that behavior has been
193// inhibited), and ensures that only one notcurses/ncdirect context is active
194// at any given time.
195int setup_signals(void* vnc, bool no_quit_sigs, bool no_winch_sigs,
196 int(*handler)(void*, void**, int)){
197 notcurses* nc = vnc;
198 void* expected = NULL;
199 struct sigaction sa;
200 // don't register ourselves if we don't intend to set up signal handlers
201 // we expect NULL (nothing registered), and want to register nc
202 if(!atomic_compare_exchange_strong(&signal_nc, &expected, nc)){
203 fprintf(stderr, "%p is already registered for signals (provided %p)" NL, expected, nc);
204 return -1;
205 }
206 pthread_mutex_lock(&lock);
207 if(!no_winch_sigs){
208 memset(&sa, 0, sizeof(sa));
209 sa.sa_handler = sigwinch_handler;
210 sigaddset(&sa.sa_mask, SIGWINCH);
211 sigaddset(&sa.sa_mask, SIGCONT);
212 int ret = 0;
213 ret |= sigaction(SIGWINCH, &sa, &old_winch);
214 ret |= sigaction(SIGCONT, &sa, &old_cont);
215 if(ret){
216 atomic_store(&signal_nc, NULL);
217 pthread_mutex_unlock(&lock);
218 fprintf(stderr, "error installing term signal handler (%s)" NL, strerror(errno));
219 return -1;
220 }
221 // we're not going to be restoring the old mask at exit, as who knows,
222 // they might have masked more things afterwards.
223 pthread_sigmask(SIG_BLOCK, &sa.sa_mask, NULL);
224 handling_winch = true;
225 }
226 if(!no_quit_sigs){
227 memset(&sa, 0, sizeof(sa));
228// AddressSanitizer doesn't want us to use sigaltstack(). we could force everyone
229// to export ASAN_OPTIONS=use_sigaltstack=0, or just not fuck with the alternate
230// signal stack when built with ASAN.
231#ifndef USE_ASAN
232#ifdef _SC_SIGSTKSZ
233 long minstksz = sysconf(_SC_SIGSTKSZ);
234#else
235 long minstksz = 0;
236#endif
237 if(minstksz <= 0){
238 minstksz = SIGSTKSZ * 4;
239 }
240 alt_signal_stack.ss_sp = malloc(minstksz);
241 if(alt_signal_stack.ss_sp == NULL){
242 fprintf(stderr, "warning: couldn't create %ldB alternate signal stack (%s)" NL, minstksz, strerror(errno));
243 }else{
244 alt_signal_stack.ss_size = minstksz;
245 if(sigaltstack(&alt_signal_stack, NULL)){
246 fprintf(stderr, "warning: couldn't set up %ldB alternate signal stack (%s)" NL, minstksz, strerror(errno));
247 free(alt_signal_stack.ss_sp);
248 alt_signal_stack.ss_sp = NULL;
249 alt_signal_stack.ss_size = 0;
250 }else{
251 sa.sa_flags = SA_ONSTACK;
252 }
253 }
254#endif
255 fatal_callback = handler;
256 sa.sa_sigaction = fatal_handler;
257 sigaddset(&sa.sa_mask, SIGABRT);
258 sigaddset(&sa.sa_mask, SIGBUS);
259 sigaddset(&sa.sa_mask, SIGFPE);
260 sigaddset(&sa.sa_mask, SIGILL);
261 sigaddset(&sa.sa_mask, SIGINT);
262 sigaddset(&sa.sa_mask, SIGQUIT);
263 sigaddset(&sa.sa_mask, SIGSEGV);
264 sigaddset(&sa.sa_mask, SIGTERM);
265 // don't try to handle fatal signals twice, and use our alternative stack
266 sa.sa_flags |= SA_SIGINFO | SA_RESETHAND;
267 int ret = 0;
268 ret |= sigaction(SIGABRT, &sa, &old_abrt);
269 ret |= sigaction(SIGBUS, &sa, &old_bus);
270 ret |= sigaction(SIGFPE, &sa, &old_fpe);
271 ret |= sigaction(SIGILL, &sa, &old_ill);
272 ret |= sigaction(SIGINT, &sa, &old_int);
273 ret |= sigaction(SIGQUIT, &sa, &old_quit);
274 ret |= sigaction(SIGSEGV, &sa, &old_segv);
275 ret |= sigaction(SIGTERM, &sa, &old_term);
276 if(ret){
277 atomic_store(&signal_nc, NULL);
278 pthread_mutex_unlock(&lock);
279 fprintf(stderr, "error installing fatal signal handlers (%s)" NL, strerror(errno));
280 return -1;
281 }
282 handling_fatals = true;
283 }
284 // we don't really want to go blocking SIGSEGV etc while we write, but
285 // we *do* temporarily block user-initiated signals.
286 sigaddset(&wblock_signals, SIGINT);
287 sigaddset(&wblock_signals, SIGTERM);
288 sigaddset(&wblock_signals, SIGQUIT);
289 pthread_mutex_unlock(&lock);
290 return 0;
291}
292#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
return NULL
Definition termdesc.h:229
int block_signals(sigset_t *old_blocked_signals)
Definition unixsig.c:82
int setup_signals(void *vnc, bool no_quit_sigs, bool no_winch_sigs, int(*handler)(void *, void **, int))
Definition unixsig.c:195
int unblock_signals(const sigset_t *old_blocked_signals)
Definition unixsig.c:89
int drop_signals(void *nc, void **altstack)
Definition unixsig.c:104
void setup_alt_sig_stack(void)
Definition unixsig.c:182