hush: better signal handling across ctrl-z
This commit is contained in:
parent
18e19f2b0d
commit
3ac0e00553
214
shell/hush.c
214
shell/hush.c
@ -193,7 +193,7 @@ struct pipe {
|
|||||||
int jobid; /* job number */
|
int jobid; /* job number */
|
||||||
int num_progs; /* total number of programs in job */
|
int num_progs; /* total number of programs in job */
|
||||||
int running_progs; /* number of programs running (not exited) */
|
int running_progs; /* number of programs running (not exited) */
|
||||||
char *text; /* name of job */
|
char *cmdtext; /* name of job */
|
||||||
char *cmdbuf; /* buffer various argv's point into */
|
char *cmdbuf; /* buffer various argv's point into */
|
||||||
pid_t pgrp; /* process group ID for the job */
|
pid_t pgrp; /* process group ID for the job */
|
||||||
struct child_prog *progs; /* array of commands in pipe */
|
struct child_prog *progs; /* array of commands in pipe */
|
||||||
@ -441,6 +441,42 @@ static void signal_SA_RESTART(int sig, void (*handler)(int))
|
|||||||
sigaction(sig, &sa, NULL);
|
sigaction(sig, &sa, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Signals are grouped, we handle them in batches */
|
||||||
|
static void set_fatal_sighandler(void (*handler)(int))
|
||||||
|
{
|
||||||
|
signal(SIGILL , handler);
|
||||||
|
signal(SIGTRAP, handler);
|
||||||
|
signal(SIGABRT, handler);
|
||||||
|
signal(SIGFPE , handler);
|
||||||
|
signal(SIGBUS , handler);
|
||||||
|
signal(SIGSEGV, handler);
|
||||||
|
/* bash 3.2 seems to handle these just like 'fatal' ones */
|
||||||
|
signal(SIGHUP , handler);
|
||||||
|
signal(SIGPIPE, handler);
|
||||||
|
signal(SIGALRM, handler);
|
||||||
|
}
|
||||||
|
static void set_jobctrl_sighandler(void (*handler)(int))
|
||||||
|
{
|
||||||
|
signal(SIGTSTP, handler);
|
||||||
|
signal(SIGTTIN, handler);
|
||||||
|
signal(SIGTTOU, handler);
|
||||||
|
}
|
||||||
|
static void set_misc_sighandler(void (*handler)(int))
|
||||||
|
{
|
||||||
|
signal(SIGINT , handler);
|
||||||
|
signal(SIGQUIT, handler);
|
||||||
|
signal(SIGTERM, handler);
|
||||||
|
}
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
|
|
||||||
struct nofork_save_area nofork_save;
|
struct nofork_save_area nofork_save;
|
||||||
static sigjmp_buf nofork_jb;
|
static sigjmp_buf nofork_jb;
|
||||||
static struct pipe *nofork_pipe;
|
static struct pipe *nofork_pipe;
|
||||||
@ -460,7 +496,7 @@ static void handler_ctrl_z(int sig)
|
|||||||
if (!pid) { /* child */
|
if (!pid) { /* child */
|
||||||
debug_jobs_printf("setting pgrp for child\n");
|
debug_jobs_printf("setting pgrp for child\n");
|
||||||
setpgrp();
|
setpgrp();
|
||||||
signal(SIGTSTP, SIG_DFL); /* make child do default action (stop) */
|
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_jobs_printf("returning to child\n");
|
debug_jobs_printf("returning to child\n");
|
||||||
/* return to nofork, it will eventually exit now,
|
/* return to nofork, it will eventually exit now,
|
||||||
@ -516,35 +552,6 @@ static void hush_exit(int exitcode)
|
|||||||
sigexit(- (exitcode & 0xff));
|
sigexit(- (exitcode & 0xff));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Signals are grouped, we handle them in batches */
|
|
||||||
static void set_fatal_sighandler(void (*handler)(int))
|
|
||||||
{
|
|
||||||
signal(SIGILL , handler);
|
|
||||||
signal(SIGTRAP, handler);
|
|
||||||
signal(SIGABRT, handler);
|
|
||||||
signal(SIGFPE , handler);
|
|
||||||
signal(SIGBUS , handler);
|
|
||||||
signal(SIGSEGV, handler);
|
|
||||||
/* bash 3.2 seems to handle these just like 'fatal' ones,
|
|
||||||
* but _without_ printing signal name. TODO: mimic this too? */
|
|
||||||
signal(SIGHUP , handler);
|
|
||||||
signal(SIGPIPE, handler);
|
|
||||||
signal(SIGALRM, handler);
|
|
||||||
}
|
|
||||||
static void set_jobctrl_sighandler(void (*handler)(int))
|
|
||||||
{
|
|
||||||
signal(SIGTSTP, handler);
|
|
||||||
signal(SIGTTIN, handler);
|
|
||||||
signal(SIGTTOU, handler);
|
|
||||||
}
|
|
||||||
static void set_misc_sighandler(void (*handler)(int))
|
|
||||||
{
|
|
||||||
signal(SIGINT , handler);
|
|
||||||
signal(SIGQUIT, handler);
|
|
||||||
signal(SIGTERM, handler);
|
|
||||||
}
|
|
||||||
/* SIGCHLD is special and handled separately */
|
|
||||||
|
|
||||||
static const char *set_cwd(void)
|
static const char *set_cwd(void)
|
||||||
{
|
{
|
||||||
if (cwd == bb_msg_unknown)
|
if (cwd == bb_msg_unknown)
|
||||||
@ -719,6 +726,7 @@ static int builtin_fg_bg(char **argv)
|
|||||||
if (i < 0) {
|
if (i < 0) {
|
||||||
if (errno == ESRCH) {
|
if (errno == ESRCH) {
|
||||||
delete_finished_bg_job(pi);
|
delete_finished_bg_job(pi);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
} else {
|
} else {
|
||||||
bb_perror_msg("kill (SIGCONT)");
|
bb_perror_msg("kill (SIGCONT)");
|
||||||
}
|
}
|
||||||
@ -759,7 +767,7 @@ static int builtin_jobs(char **argv ATTRIBUTE_UNUSED)
|
|||||||
else
|
else
|
||||||
status_string = "Running";
|
status_string = "Running";
|
||||||
|
|
||||||
printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->text);
|
printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->cmdtext);
|
||||||
}
|
}
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
@ -1266,6 +1274,35 @@ static void pseudo_exec(struct child_prog *child)
|
|||||||
_exit(EXIT_SUCCESS);
|
_exit(EXIT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *get_cmdtext(struct pipe *pi)
|
||||||
|
{
|
||||||
|
char **argv;
|
||||||
|
char *p;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
/* This is subtle. ->cmdtext is created only on first backgrounding.
|
||||||
|
* (Think "cat, <ctrl-z>, fg, <ctrl-z>, fg, <ctrl-z>...." here...)
|
||||||
|
* On subsequent bg argv can be trashed, but we won't use it */
|
||||||
|
if (pi->cmdtext)
|
||||||
|
return pi->cmdtext;
|
||||||
|
argv = pi->progs[0].argv;
|
||||||
|
if (!argv || !argv[0])
|
||||||
|
return (pi->cmdtext = xzalloc(1));
|
||||||
|
|
||||||
|
len = 0;
|
||||||
|
do len += strlen(*argv) + 1; while (*++argv);
|
||||||
|
pi->cmdtext = p = xmalloc(len);
|
||||||
|
argv = pi->progs[0].argv;
|
||||||
|
do {
|
||||||
|
len = strlen(*argv);
|
||||||
|
memcpy(p, *argv, len);
|
||||||
|
p += len;
|
||||||
|
*p++ = ' ';
|
||||||
|
} while (*++argv);
|
||||||
|
p[-1] = '\0';
|
||||||
|
return pi->cmdtext;
|
||||||
|
}
|
||||||
|
|
||||||
static void insert_bg_job(struct pipe *pi)
|
static void insert_bg_job(struct pipe *pi)
|
||||||
{
|
{
|
||||||
struct pipe *thejob;
|
struct pipe *thejob;
|
||||||
@ -1288,27 +1325,17 @@ static void insert_bg_job(struct pipe *pi)
|
|||||||
|
|
||||||
/* physically copy the struct job */
|
/* physically copy the struct job */
|
||||||
memcpy(thejob, pi, sizeof(struct pipe));
|
memcpy(thejob, pi, sizeof(struct pipe));
|
||||||
// (pi->num_progs+1) is one-too-many I think?
|
thejob->progs = xmalloc(sizeof(pi->progs[0]) * pi->num_progs);
|
||||||
thejob->progs = xmalloc(sizeof(pi->progs[0]) * (pi->num_progs+1));
|
memcpy(thejob->progs, pi->progs, sizeof(pi->progs[0]) * pi->num_progs);
|
||||||
memcpy(thejob->progs, pi->progs, sizeof(pi->progs[0]) * (pi->num_progs+1));
|
|
||||||
thejob->next = NULL;
|
thejob->next = NULL;
|
||||||
/*seems to be wrong:*/
|
/*seems to be wrong:*/
|
||||||
/*thejob->running_progs = thejob->num_progs;*/
|
/*thejob->running_progs = thejob->num_progs;*/
|
||||||
/*thejob->stopped_progs = 0;*/
|
/*thejob->stopped_progs = 0;*/
|
||||||
thejob->text = xmalloc(BUFSIZ); /* cmdedit buffer size */
|
thejob->cmdtext = xstrdup(get_cmdtext(pi));
|
||||||
//if (pi->progs[0] && pi->progs[0].argv && pi->progs[0].argv[0])
|
|
||||||
{
|
|
||||||
// FIXME: overflow check? and also trim the size, BUFSIZ can be 4K!
|
|
||||||
char *bar = thejob->text;
|
|
||||||
char **foo = pi->progs[0].argv;
|
|
||||||
if (foo)
|
|
||||||
while (*foo)
|
|
||||||
bar += sprintf(bar, "%s ", *foo++);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* we don't wait for background thejobs to return -- append it
|
/* we don't wait for background thejobs to return -- append it
|
||||||
to the list of backgrounded thejobs and leave it alone */
|
to the list of backgrounded thejobs and leave it alone */
|
||||||
printf("[%d] %d\n", thejob->jobid, thejob->progs[0].pid);
|
printf("[%d] %d %s\n", thejob->jobid, thejob->progs[0].pid, thejob->cmdtext);
|
||||||
last_bg_pid = thejob->progs[0].pid;
|
last_bg_pid = thejob->progs[0].pid;
|
||||||
last_jobid = thejob->jobid;
|
last_jobid = thejob->jobid;
|
||||||
}
|
}
|
||||||
@ -1433,7 +1460,7 @@ static int checkjobs(struct pipe* fg_pipe)
|
|||||||
pi->running_progs--;
|
pi->running_progs--;
|
||||||
if (!pi->running_progs) {
|
if (!pi->running_progs) {
|
||||||
printf(JOB_STATUS_FORMAT, pi->jobid,
|
printf(JOB_STATUS_FORMAT, pi->jobid,
|
||||||
"Done", pi->text);
|
"Done", pi->cmdtext);
|
||||||
delete_finished_bg_job(pi);
|
delete_finished_bg_job(pi);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1466,6 +1493,32 @@ static int checkjobs_and_fg_shell(struct pipe* fg_pipe)
|
|||||||
return rcode;
|
return rcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* run_pipe_real's helper */
|
||||||
|
static int run_single_fg_nofork(struct pipe *pi, const struct bb_applet *a,
|
||||||
|
char **argv)
|
||||||
|
{
|
||||||
|
int rcode;
|
||||||
|
/* TSTP handler will store pid etc in pi */
|
||||||
|
nofork_pipe = pi;
|
||||||
|
save_nofork_data(&nofork_save);
|
||||||
|
if (sigsetjmp(nofork_jb, 1) == 0) {
|
||||||
|
signal_SA_RESTART(SIGTSTP, handler_ctrl_z);
|
||||||
|
rcode = run_nofork_applet_prime(&nofork_save, a, argv);
|
||||||
|
if (--nofork_save.saved != 0) {
|
||||||
|
/* Ctrl-Z forked, we are child */
|
||||||
|
exit(rcode);
|
||||||
|
}
|
||||||
|
return rcode;
|
||||||
|
}
|
||||||
|
/* Ctrl-Z forked, we are parent.
|
||||||
|
* Sighandler has longjmped us here */
|
||||||
|
signal(SIGTSTP, SIG_IGN);
|
||||||
|
debug_jobs_printf("Exiting nofork early\n");
|
||||||
|
restore_nofork_data(&nofork_save);
|
||||||
|
insert_bg_job(pi);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* run_pipe_real() starts all the jobs, but doesn't wait for anything
|
/* run_pipe_real() starts all the jobs, but doesn't wait for anything
|
||||||
* to finish. See checkjobs().
|
* to finish. See checkjobs().
|
||||||
*
|
*
|
||||||
@ -1575,6 +1628,7 @@ static int run_pipe_real(struct pipe *pi)
|
|||||||
* This is perfect for work that comes after exec().
|
* This is perfect for work that comes after exec().
|
||||||
* Is it really safe for inline use? Experimentally,
|
* Is it really safe for inline use? Experimentally,
|
||||||
* things seem to work with glibc. */
|
* things seem to work with glibc. */
|
||||||
|
// TODO: fflush(NULL)?
|
||||||
setup_redirects(child, squirrel);
|
setup_redirects(child, squirrel);
|
||||||
rcode = x->function(argv + i);
|
rcode = x->function(argv + i);
|
||||||
restore_redirects(squirrel);
|
restore_redirects(squirrel);
|
||||||
@ -1586,32 +1640,16 @@ static int run_pipe_real(struct pipe *pi)
|
|||||||
const struct bb_applet *a = find_applet_by_name(argv[i]);
|
const struct bb_applet *a = find_applet_by_name(argv[i]);
|
||||||
if (a && a->nofork) {
|
if (a && a->nofork) {
|
||||||
setup_redirects(child, squirrel);
|
setup_redirects(child, squirrel);
|
||||||
/* TSTP handler will store pid etc in pi */
|
rcode = run_single_fg_nofork(pi, a, argv + i);
|
||||||
nofork_pipe = pi;
|
restore_redirects(squirrel);
|
||||||
save_nofork_data(&nofork_save);
|
return rcode;
|
||||||
if (sigsetjmp(nofork_jb, 1) == 0) {
|
|
||||||
signal_SA_RESTART(SIGTSTP, handler_ctrl_z);
|
|
||||||
rcode = run_nofork_applet_prime(&nofork_save, a, argv + i);
|
|
||||||
if (--nofork_save.saved != 0)
|
|
||||||
/* Ctrl-Z! forked, we are child */
|
|
||||||
exit(rcode);
|
|
||||||
restore_redirects(squirrel);
|
|
||||||
return rcode;
|
|
||||||
} else {
|
|
||||||
/* Ctrl-Z, forked, we are parent.
|
|
||||||
* Sighandler has longjmped us here */
|
|
||||||
signal(SIGTSTP, SIG_IGN);
|
|
||||||
debug_jobs_printf("Exiting nofork early\n");
|
|
||||||
restore_nofork_data(&nofork_save);
|
|
||||||
restore_redirects(squirrel);
|
|
||||||
insert_bg_job(pi);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Going to fork a child per each pipe member */
|
||||||
|
|
||||||
/* 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);
|
||||||
@ -1639,6 +1677,8 @@ static int run_pipe_real(struct pipe *pi)
|
|||||||
/* 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 (interactive_fd) {
|
if (interactive_fd) {
|
||||||
|
/* Don't do pgrp restore anymore on fatal signals */
|
||||||
|
set_fatal_sighandler(SIG_DFL);
|
||||||
if (pi->pgrp < 0) /* true for 1st process only */
|
if (pi->pgrp < 0) /* true for 1st process only */
|
||||||
pi->pgrp = getpid();
|
pi->pgrp = getpid();
|
||||||
if (setpgid(0, pi->pgrp) == 0 && pi->followup != PIPE_BG) {
|
if (setpgid(0, pi->pgrp) == 0 && pi->followup != PIPE_BG) {
|
||||||
@ -1646,8 +1686,6 @@ static int run_pipe_real(struct pipe *pi)
|
|||||||
* to avoid races */
|
* to avoid races */
|
||||||
tcsetpgrp(interactive_fd, pi->pgrp);
|
tcsetpgrp(interactive_fd, pi->pgrp);
|
||||||
}
|
}
|
||||||
/* Don't do pgrp restore anymore on fatal signals */
|
|
||||||
set_fatal_sighandler(SIG_DFL);
|
|
||||||
}
|
}
|
||||||
// in non-interactive case fatal sigs are already SIG_DFL
|
// in non-interactive case fatal sigs are already SIG_DFL
|
||||||
close_all();
|
close_all();
|
||||||
@ -1878,6 +1916,8 @@ static int free_pipe(struct pipe *pi, int indent)
|
|||||||
}
|
}
|
||||||
free(pi->progs); /* children are an array, they get freed all at once */
|
free(pi->progs); /* children are an array, they get freed all at once */
|
||||||
pi->progs = NULL;
|
pi->progs = NULL;
|
||||||
|
free(pi->cmdtext);
|
||||||
|
pi->cmdtext = NULL;
|
||||||
return ret_code;
|
return ret_code;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2252,12 +2292,13 @@ static int setup_redirect(struct p_context *ctx, int fd, redir_type style,
|
|||||||
static struct pipe *new_pipe(void)
|
static struct pipe *new_pipe(void)
|
||||||
{
|
{
|
||||||
struct pipe *pi;
|
struct pipe *pi;
|
||||||
pi = xmalloc(sizeof(struct pipe));
|
pi = xzalloc(sizeof(struct pipe));
|
||||||
pi->num_progs = 0;
|
/*pi->num_progs = 0;*/
|
||||||
pi->progs = NULL;
|
/*pi->progs = NULL;*/
|
||||||
pi->next = NULL;
|
/*pi->next = NULL;*/
|
||||||
pi->followup = 0; /* invalid */
|
/*pi->followup = 0; invalid */
|
||||||
pi->r_mode = RES_NONE;
|
if (RES_NONE)
|
||||||
|
pi->r_mode = RES_NONE;
|
||||||
return pi;
|
return pi;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2424,13 +2465,14 @@ static int done_command(struct p_context *ctx)
|
|||||||
pi->progs = xrealloc(pi->progs, sizeof(*pi->progs) * (pi->num_progs+1));
|
pi->progs = xrealloc(pi->progs, sizeof(*pi->progs) * (pi->num_progs+1));
|
||||||
|
|
||||||
prog = pi->progs + pi->num_progs;
|
prog = pi->progs + pi->num_progs;
|
||||||
prog->redirects = NULL;
|
memset(prog, 0, sizeof(*prog));
|
||||||
prog->argv = NULL;
|
/*prog->redirects = NULL;*/
|
||||||
prog->is_stopped = 0;
|
/*prog->argv = NULL;
|
||||||
prog->group = NULL;
|
/*prog->is_stopped = 0;*/
|
||||||
prog->glob_result.gl_pathv = NULL;
|
/*prog->group = NULL;*/
|
||||||
|
/*prog->glob_result.gl_pathv = NULL;*/
|
||||||
prog->family = pi;
|
prog->family = pi;
|
||||||
prog->sp = 0;
|
/*prog->sp = 0;*/
|
||||||
ctx->child = prog;
|
ctx->child = prog;
|
||||||
prog->type = ctx->type;
|
prog->type = ctx->type;
|
||||||
|
|
||||||
@ -3008,15 +3050,13 @@ static void setup_job_control(void)
|
|||||||
set_misc_sighandler(SIG_IGN);
|
set_misc_sighandler(SIG_IGN);
|
||||||
//huh? signal(SIGCHLD, SIG_IGN);
|
//huh? signal(SIGCHLD, SIG_IGN);
|
||||||
|
|
||||||
/* We _must_ do cleanup on fatal signals */
|
/* We _must_ restore tty pgrp fatal signals */
|
||||||
set_fatal_sighandler(sigexit);
|
set_fatal_sighandler(sigexit);
|
||||||
|
|
||||||
/* Put ourselves in our own process group. */
|
/* Put ourselves in our own process group. */
|
||||||
shell_pgrp = getpid();
|
setpgrp(); /* is the same as setpgid(our_pid, our_pid); */
|
||||||
setpgrp(); /* is the same as setpgid(shell_pgrp, shell_pgrp); */
|
|
||||||
|
|
||||||
/* Grab control of the terminal. */
|
/* Grab control of the terminal. */
|
||||||
tcsetpgrp(interactive_fd, shell_pgrp);
|
tcsetpgrp(interactive_fd, getpid());
|
||||||
}
|
}
|
||||||
|
|
||||||
int hush_main(int argc, char **argv);
|
int hush_main(int argc, char **argv);
|
||||||
|
Loading…
Reference in New Issue
Block a user