summaryrefslogtreecommitdiff
path: root/trap.c
blob: c60f0205b619ad1c7bdba089550b9d634490ba9d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
/*	$NetBSD: trap.c,v 1.14 2018/05/08 16:37:59 kamil Exp $	*/

/*
 * signal handling
 */
#include <sys/cdefs.h>

#ifndef lint
__RCSID("$NetBSD: trap.c,v 1.14 2018/05/08 16:37:59 kamil Exp $");
#endif

#include "sh.h"

/* Table is indexed by signal number
 *
 * The script siglist.sh generates siglist.out, which is a sorted, complete
 * list of signals
 */
Trap sigtraps[SIGNALS+1] = {
	{ .signal = SIGEXIT_, .name = "EXIT", .mess = "Signal 0" },
#include "siglist.out"	/* generated by siglist.sh */
	{ .signal = SIGERR_,  .name = "ERR",  .mess = "Error handler" },
    };

static struct sigaction Sigact_ign, Sigact_trap;

void
inittraps()
{
#ifdef HAVE_SYS_SIGLIST
# ifndef SYS_SIGLIST_DECLARED
	extern char	*sys_siglist[];
# endif
	int	i;

	/* Use system description, if available, for unknown signals... */
	for (i = 0; i < NSIG; i++)
		if (!sigtraps[i].name && sys_siglist[i] && sys_siglist[i][0])
			sigtraps[i].mess = sys_siglist[i];
#endif	/* HAVE_SYS_SIGLIST */

	sigemptyset(&Sigact_ign.sa_mask);
	Sigact_ign.sa_flags = KSH_SA_FLAGS;
	Sigact_ign.sa_handler = SIG_IGN;
	Sigact_trap = Sigact_ign;
	Sigact_trap.sa_handler = trapsig;

	sigtraps[SIGINT].flags |= TF_DFL_INTR | TF_TTY_INTR;
	sigtraps[SIGQUIT].flags |= TF_DFL_INTR | TF_TTY_INTR;
	sigtraps[SIGTERM].flags |= TF_DFL_INTR;/* not fatal for interactive */
	sigtraps[SIGHUP].flags |= TF_FATAL;
	sigtraps[SIGCHLD].flags |= TF_SHELL_USES;

	/* these are always caught so we can clean up any temporary files. */
	setsig(&sigtraps[SIGINT], trapsig, SS_RESTORE_ORIG);
	setsig(&sigtraps[SIGQUIT], trapsig, SS_RESTORE_ORIG);
	setsig(&sigtraps[SIGTERM], trapsig, SS_RESTORE_ORIG);
	setsig(&sigtraps[SIGHUP], trapsig, SS_RESTORE_ORIG);
}

#ifdef KSH
static RETSIGTYPE alarm_catcher ARGS((int sig));

void
alarm_init()
{
	sigtraps[SIGALRM].flags |= TF_SHELL_USES;
	setsig(&sigtraps[SIGALRM], alarm_catcher,
		SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP);
}

static RETSIGTYPE
alarm_catcher(sig)
	int sig;
{
	int errno_ = errno;

	if (ksh_tmout_state == TMOUT_READING) {
		int left = alarm(0);

		if (left == 0) {
			ksh_tmout_state = TMOUT_LEAVING;
			intrsig = 1;
		} else
			alarm(left);
	}
	errno = errno_;
	return RETSIGVAL;
}
#endif /* KSH */

Trap *
gettrap(name, igncase)
	const char *name;
	int igncase;
{
	int i;
	Trap *p;

	if (digit(*name)) {
		int n;

		if (getn(name, &n) && 0 <= n && n < SIGNALS)
			return &sigtraps[n];
		return NULL;
	}
	for (p = sigtraps, i = SIGNALS+1; --i >= 0; p++)
		if (p->name) {
			if (igncase) {
				if (p->name && (!strcasecmp(p->name, name) ||
				    (strlen(name) > 3 && !strncasecmp("SIG",
				    p->name, 3) &&
				    !strcasecmp(p->name, name + 3))))
					return p;
			} else {
				if (p->name && (!strcmp(p->name, name) ||
				    (strlen(name) > 3 && !strncmp("SIG",
				    p->name, 3) && !strcmp(p->name, name + 3))))
					return p;
			}
		}
	return NULL;
}

/*
 * trap signal handler
 */
RETSIGTYPE
trapsig(i)
	int i;
{
	Trap *p = &sigtraps[i];
	int errno_ = errno;

	trap = p->set = 1;
	if (p->flags & TF_DFL_INTR)
		intrsig = 1;
	if ((p->flags & TF_FATAL) && !p->trap) {
		fatal_trap = 1;
		intrsig = 1;
	}
	if (p->shtrap)
		(*p->shtrap)(i);

	errno = errno_;
	return RETSIGVAL;
}

/* called when we want to allow the user to ^C out of something - won't
 * work if user has trapped SIGINT.
 */
void
intrcheck()
{
	if (intrsig)
		runtraps(TF_DFL_INTR|TF_FATAL);
}

/* called after EINTR to check if a signal with normally causes process
 * termination has been received.
 */
int
fatal_trap_check()
{
	int i;
	Trap *p;

	/* todo: should check if signal is fatal, not the TF_DFL_INTR flag */
	for (p = sigtraps, i = SIGNALS+1; --i >= 0; p++)
		if (p->set && (p->flags & (TF_DFL_INTR|TF_FATAL)))
			/* return value is used as an exit code */
			return 128 + p->signal;
	return 0;
}

/* Returns the signal number of any pending traps: ie, a signal which has
 * occurred for which a trap has been set or for which the TF_DFL_INTR flag
 * is set.
 */
int
trap_pending()
{
	int i;
	Trap *p;

	for (p = sigtraps, i = SIGNALS+1; --i >= 0; p++)
		if (p->set && ((p->trap && p->trap[0])
			       || ((p->flags & (TF_DFL_INTR|TF_FATAL))
				   && !p->trap)))
			return p->signal;
	return 0;
}

/*
 * run any pending traps.  If intr is set, only run traps that
 * can interrupt commands.
 */
void
runtraps(flag)
	int flag;
{
	int i;
	Trap *p;

#ifdef KSH
	if (ksh_tmout_state == TMOUT_LEAVING) {
		ksh_tmout_state = TMOUT_EXECUTING;
		warningf(false, "timed out waiting for input");
		unwind(LEXIT);
	} else
		/* XXX: this means the alarm will have no effect if a trap
		 * is caught after the alarm() was started...not good.
		 */
		ksh_tmout_state = TMOUT_EXECUTING;
#endif /* KSH */
	if (!flag)
		trap = 0;
	if (flag & TF_DFL_INTR)
		intrsig = 0;
	if (flag & TF_FATAL)
		fatal_trap = 0;
	for (p = sigtraps, i = SIGNALS+1; --i >= 0; p++)
		if (p->set && (!flag
			       || ((p->flags & flag) && p->trap == (char *) 0)))
			runtrap(p);
}

void
runtrap(p)
	Trap *p;
{
	int	i = p->signal;
	char	*trapstr = p->trap;
	int	oexstat;
	int	UNINITIALIZED(old_changed);

	p->set = 0;
	if (trapstr == (char *) 0) { /* SIG_DFL */
		if (p->flags & TF_FATAL) {
			/* eg, SIGHUP */
			exstat = 128 + i;
			unwind(LLEAVE);
		}
		if (p->flags & TF_DFL_INTR) {
			/* eg, SIGINT, SIGQUIT, SIGTERM, etc. */
			exstat = 128 + i;
			unwind(LINTR);
		}
		return;
	}
	if (trapstr[0] == '\0') /* SIG_IGN */
		return;
	if (i == SIGEXIT_ || i == SIGERR_) {	/* avoid recursion on these */
		old_changed = p->flags & TF_CHANGED;
		p->flags &= ~TF_CHANGED;
		p->trap = (char *) 0;
	}
	oexstat = exstat;
	/* Note: trapstr is fully parsed before anything is executed, thus
	 * no problem with afree(p->trap) in settrap() while still in use.
	 */
	command(trapstr);
	exstat = oexstat;
	if (i == SIGEXIT_ || i == SIGERR_) {
		if (p->flags & TF_CHANGED)
			/* don't clear TF_CHANGED */
			afree(trapstr, APERM);
		else
			p->trap = trapstr;
		p->flags |= old_changed;
	}
}

/* clear pending traps and reset user's trap handlers; used after fork(2) */
void
cleartraps()
{
	int i;
	Trap *p;

	trap = 0;
	intrsig = 0;
	fatal_trap = 0;
	for (i = SIGNALS+1, p = sigtraps; --i >= 0; p++) {
		p->set = 0;
		if ((p->flags & TF_USER_SET) && (p->trap && p->trap[0]))
			settrap(p, (char *) 0);
	}
}

/* restore signals just before an exec(2) */
void
restoresigs()
{
	int i;
	Trap *p;

	for (i = SIGNALS+1, p = sigtraps; --i >= 0; p++)
		if (p->flags & (TF_EXEC_IGN|TF_EXEC_DFL))
			setsig(p, (p->flags & TF_EXEC_IGN) ? SIG_IGN : SIG_DFL,
				SS_RESTORE_CURR|SS_FORCE);
}

void
settrap(p, s)
	Trap *p;
	char *s;
{
	handler_t f;

	if (p->trap)
		afree(p->trap, APERM);
	p->flags |= TF_CHANGED|TF_USER_SET;
	if (s) {
		p->trap = str_save(s, APERM);
		f = s[0] ? trapsig : SIG_IGN;
	} else {
		p->trap = NULL;
		f = SIG_DFL;
	}
	if ((p->flags & (TF_DFL_INTR|TF_FATAL)) && f == SIG_DFL)
		f = trapsig;
	else if (p->flags & TF_SHELL_USES) {
		if (!(p->flags & TF_ORIG_IGN) || Flag(FTALKING)) {
			/* do what user wants at exec time */
			p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL);
			if (f == SIG_IGN)
				p->flags |= TF_EXEC_IGN;
			else
				p->flags |= TF_EXEC_DFL;
		}
		/* assumes handler already set to what shell wants it
		 * (normally trapsig, but could be j_sigchld() or SIG_IGN)
		 */
		return;
	}

	/* todo: should we let user know signal is ignored? how? */
	setsig(p, f, SS_RESTORE_CURR|SS_USER);
}

/* Called by c_print() when writing to a co-process to ensure SIGPIPE won't
 * kill shell (unless user catches it and exits)
 */
int
block_pipe()
{
	int restore_dfl = 0;
	Trap *p = &sigtraps[SIGPIPE];

	if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) {
		setsig(p, SIG_IGN, SS_RESTORE_CURR);
		if (p->flags & TF_ORIG_DFL)
			restore_dfl = 1;
	} else if (p->cursig == SIG_DFL) {
		setsig(p, SIG_IGN, SS_RESTORE_CURR);
		restore_dfl = 1; /* restore to SIG_DFL */
	}
	return restore_dfl;
}

/* Called by c_print() to undo whatever block_pipe() did */
void
restore_pipe(restore_dfl)
	int restore_dfl;
{
	if (restore_dfl)
		setsig(&sigtraps[SIGPIPE], SIG_DFL, SS_RESTORE_CURR);
}

/* Set action for a signal.  Action may not be set if original
 * action was SIG_IGN, depending on the value of flags and
 * FTALKING.
 */
int
setsig(p, f, flags)
	Trap *p;
	handler_t f;
	int flags;
{
	struct sigaction sigact;

	if (p->signal == SIGEXIT_ || p->signal == SIGERR_)
		return 1;

	/* First time setting this signal?  If so, get and note the current
	 * setting.
	 */
	if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) {
		sigaction(p->signal, &Sigact_ign, &sigact);
		p->flags |= sigact.sa_handler == SIG_IGN ?
					TF_ORIG_IGN : TF_ORIG_DFL;
		p->cursig = SIG_IGN;
	}

	/* Generally, an ignored signal stays ignored, except if
	 *	- the user of an interactive shell wants to change it
	 *	- the shell wants for force a change
	 */
	if ((p->flags & TF_ORIG_IGN) && !(flags & SS_FORCE)
	    && (!(flags & SS_USER) || !Flag(FTALKING)))
		return 0;

	setexecsig(p, flags & SS_RESTORE_MASK);

	/* This is here 'cause there should be a way of clearing shtraps, but
	 * don't know if this is a sane way of doing it.  At the moment,
	 * all users of shtrap are lifetime users (SIGCHLD, SIGALRM, SIGWINCH).
	 */
	if (!(flags & SS_USER))
		p->shtrap = (handler_t) 0;
	if (flags & SS_SHTRAP) {
		p->shtrap = f;
		f = trapsig;
	}

	if (p->cursig != f) {
		p->cursig = f;
		sigemptyset(&sigact.sa_mask);
		sigact.sa_flags = KSH_SA_FLAGS;
		sigact.sa_handler = f;
		sigaction(p->signal, &sigact, (struct sigaction *) 0);
	}

	return 1;
}

/* control what signal is set to before an exec() */
void
setexecsig(p, restore)
	Trap *p;
	int restore;
{
	/* XXX debugging */
	if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL)))
		internal_errorf(1, "setexecsig: unset signal %d(%s)",
			p->signal, p->name);

	/* restore original value for exec'd kids */
	p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL);
	switch (restore & SS_RESTORE_MASK) {
	  case SS_RESTORE_CURR: /* leave things as they currently are */
		break;
	  case SS_RESTORE_ORIG:
		p->flags |= p->flags & TF_ORIG_IGN ? TF_EXEC_IGN : TF_EXEC_DFL;
		break;
	  case SS_RESTORE_DFL:
		p->flags |= TF_EXEC_DFL;
		break;
	  case SS_RESTORE_IGN:
		p->flags |= TF_EXEC_IGN;
		break;
	}
}