loksh-noxz

[fork] a Linux port of OpenBSD's ksh
git clone https://noxz.tech/git/loksh-noxz.git
Log | Files | README

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
18void
19inittraps(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
55static void alarm_catcher(int sig);
56
57void
58alarm_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
65static void
66alarm_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
82Trap *
83gettrap(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 */
114void
115trapsig(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 */
135void
136intrcheck(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 */
145int
146fatal_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 */
163int
164trap_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 */
180void
181runtraps(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
207void
208runtrap(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) */
253void
254cleartraps(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) */
270void
271restoresigs(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
282void
283settrap(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 */
318int
319block_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 */
336void
337restore_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 */
347int
348setsig(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() */
398void
399setexecsig(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}