hush: fix "while true; do true; done" + ctrl-z
No binary (build failed)
—
(log yok)
| @@ -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: | |
| @@ -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 | |
| - | * <Ctrl-Z pressed> |
| + | * <ctrl-Z pressed> |
| * [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; | |
| } | |