hush: rework signal and trap handling. Some smaller bits are TODO, expect minor breakage
No binary (build failed)
—
(log yok)
| @@ -460,13 +460,13 @@ struct globals { | |
| int last_jobid; | |
| struct pipe *job_list; | |
| struct pipe *toplevel_list; | |
| - | smallint ctrl_z_flag; |
| + | //// smallint ctrl_z_flag; |
| #endif | |
| #if ENABLE_HUSH_LOOPS | |
| smallint flag_break_continue; | |
| #endif | |
| smallint fake_mode; | |
| - | /* these three support $?, $#, and $1 */ |
| + | /* These four support $?, $#, and $1 */ |
| smalluint last_return_code; | |
| /* is global_argv and global_argv[1..n] malloced? (note: not [0]) */ | |
| smalluint global_args_malloced; | |
| @@ -489,10 +489,14 @@ struct globals { | |
| #endif | |
| unsigned char charmap[256]; | |
| char user_input_buf[ENABLE_FEATURE_EDITING ? BUFSIZ : 2]; | |
| - | struct { |
| - | char *cmd; |
| - | struct sigaction oact; |
| - | } *traps; |
| + | /* Signal and trap handling */ |
| + | char **traps; /* char *traps[NSIG] */ |
| + | /* which signals have non-DFL handler (even with no traps set)? */ |
| + | unsigned non_DFL_mask; |
| + | /* which signals are known to be IGNed on entry to shell? */ |
| + | unsigned IGN_mask; |
| + | sigset_t blocked_set; |
| + | sigset_t inherited_set; |
| }; | |
| #define G (*ptr_to_globals) | |
| @@ -521,11 +525,9 @@ static int builtin_help(char **argv); | |
| static int builtin_pwd(char **argv); | |
| static int builtin_read(char **argv); | |
| static int builtin_test(char **argv); | |
| - | static void handle_trap(int sig); |
| static int builtin_trap(char **argv); | |
| static int builtin_true(char **argv); | |
| static int builtin_set(char **argv); | |
| - | static int builtin_set_mode(const char, const char); |
| static int builtin_shift(char **argv); | |
| static int builtin_source(char **argv); | |
| static int builtin_umask(char **argv); | |
| @@ -750,95 +752,270 @@ static void free_strings(char **strings) | |
| } | |
| - | /* Signals are grouped, we handle them in batches */ |
| - | static void set_misc_sighandler(void (*handler)(int)) |
| + | /* Basic theory of signal handling in shell |
| + | * ======================================== |
| + | * This does not describe what hush does, rahter, it is current understanding |
| + | * what it _should_ do. |
| + | * http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#trap |
| + | * |
| + | * Signals are handled only after each pipe ("cmd | cmd | cmd" thing) |
| + | * is finished or backgrounded. It is the same in interactive and |
| + | * non-interactive shells, and is the same regardless of whether |
| + | * a user trap handler is installed or a default one is in effect. |
| + | * ^C or ^Z from keyboard seem to execute "at once" because it usually |
| + | * backgrounds (i.e. stops) or kills all members of currently running |
| + | * pipe. |
| + | * |
| + | * Wait builtin in interruptible by signals for which user trap is set |
| + | * or by SIGINT in interactive shell. |
| + | * |
| + | * Trap handlers will execute even within trap handlers. (right?) |
| + | * |
| + | * User trap handlers are forgotten when subshell is entered. |
| + | * |
| + | * If job control is off, backgrounded commands ("cmd &") |
| + | * have SIGINT, SIGQUIT set to SIG_IGN. |
| + | * |
| + | * Commands run in command substitution ("`cmd`") |
| + | * have SIGTTIN, SIGTTOU, SIGTSTP set to SIG_IGN. |
| + | * |
| + | * Ordinary commands have IGN/DFL set as inherited by the shell |
| + | * from its parent. |
| + | * |
| + | * Default handlers which differ from DFL action |
| + | * (note: subshell is not an interactive shell): |
| + | * |
| + | * SIGQUIT: ignore |
| + | * SIGTERM (interactive): ignore |
| + | * SUGHUP (interactive): send SIGCONT to stopped jobs, |
| + | * send SIGHUP to all jobs and exit |
| + | * SIGTTIN, SIGTTOU, SIGTSTP (if job control is on): ignore |
| + | * (note that ^Z is handled not by trapping SIGTSTP, but by seeing |
| + | * that all pipe members are stopped) (right?) |
| + | * SIGINT (interactive): wait for last pipe, ignore the rest |
| + | * of the command line, show prompt. (check/expand this) |
| + | * Example 1: this waits 5 sec, but does not execute ls: |
| + | * "echo $$; sleep 5; ls -l" + "kill -INT <pid>" |
| + | * Example 2: this does not wait and does not execute ls: |
| + | * "echo $$; sleep 5 & wait; ls -l" + "kill -INT <pid>" |
| + | * |
| + | * (What happens to signals which are IGN on shell start?) |
| + | * (What happens with signal mask on shell start?) |
| + | * |
| + | * Implementation in hush |
| + | * ====================== |
| + | * We use in-kernel pending signal mask to determine which signals were sent. |
| + | * We block all signals which we don't want to take action immediately, |
| + | * i.e. we block all signals which need to have special handling as described |
| + | * above, and all signals which have traps set. |
| + | * After each pipe execution, we extract any pending signals via sigtimedwait() |
| + | * and act on them. |
| + | * |
| + | * unsigned non_DFL_mask: a mask of such "special" signals |
| + | * sigset_t blocked_set: current blocked signal set |
| + | * |
| + | * "trap - SIGxxx": clear bit in blocked_set unless it is also in non_DFL |
| + | * "trap 'cmd' SIGxxx": set bit in blocked_set (even if 'cmd' is '') |
| + | * after [v]fork, if we plan to be a shell: |
| + | * nothing for {} subshell (say, "true | { true; true; } | true") |
| + | * unset all traps if () shell. [TODO] |
| + | * after [v]fork, if we plan to exec: |
| + | * POSIX says pending signal mask is cleared in child - no need to clear it. |
| + | * restore blocked signal set to one inherited by shell just prior to exec. |
| + | * |
| + | * Note: as a result, we do not use signal handlers much. The only use |
| + | * is to restore terminal pgrp on exit. |
| + | * |
| + | * TODO: check/fix wait builtin to be interruptible. |
| + | */ |
| + | |
| + | /* called once at shell init */ |
| + | static void init_signal_mask(void) |
| { | |
| - | bb_signals(0 |
| - | + (1 << SIGINT) |
| - | + (1 << SIGQUIT) |
| - | + (1 << SIGTERM) |
| - | , handler); |
| + | unsigned sig; |
| + | unsigned mask = (1 << SIGQUIT); |
| + | #if ENABLE_HUSH_INTERACTIVE |
| + | if (G.interactive_fd) { |
| + | mask = 0 |
| + | | (1 << SIGQUIT) |
| + | | (1 << SIGTERM) |
| + | | (1 << SIGHUP) |
| + | #if ENABLE_HUSH_JOB |
| + | | (1 << SIGTTIN) | (1 << SIGTTOU) | (1 << SIGTSTP) |
| + | #endif |
| + | | (1 << SIGINT) |
| + | ; |
| + | } |
| + | #endif |
| + | G.non_DFL_mask = mask; |
| + | |
| + | /*sigemptyset(&G.blocked_set); - already is */ |
| + | sigprocmask(SIG_SETMASK, NULL, &G.blocked_set); |
| + | sig = 0; |
| + | while (mask) { |
| + | if (mask & 1) |
| + | sigaddset(&G.blocked_set, sig); |
| + | mask >>= 1; |
| + | sig++; |
| + | } |
| + | sigprocmask(SIG_SETMASK, &G.blocked_set, &G.inherited_set); |
| + | } |
| + | static void check_and_run_traps(void) |
| + | { |
| + | static const struct timespec zero_ts = { 0, 0 }; |
| + | smalluint save_rcode; |
| + | int sig; |
| + | |
| + | while (1) { |
| + | sig = sigtimedwait(&G.blocked_set, NULL, &zero_ts); |
| + | if (sig <= 0) |
| + | break; |
| + | |
| + | if (G.traps && G.traps[sig]) { |
| + | if (G.traps[sig][0]) { |
| + | /* We have user-defined handler */ |
| + | char *argv[] = { NULL, xstrdup(G.traps[sig]), NULL }; |
| + | save_rcode = G.last_return_code; |
| + | builtin_eval(argv); |
| + | free(argv[1]); |
| + | G.last_return_code = save_rcode; |
| + | } /* else: "" trap, ignoring signal */ |
| + | continue; |
| + | } |
| + | /* not a trap: special action */ |
| + | #if 0 //TODO |
| + | switch (sig) { |
| + | case SIGHUP: ... |
| + | break; |
| + | case SIGINT: ... |
| + | break; |
| + | default: /* SIGTERM, SIGQUIT, SIGTTIN, SIGTTOU, SIGTSTP */ |
| + | } |
| + | #endif |
| + | } |
| } | |
| + | /* The stuff below needs to be migrated to "Special action" above */ |
| + | |
| + | /////* Signals are grouped, we handle them in batches */ |
| + | ////static void set_misc_sighandler(void (*handler)(int)) |
| + | ////{ |
| + | //// bb_signals(0 |
| + | //// + (1 << SIGINT) |
| + | //// + (1 << SIGQUIT) |
| + | //// + (1 << SIGTERM) |
| + | //// , handler); |
| + | ////} |
| + | |
| #if ENABLE_HUSH_JOB | |
| + | /* helper */ |
| + | static unsigned maybe_set_sighandler(int sig, void (*handler)(int)) |
| + | { |
| + | /* non_DFL_mask'ed signals are, well, masked, |
| + | * no need to set handler for them. |
| + | * IGN_mask'ed signals were found to be IGN on entry, |
| + | * do not change that -> no need to set handler for them either |
| + | */ |
| + | unsigned ign = ((G.IGN_mask|G.non_DFL_mask) >> sig) & 1; |
| + | if (!ign) { |
| + | handler = signal(sig, handler); |
| + | ign = (handler == SIG_IGN); |
| + | if (ign) /* restore back to IGN! */ |
| + | signal(sig, handler); |
| + | } |
| + | return ign << sig; /* pass back knowledge about SIG_IGN */ |
| + | } |
| + | /* Used only to set handler to restore pgrp on exit, and to reset it to DFL */ |
| static void set_fatal_sighandler(void (*handler)(int)) | |
| { | |
| - | bb_signals(0 |
| - | + (1 << SIGILL) |
| - | + (1 << SIGTRAP) |
| - | + (1 << SIGABRT) |
| - | + (1 << SIGFPE) |
| - | + (1 << SIGBUS) |
| - | + (1 << SIGSEGV) |
| + | unsigned mask = 0; |
| + | |
| + | if (HUSH_DEBUG) { |
| + | mask |= maybe_set_sighandler(SIGILL , handler); |
| + | mask |= maybe_set_sighandler(SIGFPE , handler); |
| + | mask |= maybe_set_sighandler(SIGBUS , handler); |
| + | mask |= maybe_set_sighandler(SIGSEGV, handler); |
| + | mask |= maybe_set_sighandler(SIGTRAP, handler); |
| + | } /* else: hush is perfect. what SEGV? */ |
| + | |
| + | mask |= maybe_set_sighandler(SIGABRT, handler); |
| + | |
| /* bash 3.2 seems to handle these just like 'fatal' ones */ | |
| - | + (1 << SIGHUP) |
| - | + (1 << SIGPIPE) |
| - | + (1 << SIGALRM) |
| - | , handler); |
| + | mask |= maybe_set_sighandler(SIGPIPE, handler); |
| + | mask |= maybe_set_sighandler(SIGALRM, handler); |
| + | mask |= maybe_set_sighandler(SIGHUP , handler); |
| + | |
| + | /* if we aren't interactive... but in this case |
| + | * we never want to restore pgrp on exit, and this fn is not called */ |
| + | /*mask |= maybe_set_sighandler(SIGTERM, handler); */ |
| + | /*mask |= maybe_set_sighandler(SIGINT , handler); */ |
| + | |
| + | G.IGN_mask = mask; |
| } | |
| - | static void set_jobctrl_sighandler(void (*handler)(int)) |
| + | /* Used only to suppress ^Z in `cmd` */ |
| + | static void IGN_jobctrl_signals(void) |
| { | |
| bb_signals(0 | |
| + (1 << SIGTSTP) | |
| + (1 << SIGTTIN) | |
| + (1 << SIGTTOU) | |
| - | , handler); |
| + | , SIG_IGN); |
| } | |
| /* SIGCHLD is special and handled separately */ | |
| - | static void set_every_sighandler(void (*handler)(int)) |
| - | { |
| - | set_fatal_sighandler(handler); |
| - | set_jobctrl_sighandler(handler); |
| - | set_misc_sighandler(handler); |
| - | signal(SIGCHLD, handler); |
| - | } |
| - | |
| - | static void handler_ctrl_c(int sig UNUSED_PARAM) |
| - | { |
| - | debug_printf_jobs("got sig %d\n", sig); |
| - | // as usual we can have all kinds of nasty problems with leaked malloc data here |
| - | siglongjmp(G.toplevel_jb, 1); |
| - | } |
| - | |
| - | static void handler_ctrl_z(int sig UNUSED_PARAM) |
| - | { |
| - | pid_t pid; |
| - | |
| - | debug_printf_jobs("got tty sig %d in pid %d\n", sig, getpid()); |
| - | |
| - | if (!BB_MMU) { |
| - | fputs("Sorry, backgrounding (CTRL+Z) of foreground scripts not supported on nommu\n", stderr); |
| - | return; |
| - | } |
| - | |
| - | pid = fork(); |
| - | if (pid < 0) /* can't fork. Pretend there was no ctrl-Z */ |
| - | return; |
| - | G.ctrl_z_flag = 1; |
| - | if (!pid) { /* child */ |
| - | if (ENABLE_HUSH_JOB) |
| - | die_sleep = 0; /* let nofork's xfuncs die */ |
| - | bb_setpgrp(); |
| - | debug_printf_jobs("set pgrp for child %d ok\n", getpid()); |
| - | set_every_sighandler(SIG_DFL); |
| - | raise(SIGTSTP); /* resend TSTP so that child will be stopped */ |
| - | debug_printf_jobs("returning in child\n"); |
| - | /* return to nofork, it will eventually exit now, |
| - | * not return back to shell */ |
| - | return; |
| - | } |
| - | /* parent */ |
| - | /* finish filling up pipe info */ |
| - | G.toplevel_list->pgrp = pid; /* child is in its own pgrp */ |
| - | G.toplevel_list->cmds[0].pid = pid; |
| - | /* parent needs to longjmp out of running nofork. |
| - | * we will "return" exitcode 0, with child put in background */ |
| - | // as usual we can have all kinds of nasty problems with leaked malloc data here |
| - | debug_printf_jobs("siglongjmp in parent\n"); |
| - | siglongjmp(G.toplevel_jb, 1); |
| - | } |
| + | ////static void set_every_sighandler(void (*handler)(int)) |
| + | ////{ |
| + | //// set_fatal_sighandler(handler); |
| + | //// set_jobctrl_sighandler(handler); |
| + | //// set_misc_sighandler(handler); |
| + | //// signal(SIGCHLD, handler); |
| + | ////} |
| + | |
| + | ////static void handler_ctrl_c(int sig UNUSED_PARAM) |
| + | ////{ |
| + | //// debug_printf_jobs("got sig %d\n", sig); |
| + | ////// as usual we can have all kinds of nasty problems with leaked malloc data here |
| + | //// siglongjmp(G.toplevel_jb, 1); |
| + | ////} |
| + | |
| + | ////static void handler_ctrl_z(int sig UNUSED_PARAM) |
| + | ////{ |
| + | //// pid_t pid; |
| + | //// |
| + | //// debug_printf_jobs("got tty sig %d in pid %d\n", sig, getpid()); |
| + | //// |
| + | //// if (!BB_MMU) { |
| + | //// fputs("Sorry, backgrounding (CTRL+Z) of foreground scripts not supported on nommu\n", stderr); |
| + | //// return; |
| + | //// } |
| + | //// |
| + | //// pid = fork(); |
| + | //// if (pid < 0) /* can't fork. Pretend there was no ctrl-Z */ |
| + | //// return; |
| + | //// G.ctrl_z_flag = 1; |
| + | //// if (!pid) { /* child */ |
| + | //// if (ENABLE_HUSH_JOB) |
| + | //// die_sleep = 0; /* let nofork's xfuncs die */ |
| + | //// bb_setpgrp(); |
| + | //// debug_printf_jobs("set pgrp for child %d ok\n", getpid()); |
| + | //////// set_every_sighandler(SIG_DFL); |
| + | //// raise(SIGTSTP); /* resend TSTP so that child will be stopped */ |
| + | //// debug_printf_jobs("returning in child\n"); |
| + | //// /* return to nofork, it will eventually exit now, |
| + | //// * not return back to shell */ |
| + | //// return; |
| + | //// } |
| + | //// /* parent */ |
| + | //// /* finish filling up pipe info */ |
| + | //// G.toplevel_list->pgrp = pid; /* child is in its own pgrp */ |
| + | //// G.toplevel_list->cmds[0].pid = pid; |
| + | //// /* parent needs to longjmp out of running nofork. |
| + | //// * we will "return" exitcode 0, with child put in background */ |
| + | ////// as usual we can have all kinds of nasty problems with leaked malloc data here |
| + | //// debug_printf_jobs("siglongjmp in parent\n"); |
| + | //// siglongjmp(G.toplevel_jb, 1); |
| + | ////} |
| /* Restores tty foreground process group, and exits. | |
| * May be called as signal handler for fatal signal | |
| @@ -865,8 +1042,8 @@ static void sigexit(int sig) | |
| #else /* !JOB */ | |
| - | #define set_fatal_sighandler(handler) ((void)0) |
| - | #define set_jobctrl_sighandler(handler) ((void)0) |
| + | #define set_fatal_sighandler(handler) ((void)0) |
| + | #define IGN_jobctrl_signals(handler) ((void)0) |
| #endif /* JOB */ | |
| @@ -874,8 +1051,11 @@ static void sigexit(int sig) | |
| static void hush_exit(int exitcode) NORETURN; | |
| static void hush_exit(int exitcode) | |
| { | |
| - | if (G.traps && G.traps[0].cmd) |
| - | handle_trap(0); |
| + | if (G.traps && G.traps[0] && G.traps[0][0]) { |
| + | char *argv[] = { NULL, xstrdup(G.traps[0]), NULL }; |
| + | builtin_eval(argv); |
| + | free(argv[1]); |
| + | } |
| if (ENABLE_HUSH_JOB) { | |
| fflush(NULL); /* flush all streams */ | |
| @@ -2052,6 +2232,8 @@ static void pseudo_exec_argv(nommu_save_t *nommu_save, char **argv, int assignme | |
| } | |
| #endif | |
| + | sigprocmask(SIG_SETMASK, &G.inherited_set, NULL); |
| + | |
| debug_printf_exec("execing '%s'\n", argv[0]); | |
| execvp(argv[0], argv); | |
| bb_perror_msg("can't exec '%s'", argv[0]); | |
| @@ -2200,6 +2382,8 @@ static int checkjobs(struct pipe* fg_pipe) | |
| pid_t childpid; | |
| int rcode = 0; | |
| + | debug_printf_jobs("checkjobs %p\n", fg_pipe); |
| + | |
| attributes = WUNTRACED; | |
| if (fg_pipe == NULL) | |
| attributes |= WNOHANG; | |
| @@ -2459,7 +2643,7 @@ static int run_pipe(struct pipe *pi) | |
| /* Disable job control signals for shell (parent) and | |
| * for initial child code after fork */ | |
| - | set_jobctrl_sighandler(SIG_IGN); |
| + | //// set_jobctrl_sighandler(SIG_IGN); |
| /* Going to fork a child per each pipe member */ | |
| pi->alive_cmds = 0; | |
| @@ -2486,9 +2670,9 @@ static int run_pipe(struct pipe *pi) | |
| command->pid = BB_MMU ? fork() : vfork(); | |
| if (!command->pid) { /* child */ | |
| - | if (ENABLE_HUSH_JOB) |
| - | die_sleep = 0; /* let nofork's xfuncs die */ |
| #if ENABLE_HUSH_JOB | |
| + | die_sleep = 0; /* let nofork's xfuncs die */ |
| + | |
| /* Every child adds itself to new process group | |
| * with pgid == pid_of_first_child_in_pipe */ | |
| if (G.run_list_level == 1 && G.interactive_fd) { | |
| @@ -2514,9 +2698,9 @@ static int run_pipe(struct pipe *pi) | |
| setup_redirects(command, NULL); | |
| /* Restore default handlers just prior to exec */ | |
| - | set_jobctrl_sighandler(SIG_DFL); |
| - | set_misc_sighandler(SIG_DFL); |
| - | signal(SIGCHLD, SIG_DFL); |
| + | //// set_jobctrl_sighandler(SIG_DFL); |
| + | //// set_misc_sighandler(SIG_DFL); |
| + | //// signal(SIGCHLD, SIG_DFL); |
| /* Stores to nommu_save list of env vars putenv'ed | |
| * (NOMMU, on MMU we don't need that) */ | |
| /* cast away volatility... */ | |
| @@ -2693,6 +2877,8 @@ static int run_list(struct pipe *pi) | |
| * in order to return, no direct "return" statements please. | |
| * This helps to ensure that no memory is leaked. */ | |
| + | //TODO: needs re-thinking |
| + | |
| #if ENABLE_HUSH_JOB | |
| /* Example of nested list: "while true; do { sleep 1 | exit 2; } done". | |
| * We are saving state before entering outermost list ("while...done") | |
| @@ -2713,27 +2899,27 @@ static int run_list(struct pipe *pi) | |
| restore_nofork_data(&G.nofork_save); | |
| } | |
| #endif | |
| - | if (G.ctrl_z_flag) { |
| - | /* ctrl-Z has forked and stored pid of the child in pi->pid. |
| - | * Remember this child as background job */ |
| - | insert_bg_job(pi); |
| - | } else { |
| + | //// if (G.ctrl_z_flag) { |
| + | //// /* ctrl-Z has forked and stored pid of the child in pi->pid. |
| + | //// * Remember this child as background job */ |
| + | //// insert_bg_job(pi); |
| + | //// } else { |
| /* ctrl-C. We just stop doing whatever we were doing */ | |
| bb_putchar('\n'); | |
| - | } |
| + | //// } |
| USE_HUSH_LOOPS(loop_top = NULL;) | |
| USE_HUSH_LOOPS(G.depth_of_loop = 0;) | |
| rcode = 0; | |
| goto ret; | |
| } | |
| - | /* ctrl-Z handler will store pid etc in pi */ |
| - | G.toplevel_list = pi; |
| - | G.ctrl_z_flag = 0; |
| - | #if ENABLE_FEATURE_SH_STANDALONE |
| - | G.nofork_save.saved = 0; /* in case we will run a nofork later */ |
| - | #endif |
| - | signal_SA_RESTART_empty_mask(SIGTSTP, handler_ctrl_z); |
| - | signal(SIGINT, handler_ctrl_c); |
| + | //// /* ctrl-Z handler will store pid etc in pi */ |
| + | //// G.toplevel_list = pi; |
| + | //// G.ctrl_z_flag = 0; |
| + | ////#if ENABLE_FEATURE_SH_STANDALONE |
| + | //// G.nofork_save.saved = 0; /* in case we will run a nofork later */ |
| + | ////#endif |
| + | //// signal_SA_RESTART_empty_mask(SIGTSTP, handler_ctrl_z); |
| + | //// signal(SIGINT, handler_ctrl_c); |
| } | |
| #endif /* JOB */ | |
| @@ -2854,11 +3040,17 @@ static int run_list(struct pipe *pi) | |
| continue; /* not matched yet, skip this pipe */ | |
| } | |
| #endif | |
| - | if (pi->num_cmds == 0) |
| - | goto check_jobs_and_continue; |
| + | /* Just pressing <enter> in shell should check for jobs. |
| + | * OTOH, in non-interactive shell this is useless |
| + | * and only leads to extra job checks */ |
| + | if (pi->num_cmds == 0) { |
| + | if (G.interactive_fd) |
| + | goto check_jobs_and_continue; |
| + | continue; |
| + | } |
| /* After analyzing all keywords and conditions, we decided | |
| - | * to execute this pipe. NB: has to do checkjobs(NULL) |
| + | * to execute this pipe. NB: have to do checkjobs(NULL) |
| * after run_pipe() to collect any background children, | |
| * even if list execution is to be stopped. */ | |
| debug_printf_exec(": run_pipe with %d members\n", pi->num_cmds); | |
| @@ -2871,6 +3063,7 @@ static int run_list(struct pipe *pi) | |
| if (r != -1) { | |
| /* we only ran a builtin: rcode is already known | |
| * and we don't need to wait for anything. */ | |
| + | check_and_run_traps(); |
| #if ENABLE_HUSH_LOOPS | |
| /* was it "break" or "continue"? */ | |
| if (G.flag_break_continue) { | |
| @@ -2895,6 +3088,7 @@ static int run_list(struct pipe *pi) | |
| /* even bash 3.2 doesn't do that well with nested bg: | |
| * try "{ { sleep 10; echo DEEP; } & echo HERE; } &". | |
| * I'm NOT treating inner &'s as jobs */ | |
| + | check_and_run_traps(); |
| #if ENABLE_HUSH_JOB | |
| if (G.run_list_level == 1) | |
| insert_bg_job(pi); | |
| @@ -2905,11 +3099,13 @@ static int run_list(struct pipe *pi) | |
| if (G.run_list_level == 1 && G.interactive_fd) { | |
| /* waits for completion, then fg's main shell */ | |
| rcode = checkjobs_and_fg_shell(pi); | |
| + | check_and_run_traps(); |
| debug_printf_exec(": checkjobs_and_fg_shell returned %d\n", rcode); | |
| } else | |
| #endif | |
| { /* this one just waits for completion */ | |
| rcode = checkjobs(pi); | |
| + | check_and_run_traps(); |
| debug_printf_exec(": checkjobs returned %d\n", rcode); | |
| } | |
| } | |
| @@ -2948,17 +3144,18 @@ static int run_list(struct pipe *pi) | |
| } /* for (pi) */ | |
| #if ENABLE_HUSH_JOB | |
| - | if (G.ctrl_z_flag) { |
| - | /* ctrl-Z forked somewhere in the past, we are the child, |
| - | * and now we completed running the list. Exit. */ |
| - | //TODO: _exit? |
| - | exit(rcode); |
| - | } |
| + | //// if (G.ctrl_z_flag) { |
| + | //// /* ctrl-Z forked somewhere in the past, we are the child, |
| + | //// * and now we completed running the list. Exit. */ |
| + | //////TODO: _exit? |
| + | //// exit(rcode); |
| + | //// } |
| ret: | |
| - | if (!--G.run_list_level && G.interactive_fd) { |
| - | signal(SIGTSTP, SIG_IGN); |
| - | signal(SIGINT, SIG_IGN); |
| - | } |
| + | G.run_list_level--; |
| + | //// if (!G.run_list_level && G.interactive_fd) { |
| + | //// signal(SIGTSTP, SIG_IGN); |
| + | //// signal(SIGINT, SIG_IGN); |
| + | //// } |
| #endif | |
| debug_printf_exec("run_list lvl %d return %d\n", G.run_list_level + 1, rcode); | |
| #if ENABLE_HUSH_LOOPS | |
| @@ -3478,10 +3675,10 @@ static FILE *generate_stream_from_list(struct pipe *head) | |
| /* Process substitution is not considered to be usual | |
| * 'command execution'. | |
| * SUSv3 says ctrl-Z should be ignored, ctrl-C should not. */ | |
| - | /* Not needed, we are relying on it being disabled |
| - | * everywhere outside actual command execution. */ |
| - | /*set_jobctrl_sighandler(SIG_IGN);*/ |
| - | set_misc_sighandler(SIG_DFL); |
| + | //// /* Not needed, we are relying on it being disabled |
| + | //// * everywhere outside actual command execution. */ |
| + | IGN_jobctrl_signals(); |
| + | //// set_misc_sighandler(SIG_DFL); |
| /* Freeing 'head' here would break NOMMU. */ | |
| _exit(run_list(head)); | |
| } | |
| @@ -4287,7 +4484,6 @@ static void setup_job_control(void) | |
| pid_t shell_pgrp; | |
| shell_pgrp = getpgrp(); | |
| - | close_on_exec_on(G.interactive_fd); |
| /* If we were ran as 'hush &', | |
| * sleep until we are in the foreground. */ | |
| @@ -4298,8 +4494,8 @@ static void setup_job_control(void) | |
| } | |
| /* Ignore job-control and misc signals. */ | |
| - | set_jobctrl_sighandler(SIG_IGN); |
| - | set_misc_sighandler(SIG_IGN); |
| + | //// set_jobctrl_sighandler(SIG_IGN); |
| + | //// set_misc_sighandler(SIG_IGN); |
| //huh? signal(SIGCHLD, SIG_IGN); | |
| /* We _must_ restore tty pgrp on fatal signals */ | |
| @@ -4312,6 +4508,16 @@ static void setup_job_control(void) | |
| } | |
| #endif | |
| + | static int set_mode(const char cstate, const char mode) |
| + | { |
| + | int state = (cstate == '-' ? 1 : 0); |
| + | switch (mode) { |
| + | case 'n': G.fake_mode = state; break; |
| + | case 'x': /*G.debug_mode = state;*/ break; |
| + | default: return EXIT_FAILURE; |
| + | } |
| + | return EXIT_SUCCESS; |
| + | } |
| int hush_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | |
| int hush_main(int argc, char **argv) | |
| @@ -4408,7 +4614,7 @@ int hush_main(int argc, char **argv) | |
| break; | |
| case 'n': | |
| case 'x': | |
| - | if (!builtin_set_mode('-', opt)) |
| + | if (!set_mode('-', opt)) |
| break; | |
| default: | |
| #ifndef BB_VER | |
| @@ -4447,6 +4653,7 @@ int hush_main(int argc, char **argv) | |
| // to (inadvertently) close/redirect it | |
| } | |
| } | |
| + | init_signal_mask(); |
| debug_printf("G.interactive_fd=%d\n", G.interactive_fd); | |
| if (G.interactive_fd) { | |
| fcntl(G.interactive_fd, F_SETFD, FD_CLOEXEC); | |
| @@ -4475,9 +4682,10 @@ int hush_main(int argc, char **argv) | |
| } | |
| if (G.interactive_fd) { | |
| fcntl(G.interactive_fd, F_SETFD, FD_CLOEXEC); | |
| - | set_misc_sighandler(SIG_IGN); |
| + | //// set_misc_sighandler(SIG_IGN); |
| } | |
| } | |
| + | init_signal_mask(); |
| #endif | |
| #if ENABLE_HUSH_INTERACTIVE && !ENABLE_FEATURE_SH_EXTRA_QUIET | |
| @@ -4530,93 +4738,63 @@ int lash_main(int argc, char **argv) | |
| /* | |
| * Built-ins | |
| */ | |
| - | /* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#trap |
| - | * |
| - | * Traps are also evaluated immediately instead of being delayed properly: |
| - | * http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_11 |
| - | * Example: hush -c 'trap "echo hi" 31; sleep 10; echo moo' & sleep 1; kill -31 $! |
| - | * "hi" should not be displayed until the sleep finishes |
| - | * This will have to get fixed ... |
| - | */ |
| - | static void handle_trap(int sig) |
| - | { |
| - | int save_errno, save_rcode; |
| - | char *argv[] = { NULL, G.traps[sig].cmd, NULL }; |
| - | /* Race! We transitioned from handled to ignore/default, but |
| - | * the signal came in after updating .cmd but before we could |
| - | * register the new signal handler. |
| - | */ |
| - | if (!argv[1] || argv[1][0] == '\0') |
| - | return; |
| - | /* need to save/restore errno/$? across traps */ |
| - | save_errno = errno; |
| - | save_rcode = G.last_return_code; |
| - | builtin_eval(argv); |
| - | errno = save_errno; |
| - | G.last_return_code = save_rcode; |
| - | } |
| static int builtin_trap(char **argv) | |
| { | |
| - | size_t i; |
| + | int i; |
| int sig; | |
| - | bool ign = false; |
| - | char *new_cmd = NULL; |
| + | char *new_cmd; |
| if (!G.traps) | |
| - | G.traps = xzalloc(sizeof(*G.traps) * NSIG); |
| + | G.traps = xzalloc(sizeof(G.traps[0]) * NSIG); |
| if (!argv[1]) { | |
| /* No args: print all trapped. This isn't 100% correct as we should | |
| * be escaping the cmd so that it can be pasted back in ... | |
| */ | |
| for (i = 0; i < NSIG; ++i) | |
| - | if (G.traps[i].cmd) |
| - | printf("trap -- '%s' %s\n", G.traps[i].cmd, get_signame(i)); |
| + | if (G.traps[i]) |
| + | printf("trap -- '%s' %s\n", G.traps[i], get_signame(i)); |
| return EXIT_SUCCESS; | |
| } | |
| - | /* first arg is decimal: reset all specified */ |
| - | sig = bb_strtou(argv[1], NULL, 10); |
| + | new_cmd = NULL; |
| + | i = 0; |
| + | /* if first arg is decimal: reset all specified */ |
| + | sig = bb_strtou(*++argv, NULL, 10); |
| if (errno == 0) { | |
| int ret; | |
| - | i = 0; |
| set_all: | |
| ret = EXIT_SUCCESS; | |
| - | while (argv[++i]) { |
| - | char *old_cmd; |
| - | |
| - | sig = get_signum(argv[i]); |
| + | while (*argv) { |
| + | sig = get_signum(*argv++); |
| if (sig < 0 || sig >= NSIG) { | |
| ret = EXIT_FAILURE; | |
| + | /* mimic bash message exactly */ |
| bb_perror_msg("trap: %s: invalid signal specification", argv[i]); | |
| continue; | |
| } | |
| - | /* Make sure .cmd is always a valid command list since |
| - | * signals can occur at any time ... |
| - | */ |
| - | old_cmd = G.traps[sig].cmd; |
| - | G.traps[sig].cmd = xstrdup(new_cmd); |
| - | free(old_cmd); |
| + | free(G.traps[sig]); |
| + | G.traps[sig] = xstrdup(new_cmd); |
| - | debug_printf("trap: setting SIG%s (%i) to: %s", |
| - | get_signame(sig), sig, G.traps[sig].cmd); |
| + | debug_printf("trap: setting SIG%s (%i) to '%s'", |
| + | get_signame(sig), sig, G.traps[sig]); |
| /* There is no signal for 0 (EXIT) */ | |
| if (sig == 0) | |
| continue; | |
| if (new_cmd) { | |
| - | /* add/update a handler */ |
| - | struct sigaction act = { |
| - | .sa_handler = ign ? SIG_IGN : handle_trap, |
| - | .sa_flags = SA_RESTART, |
| - | }; |
| - | sigemptyset(&act.sa_mask); |
| - | sigaction(sig, &act, old_cmd ? NULL : &G.traps[sig].oact); |
| - | } else if (old_cmd && !new_cmd) |
| - | /* there was a handler, and we are removing it */ |
| - | sigaction_set(sig, &G.traps[sig].oact); |
| + | sigaddset(&G.blocked_set, sig); |
| + | } else { |
| + | /* there was a trap handler, we are removing it |
| + | * (if sig has non-DFL handling, |
| + | * we don't need to do anything) */ |
| + | if (sig < 32 && (G.non_DFL_mask & (1 << sig))) |
| + | continue; |
| + | sigdelset(&G.blocked_set, sig); |
| + | } |
| + | sigprocmask(SIG_SETMASK, &G.blocked_set, NULL); |
| } | |
| return ret; | |
| } | |
| @@ -4624,17 +4802,15 @@ static int builtin_trap(char **argv) | |
| /* first arg is "-": reset all specified to default */ | |
| /* first arg is "": ignore all specified */ | |
| /* everything else: execute first arg upon signal */ | |
| - | if (!argv[2]) { |
| + | if (!argv[1]) { |
| bb_error_msg("trap: invalid arguments"); | |
| return EXIT_FAILURE; | |
| } | |
| - | if (LONE_DASH(argv[1])) |
| + | if (LONE_DASH(*argv)) |
| /* nothing! */; | |
| else | |
| - | new_cmd = argv[1]; |
| - | if (argv[1][0] == '\0') |
| - | ign = true; |
| - | i = 1; |
| + | new_cmd = *argv; |
| + | argv++; |
| goto set_all; | |
| } | |
| @@ -4891,16 +5067,6 @@ static int builtin_read(char **argv) | |
| * | |
| * So far, we only support "set -- [argument...]" and some of the short names. | |
| */ | |
| - | static int builtin_set_mode(const char cstate, const char mode) |
| - | { |
| - | int state = (cstate == '-' ? 1 : 0); |
| - | switch (mode) { |
| - | case 'n': G.fake_mode = state; break; |
| - | case 'x': /*G.debug_mode = state;*/ break; |
| - | default: return EXIT_FAILURE; |
| - | } |
| - | return EXIT_SUCCESS; |
| - | } |
| static int builtin_set(char **argv) | |
| { | |
| int n; | |
| @@ -4922,7 +5088,7 @@ static int builtin_set(char **argv) | |
| if (arg[0] == '+' || arg[0] == '-') { | |
| for (n = 1; arg[n]; ++n) | |
| - | if (builtin_set_mode(arg[0], arg[n])) |
| + | if (set_mode(arg[0], arg[n])) |
| goto error; | |
| continue; | |
| } | |
| @@ -5068,27 +5234,29 @@ static int builtin_wait(char **argv) | |
| int ret = EXIT_SUCCESS; | |
| int status; | |
| - | if (argv[1] == NULL) |
| + | if (*++argv == NULL) |
| /* don't care about exit status */ | |
| - | wait(&status); |
| + | wait(NULL); |
| - | while (argv[1]) { |
| - | pid_t pid = bb_strtou(argv[1], NULL, 10); |
| + | while (*argv) { |
| + | pid_t pid = bb_strtou(*argv, NULL, 10); |
| if (errno) { | |
| - | bb_perror_msg("wait %s", argv[1]); |
| + | /* mimic bash message */ |
| + | bb_error_msg("wait: '%s': not a pid or valid job spec", *argv); |
| return EXIT_FAILURE; | |
| - | } else if (waitpid(pid, &status, 0) == pid) { |
| + | } |
| + | if (waitpid(pid, &status, 0) == pid) { |
| if (WIFSIGNALED(status)) | |
| ret = 128 + WTERMSIG(status); | |
| else if (WIFEXITED(status)) | |
| ret = WEXITSTATUS(status); | |
| - | else |
| + | else /* wtf? */ |
| ret = EXIT_FAILURE; | |
| } else { | |
| - | bb_perror_msg("wait %s", argv[1]); |
| + | bb_perror_msg("wait %s", *argv); |
| ret = 127; | |
| } | |
| - | ++argv; |
| + | argv++; |
| } | |
| return ret; | |