hush: do not reset to default "" traps in subshell
function old new delta
reset_traps_to_defaults 164 211 +47
builtin_umask 123 121 -2
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/shell/hush.c b/shell/hush.c
index 00daf3d..d75b0da 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -1112,13 +1112,10 @@
* are to count SIGCHLDs
* and to restore tty pgrp on signal-induced exit.
*
- * TODO compat:
+ * 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 reset them to default.
- * bash example:
- * # trap '' SYS; (bash -c 'kill -SYS $PPID'; echo YES)
- * YES <-- subshell was not killed by SIGSYS
+ * are set to "" (ignore) are NOT reset to defaults. We do the same.
*/
enum {
SPECIAL_INTERACTIVE_SIGS = 0
@@ -2605,43 +2602,51 @@
{
/* This function is always called in a child shell
* after fork (not vfork, NOMMU doesn't use this function).
- * Child shells are not interactive.
- * SIGTTIN/SIGTTOU/SIGTSTP should not have special handling.
- * Testcase: (while :; do :; done) + ^Z should background.
- * Same goes for SIGTERM, SIGHUP, SIGINT.
*/
unsigned sig;
unsigned mask;
+ /* Child shells are not interactive.
+ * SIGTTIN/SIGTTOU/SIGTSTP should not have special handling.
+ * Testcase: (while :; do :; done) + ^Z should background.
+ * Same goes for SIGTERM, SIGHUP, SIGINT.
+ */
if (!G.traps && !(G.non_DFL_mask & SPECIAL_INTERACTIVE_SIGS))
- return;
+ return; /* already no traps and no 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... */
+ /* 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 >> 1);
sig = 1;
while (1) {
- if (mask & 1)
- sigdelset(&G.blocked_set, 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);
+ }
mask >>= 1;
if (!mask)
break;
sig++;
}
-
+ /* Our homegrown sig mask is saner to work with :) */
G.non_DFL_mask &= ~SPECIAL_INTERACTIVE_SIGS;
+
+ /* Resetting all traps to default except empty ones */
mask = G.non_DFL_mask;
if (G.traps) for (sig = 0; sig < NSIG; sig++, mask >>= 1) {
- if (!G.traps[sig])
+ if (!G.traps[sig] || !G.traps[sig][0])
continue;
free(G.traps[sig]);
G.traps[sig] = NULL;
/* There is no signal for 0 (EXIT) */
if (sig == 0)
continue;
- /* There was a trap handler, we are removing it.
+ /* There was a trap handler, we just removed it.
* But if sig still has non-DFL handling,
- * we should not unblock it. */
+ * we should not unblock the sig. */
if (mask & 1)
continue;
sigdelset(&G.blocked_set, sig);
diff --git a/shell/hush_test/hush-trap/subshell.right b/shell/hush_test/hush-trap/subshell.right
new file mode 100644
index 0000000..0d20ed4
--- /dev/null
+++ b/shell/hush_test/hush-trap/subshell.right
@@ -0,0 +1,6 @@
+Ok
+Ok
+Ok
+Ok
+TERM
+Done
diff --git a/shell/hush_test/hush-trap/subshell.tests b/shell/hush_test/hush-trap/subshell.tests
new file mode 100755
index 0000000..b5d6094
--- /dev/null
+++ b/shell/hush_test/hush-trap/subshell.tests
@@ -0,0 +1,20 @@
+# Non-empty traps should be reset in subshell
+
+# HUP is special in interactive shells
+trap '' HUP
+# QUIT is always special
+trap '' QUIT
+# SYS is not special
+trap '' SYS
+# WINCH is harmless
+trap 'bad: caught WINCH' WINCH
+# With TERM we'll check whether it is reset
+trap 'bad: caught TERM' TERM
+
+# using bash, becuase 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)
+echo Done