summaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
authorDenis Vlasenko2007-05-04 21:37:27 +0000
committerDenis Vlasenko2007-05-04 21:37:27 +0000
commitac0e5ab96ac35d46a6e0755f6f24f016ce788d90 (patch)
treeaccdf0c9bacae6e89a658d0382cbe343c062a84f /shell
parent3349fc4da948eb1393cd9201cd9c2615c0056c97 (diff)
downloadbusybox-ac0e5ab96ac35d46a6e0755f6f24f016ce788d90.zip
busybox-ac0e5ab96ac35d46a6e0755f6f24f016ce788d90.tar.gz
hush: fix "while true; do true; done" + ctrl-z
Diffstat (limited to 'shell')
-rw-r--r--shell/README3
-rw-r--r--shell/hush.c212
2 files changed, 123 insertions, 92 deletions
diff --git a/shell/README b/shell/README
index d962be9..284c691 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 c51ed1a..78531e8 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
- * <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;
}