diff --git a/shell/hush.c b/shell/hush.c index 7d851ed97..8de8d3c2a 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -520,6 +520,7 @@ 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; @@ -1050,7 +1051,8 @@ static void restore_G_args(save_arg_t *sv, char **argv) * * Trap handlers will execute even within trap handlers. (right?) * - * User trap handlers are forgotten when subshell ("(cmd)") is entered. + * User trap handlers are forgotten when subshell ("(cmd)") is entered, + * except for handlers set to '' (empty string). * * If job control is off, backgrounded commands ("cmd &") * have SIGINT, SIGQUIT set to SIG_IGN. @@ -1106,7 +1108,7 @@ static void restore_G_args(save_arg_t *sv, char **argv) * after [v]fork, if we plan to be a shell: * unblock signals with special interactive handling * (child shell is not interactive), - * unset all traps (note: regardless of child shell's type - {}, (), etc) + * 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. * Restore blocked signal set to one inherited by shell just prior to exec. @@ -1118,7 +1120,7 @@ static void restore_G_args(save_arg_t *sv, char **argv) * Note 2 (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. + * are set to '' (ignore) are NOT reset to defaults. We do the same. */ enum { SPECIAL_INTERACTIVE_SIGS = 0 @@ -2925,6 +2927,7 @@ static void re_execute_shell(char ***to_free, const char *s, # endif char **argv, **pp; unsigned cnt; + unsigned long long empty_trap_mask; if (!g_argv0) { /* heredoc */ argv = heredoc_argv; @@ -2941,12 +2944,22 @@ static void re_execute_shell(char ***to_free, const char *s, if (pp) while (*pp++) cnt++; - sprintf(param_buf, "-$%x:%x:%x:%x:%x" IF_HUSH_LOOPS(":%x") + empty_trap_mask = 0; + if (G.traps) { + int sig; + for (sig = 1; sig < NSIG; sig++) { + if (G.traps[sig] && !G.traps[sig][0]) + empty_trap_mask |= 1LL << sig; + } + } + + sprintf(param_buf, "-$%x:%x:%x:%x:%x:%llx" IF_HUSH_LOOPS(":%x") , (unsigned) G.root_pid , (unsigned) G.root_ppid , (unsigned) G.last_bg_pid , (unsigned) G.last_exitcode , cnt + , empty_trap_mask IF_HUSH_LOOPS(, G.depth_of_loop) ); /* 1:hush 2:-$::: @@ -3002,7 +3015,9 @@ static void re_execute_shell(char ***to_free, const char *s, * _inside_ group (just before echo 1), it works. * * I conclude it means we don't need to pass active traps here. - * exec syscall below resets them to SIG_DFL for us. + * Even if we would use signal handlers instead of signal masking + * in order to implement trap handling, + * exec syscall below resets signals to SIG_DFL for us. */ *pp++ = (char *) "-c"; *pp++ = (char *) s; @@ -5447,7 +5462,7 @@ static FILE *generate_stream_from_string(const char *s, pid_t *pid_p) pid_t pid; int channel[2]; # if !BB_MMU - char **to_free; + char **to_free = NULL; # endif xpipe(channel); @@ -6677,10 +6692,17 @@ static void parse_and_run_file(FILE *f) } /* Called a few times only (or even once if "sh -c") */ -static void block_signals(int second_time) +static void init_sigmasks(void) { unsigned sig; unsigned mask; + sigset_t old_blocked_set; + + if (!G.inherited_set_is_saved) { + sigprocmask(SIG_SETMASK, NULL, &G.blocked_set); + G.inherited_set = G.blocked_set; + } + old_blocked_set = G.blocked_set; mask = (1 << SIGQUIT); if (G_interactive_fd) { @@ -6690,8 +6712,6 @@ static void block_signals(int second_time) } G.non_DFL_mask = mask; - if (!second_time) - sigprocmask(SIG_SETMASK, NULL, &G.blocked_set); sig = 0; while (mask) { if (mask & 1) @@ -6701,18 +6721,21 @@ static void block_signals(int second_time) } sigdelset(&G.blocked_set, SIGCHLD); - sigprocmask(SIG_SETMASK, &G.blocked_set, - second_time ? NULL : &G.inherited_set); + if (memcmp(&old_blocked_set, &G.blocked_set, sizeof(old_blocked_set)) != 0) + sigprocmask(SIG_SETMASK, &G.blocked_set, NULL); + /* POSIX allows shell to re-enable SIGCHLD * even if it was SIG_IGN on entry */ #if ENABLE_HUSH_FAST G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */ - if (!second_time) + if (!G.inherited_set_is_saved) signal(SIGCHLD, SIGCHLD_handler); #else - if (!second_time) + if (!G.inherited_set_is_saved) signal(SIGCHLD, SIG_DFL); #endif + + G.inherited_set_is_saved = 1; } #if ENABLE_HUSH_JOB @@ -6774,7 +6797,6 @@ int hush_main(int argc, char **argv) .flg_export = 1, .flg_read_only = 1, }; - int signal_mask_is_inited = 0; int opt; unsigned builtin_argc; char **e; @@ -6865,10 +6887,9 @@ int hush_main(int argc, char **argv) } /* Shell is non-interactive at first. We need to call - * block_signals(0) if we are going to execute "sh