diff options
-rw-r--r-- | shell/hush.c | 510 |
1 files changed, 278 insertions, 232 deletions
diff --git a/shell/hush.c b/shell/hush.c index 509bd415b..b2c3a752e 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; } |