aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--shell/hush.c104
-rw-r--r--shell/hush_test/hush-trap/subshell.right15
-rwxr-xr-xshell/hush_test/hush-trap/subshell.tests10
3 files changed, 88 insertions, 41 deletions
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:-$<pid>:<pid>:<exitcode>:<depth> <vars...> <funcs...>
@@ -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 <script>",
+ * init_sigmasks() 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 block_signals(0)
- * (or re-run block_signals(1) if we ran block_signals(0) before)
+ * If we later decide that we are interactive, we run init_sigmasks()
* in order to intercept (more) signals.
*/
@@ -6908,7 +6929,7 @@ int hush_main(int argc, char **argv)
/* -c 'builtin' [BARGV...] "" ARG0 [ARG1...] */
const struct built_in_command *x;
- block_signals(0); /* 0: called 1st time */
+ init_sigmasks();
x = find_builtin(optarg);
if (x) { /* paranoia */
G.global_argc -= builtin_argc; /* skip [BARGV...] "" */
@@ -6924,7 +6945,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 */
- block_signals(0); /* 0: called 1st time */
+ init_sigmasks();
parse_and_run_string(optarg);
goto final_return;
case 'i':
@@ -6940,7 +6961,9 @@ int hush_main(int argc, char **argv)
case '<': /* "big heredoc" support */
full_write(STDOUT_FILENO, optarg, strlen(optarg));
_exit(0);
- case '$':
+ case '$': {
+ unsigned long long empty_trap_mask;
+
G.root_pid = bb_strtou(optarg, &optarg, 16);
optarg++;
G.root_ppid = bb_strtou(optarg, &optarg, 16);
@@ -6950,11 +6973,26 @@ int hush_main(int argc, char **argv)
G.last_exitcode = bb_strtou(optarg, &optarg, 16);
optarg++;
builtin_argc = bb_strtou(optarg, &optarg, 16);
+ optarg++;
+ empty_trap_mask = bb_strtoull(optarg, &optarg, 16);
+ if (empty_trap_mask != 0) {
+ int sig;
+ init_sigmasks();
+ G.traps = xzalloc(sizeof(G.traps[0]) * NSIG);
+ for (sig = 1; sig < NSIG; sig++) {
+ if (empty_trap_mask & (1LL << sig)) {
+ G.traps[sig] = xzalloc(1); /* == xstrdup(""); */
+ sigaddset(&G.blocked_set, sig);
+ }
+ }
+ sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
+ }
# if ENABLE_HUSH_LOOPS
optarg++;
G.depth_of_loop = bb_strtou(optarg, &optarg, 16);
# endif
break;
+ }
case 'R':
case 'V':
set_local_var(xstrdup(optarg), /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ opt == 'R');
@@ -6997,8 +7035,7 @@ int hush_main(int argc, char **argv)
input = fopen_for_read("/etc/profile");
if (input != NULL) {
close_on_exec_on(fileno(input));
- block_signals(0); /* 0: called 1st time */
- signal_mask_is_inited = 1;
+ init_sigmasks();
parse_and_run_file(input);
fclose(input);
}
@@ -7023,8 +7060,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));
- if (!signal_mask_is_inited)
- block_signals(0); /* 0: called 1st time */
+ init_sigmasks();
parse_and_run_file(input);
#if ENABLE_FEATURE_CLEAN_UP
fclose(input);
@@ -7033,7 +7069,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 block_signals(0/1) as needed.
+ * NB: don't forget to (re)run init_sigmasks() as needed.
*/
/* A shell is interactive if the '-i' flag was given,
@@ -7086,7 +7122,7 @@ int hush_main(int argc, char **argv)
}
/* Block some signals */
- block_signals(signal_mask_is_inited);
+ init_sigmasks();
if (G_saved_tty_pgrp) {
/* Set other signals to restore saved_tty_pgrp */
@@ -7100,9 +7136,9 @@ int hush_main(int argc, char **argv)
/* -1 is special - makes xfuncs longjmp, not exit
* (we reset die_sleep = 0 whereever we [v]fork) */
enable_restore_tty_pgrp_on_exit(); /* sets die_sleep = -1 */
- } else if (!signal_mask_is_inited) {
- block_signals(0); /* 0: called 1st time */
- } /* else: block_signals(0) was done before */
+ } else {
+ init_sigmasks();
+ }
#elif ENABLE_HUSH_INTERACTIVE
/* No job control compiled in, only prompt/line editing */
if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
@@ -7117,15 +7153,11 @@ int hush_main(int argc, char **argv)
}
if (G_interactive_fd) {
close_on_exec_on(G_interactive_fd);
- block_signals(signal_mask_is_inited);
- } else if (!signal_mask_is_inited) {
- block_signals(0);
}
+ init_sigmasks();
#else
/* We have interactiveness code disabled */
- if (!signal_mask_is_inited) {
- block_signals(0);
- }
+ init_sigmasks();
#endif
/* bash:
* if interactive but not a login shell, sources ~/.bashrc
@@ -7471,7 +7503,7 @@ static int FAST_FUNC builtin_trap(char **argv)
free(G.traps[sig]);
G.traps[sig] = xstrdup(new_cmd);
- debug_printf("trap: setting SIG%s (%i) to '%s'",
+ debug_printf("trap: setting SIG%s (%i) to '%s'\n",
get_signame(sig), sig, G.traps[sig]);
/* There is no signal for 0 (EXIT) */
diff --git a/shell/hush_test/hush-trap/subshell.right b/shell/hush_test/hush-trap/subshell.right
index 0d20ed4e9..f865b932b 100644
--- a/shell/hush_test/hush-trap/subshell.right
+++ b/shell/hush_test/hush-trap/subshell.right
@@ -1,6 +1,21 @@
+trap -- '' HUP
+trap -- '' QUIT
+trap -- '' SYS
Ok
+trap -- '' HUP
+trap -- '' QUIT
+trap -- '' SYS
Ok
+trap -- '' HUP
+trap -- '' QUIT
+trap -- '' SYS
Ok
+trap -- '' HUP
+trap -- '' QUIT
+trap -- '' SYS
Ok
+trap -- '' HUP
+trap -- '' QUIT
+trap -- '' SYS
TERM
Done
diff --git a/shell/hush_test/hush-trap/subshell.tests b/shell/hush_test/hush-trap/subshell.tests
index 4564c2ee2..045294bf4 100755
--- a/shell/hush_test/hush-trap/subshell.tests
+++ b/shell/hush_test/hush-trap/subshell.tests
@@ -12,9 +12,9 @@ trap 'bad: caught WINCH' WINCH
trap 'bad: caught TERM' TERM
# using bash, because we don't have $PPID (yet)
-(bash -c 'kill -HUP $PPID'; echo Ok)
-(bash -c 'kill -QUIT $PPID'; echo Ok)
-(bash -c 'kill -SYS $PPID'; echo Ok)
-(bash -c 'kill -WINCH $PPID'; echo Ok)
-(bash -c 'kill -TERM $PPID'; echo Bad: TERM is not reset)
+(trap; bash -c 'kill -HUP $PPID'; echo Ok)
+(trap; bash -c 'kill -QUIT $PPID'; echo Ok)
+(trap; bash -c 'kill -SYS $PPID'; echo Ok)
+(trap; bash -c 'kill -WINCH $PPID'; echo Ok)
+(trap; bash -c 'kill -TERM $PPID'; echo Bad: TERM is not reset)
echo Done