[git commit] hush: replace signal handling machinery

Denys Vlasenko vda.linux at googlemail.com
Wed May 11 21:56:11 UTC 2011


commit: http://git.busybox.net/busybox/commit/?id=9d6cbafe728c9100a35e29ba7a3729c5303e73ea
branch: http://git.busybox.net/busybox/commit/?id=refs/heads/master

With new version of signal handling, read builtin should be less buggy
wrt signals.

function                                             old     new   delta
install_sighandlers                                    -     121    +121
switch_off_special_sigs                                -      84     +84
pick_sighandler                                        -      58     +58
install_special_sighandlers                            -      47     +47
builtin_wait                                         284     319     +35
record_pending_signo                                   -      21     +21
execvp_or_die                                         43      48      +5
file_get                                             290     288      -2
run_list                                            1004     998      -6
static.zero_timespec                                   8       -      -8
sigprocmask_set                                       14       -     -14
sigwaitinfo                                           23       -     -23
record_signal                                         23       -     -23
__GI_sigwaitinfo                                      23       -     -23
sigtimedwait                                          25       -     -25
builtin_trap                                         417     392     -25
__GI_sigtimedwait                                     25       -     -25
hush_main                                           1003     965     -38
check_and_run_traps                                  263     217     -46
__rt_sigtimedwait                                     52       -     -52
reset_traps_to_defaults                              213     126     -87
init_sigmasks                                        198       -    -198
builtin_read                                         536     197    -339
------------------------------------------------------------------------------
(add/remove: 5/10 grow/shrink: 2/7 up/down: 371/-934)        Total: -563 bytes
   text	   data	    bss	    dec	    hex	filename
 903075	    936	  17736	 921747	  e1093	busybox_old
 902547	    936	  17736	 921219	  e0e83	busybox_unstripped

Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 shell/hush.c |  510 ++++++++++++++++++++++++++++++++--------------------------
 1 files changed, 278 insertions(+), 232 deletions(-)

diff --git a/shell/hush.c b/shell/hush.c
index 509bd41..b2c3a75 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -106,6 +106,10 @@
 # define PIPE_BUF 4096  /* amount of buffering in a pipe */
 #endif
 
+/* Not every libc has sighandler_t. Fix it */
+typedef void (*hush_sighandler_t)(int);
+#define sighandler_t hush_sighandler_t
+
 //config:config HUSH
 //config:	bool "hush"
 //config:	default y
@@ -764,7 +768,6 @@ struct globals {
 	smalluint last_exitcode;
 	/* are global_argv and global_argv[1..n] malloced? (note: not [0]) */
 	smalluint global_args_malloced;
-	smalluint inherited_set_is_saved;
 	/* how many non-NULL argv's we have. NB: $# + 1 */
 	int global_argc;
 	char **global_argv;
@@ -794,21 +797,20 @@ struct globals {
 #endif
 	/* Which signals have non-DFL handler (even with no traps set)?
 	 * Set at the start to:
-	 * (SIGQUIT + maybe SPECIAL_INTERACTIVE_SIGS + maybe SPECIAL_JOB_SIGS)
+	 * (SIGQUIT + maybe SPECIAL_INTERACTIVE_SIGS + maybe SPECIAL_JOBSTOP_SIGS)
 	 * SPECIAL_INTERACTIVE_SIGS are cleared after fork.
+	 * The rest is cleared right before execv syscalls.
 	 * Other than these two times, never modified.
 	 */
 	unsigned special_sig_mask;
+#if ENABLE_HUSH_JOB
+	unsigned fatal_sig_mask;
+#define G_fatal_sig_mask G.fatal_sig_mask
+#else
+#define G_fatal_sig_mask 0
+#endif
 	char **traps; /* char *traps[NSIG] */
-	/* Signal mask on the entry to the (top-level) shell. Never modified. */
-	sigset_t inherited_set;
-	/* Starts equal to inherited_set,
-	 * but shell-special signals are added and SIGCHLD is removed.
-	 * When a trap is set/cleared, signal is added to/removed from it:
-	 */
-	sigset_t blocked_set;
-	/* Used by read() */
-	sigset_t detected_set;
+	sigset_t pending_set;
 #if HUSH_DEBUG
 	unsigned long memleak_value;
 	int debug_indent;
@@ -1337,8 +1339,8 @@ static void restore_G_args(save_arg_t *sv, char **argv)
  * (What happens to signals which are IGN on shell start?)
  * (What happens with signal mask on shell start?)
  *
- * Implementation in hush
- * ======================
+ * Old implementation
+ * ==================
  * We use in-kernel pending signal mask to determine which signals were sent.
  * We block all signals which we don't want to take action immediately,
  * i.e. we block all signals which need to have special handling as described
@@ -1369,6 +1371,49 @@ static void restore_G_args(save_arg_t *sv, char **argv)
  * Standard says "When a subshell is entered, traps that are not being ignored
  * are set to the default actions". bash interprets it so that traps which
  * are set to '' (ignore) are NOT reset to defaults. We do the same.
+ *
+ * Problem: the above approach makes it unwieldy to catch signals while
+ * we are in read builtin, of while we read commands from stdin:
+ * masked signals are not visible!
+ *
+ * New implementation
+ * ==================
+ * We record each signal we are interested in by installing signal handler
+ * for them - a bit like emulating kernel pending signal mask in userspace.
+ * We are interested in: signals which need to have special handling
+ * as described above, and all signals which have traps set.
+ * Signals are rocorded in pending_set.
+ * After each pipe execution, we extract any pending signals
+ * and act on them.
+ *
+ * unsigned special_sig_mask: a mask of shell-special signals.
+ * unsigned fatal_sig_mask: a mask of signals on which we restore tty pgrp.
+ * char *traps[sig] if trap for sig is set (even if it's '').
+ * sigset_t pending_set: set of sigs we received.
+ *
+ * "trap - SIGxxx":
+ *    if sig is in special_sig_mask, set handler back to:
+ *        record_pending_signo, or to IGN if it's a tty stop signal
+ *    if sig is in fatal_sig_mask, set handler back to sigexit.
+ *    else: set handler back to SIG_DFL
+ * "trap 'cmd' SIGxxx":
+ *    set handler to record_pending_signo.
+ * "trap '' SIGxxx":
+ *    set handler to SIG_IGN.
+ * after [v]fork, if we plan to be a shell:
+ *    set signals with special interactive handling to SIG_DFL
+ *    (because child shell is not interactive),
+ *    unset all traps except '' (note: regardless of child shell's type - {}, (), etc)
+ * after [v]fork, if we plan to exec:
+ *    POSIX says fork clears pending signal mask in child - no need to clear it.
+ *
+ * To make wait builtin interruptible, we handle SIGCHLD as special signal,
+ * otherwise (if we leave it SIG_DFL) sigsuspend in wait builtin will not wake up on it.
+ *
+ * Note (compat):
+ * Standard says "When a subshell is entered, traps that are not being ignored
+ * are set to the default actions". bash interprets it so that traps which
+ * are set to '' (ignore) are NOT reset to defaults. We do the same.
  */
 enum {
 	SPECIAL_INTERACTIVE_SIGS = 0
@@ -1376,26 +1421,25 @@ enum {
 		| (1 << SIGINT)
 		| (1 << SIGHUP)
 		,
-	SPECIAL_JOB_SIGS = 0
+	SPECIAL_JOBSTOP_SIGS = 0
 #if ENABLE_HUSH_JOB
 		| (1 << SIGTTIN)
 		| (1 << SIGTTOU)
 		| (1 << SIGTSTP)
 #endif
+		,
 };
 
-static void sigprocmask_set(sigset_t *set)
+static void record_pending_signo(int sig)
 {
-	sigprocmask(SIG_SETMASK, set, NULL);
-}
-
+	sigaddset(&G.pending_set, sig);
 #if ENABLE_HUSH_FAST
-static void SIGCHLD_handler(int sig UNUSED_PARAM)
-{
-	G.count_SIGCHLD++;
+	if (sig == SIGCHLD) {
+		G.count_SIGCHLD++;
 //bb_error_msg("[%d] SIGCHLD_handler: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
-}
+	}
 #endif
+}
 
 #if ENABLE_HUSH_JOB
 
@@ -1433,6 +1477,31 @@ static void sigexit(int sig)
 
 #endif
 
+static sighandler_t pick_sighandler(unsigned sig)
+{
+	sighandler_t handler = SIG_DFL;
+	if (sig < sizeof(unsigned)*8) {
+		unsigned sigmask = (1 << sig);
+
+#if ENABLE_HUSH_JOB
+		/* sig is fatal? */
+		if (G_fatal_sig_mask & sigmask)
+			handler = sigexit;
+#endif
+		/* sig has special handling? */
+		else if (G.special_sig_mask & sigmask)
+			handler = record_pending_signo;
+			/* TTIN/TTOU/TSTS can't be set to record_pending_signo
+			 * in order to ignore them: they will be raised
+			 * in an endless loop then when we try to do some
+			 * terminal ioctls! We do nave to _ignore_ these.
+			 */
+			if (SPECIAL_JOBSTOP_SIGS & sigmask)
+				handler = SIG_IGN;
+	}
+	return handler;
+}
+
 /* Restores tty foreground process group, and exits. */
 static void hush_exit(int exitcode) NORETURN;
 static void hush_exit(int exitcode)
@@ -1478,39 +1547,30 @@ static void hush_exit(int exitcode)
 }
 
 
-static int check_and_run_traps(int sig)
+//TODO: return a mask of ALL handled sigs?
+static int check_and_run_traps(void)
 {
-	/* I want it in rodata, not in bss.
-	 * gcc 4.2.1 puts it in rodata only if it has { 0, 0 }
-	 * initializer. But other compilers may still use bss.
-	 * TODO: find more portable solution.
-	 */
-	static const struct timespec zero_timespec = { 0, 0 };
-	smalluint save_rcode;
 	int last_sig = 0;
 
-	if (sig)
-		goto got_sig;
-
 	while (1) {
-		if (!sigisemptyset(&G.detected_set)) {
-			sig = 0;
-			do {
-				sig++;
-				if (sigismember(&G.detected_set, sig)) {
-					sigdelset(&G.detected_set, sig);
-					goto got_sig;
-				}
-			} while (sig < NSIG);
-		}
+		int sig;
 
-		sig = sigtimedwait(&G.blocked_set, NULL, &zero_timespec);
-		if (sig <= 0)
+		if (sigisemptyset(&G.pending_set))
 			break;
+		sig = 0;
+		do {
+			sig++;
+			if (sigismember(&G.pending_set, sig)) {
+				sigdelset(&G.pending_set, sig);
+				goto got_sig;
+			}
+		} while (sig < NSIG);
+		break;
  got_sig:
 		if (G.traps && G.traps[sig]) {
 			if (G.traps[sig][0]) {
 				/* We have user-defined handler */
+				smalluint save_rcode;
 				char *argv[3];
 				/* argv[0] is unused */
 				argv[1] = G.traps[sig];
@@ -1524,12 +1584,6 @@ static int check_and_run_traps(int sig)
 		}
 		/* not a trap: special action */
 		switch (sig) {
-#if ENABLE_HUSH_FAST
-		case SIGCHLD:
-			G.count_SIGCHLD++;
-//bb_error_msg("[%d] check_and_run_traps: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
-			break;
-#endif
 		case SIGINT:
 			/* Builtin was ^C'ed, make it look prettier: */
 			bb_putchar('\n');
@@ -1551,11 +1605,21 @@ static int check_and_run_traps(int sig)
 			sigexit(SIGHUP);
 		}
 #endif
+#if ENABLE_HUSH_FAST
+		case SIGCHLD:
+			G.count_SIGCHLD++;
+//bb_error_msg("[%d] check_and_run_traps: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
+			/* Note:
+			 * We dont do 'last_sig = sig' here -> NOT returning this sig.
+			 * This simplifies wait builtin a bit.
+			 */
+			break;
+#endif
 		default: /* ignored: */
 			/* SIGTERM, SIGQUIT, SIGTTIN, SIGTTOU, SIGTSTP */
-			/* note:
-			 * we dont do 'last_sig = sig' here -> NOT returning this sig.
-			 * example: wait is not interrupted by TERM
+			/* Note:
+			 * We dont do 'last_sig = sig' here -> NOT returning this sig.
+			 * Example: wait is not interrupted by TERM
 			 * in interactive shell, because TERM is ignored.
 			 */
 			break;
@@ -1948,7 +2012,7 @@ static void get_user_input(struct in_str *i)
 		 * only after <Enter>. (^C will work) */
 		r = read_line_input(G.line_input_state, prompt_str, G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1, /*timeout*/ -1);
 		/* catch *SIGINT* etc (^C is handled by read_line_input) */
-		check_and_run_traps(0);
+		check_and_run_traps();
 	} while (r == 0 || G.flag_SIGINT); /* repeat if ^C or SIGINT */
 	i->eof_flag = (r < 0);
 	if (i->eof_flag) { /* EOF/error detected */
@@ -1964,7 +2028,7 @@ static void get_user_input(struct in_str *i)
 			 * $ <[enter], repeatedly...>
 			 * Without check_and_run_traps, handler never runs.
 			 */
-			check_and_run_traps(0);
+			check_and_run_traps();
 			fputs(prompt_str, stdout);
 		}
 		fflush_all();
@@ -5368,6 +5432,25 @@ void re_execute_shell(char ***to_free, const char *s,
 		char *g_argv0, char **g_argv,
 		char **builtin_argv) NORETURN;
 
+static void switch_off_special_sigs(unsigned mask)
+{
+	unsigned sig = 0;
+	while ((mask >>= 1) != 0) {
+		sig++;
+		if (!(mask & 1))
+			continue;
+		if (G.traps) {
+			if (G.traps[sig] && !G.traps[sig][0])
+				/* trap is '', has to remain SIG_IGN */
+				continue;
+			free(G.traps[sig]);
+			G.traps[sig] = NULL;
+		}
+		/* We are here only if no trap or trap was not '' */
+		signal(sig, SIG_DFL);
+	}
+}
+
 static void reset_traps_to_defaults(void)
 {
 	/* This function is always called in a child shell
@@ -5381,44 +5464,35 @@ static void reset_traps_to_defaults(void)
 	 * Testcase: (while :; do :; done) + ^Z should background.
 	 * Same goes for SIGTERM, SIGHUP, SIGINT.
 	 */
-	if (!G.traps && !(G.special_sig_mask & SPECIAL_INTERACTIVE_SIGS))
-		return; /* already no traps and no SPECIAL_INTERACTIVE_SIGS */
-
-	/* Switching off SPECIAL_INTERACTIVE_SIGS.
-	 * Stupid. It can be done with *single* &= op, but we can't use
-	 * the fact that G.blocked_set is implemented as a bitmask
-	 * in libc... */
-	mask = SPECIAL_INTERACTIVE_SIGS;
-	sig = 0;
-	while ((mask >>= 1) != 0) {
-		sig++;
-		if (mask & 1) {
-			/* Careful. Only if no trap or trap is not "" */
-			if (!G.traps || !G.traps[sig] || G.traps[sig][0])
-				sigdelset(&G.blocked_set, sig);
-		}
-	}
-	/* Our homegrown sig mask is saner to work with :) */
+	mask = (G.special_sig_mask & SPECIAL_INTERACTIVE_SIGS) | G_fatal_sig_mask;
+	if (!G.traps && !mask)
+		return; /* already no traps and no special sigs */
+
+	/* Switch off special sigs */
+	switch_off_special_sigs(mask);
+#if ENABLE_HUSH_JOB
+	G_fatal_sig_mask = 0;
+#endif
 	G.special_sig_mask &= ~SPECIAL_INTERACTIVE_SIGS;
+	/* SIGQUIT and maybe SPECIAL_JOBSTOP_SIGS remain set in G.special_sig_mask */
 
-	/* Resetting all traps to default except empty ones */
-	mask = G.special_sig_mask;
-	if (G.traps) for (sig = 0; sig < NSIG; sig++, mask >>= 1) {
-		if (!G.traps[sig] || !G.traps[sig][0])
-			continue;
+	if (!G.traps)
+		return;
+
+	/* Reset all sigs to default except ones with empty traps */
+	for (sig = 0; sig < NSIG; sig++) {
+		if (!G.traps[sig])
+			continue; /* no trap: nothing to do */
+		if (!G.traps[sig][0])
+			continue; /* empty trap: has to remain SIG_IGN */
+		/* sig has non-empty trap, reset it: */
 		free(G.traps[sig]);
 		G.traps[sig] = NULL;
-		/* There is no signal for 0 (EXIT) */
+		/* There is no signal for trap 0 (EXIT) */
 		if (sig == 0)
 			continue;
-		/* There was a trap handler, we just removed it.
-		 * But if sig still has non-DFL handling,
-		 * we should not unblock the sig. */
-		if (mask & 1)
-			continue;
-		sigdelset(&G.blocked_set, sig);
+		signal(sig, pick_sighandler(sig));
 	}
-	sigprocmask_set(&G.blocked_set);
 }
 
 #else /* !BB_MMU */
@@ -5463,6 +5537,7 @@ static void re_execute_shell(char ***to_free, const char *s,
 		for (sig = 1; sig < NSIG; sig++) {
 			if (G.traps[sig] && !G.traps[sig][0])
 				empty_trap_mask |= 1LL << sig;
+///vda: optimize
 		}
 	}
 
@@ -5548,7 +5623,7 @@ static void re_execute_shell(char ***to_free, const char *s,
 
  do_exec:
 	debug_printf_exec("re_execute_shell pid:%d cmd:'%s'\n", getpid(), s);
-	sigprocmask_set(&G.inherited_set);
+	switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS);
 	execve(bb_busybox_exec_path, argv, pp);
 	/* Fallback. Useful for init=/bin/hush usage etc */
 	if (argv[0][0] == '/')
@@ -6202,7 +6277,7 @@ static void execvp_or_die(char **argv) NORETURN;
 static void execvp_or_die(char **argv)
 {
 	debug_printf_exec("execing '%s'\n", argv[0]);
-	sigprocmask_set(&G.inherited_set);
+	switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS);
 	execvp(argv[0], argv);
 	bb_perror_msg("can't execute '%s'", argv[0]);
 	_exit(127); /* bash compat */
@@ -6334,7 +6409,7 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
 # endif
 			/* Re-exec ourselves */
 			debug_printf_exec("re-execing applet '%s'\n", argv[0]);
-			sigprocmask_set(&G.inherited_set);
+			switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS);
 			execv(bb_busybox_exec_path, argv);
 			/* If they called chroot or otherwise made the binary no longer
 			 * executable, fall through */
@@ -7033,9 +7108,6 @@ static NOINLINE int run_pipe(struct pipe *pi)
 			if (setup_redirects(command, NULL))
 				_exit(1);
 
-			/* Restore default handlers just prior to exec */
-			/*signal(SIGCHLD, SIG_DFL); - so far we don't have any handlers */
-
 			/* Stores to nommu_save list of env vars putenv'ed
 			 * (NOMMU, on MMU we don't need that) */
 			/* cast away volatility... */
@@ -7313,7 +7385,7 @@ static int run_list(struct pipe *pi)
 				 * and we don't need to wait for anything. */
 				G.last_exitcode = rcode;
 				debug_printf_exec(": builtin/func exitcode %d\n", rcode);
-				check_and_run_traps(0);
+				check_and_run_traps();
 #if ENABLE_HUSH_LOOPS
 				/* Was it "break" or "continue"? */
 				if (G.flag_break_continue) {
@@ -7345,7 +7417,7 @@ static int run_list(struct pipe *pi)
 				/* even bash 3.2 doesn't do that well with nested bg:
 				 * try "{ { sleep 10; echo DEEP; } & echo HERE; } &".
 				 * I'm NOT treating inner &'s as jobs */
-				check_and_run_traps(0);
+				check_and_run_traps();
 #if ENABLE_HUSH_JOB
 				if (G.run_list_level == 1)
 					insert_bg_job(pi);
@@ -7360,13 +7432,13 @@ static int run_list(struct pipe *pi)
 					/* Waits for completion, then fg's main shell */
 					rcode = checkjobs_and_fg_shell(pi);
 					debug_printf_exec(": checkjobs_and_fg_shell exitcode %d\n", rcode);
-					check_and_run_traps(0);
+					check_and_run_traps();
 				} else
 #endif
 				{ /* This one just waits for completion */
 					rcode = checkjobs(pi);
 					debug_printf_exec(": checkjobs exitcode %d\n", rcode);
-					check_and_run_traps(0);
+					check_and_run_traps();
 				}
 				G.last_exitcode = rcode;
 			}
@@ -7437,58 +7509,61 @@ static int run_and_free_list(struct pipe *pi)
 }
 
 
+static void install_sighandlers(unsigned mask)
+{
+	sighandler_t old_handler;
+	unsigned sig = 0;
+	while ((mask >>= 1) != 0) {
+		sig++;
+		if (!(mask & 1))
+			continue;
+		old_handler = signal(sig, pick_sighandler(sig));
+		/* POSIX allows shell to re-enable SIGCHLD
+		 * even if it was SIG_IGN on entry.
+		 * Therefore we skip IGN check for it:
+		 */
+		if (sig == SIGCHLD)
+			continue;
+		if (old_handler == SIG_IGN) {
+			/* oops... restore back to IGN, and record this fact */
+			signal(sig, old_handler);
+			if (!G.traps)
+				G.traps = xzalloc(sizeof(G.traps[0]) * NSIG);
+			free(G.traps[sig]);
+			G.traps[sig] = xzalloc(1); /* == xstrdup(""); */
+		}
+	}
+}
+
 /* Called a few times only (or even once if "sh -c") */
-static void init_sigmasks(void)
+static void install_special_sighandlers(void)
 {
-	unsigned sig;
 	unsigned mask;
 
-	/* POSIX allows shell to re-enable SIGCHLD
-	 * even if it was SIG_IGN on entry */
-	if (!G.inherited_set_is_saved) {
-#if ENABLE_HUSH_FAST
-		signal(SIGCHLD, SIGCHLD_handler);
-#else
-		signal(SIGCHLD, SIG_DFL);
-#endif
-		sigprocmask(SIG_SETMASK, NULL, &G.blocked_set);
-		G.inherited_set = G.blocked_set;
-	}
+	if (G.special_sig_mask != 0)
+		return;
 
 	/* Which signals are shell-special? */
-	mask = (1 << SIGQUIT);
+	mask = (1 << SIGQUIT) | (1 << SIGCHLD);
 	if (G_interactive_fd) {
 		mask |= SPECIAL_INTERACTIVE_SIGS;
 		if (G_saved_tty_pgrp) /* we have ctty, job control sigs work */
-			mask |= SPECIAL_JOB_SIGS;
+			mask |= SPECIAL_JOBSTOP_SIGS;
 	}
 	G.special_sig_mask = mask;
 
-	/* Block them. And unblock SIGCHLD */
-	sig = 0;
-	while ((mask >>= 1) != 0) {
-		sig++;
-		if (mask & 1)
-			sigaddset(&G.blocked_set, sig);
-	}
-	sigdelset(&G.blocked_set, SIGCHLD);
-
-	if (memcmp(&G.inherited_set, &G.blocked_set, sizeof(G.inherited_set)) != 0)
-		sigprocmask_set(&G.blocked_set);
-
-	G.inherited_set_is_saved = 1;
+	install_sighandlers(mask);
 }
 
 #if ENABLE_HUSH_JOB
 /* helper */
 /* Set handlers to restore tty pgrp and exit */
-static void set_fatal_handlers_to_sigexit(void)
+static void install_fatal_sighandlers(void)
 {
-	void (*handler)(int);
-	unsigned fatal_sigs, sig;
+	unsigned mask;
 
 	/* We will restore tty pgrp on these signals */
-	fatal_sigs = 0
+	mask = 0
 		+ (1 << SIGILL ) * HUSH_DEBUG
 		+ (1 << SIGFPE ) * HUSH_DEBUG
 		+ (1 << SIGBUS ) * HUSH_DEBUG
@@ -7505,22 +7580,13 @@ static void set_fatal_handlers_to_sigexit(void)
 		/*+ (1 << SIGTERM)*/
 		/*+ (1 << SIGINT )*/
 	;
-
-	/* special_sig_mask'ed signals are, well, masked,
+	/* special_sig_mask'ed signals are set to record_pending_signo
 	 * no need to set handler for them.
 	 */
-	fatal_sigs &= ~G.special_sig_mask;
+	/*mask &= ~G.special_sig_mask; - they never overlap */
+	G_fatal_sig_mask = mask;
 
-        /* For each sig in fatal_sigs... */
-	sig = 0;
-	while ((fatal_sigs >>= 1) != 0) {
-		sig++;
-		if (!(fatal_sigs & 1))
-			continue;
-		handler = signal(sig, sigexit);
-		if (handler == SIG_IGN) /* oops... restore back to IGN! */
-			signal(sig, handler);
-	}
+	install_sighandlers(mask);
 }
 #endif
 
@@ -7682,10 +7748,11 @@ int hush_main(int argc, char **argv)
 	}
 
 	/* Shell is non-interactive at first. We need to call
-	 * init_sigmasks() if we are going to execute "sh <script>",
+	 * install_special_sighandlers() if we are going to execute "sh <script>",
 	 * "sh -c <cmds>" or login shell's /etc/profile and friends.
-	 * If we later decide that we are interactive, we run init_sigmasks()
+	 * If we later decide that we are interactive, we run install_special_sighandlers()
 	 * in order to intercept (more) signals.
+//FIXME: re-running is currently most likely broken, it's a no-op.
 	 */
 
 	/* Parse options */
@@ -7724,7 +7791,7 @@ int hush_main(int argc, char **argv)
 				/* -c 'builtin' [BARGV...] "" ARG0 [ARG1...] */
 				const struct built_in_command *x;
 
-				init_sigmasks();
+				install_special_sighandlers();
 				x = find_builtin(optarg);
 				if (x) { /* paranoia */
 					G.global_argc -= builtin_argc; /* skip [BARGV...] "" */
@@ -7741,7 +7808,7 @@ int hush_main(int argc, char **argv)
 				G.global_argv[0] = argv[0];
 				G.global_argc++;
 			} /* else -c 'script' ARG0 [ARG1...]: $0 is ARG0 */
-			init_sigmasks();
+			install_special_sighandlers();
 			parse_and_run_string(optarg);
 			goto final_return;
 		case 'i':
@@ -7773,15 +7840,15 @@ int hush_main(int argc, char **argv)
 			empty_trap_mask = bb_strtoull(optarg, &optarg, 16);
 			if (empty_trap_mask != 0) {
 				int sig;
-				init_sigmasks();
+				install_special_sighandlers();
 				G.traps = xzalloc(sizeof(G.traps[0]) * NSIG);
 				for (sig = 1; sig < NSIG; sig++) {
+///vda: fixme: more efficient code
 					if (empty_trap_mask & (1LL << sig)) {
 						G.traps[sig] = xzalloc(1); /* == xstrdup(""); */
-						sigaddset(&G.blocked_set, sig);
+						signal(sig, SIG_IGN);
 					}
 				}
-				sigprocmask_set(&G.blocked_set);
 			}
 # if ENABLE_HUSH_LOOPS
 			optarg++;
@@ -7831,7 +7898,7 @@ int hush_main(int argc, char **argv)
 		input = fopen_for_read("/etc/profile");
 		if (input != NULL) {
 			close_on_exec_on(fileno(input));
-			init_sigmasks();
+			install_special_sighandlers();
 			parse_and_run_file(input);
 			fclose(input);
 		}
@@ -7856,7 +7923,7 @@ int hush_main(int argc, char **argv)
 		G.global_argc = argc - optind;
 		input = xfopen_for_read(argv[optind]);
 		close_on_exec_on(fileno(input));
-		init_sigmasks();
+		install_special_sighandlers();
 		parse_and_run_file(input);
 #if ENABLE_FEATURE_CLEAN_UP
 		fclose(input);
@@ -7865,7 +7932,7 @@ int hush_main(int argc, char **argv)
 	}
 
 	/* Up to here, shell was non-interactive. Now it may become one.
-	 * NB: don't forget to (re)run init_sigmasks() as needed.
+	 * NB: don't forget to (re)run install_special_sighandlers() as needed.
 	 */
 
 	/* A shell is interactive if the '-i' flag was given,
@@ -7918,11 +7985,11 @@ int hush_main(int argc, char **argv)
 		}
 
 		/* Block some signals */
-		init_sigmasks();
+		install_special_sighandlers();
 
 		if (G_saved_tty_pgrp) {
 			/* Set other signals to restore saved_tty_pgrp */
-			set_fatal_handlers_to_sigexit();
+			install_fatal_sighandlers();
 			/* Put ourselves in our own process group
 			 * (bash, too, does this only if ctty is available) */
 			bb_setpgrp(); /* is the same as setpgid(our_pid, our_pid); */
@@ -7933,7 +8000,7 @@ int hush_main(int argc, char **argv)
 		 * (we reset die_sleep = 0 whereever we [v]fork) */
 		enable_restore_tty_pgrp_on_exit(); /* sets die_sleep = -1 */
 	} else {
-		init_sigmasks();
+		install_special_sighandlers();
 	}
 #elif ENABLE_HUSH_INTERACTIVE
 	/* No job control compiled in, only prompt/line editing */
@@ -7950,10 +8017,10 @@ int hush_main(int argc, char **argv)
 	if (G_interactive_fd) {
 		close_on_exec_on(G_interactive_fd);
 	}
-	init_sigmasks();
+	install_special_sighandlers();
 #else
 	/* We have interactiveness code disabled */
-	init_sigmasks();
+	install_special_sighandlers();
 #endif
 	/* bash:
 	 * if interactive but not a login shell, sources ~/.bashrc
@@ -8087,7 +8154,7 @@ static int FAST_FUNC builtin_exec(char **argv)
 		tcsetpgrp(G_interactive_fd, G_saved_tty_pgrp);
 
 	/* TODO: if exec fails, bash does NOT exit! We do.
-	 * We'll need to undo sigprocmask (it's inside execvp_or_die)
+	 * We'll need to undo trap cleanup (it's inside execvp_or_die)
 	 * and tcsetpgrp, and this is inherently racy.
 	 */
 	execvp_or_die(argv);
@@ -8284,6 +8351,8 @@ static int FAST_FUNC builtin_trap(char **argv)
  process_sig_list:
 		ret = EXIT_SUCCESS;
 		while (*argv) {
+			sighandler_t handler;
+
 			sig = get_signum(*argv++);
 			if (sig < 0 || sig >= NSIG) {
 				ret = EXIT_FAILURE;
@@ -8302,18 +8371,13 @@ static int FAST_FUNC builtin_trap(char **argv)
 			if (sig == 0)
 				continue;
 
-			if (new_cmd) {
-				sigaddset(&G.blocked_set, sig);
-			} else {
-				/* There was a trap handler, we are removing it
-				 * (if sig has non-DFL handling,
-				 * we don't need to do anything) */
-				if (sig < sizeof(G.special_sig_mask)*8 && (G.special_sig_mask & (1 << sig)))
-					continue;
-				sigdelset(&G.blocked_set, sig);
-			}
+			if (new_cmd)
+				handler = (new_cmd[0] ? record_pending_signo : SIG_IGN);
+			else
+				/* We are removing trap handler */
+				handler = pick_sighandler(sig);
+			signal(sig, handler);
 		}
-		sigprocmask_set(&G.blocked_set);
 		return ret;
 	}
 
@@ -8535,11 +8599,6 @@ static int FAST_FUNC builtin_pwd(char **argv UNUSED_PARAM)
  * if it has non-empty trap:
  * - executes trap and returns to read;
  */
-/* helper */
-static void record_signal(int sig)
-{
-	sigaddset(&G.detected_set, sig);
-}
 static int FAST_FUNC builtin_read(char **argv)
 {
 	const char *r;
@@ -8549,7 +8608,6 @@ static int FAST_FUNC builtin_read(char **argv)
 	char *opt_u = NULL;
 	const char *ifs;
 	int read_flags;
-	sigset_t saved_blkd_set;
 
 	/* "!": do not abort on errors.
 	 * Option string must start with "sr" to match BUILTIN_READ_xxx
@@ -8561,41 +8619,6 @@ static int FAST_FUNC builtin_read(char **argv)
 	ifs = get_local_var_value("IFS"); /* can be NULL */
 
  again:
-	/* We need to temporarily unblock and record signals around read */
-
-	saved_blkd_set = G.blocked_set;
-	{
-		unsigned sig;
-		struct sigaction sa, old_sa;
-
-		memset(&sa, 0, sizeof(sa));
-		sigfillset(&sa.sa_mask);
-		sa.sa_flags = SA_RESTART;
-		sa.sa_handler = record_signal;
-
-		sig = 0;
-		do {
-			sig++;
-			if (sigismember(&G.blocked_set, sig)) {
-				char *sig_trap = (G.traps && G.traps[sig]) ? G.traps[sig] : NULL;
-				/* If has a nonempty trap... */
-				if ((sig_trap && sig_trap[0])
-				/* ...or has no trap and is SIGINT or SIGHUP */
-				 || (!sig_trap && (sig == SIGINT || sig == SIGHUP))
-				) {
-					sigaction(sig, &sa, &old_sa);
-					if (old_sa.sa_handler == SIG_IGN) /* oops... restore back to IGN! */
-						sigaction_set(sig, &old_sa);
-					else
-						sigdelset(&G.blocked_set, sig);
-				}
-			}
-		} while (sig < NSIG-1);
-	}
-
-	if (memcmp(&saved_blkd_set, &G.blocked_set, sizeof(saved_blkd_set)) != 0)
-		sigprocmask_set(&G.blocked_set);
-
 	r = shell_builtin_read(set_local_var_from_halves,
 		argv,
 		ifs,
@@ -8606,13 +8629,8 @@ static int FAST_FUNC builtin_read(char **argv)
 		opt_u
 	);
 
-	if (memcmp(&saved_blkd_set, &G.blocked_set, sizeof(saved_blkd_set)) != 0) {
-		G.blocked_set = saved_blkd_set;
-		sigprocmask_set(&G.blocked_set);
-	}
-
 	if ((uintptr_t)r == 1 && errno == EINTR) {
-		unsigned sig = check_and_run_traps(0);
+		unsigned sig = check_and_run_traps();
 		if (sig && sig != SIGINT)
 			goto again;
 	}
@@ -8849,7 +8867,7 @@ static int FAST_FUNC builtin_unset(char **argv)
 static int FAST_FUNC builtin_wait(char **argv)
 {
 	int ret = EXIT_SUCCESS;
-	int status, sig;
+	int status;
 
 	argv = skip_dash_dash(argv);
 	if (argv[0] == NULL) {
@@ -8869,25 +8887,53 @@ static int FAST_FUNC builtin_wait(char **argv)
 		 * ^C <-- after ~4 sec from keyboard
 		 * $
 		 */
-		sigaddset(&G.blocked_set, SIGCHLD);
-		sigprocmask_set(&G.blocked_set);
 		while (1) {
-			checkjobs(NULL);
-			if (errno == ECHILD)
+			int sig;
+			sigset_t oldset, allsigs;
+
+			/* waitpid is not interruptible by SA_RESTARTed
+			 * signals which we use. Thus, this ugly dance:
+			 */
+
+			/* Make sure possible SIGCHLD is stored in kernel's
+			 * pending signal mask before we call waitpid.
+			 * Or else we may race with SIGCHLD, lose it,
+			 * and get stuck in sigwaitinfo...
+			 */
+			sigfillset(&allsigs);
+			sigprocmask(SIG_SETMASK, &allsigs, &oldset);
+
+			if (!sigisemptyset(&G.pending_set)) {
+				/* Crap! we raced with some signal! */
+			//	sig = 0;
+				goto restore;
+			}
+
+			checkjobs(NULL); /* waitpid(WNOHANG) inside */
+			if (errno == ECHILD) {
+				sigprocmask(SIG_SETMASK, &oldset, NULL);
+				break;
+			}
+
+			/* Wait for SIGCHLD or any other signal */
+			//sig = sigwaitinfo(&allsigs, NULL);
+			/* It is vitally important for sigsuspend that SIGCHLD has non-DFL handler! */
+			/* Note: sigsuspend invokes signal handler */
+			sigsuspend(&oldset);
+ restore:
+			sigprocmask(SIG_SETMASK, &oldset, NULL);
+
+			/* So, did we get a signal? */
+			//if (sig > 0)
+			//	raise(sig); /* run handler */
+			sig = check_and_run_traps();
+			if (sig /*&& sig != SIGCHLD - always true */) {
+				/* see note 2 */
+				ret = 128 + sig;
 				break;
-			/* Wait for SIGCHLD or any other signal of interest */
-			/* sigtimedwait with infinite timeout: */
-			sig = sigwaitinfo(&G.blocked_set, NULL);
-			if (sig > 0) {
-				sig = check_and_run_traps(sig);
-				if (sig && sig != SIGCHLD) { /* see note 2 */
-					ret = 128 + sig;
-					break;
-				}
 			}
+			/* SIGCHLD, or no signal, or ignored one, such as SIGQUIT. Repeat */
 		}
-		sigdelset(&G.blocked_set, SIGCHLD);
-		sigprocmask_set(&G.blocked_set);
 		return ret;
 	}
 
-- 
1.7.3.4



More information about the busybox-cvs mailing list