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