From ac0e5ab96ac35d46a6e0755f6f24f016ce788d90 Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Fri, 4 May 2007 21:37:27 +0000 Subject: hush: fix "while true; do true; done" + ctrl-z --- shell/README | 3 + shell/hush.c | 212 +++++++++++++++++++++++++++++++++-------------------------- 2 files changed, 123 insertions(+), 92 deletions(-) (limited to 'shell') diff --git a/shell/README b/shell/README index d962be9bf..284c69145 100644 --- a/shell/README +++ b/shell/README @@ -1,5 +1,8 @@ Various bits of what is known about busybox shells, in no particular order. +2007-05-04 +hush: make ctrl-Z/C work correctly for "while true; do true; done" + 2007-05-03 hush: new bug spotted: Ctrl-C on "while true; do true; done" doesn't work right: diff --git a/shell/hush.c b/shell/hush.c index c51ed1a51..78531e864 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -468,7 +468,6 @@ static const struct built_in_command bltins[] = { #if ENABLE_HUSH_JOB -#if ENABLE_FEATURE_SH_STANDALONE /* move to libbb? */ static void signal_SA_RESTART(int sig, void (*handler)(int)) { @@ -478,7 +477,6 @@ static void signal_SA_RESTART(int sig, void (*handler)(int)) sigemptyset(&sa.sa_mask); sigaction(sig, &sa, NULL); } -#endif /* Signals are grouped, we handle them in batches */ static void set_fatal_sighandler(void (*handler)(int)) @@ -508,7 +506,6 @@ static void set_misc_sighandler(void (*handler)(int)) } /* SIGCHLD is special and handled separately */ -#if ENABLE_FEATURE_SH_STANDALONE static void set_every_sighandler(void (*handler)(int)) { set_fatal_sighandler(handler); @@ -517,53 +514,57 @@ static void set_every_sighandler(void (*handler)(int)) signal(SIGCHLD, handler); } -static struct pipe *nofork_pipe; +static struct pipe *toplevel_list; +static sigjmp_buf toplevel_jb; +smallint ctrl_z_flag; +#if ENABLE_FEATURE_SH_STANDALONE struct nofork_save_area nofork_save; -static sigjmp_buf nofork_jb; +#endif static void handler_ctrl_c(int sig) { debug_printf_jobs("got sig %d\n", sig); // as usual we can have all kinds of nasty problems with leaked malloc data here - siglongjmp(nofork_jb, 1); + siglongjmp(toplevel_jb, 1); } static void handler_ctrl_z(int sig) { pid_t pid; - debug_printf_jobs("got tty sig %d\n", sig); + debug_printf_jobs("got tty sig %d in pid %d\n", sig, getpid()); pid = fork(); - if (pid < 0) /* can't fork. Pretend there were no Ctrl-Z */ + if (pid < 0) /* can't fork. Pretend there were no ctrl-Z */ return; - debug_printf_jobs("bg'ing nofork\n"); - nofork_save.saved = 0; /* flag the fact that Ctrl-Z was handled */ - nofork_pipe->running_progs = 1; - nofork_pipe->stopped_progs = 0; + ctrl_z_flag = 1; +//vda: wrong!! +// toplevel_list->running_progs = 1; +// toplevel_list->stopped_progs = 0; +// if (!pid) { /* child */ - debug_printf_jobs("setting pgrp for child\n"); 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 to child\n"); + 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 */ - nofork_pipe->pgrp = pid; /* child is in its own pgrp */ - nofork_pipe->progs[0].pid = pid; - nofork_pipe->running_progs = 1; - nofork_pipe->stopped_progs = 0; + toplevel_list->pgrp = pid; /* child is in its own pgrp */ + toplevel_list->progs[0].pid = pid; +//vda: wrong!! +// toplevel_list->running_progs = 1; +// toplevel_list->stopped_progs = 0; /* 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 - siglongjmp(nofork_jb, 1); + debug_printf_jobs("siglongjmp in parent\n"); + siglongjmp(toplevel_jb, 1); } -#endif - /* Restores tty foreground process group, and exits. * May be called as signal handler for fatal signal * (will faithfully resend signal to itself, producing correct exit state) @@ -1039,6 +1040,7 @@ static int static_peek(struct in_str *i) } #if ENABLE_HUSH_INTERACTIVE +#if ENABLE_FEATURE_EDITING static void cmdedit_set_initial_prompt(void) { #if !ENABLE_FEATURE_EDITING_FANCY_PROMPT @@ -1049,6 +1051,7 @@ static void cmdedit_set_initial_prompt(void) PS1 = "\\w \\$ "; #endif } +#endif static const char* setup_prompt_string(int promptmode) { @@ -1072,7 +1075,7 @@ static const char* setup_prompt_string(int promptmode) debug_printf("result %s\n", prompt_str); return prompt_str; } -#endif +#endif /* ENABLE_HUSH_INTERACTIVE */ #if ENABLE_FEATURE_EDITING static line_input_t *line_input_state; @@ -1470,7 +1473,7 @@ static int checkjobs(struct pipe* fg_pipe) /* Do we do this right? * bash-3.00# sleep 20 | false - * + * * [3]+ Stopped sleep 20 | false * bash-3.00# echo $? * 1 <========== bg pipe is not fully done, but exitcode is already known! @@ -1590,43 +1593,6 @@ static int checkjobs_and_fg_shell(struct pipe* fg_pipe) } #endif -#if ENABLE_FEATURE_SH_STANDALONE -/* run_pipe_real's helper */ -static int run_single_fg_nofork(struct pipe *pi, const struct bb_applet *a, - char **argv) -{ -#if ENABLE_HUSH_JOB - 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); - signal(SIGINT, handler_ctrl_c); - 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; or Ctrl-C. - * Sighandler has longjmped us here */ - signal(SIGINT, SIG_IGN); - signal(SIGTSTP, SIG_IGN); - debug_printf_jobs("Exiting nofork early\n"); - restore_nofork_data(&nofork_save); - if (nofork_save.saved == 0) /* Ctrl-Z, not Ctrl-C */ - insert_bg_job(pi); - else - putchar('\n'); /* bash does this on Ctrl-C */ - return 0; -#else - return run_nofork_applet(a, argv); -#endif -} -#endif - /* run_pipe_real() starts all the jobs, but doesn't wait for anything * to finish. See checkjobs(). * @@ -1662,7 +1628,7 @@ static int run_pipe_real(struct pipe *pi) #if ENABLE_HUSH_JOB pi->pgrp = -1; #endif - pi->running_progs = 0; + pi->running_progs = 1; pi->stopped_progs = 0; /* Check if this is a simple builtin (not part of a pipe). @@ -1673,8 +1639,6 @@ static int run_pipe_real(struct pipe *pi) if (single_fg && child->group && child->subshell == 0) { debug_printf("non-subshell grouping\n"); setup_redirects(child, squirrel); - /* XXX could we merge code with following builtin case, - * by creating a pseudo builtin that calls run_list_real? */ debug_printf_exec(": run_list_real\n"); rcode = run_list_real(child->group); restore_redirects(squirrel); @@ -1758,8 +1722,9 @@ 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); - debug_printf_exec(": run_single_fg_nofork '%s' '%s'...\n", argv[i], argv[i+1]); - rcode = run_single_fg_nofork(pi, a, argv + i); + debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", argv[i], argv[i+1]); + save_nofork_data(&nofork_save); + rcode = run_nofork_applet_prime(&nofork_save, a, argv); restore_redirects(squirrel); debug_printf_exec("run_pipe_real return %d\n", rcode); return rcode; @@ -1769,6 +1734,7 @@ static int run_pipe_real(struct pipe *pi) } /* Going to fork a child per each pipe member */ + pi->running_progs = 0; /* Disable job control signals for shell (parent) and * for initial child code after fork */ @@ -1865,26 +1831,26 @@ static int run_pipe_real(struct pipe *pi) static void debug_print_tree(struct pipe *pi, int lvl) { static const char *PIPE[] = { - [PIPE_SEQ] = "SEQ", - [PIPE_AND] = "AND", - [PIPE_OR ] = "OR", - [PIPE_BG ] = "BG", + [PIPE_SEQ] = "SEQ", + [PIPE_AND] = "AND", + [PIPE_OR ] = "OR", + [PIPE_BG ] = "BG", }; static const char *RES[] = { - [RES_NONE ] = "NONE" , - [RES_IF ] = "IF" , - [RES_THEN ] = "THEN" , - [RES_ELIF ] = "ELIF" , - [RES_ELSE ] = "ELSE" , - [RES_FI ] = "FI" , - [RES_FOR ] = "FOR" , - [RES_WHILE] = "WHILE", - [RES_UNTIL] = "UNTIL", - [RES_DO ] = "DO" , - [RES_DONE ] = "DONE" , - [RES_XXXX ] = "XXXX" , - [RES_IN ] = "IN" , - [RES_SNTX ] = "SNTX" , + [RES_NONE ] = "NONE" , + [RES_IF ] = "IF" , + [RES_THEN ] = "THEN" , + [RES_ELIF ] = "ELIF" , + [RES_ELSE ] = "ELSE" , + [RES_FI ] = "FI" , + [RES_FOR ] = "FOR" , + [RES_WHILE] = "WHILE", + [RES_UNTIL] = "UNTIL", + [RES_DO ] = "DO" , + [RES_DONE ] = "DONE" , + [RES_XXXX ] = "XXXX" , + [RES_IN ] = "IN" , + [RES_SNTX ] = "SNTX" , }; int pin, prn; @@ -1897,7 +1863,9 @@ static void debug_print_tree(struct pipe *pi, int lvl) while (prn < pi->num_progs) { fprintf(stderr, "%*s prog %d", lvl*2, "", prn); if (pi->progs[prn].group) { - fprintf(stderr, " group: (argv=%p)\n", pi->progs[prn].argv); + fprintf(stderr, " group %s: (argv=%p)\n", + (pi->subshell ? "()" : "{}"), + pi->progs[prn].argv); debug_print_tree(pi->progs[prn].group, lvl+1); prn++; continue; @@ -1920,18 +1888,25 @@ static void debug_print_tree(struct pipe *pi, int lvl) // global data until exec/_exit (we can be a child after vfork!) static int run_list_real(struct pipe *pi) { +#if ENABLE_HUSH_JOB + static int level; +#else + enum { level = 0 }; +#endif + char *save_name = NULL; char **list = NULL; char **save_list = NULL; struct pipe *rpipe; int flag_rep = 0; int save_num_progs; - int rcode = 0, flag_skip = 1; + int flag_skip = 1; + int rcode = 0; /* probaly for gcc only */ int flag_restore = 0; int if_code = 0, next_if_code = 0; /* need double-buffer to handle elif */ reserved_style rmode, skip_more_in_this_rmode = RES_XXXX; - debug_printf_exec("run_list_real start:\n"); + debug_printf_exec("run_list_real start lvl %d\n", level + 1); /* check syntax for "for" */ for (rpipe = pi; rpipe; rpipe = rpipe->next) { @@ -1939,17 +1914,60 @@ static int run_list_real(struct pipe *pi) && (rpipe->next == NULL) ) { syntax(); - debug_printf_exec("run_list_real return 1\n"); + debug_printf_exec("run_list_real lvl %d return 1\n", level); return 1; } if ((rpipe->r_mode == RES_IN && rpipe->next->r_mode == RES_IN && rpipe->next->progs->argv != NULL) || (rpipe->r_mode == RES_FOR && rpipe->next->r_mode != RES_IN) ) { syntax(); - debug_printf_exec("run_list_real return 1\n"); + debug_printf_exec("run_list_real lvl %d return 1\n", level); return 1; } } + +#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") + * so that ctrl-Z will correctly background _entire_ outermost list, + * not just a part of it (like "sleep 1 | exit 2") */ + if (++level == 1 && interactive_fd) { + if (sigsetjmp(toplevel_jb, 1)) { + /* ctrl-Z forked and we are parent; or ctrl-C. + * Sighandler has longjmped us here */ + signal(SIGINT, SIG_IGN); + signal(SIGTSTP, SIG_IGN); + /* Restore level (we can be coming from deep inside + * nested levels) */ + level = 1; +#if ENABLE_FEATURE_SH_STANDALONE + if (nofork_save.saved) { /* if save area is valid */ + debug_printf_jobs("exiting nofork early\n"); + restore_nofork_data(&nofork_save); + } +#endif + if (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 was doing */ + putchar('\n'); + } + rcode = 0; + goto ret; + } + /* ctrl-Z handler will store pid etc in pi */ + toplevel_list = pi; + ctrl_z_flag = 0; +#if ENABLE_FEATURE_SH_STANDALONE + nofork_save.saved = 0; /* in case we will run a nofork later */ +#endif + signal_SA_RESTART(SIGTSTP, handler_ctrl_z); + signal(SIGINT, handler_ctrl_c); + } +#endif + for (; pi; pi = (flag_restore != 0) ? rpipe : pi->next) { if (pi->r_mode == RES_WHILE || pi->r_mode == RES_UNTIL || pi->r_mode == RES_FOR @@ -1961,7 +1979,7 @@ static int run_list_real(struct pipe *pi) } } rmode = pi->r_mode; - debug_printf("rmode=%d if_code=%d next_if_code=%d skip_more=%d\n", + debug_printf_exec(": rmode=%d if_code=%d next_if_code=%d skip_more=%d\n", rmode, if_code, next_if_code, skip_more_in_this_rmode); if (rmode == skip_more_in_this_rmode && flag_skip) { if (pi->followup == PIPE_SEQ) @@ -2044,9 +2062,9 @@ static int run_list_real(struct pipe *pi) { rcode = checkjobs(pi); } - debug_printf_exec("checkjobs returned %d\n", rcode); + debug_printf_exec(": checkjobs returned %d\n", rcode); } - debug_printf_exec("setting last_return_code=%d\n", rcode); + debug_printf_exec(": setting last_return_code=%d\n", rcode); last_return_code = rcode; pi->num_progs = save_num_progs; /* restore number of programs */ if (rmode == RES_IF || rmode == RES_ELIF) @@ -2062,7 +2080,17 @@ static int run_list_real(struct pipe *pi) } checkjobs(NULL); } - debug_printf_exec("run_list_real return %d\n", rcode); + +#if ENABLE_HUSH_JOB + if (ctrl_z_flag) { + /* ctrl-Z forked somewhere in the past, we are the child, + * and now we completed running the list. Exit. */ + exit(rcode); + } + ret: + level--; +#endif + debug_printf_exec("run_list_real lvl %d return %d\n", level + 1, rcode); return rcode; } -- cgit v1.2.3