hush: fix subshell.tests failure on NOMMU
authorDenys Vlasenko <vda.linux@googlemail.com>
Tue, 12 Jan 2010 14:19:31 +0000 (15:19 +0100)
committerDenys Vlasenko <vda.linux@googlemail.com>
Tue, 12 Jan 2010 14:19:31 +0000 (15:19 +0100)
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
shell/hush.c
shell/hush_test/hush-trap/subshell.right
shell/hush_test/hush-trap/subshell.tests

index 7d851ed..8de8d3c 100644 (file)
@@ -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) */
index 0d20ed4..f865b93 100644 (file)
@@ -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
index 4564c2e..045294b 100755 (executable)
@@ -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