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