diff options
-rw-r--r-- | shell/hush.c | 41 | ||||
-rw-r--r-- | shell/hush_test/hush-trap/subshell.right | 6 | ||||
-rwxr-xr-x | shell/hush_test/hush-trap/subshell.tests | 20 |
3 files changed, 49 insertions, 18 deletions
diff --git a/shell/hush.c b/shell/hush.c index 00daf3ddd..d75b0da7e 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
@@ -1112,13 +1112,10 @@ static void restore_G_args(save_arg_t *sv, char **argv) | |||
1112 | * are to count SIGCHLDs | 1112 | * are to count SIGCHLDs |
1113 | * and to restore tty pgrp on signal-induced exit. | 1113 | * and to restore tty pgrp on signal-induced exit. |
1114 | * | 1114 | * |
1115 | * TODO compat: | 1115 | * Note 2 (compat): |
1116 | * Standard says "When a subshell is entered, traps that are not being ignored | 1116 | * Standard says "When a subshell is entered, traps that are not being ignored |
1117 | * are set to the default actions". bash interprets it so that traps which | 1117 | * are set to the default actions". bash interprets it so that traps which |
1118 | * are set to "" (ignore) are NOT reset to defaults. We reset them to default. | 1118 | * are set to "" (ignore) are NOT reset to defaults. We do the same. |
1119 | * bash example: | ||
1120 | * # trap '' SYS; (bash -c 'kill -SYS $PPID'; echo YES) | ||
1121 | * YES <-- subshell was not killed by SIGSYS | ||
1122 | */ | 1119 | */ |
1123 | enum { | 1120 | enum { |
1124 | SPECIAL_INTERACTIVE_SIGS = 0 | 1121 | SPECIAL_INTERACTIVE_SIGS = 0 |
@@ -2605,43 +2602,51 @@ static void reset_traps_to_defaults(void) | |||
2605 | { | 2602 | { |
2606 | /* This function is always called in a child shell | 2603 | /* This function is always called in a child shell |
2607 | * after fork (not vfork, NOMMU doesn't use this function). | 2604 | * after fork (not vfork, NOMMU doesn't use this function). |
2608 | * Child shells are not interactive. | ||
2609 | * SIGTTIN/SIGTTOU/SIGTSTP should not have special handling. | ||
2610 | * Testcase: (while :; do :; done) + ^Z should background. | ||
2611 | * Same goes for SIGTERM, SIGHUP, SIGINT. | ||
2612 | */ | 2605 | */ |
2613 | unsigned sig; | 2606 | unsigned sig; |
2614 | unsigned mask; | 2607 | unsigned mask; |
2615 | 2608 | ||
2609 | /* Child shells are not interactive. | ||
2610 | * SIGTTIN/SIGTTOU/SIGTSTP should not have special handling. | ||
2611 | * Testcase: (while :; do :; done) + ^Z should background. | ||
2612 | * Same goes for SIGTERM, SIGHUP, SIGINT. | ||
2613 | */ | ||
2616 | if (!G.traps && !(G.non_DFL_mask & SPECIAL_INTERACTIVE_SIGS)) | 2614 | if (!G.traps && !(G.non_DFL_mask & SPECIAL_INTERACTIVE_SIGS)) |
2617 | return; | 2615 | return; /* already no traps and no SPECIAL_INTERACTIVE_SIGS */ |
2618 | 2616 | ||
2619 | /* Stupid. It can be done with *single* &= op, but we can't use | 2617 | /* Switching off SPECIAL_INTERACTIVE_SIGS. |
2620 | * the fact that G.blocked_set is implemented as a bitmask... */ | 2618 | * Stupid. It can be done with *single* &= op, but we can't use |
2619 | * the fact that G.blocked_set is implemented as a bitmask | ||
2620 | * in libc... */ | ||
2621 | mask = (SPECIAL_INTERACTIVE_SIGS >> 1); | 2621 | mask = (SPECIAL_INTERACTIVE_SIGS >> 1); |
2622 | sig = 1; | 2622 | sig = 1; |
2623 | while (1) { | 2623 | while (1) { |
2624 | if (mask & 1) | 2624 | if (mask & 1) { |
2625 | sigdelset(&G.blocked_set, sig); | 2625 | /* Careful. Only if no trap or trap is not "" */ |
2626 | if (!G.traps || !G.traps[sig] || G.traps[sig][0]) | ||
2627 | sigdelset(&G.blocked_set, sig); | ||
2628 | } | ||
2626 | mask >>= 1; | 2629 | mask >>= 1; |
2627 | if (!mask) | 2630 | if (!mask) |
2628 | break; | 2631 | break; |
2629 | sig++; | 2632 | sig++; |
2630 | } | 2633 | } |
2631 | 2634 | /* Our homegrown sig mask is saner to work with :) */ | |
2632 | G.non_DFL_mask &= ~SPECIAL_INTERACTIVE_SIGS; | 2635 | G.non_DFL_mask &= ~SPECIAL_INTERACTIVE_SIGS; |
2636 | |||
2637 | /* Resetting all traps to default except empty ones */ | ||
2633 | mask = G.non_DFL_mask; | 2638 | mask = G.non_DFL_mask; |
2634 | if (G.traps) for (sig = 0; sig < NSIG; sig++, mask >>= 1) { | 2639 | if (G.traps) for (sig = 0; sig < NSIG; sig++, mask >>= 1) { |
2635 | if (!G.traps[sig]) | 2640 | if (!G.traps[sig] || !G.traps[sig][0]) |
2636 | continue; | 2641 | continue; |
2637 | free(G.traps[sig]); | 2642 | free(G.traps[sig]); |
2638 | G.traps[sig] = NULL; | 2643 | G.traps[sig] = NULL; |
2639 | /* There is no signal for 0 (EXIT) */ | 2644 | /* There is no signal for 0 (EXIT) */ |
2640 | if (sig == 0) | 2645 | if (sig == 0) |
2641 | continue; | 2646 | continue; |
2642 | /* There was a trap handler, we are removing it. | 2647 | /* There was a trap handler, we just removed it. |
2643 | * But if sig still has non-DFL handling, | 2648 | * But if sig still has non-DFL handling, |
2644 | * we should not unblock it. */ | 2649 | * we should not unblock the sig. */ |
2645 | if (mask & 1) | 2650 | if (mask & 1) |
2646 | continue; | 2651 | continue; |
2647 | sigdelset(&G.blocked_set, sig); | 2652 | 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 000000000..0d20ed4e9 --- /dev/null +++ b/shell/hush_test/hush-trap/subshell.right | |||
@@ -0,0 +1,6 @@ | |||
1 | Ok | ||
2 | Ok | ||
3 | Ok | ||
4 | Ok | ||
5 | TERM | ||
6 | Done | ||
diff --git a/shell/hush_test/hush-trap/subshell.tests b/shell/hush_test/hush-trap/subshell.tests new file mode 100755 index 000000000..b5d6094d6 --- /dev/null +++ b/shell/hush_test/hush-trap/subshell.tests | |||
@@ -0,0 +1,20 @@ | |||
1 | # Non-empty traps should be reset in subshell | ||
2 | |||
3 | # HUP is special in interactive shells | ||
4 | trap '' HUP | ||
5 | # QUIT is always special | ||
6 | trap '' QUIT | ||
7 | # SYS is not special | ||
8 | trap '' SYS | ||
9 | # WINCH is harmless | ||
10 | trap 'bad: caught WINCH' WINCH | ||
11 | # With TERM we'll check whether it is reset | ||
12 | trap 'bad: caught TERM' TERM | ||
13 | |||
14 | # using bash, becuase we don't have $PPID (yet) | ||
15 | (bash -c 'kill -HUP $PPID'; echo Ok) | ||
16 | (bash -c 'kill -QUIT $PPID'; echo Ok) | ||
17 | (bash -c 'kill -SYS $PPID'; echo Ok) | ||
18 | (bash -c 'kill -WINCH $PPID'; echo Ok) | ||
19 | (bash -c 'kill -TERM $PPID'; echo Bad: TERM is not reset) | ||
20 | echo Done | ||