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