hush: replace signal handling machinery
No binary (build failed)
—
(log yok)
| @@ -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'); | |
| @@ -1550,12 +1604,22 @@ 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; | |
| } | |