trap.c
1/* $OpenBSD: trap.c,v 1.33 2018/12/08 21:03:51 jca Exp $ */
2
3/*
4 * signal handling
5 */
6
7#include <ctype.h>
8#include <errno.h>
9#include <string.h>
10#include <unistd.h>
11
12#include "sh.h"
13
14Trap sigtraps[NSIG + 1];
15
16static struct sigaction Sigact_ign, Sigact_trap;
17
18#ifndef HAVE_SIGNAME
19extern const char *oksh_sig2str(int);
20#endif
21
22void
23inittraps(void)
24{
25 int i;
26
27 /* Populate sigtraps based on sys_signame and sys_siglist. */
28 for (i = 0; i <= NSIG; i++) {
29 sigtraps[i].signal = i;
30 if (i == SIGERR_) {
31 sigtraps[i].name = "ERR";
32 sigtraps[i].mess = "Error handler";
33 } else {
34#ifdef HAVE_SIGNAME
35 sigtraps[i].name = sys_signame[i];
36#else
37 sigtraps[i].name = oksh_sig2str(i);
38#endif
39#if defined(HAVE_SIGLIST) && !defined(__linux__)
40 sigtraps[i].mess = sys_siglist[i];
41#else
42 static char *mess[NSIG + 1] = { NULL };
43 if (!mess[i])
44 mess[i] = strdup(strsignal(i));
45 sigtraps[i].mess = mess[i];
46#endif
47 }
48 }
49 sigtraps[SIGEXIT_].name = "EXIT"; /* our name for signal 0 */
50
51 sigemptyset(&Sigact_ign.sa_mask);
52 Sigact_ign.sa_flags = 0; /* interruptible */
53 Sigact_ign.sa_handler = SIG_IGN;
54 Sigact_trap = Sigact_ign;
55 Sigact_trap.sa_handler = trapsig;
56
57 sigtraps[SIGINT].flags |= TF_DFL_INTR | TF_TTY_INTR;
58 sigtraps[SIGQUIT].flags |= TF_DFL_INTR | TF_TTY_INTR;
59 sigtraps[SIGTERM].flags |= TF_DFL_INTR;/* not fatal for interactive */
60 sigtraps[SIGHUP].flags |= TF_FATAL;
61 sigtraps[SIGCHLD].flags |= TF_SHELL_USES;
62
63 /* these are always caught so we can clean up any temporary files. */
64 setsig(&sigtraps[SIGINT], trapsig, SS_RESTORE_ORIG);
65 setsig(&sigtraps[SIGQUIT], trapsig, SS_RESTORE_ORIG);
66 setsig(&sigtraps[SIGTERM], trapsig, SS_RESTORE_ORIG);
67 setsig(&sigtraps[SIGHUP], trapsig, SS_RESTORE_ORIG);
68}
69
70static void alarm_catcher(int sig);
71
72void
73alarm_init(void)
74{
75 sigtraps[SIGALRM].flags |= TF_SHELL_USES;
76 setsig(&sigtraps[SIGALRM], alarm_catcher,
77 SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP);
78}
79
80static void
81alarm_catcher(int sig)
82{
83 int errno_ = errno;
84
85 if (ksh_tmout_state == TMOUT_READING) {
86 int left = alarm(0);
87
88 if (left == 0) {
89 ksh_tmout_state = TMOUT_LEAVING;
90 intrsig = 1;
91 } else
92 alarm(left);
93 }
94 errno = errno_;
95}
96
97Trap *
98gettrap(const char *name, int igncase)
99{
100 int i;
101 Trap *p;
102
103 if (digit(*name)) {
104 int n;
105
106 if (getn(name, &n) && 0 <= n && n < NSIG)
107 return &sigtraps[n];
108 return NULL;
109 }
110
111 if (igncase && strncasecmp(name, "SIG", 3) == 0)
112 name += 3;
113 if (!igncase && strncmp(name, "SIG", 3) == 0)
114 name += 3;
115
116 for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
117 if (p->name) {
118 if (igncase && strcasecmp(p->name, name) == 0)
119 return p;
120 if (!igncase && strcmp(p->name, name) == 0)
121 return p;
122 }
123 return NULL;
124}
125
126/*
127 * trap signal handler
128 */
129void
130trapsig(int i)
131{
132 Trap *p = &sigtraps[i];
133 int errno_ = errno;
134
135 trap = p->set = 1;
136 if (p->flags & TF_DFL_INTR)
137 intrsig = 1;
138 if ((p->flags & TF_FATAL) && !p->trap) {
139 fatal_trap = 1;
140 intrsig = 1;
141 }
142 if (p->shtrap)
143 (*p->shtrap)(i);
144 errno = errno_;
145}
146
147/* called when we want to allow the user to ^C out of something - won't
148 * work if user has trapped SIGINT.
149 */
150void
151intrcheck(void)
152{
153 if (intrsig)
154 runtraps(TF_DFL_INTR|TF_FATAL);
155}
156
157/* called after EINTR to check if a signal with normally causes process
158 * termination has been received.
159 */
160int
161fatal_trap_check(void)
162{
163 int i;
164 Trap *p;
165
166 /* todo: should check if signal is fatal, not the TF_DFL_INTR flag */
167 for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
168 if (p->set && (p->flags & (TF_DFL_INTR|TF_FATAL)))
169 /* return value is used as an exit code */
170 return 128 + p->signal;
171 return 0;
172}
173
174/* Returns the signal number of any pending traps: ie, a signal which has
175 * occurred for which a trap has been set or for which the TF_DFL_INTR flag
176 * is set.
177 */
178int
179trap_pending(void)
180{
181 int i;
182 Trap *p;
183
184 for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
185 if (p->set && ((p->trap && p->trap[0]) ||
186 ((p->flags & (TF_DFL_INTR|TF_FATAL)) && !p->trap)))
187 return p->signal;
188 return 0;
189}
190
191/*
192 * run any pending traps. If intr is set, only run traps that
193 * can interrupt commands.
194 */
195void
196runtraps(int flag)
197{
198 int i;
199 Trap *p;
200
201 if (ksh_tmout_state == TMOUT_LEAVING) {
202 ksh_tmout_state = TMOUT_EXECUTING;
203 warningf(false, "timed out waiting for input");
204 unwind(LEXIT);
205 } else
206 /* XXX: this means the alarm will have no effect if a trap
207 * is caught after the alarm() was started...not good.
208 */
209 ksh_tmout_state = TMOUT_EXECUTING;
210 if (!flag)
211 trap = 0;
212 if (flag & TF_DFL_INTR)
213 intrsig = 0;
214 if (flag & TF_FATAL)
215 fatal_trap = 0;
216 for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
217 if (p->set && (!flag ||
218 ((p->flags & flag) && p->trap == NULL)))
219 runtrap(p);
220}
221
222void
223runtrap(Trap *p)
224{
225 int i = p->signal;
226 char *trapstr = p->trap;
227 int oexstat;
228 int old_changed = 0;
229
230 p->set = 0;
231 if (trapstr == NULL) { /* SIG_DFL */
232 if (p->flags & TF_FATAL) {
233 /* eg, SIGHUP */
234 exstat = 128 + i;
235 unwind(LLEAVE);
236 }
237 if (p->flags & TF_DFL_INTR) {
238 /* eg, SIGINT, SIGQUIT, SIGTERM, etc. */
239 exstat = 128 + i;
240 unwind(LINTR);
241 }
242 return;
243 }
244 if (trapstr[0] == '\0') /* SIG_IGN */
245 return;
246 if (i == SIGEXIT_ || i == SIGERR_) { /* avoid recursion on these */
247 old_changed = p->flags & TF_CHANGED;
248 p->flags &= ~TF_CHANGED;
249 p->trap = NULL;
250 }
251 oexstat = exstat;
252 /* Note: trapstr is fully parsed before anything is executed, thus
253 * no problem with afree(p->trap) in settrap() while still in use.
254 */
255 command(trapstr, current_lineno);
256 exstat = oexstat;
257 if (i == SIGEXIT_ || i == SIGERR_) {
258 if (p->flags & TF_CHANGED)
259 /* don't clear TF_CHANGED */
260 afree(trapstr, APERM);
261 else
262 p->trap = trapstr;
263 p->flags |= old_changed;
264 }
265}
266
267/* clear pending traps and reset user's trap handlers; used after fork(2) */
268void
269cleartraps(void)
270{
271 int i;
272 Trap *p;
273
274 trap = 0;
275 intrsig = 0;
276 fatal_trap = 0;
277 for (i = NSIG+1, p = sigtraps; --i >= 0; p++) {
278 p->set = 0;
279 if ((p->flags & TF_USER_SET) && (p->trap && p->trap[0]))
280 settrap(p, NULL);
281 }
282}
283
284/* restore signals just before an exec(2) */
285void
286restoresigs(void)
287{
288 int i;
289 Trap *p;
290
291 for (i = NSIG+1, p = sigtraps; --i >= 0; p++)
292 if (p->flags & (TF_EXEC_IGN|TF_EXEC_DFL))
293 setsig(p, (p->flags & TF_EXEC_IGN) ? SIG_IGN : SIG_DFL,
294 SS_RESTORE_CURR|SS_FORCE);
295}
296
297void
298settrap(Trap *p, char *s)
299{
300 sig_t f;
301
302 afree(p->trap, APERM);
303 p->trap = str_save(s, APERM); /* handles s == 0 */
304 p->flags |= TF_CHANGED;
305 f = !s ? SIG_DFL : s[0] ? trapsig : SIG_IGN;
306
307 p->flags |= TF_USER_SET;
308 if ((p->flags & (TF_DFL_INTR|TF_FATAL)) && f == SIG_DFL)
309 f = trapsig;
310 else if (p->flags & TF_SHELL_USES) {
311 if (!(p->flags & TF_ORIG_IGN) || Flag(FTALKING)) {
312 /* do what user wants at exec time */
313 p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL);
314 if (f == SIG_IGN)
315 p->flags |= TF_EXEC_IGN;
316 else
317 p->flags |= TF_EXEC_DFL;
318 }
319
320 /* assumes handler already set to what shell wants it
321 * (normally trapsig, but could be j_sigchld() or SIG_IGN)
322 */
323 return;
324 }
325
326 /* todo: should we let user know signal is ignored? how? */
327 setsig(p, f, SS_RESTORE_CURR|SS_USER);
328}
329
330/* Called by c_print() when writing to a co-process to ensure SIGPIPE won't
331 * kill shell (unless user catches it and exits)
332 */
333int
334block_pipe(void)
335{
336 int restore_dfl = 0;
337 Trap *p = &sigtraps[SIGPIPE];
338
339 if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) {
340 setsig(p, SIG_IGN, SS_RESTORE_CURR);
341 if (p->flags & TF_ORIG_DFL)
342 restore_dfl = 1;
343 } else if (p->cursig == SIG_DFL) {
344 setsig(p, SIG_IGN, SS_RESTORE_CURR);
345 restore_dfl = 1; /* restore to SIG_DFL */
346 }
347 return restore_dfl;
348}
349
350/* Called by c_print() to undo whatever block_pipe() did */
351void
352restore_pipe(int restore_dfl)
353{
354 if (restore_dfl)
355 setsig(&sigtraps[SIGPIPE], SIG_DFL, SS_RESTORE_CURR);
356}
357
358/* Set action for a signal. Action may not be set if original
359 * action was SIG_IGN, depending on the value of flags and
360 * FTALKING.
361 */
362int
363setsig(Trap *p, sig_t f, int flags)
364{
365 struct sigaction sigact;
366
367 if (p->signal == SIGEXIT_ || p->signal == SIGERR_)
368 return 1;
369
370 /* First time setting this signal? If so, get and note the current
371 * setting.
372 */
373 if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) {
374 sigaction(p->signal, &Sigact_ign, &sigact);
375 p->flags |= sigact.sa_handler == SIG_IGN ?
376 TF_ORIG_IGN : TF_ORIG_DFL;
377 p->cursig = SIG_IGN;
378 }
379
380 /* Generally, an ignored signal stays ignored, except if
381 * - the user of an interactive shell wants to change it
382 * - the shell wants for force a change
383 */
384 if ((p->flags & TF_ORIG_IGN) && !(flags & SS_FORCE) &&
385 (!(flags & SS_USER) || !Flag(FTALKING)))
386 return 0;
387
388 setexecsig(p, flags & SS_RESTORE_MASK);
389
390 /* This is here 'cause there should be a way of clearing shtraps, but
391 * don't know if this is a sane way of doing it. At the moment,
392 * all users of shtrap are lifetime users (SIGCHLD, SIGALRM, SIGWINCH).
393 */
394 if (!(flags & SS_USER))
395 p->shtrap = NULL;
396 if (flags & SS_SHTRAP) {
397 p->shtrap = f;
398 f = trapsig;
399 }
400
401 if (p->cursig != f) {
402 p->cursig = f;
403 sigemptyset(&sigact.sa_mask);
404 sigact.sa_flags = 0 /* interruptible */;
405 sigact.sa_handler = f;
406 sigaction(p->signal, &sigact, NULL);
407 }
408
409 return 1;
410}
411
412/* control what signal is set to before an exec() */
413void
414setexecsig(Trap *p, int restore)
415{
416 /* XXX debugging */
417 if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL)))
418 internal_errorf("%s: unset signal %d(%s)",
419 __func__, p->signal, p->name);
420
421 /* restore original value for exec'd kids */
422 p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL);
423 switch (restore & SS_RESTORE_MASK) {
424 case SS_RESTORE_CURR: /* leave things as they currently are */
425 break;
426 case SS_RESTORE_ORIG:
427 p->flags |= p->flags & TF_ORIG_IGN ? TF_EXEC_IGN : TF_EXEC_DFL;
428 break;
429 case SS_RESTORE_DFL:
430 p->flags |= TF_EXEC_DFL;
431 break;
432 case SS_RESTORE_IGN:
433 p->flags |= TF_EXEC_IGN;
434 break;
435 }
436}