hush: rework signal and trap handling. Some smaller bits are TODO,

expect minor breakage

function                                             old     new   delta
set_fatal_sighandler                                  12     186    +174
check_and_run_traps                                    -     122    +122
maybe_set_sighandler                                   -      76     +76
hush_main                                            831     887     +56
sigtimedwait                                           -      50     +50
__GI_sigtimedwait                                      -      50     +50
hush_exit                                             49      93     +44
set_mode                                             749     777     +28
pseudo_exec_argv                                     131     151     +20
static.zero_ts                                         -       8      +8
expand_variables                                    1962    1970      +8
builtin_wait                                         172     174      +2
set_misc_sighandler                                   12       -     -12
set_jobctrl_sighandler                                12       -     -12
handler_ctrl_c                                        16       -     -16
builtin_set_mode                                      28       -     -28
handle_trap                                           97       -     -97
handler_ctrl_z                                       107       -    -107
builtin_trap                                         545     438    -107
run_list                                            2149    2006    -143
------------------------------------------------------------------------------
(add/remove: 5/6 grow/shrink: 7/2 up/down: 638/-522)          Total: 116 bytes
This commit is contained in:
Denis Vlasenko 2009-03-31 11:22:57 +00:00
parent d690f68554
commit d5762932fb

View File

@ -460,13 +460,13 @@ struct globals {
int last_jobid; int last_jobid;
struct pipe *job_list; struct pipe *job_list;
struct pipe *toplevel_list; struct pipe *toplevel_list;
smallint ctrl_z_flag; //// smallint ctrl_z_flag;
#endif #endif
#if ENABLE_HUSH_LOOPS #if ENABLE_HUSH_LOOPS
smallint flag_break_continue; smallint flag_break_continue;
#endif #endif
smallint fake_mode; smallint fake_mode;
/* these three support $?, $#, and $1 */ /* These four support $?, $#, and $1 */
smalluint last_return_code; smalluint last_return_code;
/* is global_argv and global_argv[1..n] malloced? (note: not [0]) */ /* is global_argv and global_argv[1..n] malloced? (note: not [0]) */
smalluint global_args_malloced; smalluint global_args_malloced;
@ -489,10 +489,14 @@ struct globals {
#endif #endif
unsigned char charmap[256]; unsigned char charmap[256];
char user_input_buf[ENABLE_FEATURE_EDITING ? BUFSIZ : 2]; char user_input_buf[ENABLE_FEATURE_EDITING ? BUFSIZ : 2];
struct { /* Signal and trap handling */
char *cmd; char **traps; /* char *traps[NSIG] */
struct sigaction oact; /* which signals have non-DFL handler (even with no traps set)? */
} *traps; 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) #define G (*ptr_to_globals)
@ -521,11 +525,9 @@ static int builtin_help(char **argv);
static int builtin_pwd(char **argv); static int builtin_pwd(char **argv);
static int builtin_read(char **argv); static int builtin_read(char **argv);
static int builtin_test(char **argv); static int builtin_test(char **argv);
static void handle_trap(int sig);
static int builtin_trap(char **argv); static int builtin_trap(char **argv);
static int builtin_true(char **argv); static int builtin_true(char **argv);
static int builtin_set(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_shift(char **argv);
static int builtin_source(char **argv); static int builtin_source(char **argv);
static int builtin_umask(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 */ /* Basic theory of signal handling in shell
static void set_misc_sighandler(void (*handler)(int)) * ========================================
* 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 unsigned sig;
+ (1 << SIGINT) unsigned mask = (1 << SIGQUIT);
+ (1 << SIGQUIT) #if ENABLE_HUSH_INTERACTIVE
+ (1 << SIGTERM) if (G.interactive_fd) {
, handler); 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 #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)) static void set_fatal_sighandler(void (*handler)(int))
{ {
bb_signals(0 unsigned mask = 0;
+ (1 << SIGILL)
+ (1 << SIGTRAP) if (HUSH_DEBUG) {
+ (1 << SIGABRT) mask |= maybe_set_sighandler(SIGILL , handler);
+ (1 << SIGFPE) mask |= maybe_set_sighandler(SIGFPE , handler);
+ (1 << SIGBUS) mask |= maybe_set_sighandler(SIGBUS , handler);
+ (1 << SIGSEGV) 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 */ /* bash 3.2 seems to handle these just like 'fatal' ones */
+ (1 << SIGHUP) mask |= maybe_set_sighandler(SIGPIPE, handler);
+ (1 << SIGPIPE) mask |= maybe_set_sighandler(SIGALRM, handler);
+ (1 << SIGALRM) mask |= maybe_set_sighandler(SIGHUP , handler);
, 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 bb_signals(0
+ (1 << SIGTSTP) + (1 << SIGTSTP)
+ (1 << SIGTTIN) + (1 << SIGTTIN)
+ (1 << SIGTTOU) + (1 << SIGTTOU)
, handler); , SIG_IGN);
} }
/* SIGCHLD is special and handled separately */ /* SIGCHLD is special and handled separately */
static void set_every_sighandler(void (*handler)(int)) ////static void set_every_sighandler(void (*handler)(int))
{ ////{
set_fatal_sighandler(handler); //// set_fatal_sighandler(handler);
set_jobctrl_sighandler(handler); //// set_jobctrl_sighandler(handler);
set_misc_sighandler(handler); //// set_misc_sighandler(handler);
signal(SIGCHLD, handler); //// signal(SIGCHLD, handler);
} ////}
static void handler_ctrl_c(int sig UNUSED_PARAM) ////static void handler_ctrl_c(int sig UNUSED_PARAM)
{ ////{
debug_printf_jobs("got sig %d\n", sig); //// debug_printf_jobs("got sig %d\n", sig);
// as usual we can have all kinds of nasty problems with leaked malloc data here ////// as usual we can have all kinds of nasty problems with leaked malloc data here
siglongjmp(G.toplevel_jb, 1); //// siglongjmp(G.toplevel_jb, 1);
} ////}
static void handler_ctrl_z(int sig UNUSED_PARAM) ////static void handler_ctrl_z(int sig UNUSED_PARAM)
{ ////{
pid_t pid; //// pid_t pid;
////
debug_printf_jobs("got tty sig %d in pid %d\n", sig, getpid()); //// debug_printf_jobs("got tty sig %d in pid %d\n", sig, getpid());
////
if (!BB_MMU) { //// if (!BB_MMU) {
fputs("Sorry, backgrounding (CTRL+Z) of foreground scripts not supported on nommu\n", stderr); //// fputs("Sorry, backgrounding (CTRL+Z) of foreground scripts not supported on nommu\n", stderr);
return; //// return;
} //// }
////
pid = fork(); //// pid = fork();
if (pid < 0) /* can't fork. Pretend there was no ctrl-Z */ //// if (pid < 0) /* can't fork. Pretend there was no ctrl-Z */
return; //// return;
G.ctrl_z_flag = 1; //// G.ctrl_z_flag = 1;
if (!pid) { /* child */ //// if (!pid) { /* child */
if (ENABLE_HUSH_JOB) //// if (ENABLE_HUSH_JOB)
die_sleep = 0; /* let nofork's xfuncs die */ //// die_sleep = 0; /* let nofork's xfuncs die */
bb_setpgrp(); //// bb_setpgrp();
debug_printf_jobs("set pgrp for child %d ok\n", getpid()); //// debug_printf_jobs("set pgrp for child %d ok\n", getpid());
set_every_sighandler(SIG_DFL); //////// set_every_sighandler(SIG_DFL);
raise(SIGTSTP); /* resend TSTP so that child will be stopped */ //// raise(SIGTSTP); /* resend TSTP so that child will be stopped */
debug_printf_jobs("returning in child\n"); //// debug_printf_jobs("returning in child\n");
/* return to nofork, it will eventually exit now, //// /* return to nofork, it will eventually exit now,
* not return back to shell */ //// * not return back to shell */
return; //// return;
} //// }
/* parent */ //// /* parent */
/* finish filling up pipe info */ //// /* finish filling up pipe info */
G.toplevel_list->pgrp = pid; /* child is in its own pgrp */ //// G.toplevel_list->pgrp = pid; /* child is in its own pgrp */
G.toplevel_list->cmds[0].pid = pid; //// G.toplevel_list->cmds[0].pid = pid;
/* parent needs to longjmp out of running nofork. //// /* parent needs to longjmp out of running nofork.
* we will "return" exitcode 0, with child put in background */ //// * 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 ////// as usual we can have all kinds of nasty problems with leaked malloc data here
debug_printf_jobs("siglongjmp in parent\n"); //// debug_printf_jobs("siglongjmp in parent\n");
siglongjmp(G.toplevel_jb, 1); //// siglongjmp(G.toplevel_jb, 1);
} ////}
/* Restores tty foreground process group, and exits. /* Restores tty foreground process group, and exits.
* May be called as signal handler for fatal signal * May be called as signal handler for fatal signal
@ -865,8 +1042,8 @@ static void sigexit(int sig)
#else /* !JOB */ #else /* !JOB */
#define set_fatal_sighandler(handler) ((void)0) #define set_fatal_sighandler(handler) ((void)0)
#define set_jobctrl_sighandler(handler) ((void)0) #define IGN_jobctrl_signals(handler) ((void)0)
#endif /* JOB */ #endif /* JOB */
@ -874,8 +1051,11 @@ static void sigexit(int sig)
static void hush_exit(int exitcode) NORETURN; static void hush_exit(int exitcode) NORETURN;
static void hush_exit(int exitcode) static void hush_exit(int exitcode)
{ {
if (G.traps && G.traps[0].cmd) if (G.traps && G.traps[0] && G.traps[0][0]) {
handle_trap(0); char *argv[] = { NULL, xstrdup(G.traps[0]), NULL };
builtin_eval(argv);
free(argv[1]);
}
if (ENABLE_HUSH_JOB) { if (ENABLE_HUSH_JOB) {
fflush(NULL); /* flush all streams */ fflush(NULL); /* flush all streams */
@ -2052,6 +2232,8 @@ static void pseudo_exec_argv(nommu_save_t *nommu_save, char **argv, int assignme
} }
#endif #endif
sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
debug_printf_exec("execing '%s'\n", argv[0]); debug_printf_exec("execing '%s'\n", argv[0]);
execvp(argv[0], argv); execvp(argv[0], argv);
bb_perror_msg("can't exec '%s'", argv[0]); bb_perror_msg("can't exec '%s'", argv[0]);
@ -2200,6 +2382,8 @@ static int checkjobs(struct pipe* fg_pipe)
pid_t childpid; pid_t childpid;
int rcode = 0; int rcode = 0;
debug_printf_jobs("checkjobs %p\n", fg_pipe);
attributes = WUNTRACED; attributes = WUNTRACED;
if (fg_pipe == NULL) if (fg_pipe == NULL)
attributes |= WNOHANG; attributes |= WNOHANG;
@ -2459,7 +2643,7 @@ static int run_pipe(struct pipe *pi)
/* Disable job control signals for shell (parent) and /* Disable job control signals for shell (parent) and
* for initial child code after fork */ * 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 */ /* Going to fork a child per each pipe member */
pi->alive_cmds = 0; pi->alive_cmds = 0;
@ -2486,9 +2670,9 @@ static int run_pipe(struct pipe *pi)
command->pid = BB_MMU ? fork() : vfork(); command->pid = BB_MMU ? fork() : vfork();
if (!command->pid) { /* child */ if (!command->pid) { /* child */
if (ENABLE_HUSH_JOB)
die_sleep = 0; /* let nofork's xfuncs die */
#if ENABLE_HUSH_JOB #if ENABLE_HUSH_JOB
die_sleep = 0; /* let nofork's xfuncs die */
/* Every child adds itself to new process group /* Every child adds itself to new process group
* with pgid == pid_of_first_child_in_pipe */ * with pgid == pid_of_first_child_in_pipe */
if (G.run_list_level == 1 && G.interactive_fd) { if (G.run_list_level == 1 && G.interactive_fd) {
@ -2514,9 +2698,9 @@ static int run_pipe(struct pipe *pi)
setup_redirects(command, NULL); setup_redirects(command, NULL);
/* Restore default handlers just prior to exec */ /* Restore default handlers just prior to exec */
set_jobctrl_sighandler(SIG_DFL); //// set_jobctrl_sighandler(SIG_DFL);
set_misc_sighandler(SIG_DFL); //// set_misc_sighandler(SIG_DFL);
signal(SIGCHLD, SIG_DFL); //// signal(SIGCHLD, SIG_DFL);
/* Stores to nommu_save list of env vars putenv'ed /* Stores to nommu_save list of env vars putenv'ed
* (NOMMU, on MMU we don't need that) */ * (NOMMU, on MMU we don't need that) */
/* cast away volatility... */ /* cast away volatility... */
@ -2693,6 +2877,8 @@ static int run_list(struct pipe *pi)
* in order to return, no direct "return" statements please. * in order to return, no direct "return" statements please.
* This helps to ensure that no memory is leaked. */ * This helps to ensure that no memory is leaked. */
//TODO: needs re-thinking
#if ENABLE_HUSH_JOB #if ENABLE_HUSH_JOB
/* Example of nested list: "while true; do { sleep 1 | exit 2; } done". /* Example of nested list: "while true; do { sleep 1 | exit 2; } done".
* We are saving state before entering outermost list ("while...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); restore_nofork_data(&G.nofork_save);
} }
#endif #endif
if (G.ctrl_z_flag) { //// if (G.ctrl_z_flag) {
/* ctrl-Z has forked and stored pid of the child in pi->pid. //// /* ctrl-Z has forked and stored pid of the child in pi->pid.
* Remember this child as background job */ //// * Remember this child as background job */
insert_bg_job(pi); //// insert_bg_job(pi);
} else { //// } else {
/* ctrl-C. We just stop doing whatever we were doing */ /* ctrl-C. We just stop doing whatever we were doing */
bb_putchar('\n'); bb_putchar('\n');
} //// }
USE_HUSH_LOOPS(loop_top = NULL;) USE_HUSH_LOOPS(loop_top = NULL;)
USE_HUSH_LOOPS(G.depth_of_loop = 0;) USE_HUSH_LOOPS(G.depth_of_loop = 0;)
rcode = 0; rcode = 0;
goto ret; goto ret;
} }
/* ctrl-Z handler will store pid etc in pi */ //// /* ctrl-Z handler will store pid etc in pi */
G.toplevel_list = pi; //// G.toplevel_list = pi;
G.ctrl_z_flag = 0; //// G.ctrl_z_flag = 0;
#if ENABLE_FEATURE_SH_STANDALONE ////#if ENABLE_FEATURE_SH_STANDALONE
G.nofork_save.saved = 0; /* in case we will run a nofork later */ //// G.nofork_save.saved = 0; /* in case we will run a nofork later */
#endif ////#endif
signal_SA_RESTART_empty_mask(SIGTSTP, handler_ctrl_z); //// signal_SA_RESTART_empty_mask(SIGTSTP, handler_ctrl_z);
signal(SIGINT, handler_ctrl_c); //// signal(SIGINT, handler_ctrl_c);
} }
#endif /* JOB */ #endif /* JOB */
@ -2854,11 +3040,17 @@ static int run_list(struct pipe *pi)
continue; /* not matched yet, skip this pipe */ continue; /* not matched yet, skip this pipe */
} }
#endif #endif
if (pi->num_cmds == 0) /* Just pressing <enter> in shell should check for jobs.
goto check_jobs_and_continue; * 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 /* 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, * after run_pipe() to collect any background children,
* even if list execution is to be stopped. */ * even if list execution is to be stopped. */
debug_printf_exec(": run_pipe with %d members\n", pi->num_cmds); 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) { if (r != -1) {
/* we only ran a builtin: rcode is already known /* we only ran a builtin: rcode is already known
* and we don't need to wait for anything. */ * and we don't need to wait for anything. */
check_and_run_traps();
#if ENABLE_HUSH_LOOPS #if ENABLE_HUSH_LOOPS
/* was it "break" or "continue"? */ /* was it "break" or "continue"? */
if (G.flag_break_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: /* even bash 3.2 doesn't do that well with nested bg:
* try "{ { sleep 10; echo DEEP; } & echo HERE; } &". * try "{ { sleep 10; echo DEEP; } & echo HERE; } &".
* I'm NOT treating inner &'s as jobs */ * I'm NOT treating inner &'s as jobs */
check_and_run_traps();
#if ENABLE_HUSH_JOB #if ENABLE_HUSH_JOB
if (G.run_list_level == 1) if (G.run_list_level == 1)
insert_bg_job(pi); insert_bg_job(pi);
@ -2905,11 +3099,13 @@ static int run_list(struct pipe *pi)
if (G.run_list_level == 1 && G.interactive_fd) { if (G.run_list_level == 1 && G.interactive_fd) {
/* waits for completion, then fg's main shell */ /* waits for completion, then fg's main shell */
rcode = checkjobs_and_fg_shell(pi); rcode = checkjobs_and_fg_shell(pi);
check_and_run_traps();
debug_printf_exec(": checkjobs_and_fg_shell returned %d\n", rcode); debug_printf_exec(": checkjobs_and_fg_shell returned %d\n", rcode);
} else } else
#endif #endif
{ /* this one just waits for completion */ { /* this one just waits for completion */
rcode = checkjobs(pi); rcode = checkjobs(pi);
check_and_run_traps();
debug_printf_exec(": checkjobs returned %d\n", rcode); debug_printf_exec(": checkjobs returned %d\n", rcode);
} }
} }
@ -2948,17 +3144,18 @@ static int run_list(struct pipe *pi)
} /* for (pi) */ } /* for (pi) */
#if ENABLE_HUSH_JOB #if ENABLE_HUSH_JOB
if (G.ctrl_z_flag) { //// if (G.ctrl_z_flag) {
/* ctrl-Z forked somewhere in the past, we are the child, //// /* ctrl-Z forked somewhere in the past, we are the child,
* and now we completed running the list. Exit. */ //// * and now we completed running the list. Exit. */
//TODO: _exit? //////TODO: _exit?
exit(rcode); //// exit(rcode);
} //// }
ret: ret:
if (!--G.run_list_level && G.interactive_fd) { G.run_list_level--;
signal(SIGTSTP, SIG_IGN); //// if (!G.run_list_level && G.interactive_fd) {
signal(SIGINT, SIG_IGN); //// signal(SIGTSTP, SIG_IGN);
} //// signal(SIGINT, SIG_IGN);
//// }
#endif #endif
debug_printf_exec("run_list lvl %d return %d\n", G.run_list_level + 1, rcode); debug_printf_exec("run_list lvl %d return %d\n", G.run_list_level + 1, rcode);
#if ENABLE_HUSH_LOOPS #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 /* Process substitution is not considered to be usual
* 'command execution'. * 'command execution'.
* SUSv3 says ctrl-Z should be ignored, ctrl-C should not. */ * SUSv3 says ctrl-Z should be ignored, ctrl-C should not. */
/* Not needed, we are relying on it being disabled //// /* Not needed, we are relying on it being disabled
* everywhere outside actual command execution. */ //// * everywhere outside actual command execution. */
/*set_jobctrl_sighandler(SIG_IGN);*/ IGN_jobctrl_signals();
set_misc_sighandler(SIG_DFL); //// set_misc_sighandler(SIG_DFL);
/* Freeing 'head' here would break NOMMU. */ /* Freeing 'head' here would break NOMMU. */
_exit(run_list(head)); _exit(run_list(head));
} }
@ -4287,7 +4484,6 @@ static void setup_job_control(void)
pid_t shell_pgrp; pid_t shell_pgrp;
shell_pgrp = getpgrp(); shell_pgrp = getpgrp();
close_on_exec_on(G.interactive_fd);
/* If we were ran as 'hush &', /* If we were ran as 'hush &',
* sleep until we are in the foreground. */ * sleep until we are in the foreground. */
@ -4298,8 +4494,8 @@ static void setup_job_control(void)
} }
/* Ignore job-control and misc signals. */ /* Ignore job-control and misc signals. */
set_jobctrl_sighandler(SIG_IGN); //// set_jobctrl_sighandler(SIG_IGN);
set_misc_sighandler(SIG_IGN); //// set_misc_sighandler(SIG_IGN);
//huh? signal(SIGCHLD, SIG_IGN); //huh? signal(SIGCHLD, SIG_IGN);
/* We _must_ restore tty pgrp on fatal signals */ /* We _must_ restore tty pgrp on fatal signals */
@ -4312,6 +4508,16 @@ static void setup_job_control(void)
} }
#endif #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) MAIN_EXTERNALLY_VISIBLE;
int hush_main(int argc, char **argv) int hush_main(int argc, char **argv)
@ -4408,7 +4614,7 @@ int hush_main(int argc, char **argv)
break; break;
case 'n': case 'n':
case 'x': case 'x':
if (!builtin_set_mode('-', opt)) if (!set_mode('-', opt))
break; break;
default: default:
#ifndef BB_VER #ifndef BB_VER
@ -4447,6 +4653,7 @@ int hush_main(int argc, char **argv)
// to (inadvertently) close/redirect it // to (inadvertently) close/redirect it
} }
} }
init_signal_mask();
debug_printf("G.interactive_fd=%d\n", G.interactive_fd); debug_printf("G.interactive_fd=%d\n", G.interactive_fd);
if (G.interactive_fd) { if (G.interactive_fd) {
fcntl(G.interactive_fd, F_SETFD, FD_CLOEXEC); fcntl(G.interactive_fd, F_SETFD, FD_CLOEXEC);
@ -4475,9 +4682,10 @@ int hush_main(int argc, char **argv)
} }
if (G.interactive_fd) { if (G.interactive_fd) {
fcntl(G.interactive_fd, F_SETFD, FD_CLOEXEC); fcntl(G.interactive_fd, F_SETFD, FD_CLOEXEC);
set_misc_sighandler(SIG_IGN); //// set_misc_sighandler(SIG_IGN);
} }
} }
init_signal_mask();
#endif #endif
#if ENABLE_HUSH_INTERACTIVE && !ENABLE_FEATURE_SH_EXTRA_QUIET #if ENABLE_HUSH_INTERACTIVE && !ENABLE_FEATURE_SH_EXTRA_QUIET
@ -4530,93 +4738,63 @@ int lash_main(int argc, char **argv)
/* /*
* Built-ins * 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) static int builtin_trap(char **argv)
{ {
size_t i; int i;
int sig; int sig;
bool ign = false; char *new_cmd;
char *new_cmd = NULL;
if (!G.traps) if (!G.traps)
G.traps = xzalloc(sizeof(*G.traps) * NSIG); G.traps = xzalloc(sizeof(G.traps[0]) * NSIG);
if (!argv[1]) { if (!argv[1]) {
/* No args: print all trapped. This isn't 100% correct as we should /* 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 ... * be escaping the cmd so that it can be pasted back in ...
*/ */
for (i = 0; i < NSIG; ++i) for (i = 0; i < NSIG; ++i)
if (G.traps[i].cmd) if (G.traps[i])
printf("trap -- '%s' %s\n", G.traps[i].cmd, get_signame(i)); printf("trap -- '%s' %s\n", G.traps[i], get_signame(i));
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
/* first arg is decimal: reset all specified */ new_cmd = NULL;
sig = bb_strtou(argv[1], NULL, 10); i = 0;
/* if first arg is decimal: reset all specified */
sig = bb_strtou(*++argv, NULL, 10);
if (errno == 0) { if (errno == 0) {
int ret; int ret;
i = 0;
set_all: set_all:
ret = EXIT_SUCCESS; ret = EXIT_SUCCESS;
while (argv[++i]) { while (*argv) {
char *old_cmd; sig = get_signum(*argv++);
sig = get_signum(argv[i]);
if (sig < 0 || sig >= NSIG) { if (sig < 0 || sig >= NSIG) {
ret = EXIT_FAILURE; ret = EXIT_FAILURE;
/* mimic bash message exactly */
bb_perror_msg("trap: %s: invalid signal specification", argv[i]); bb_perror_msg("trap: %s: invalid signal specification", argv[i]);
continue; continue;
} }
/* Make sure .cmd is always a valid command list since free(G.traps[sig]);
* signals can occur at any time ... G.traps[sig] = xstrdup(new_cmd);
*/
old_cmd = G.traps[sig].cmd;
G.traps[sig].cmd = xstrdup(new_cmd);
free(old_cmd);
debug_printf("trap: setting SIG%s (%i) to: %s", debug_printf("trap: setting SIG%s (%i) to '%s'",
get_signame(sig), sig, G.traps[sig].cmd); get_signame(sig), sig, G.traps[sig]);
/* There is no signal for 0 (EXIT) */ /* There is no signal for 0 (EXIT) */
if (sig == 0) if (sig == 0)
continue; continue;
if (new_cmd) { if (new_cmd) {
/* add/update a handler */ sigaddset(&G.blocked_set, sig);
struct sigaction act = { } else {
.sa_handler = ign ? SIG_IGN : handle_trap, /* there was a trap handler, we are removing it
.sa_flags = SA_RESTART, * (if sig has non-DFL handling,
}; * we don't need to do anything) */
sigemptyset(&act.sa_mask); if (sig < 32 && (G.non_DFL_mask & (1 << sig)))
sigaction(sig, &act, old_cmd ? NULL : &G.traps[sig].oact); continue;
} else if (old_cmd && !new_cmd) sigdelset(&G.blocked_set, sig);
/* there was a handler, and we are removing it */ }
sigaction_set(sig, &G.traps[sig].oact); sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
} }
return ret; return ret;
} }
@ -4624,17 +4802,15 @@ static int builtin_trap(char **argv)
/* first arg is "-": reset all specified to default */ /* first arg is "-": reset all specified to default */
/* first arg is "": ignore all specified */ /* first arg is "": ignore all specified */
/* everything else: execute first arg upon signal */ /* everything else: execute first arg upon signal */
if (!argv[2]) { if (!argv[1]) {
bb_error_msg("trap: invalid arguments"); bb_error_msg("trap: invalid arguments");
return EXIT_FAILURE; return EXIT_FAILURE;
} }
if (LONE_DASH(argv[1])) if (LONE_DASH(*argv))
/* nothing! */; /* nothing! */;
else else
new_cmd = argv[1]; new_cmd = *argv;
if (argv[1][0] == '\0') argv++;
ign = true;
i = 1;
goto set_all; 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. * 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) static int builtin_set(char **argv)
{ {
int n; int n;
@ -4922,7 +5088,7 @@ static int builtin_set(char **argv)
if (arg[0] == '+' || arg[0] == '-') { if (arg[0] == '+' || arg[0] == '-') {
for (n = 1; arg[n]; ++n) for (n = 1; arg[n]; ++n)
if (builtin_set_mode(arg[0], arg[n])) if (set_mode(arg[0], arg[n]))
goto error; goto error;
continue; continue;
} }
@ -5068,27 +5234,29 @@ static int builtin_wait(char **argv)
int ret = EXIT_SUCCESS; int ret = EXIT_SUCCESS;
int status; int status;
if (argv[1] == NULL) if (*++argv == NULL)
/* don't care about exit status */ /* don't care about exit status */
wait(&status); wait(NULL);
while (argv[1]) { while (*argv) {
pid_t pid = bb_strtou(argv[1], NULL, 10); pid_t pid = bb_strtou(*argv, NULL, 10);
if (errno) { 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; return EXIT_FAILURE;
} else if (waitpid(pid, &status, 0) == pid) { }
if (waitpid(pid, &status, 0) == pid) {
if (WIFSIGNALED(status)) if (WIFSIGNALED(status))
ret = 128 + WTERMSIG(status); ret = 128 + WTERMSIG(status);
else if (WIFEXITED(status)) else if (WIFEXITED(status))
ret = WEXITSTATUS(status); ret = WEXITSTATUS(status);
else else /* wtf? */
ret = EXIT_FAILURE; ret = EXIT_FAILURE;
} else { } else {
bb_perror_msg("wait %s", argv[1]); bb_perror_msg("wait %s", *argv);
ret = 127; ret = 127;
} }
++argv; argv++;
} }
return ret; return ret;