From 3ac0e00553adcd7962dc99214fda5d4c3a2d811b Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Sat, 28 Apr 2007 16:45:22 +0000 Subject: [PATCH] hush: better signal handling across ctrl-z --- shell/hush.c | 214 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 127 insertions(+), 87 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index b2ff0cb2e..d249dd59e 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -193,7 +193,7 @@ struct pipe { int jobid; /* job number */ int num_progs; /* total number of programs in job */ 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 */ pid_t pgrp; /* process group ID for the job */ 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); } +/* 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; static sigjmp_buf nofork_jb; static struct pipe *nofork_pipe; @@ -460,7 +496,7 @@ static void handler_ctrl_z(int sig) if (!pid) { /* child */ debug_jobs_printf("setting pgrp for child\n"); 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 */ debug_jobs_printf("returning to child\n"); /* return to nofork, it will eventually exit now, @@ -516,35 +552,6 @@ static void hush_exit(int exitcode) 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) { if (cwd == bb_msg_unknown) @@ -719,6 +726,7 @@ static int builtin_fg_bg(char **argv) if (i < 0) { if (errno == ESRCH) { delete_finished_bg_job(pi); + return EXIT_SUCCESS; } else { bb_perror_msg("kill (SIGCONT)"); } @@ -759,7 +767,7 @@ static int builtin_jobs(char **argv ATTRIBUTE_UNUSED) else 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; } @@ -1266,6 +1274,35 @@ static void pseudo_exec(struct child_prog *child) _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, , fg, , fg, ...." 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) { struct pipe *thejob; @@ -1288,27 +1325,17 @@ static void insert_bg_job(struct pipe *pi) /* physically copy the struct job */ 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+1)); - memcpy(thejob->progs, pi->progs, sizeof(pi->progs[0]) * (pi->num_progs+1)); + thejob->progs = xmalloc(sizeof(pi->progs[0]) * pi->num_progs); + memcpy(thejob->progs, pi->progs, sizeof(pi->progs[0]) * pi->num_progs); thejob->next = NULL; /*seems to be wrong:*/ /*thejob->running_progs = thejob->num_progs;*/ /*thejob->stopped_progs = 0;*/ - thejob->text = xmalloc(BUFSIZ); /* cmdedit buffer size */ - //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++); - } + thejob->cmdtext = xstrdup(get_cmdtext(pi)); /* we don't wait for background thejobs to return -- append it 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_jobid = thejob->jobid; } @@ -1433,7 +1460,7 @@ static int checkjobs(struct pipe* fg_pipe) pi->running_progs--; if (!pi->running_progs) { printf(JOB_STATUS_FORMAT, pi->jobid, - "Done", pi->text); + "Done", pi->cmdtext); delete_finished_bg_job(pi); } } else { @@ -1466,6 +1493,32 @@ static int checkjobs_and_fg_shell(struct pipe* fg_pipe) 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 * 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(). * Is it really safe for inline use? Experimentally, * things seem to work with glibc. */ +// TODO: fflush(NULL)? setup_redirects(child, squirrel); rcode = x->function(argv + i); 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]); if (a && a->nofork) { setup_redirects(child, squirrel); - /* 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 + 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; - } + rcode = run_single_fg_nofork(pi, a, argv + i); + restore_redirects(squirrel); + return rcode; } } #endif } + /* Going to fork a child per each pipe member */ + /* Disable job control signals for shell (parent) and * for initial child code after fork */ 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 * with pgid == pid of first child in pipe */ 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 */ pi->pgrp = getpid(); 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 */ 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 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 */ pi->progs = NULL; + free(pi->cmdtext); + pi->cmdtext = NULL; 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) { struct pipe *pi; - pi = xmalloc(sizeof(struct pipe)); - pi->num_progs = 0; - pi->progs = NULL; - pi->next = NULL; - pi->followup = 0; /* invalid */ - pi->r_mode = RES_NONE; + pi = xzalloc(sizeof(struct pipe)); + /*pi->num_progs = 0;*/ + /*pi->progs = NULL;*/ + /*pi->next = NULL;*/ + /*pi->followup = 0; invalid */ + if (RES_NONE) + pi->r_mode = RES_NONE; 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)); prog = pi->progs + pi->num_progs; - prog->redirects = NULL; - prog->argv = NULL; - prog->is_stopped = 0; - prog->group = NULL; - prog->glob_result.gl_pathv = NULL; + memset(prog, 0, sizeof(*prog)); + /*prog->redirects = NULL;*/ + /*prog->argv = NULL; + /*prog->is_stopped = 0;*/ + /*prog->group = NULL;*/ + /*prog->glob_result.gl_pathv = NULL;*/ prog->family = pi; - prog->sp = 0; + /*prog->sp = 0;*/ ctx->child = prog; prog->type = ctx->type; @@ -3008,15 +3050,13 @@ static void setup_job_control(void) set_misc_sighandler(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); /* Put ourselves in our own process group. */ - shell_pgrp = getpid(); - setpgrp(); /* is the same as setpgid(shell_pgrp, shell_pgrp); */ - + setpgrp(); /* is the same as setpgid(our_pid, our_pid); */ /* Grab control of the terminal. */ - tcsetpgrp(interactive_fd, shell_pgrp); + tcsetpgrp(interactive_fd, getpid()); } int hush_main(int argc, char **argv); -- 2.25.1