hush: fix "while true; do true; done" + ctrl-z
This commit is contained in:
parent
3349fc4da9
commit
ac0e5ab96a
@ -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:
|
||||
|
176
shell/hush.c
176
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
|
||||
* <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 */
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user