hush: make Ctrl-Z work (at least sometimes)
authorDenis Vlasenko <vda.linux@googlemail.com>
Sat, 21 Apr 2007 23:27:30 +0000 (23:27 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Sat, 21 Apr 2007 23:27:30 +0000 (23:27 -0000)
shell/README
shell/hush.c

index b78d651a587c3a52c65a4d5c5cdf08931f6e835e..2b1d05cebfc077a2c1de37024aefdd49bd02fa32 100644 (file)
@@ -1,5 +1,10 @@
 Various bits of what is known about busybox shells, in no particular order.
 
+2007-04-21
+hush: "sleep 5 | sleep 6" + Ctrl-Z + fg seems to work.
+"rm -i" + Ctrl-C, "sleep 5" + Ctrl-Z still doesn't work
+for SH_STANDALONE case :(
+
 2007-04-21
 hush: fixed non-backgrounding of "sleep 1 &" and totally broken
 "sleep 1 | sleep 2 &". Noticed a bug where successive jobs
index b367a08b280c25a2afaa00812f845ac626ed79bc..e2ce3676d2d9262a17feebad91396982e5042615 100644 (file)
@@ -82,7 +82,9 @@
 #include <getopt.h>    /* should be pretty obvious */
 
 /* #include <dmalloc.h> */
-/* #define DEBUG_SHELL */
+//#define DEBUG_SHELL
+/* Finer-grained debug switch */
+//#define DEBUG_SHELL_JOBS
 
 
 #define SPECIAL_VAR_SYMBOL 03
@@ -274,9 +276,9 @@ struct in_str {
 #define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n"
 
 struct built_in_command {
-       const char *cmd;                        /* name */
-       const char *descr;                      /* description */
-       int (*function) (struct child_prog *);  /* function ptr */
+       const char *cmd;                /* name */
+       const char *descr;              /* description */
+       int (*function) (char **argv);  /* function ptr */
 };
 
 /* belongs in busybox.h */
@@ -285,24 +287,24 @@ static int max(int a, int b)
        return (a > b) ? a : b;
 }
 
-/* This should be in utility.c */
 #ifdef DEBUG_SHELL
-static void debug_printf(const char *format, ...)
-{
-       va_list args;
-       va_start(args, format);
-       vfprintf(stderr, format, args);
-       va_end(args);
-}
+#define debug_printf(...) fprintf(stderr, __VA_ARGS__)
 /* broken, of course, but OK for testing */
 static char *indenter(int i)
 {
        static char blanks[] = "                                    ";
-       return &blanks[sizeof(blanks)-i-1];
+       return &blanks[sizeof(blanks) - i - 1];
 }
 #else
 #define debug_printf(...) do {} while (0)
 #endif
+
+#ifdef DEBUG_SHELL_JOBS
+#define debug_jobs_printf(...) fprintf(stderr, __VA_ARGS__)
+#else
+#define debug_jobs_printf(...) do {} while (0)
+#endif
+
 #define final_printf debug_printf
 
 static void __syntax(const char *file, int line)
@@ -314,23 +316,23 @@ static void __syntax(const char *file, int line)
 
 /* Index of subroutines: */
 /*   function prototypes for builtins */
-static int builtin_cd(struct child_prog *child);
-static int builtin_env(struct child_prog *child);
-static int builtin_eval(struct child_prog *child);
-static int builtin_exec(struct child_prog *child);
-static int builtin_exit(struct child_prog *child);
-static int builtin_export(struct child_prog *child);
-static int builtin_fg_bg(struct child_prog *child);
-static int builtin_help(struct child_prog *child);
-static int builtin_jobs(struct child_prog *child);
-static int builtin_pwd(struct child_prog *child);
-static int builtin_read(struct child_prog *child);
-static int builtin_set(struct child_prog *child);
-static int builtin_shift(struct child_prog *child);
-static int builtin_source(struct child_prog *child);
-static int builtin_umask(struct child_prog *child);
-static int builtin_unset(struct child_prog *child);
-static int builtin_not_written(struct child_prog *child);
+static int builtin_cd(char **argv);
+static int builtin_env(char **argv);
+static int builtin_eval(char **argv);
+static int builtin_exec(char **argv);
+static int builtin_exit(char **argv);
+static int builtin_export(char **argv);
+static int builtin_fg_bg(char **argv);
+static int builtin_help(char **argv);
+static int builtin_jobs(char **argv);
+static int builtin_pwd(char **argv);
+static int builtin_read(char **argv);
+static int builtin_set(char **argv);
+static int builtin_shift(char **argv);
+static int builtin_source(char **argv);
+static int builtin_umask(char **argv);
+static int builtin_unset(char **argv);
+static int builtin_not_written(char **argv);
 /*   o_string manipulation: */
 static int b_check_space(o_string *o, int len);
 static int b_addchr(o_string *o, int ch);
@@ -354,6 +356,7 @@ static int free_pipe(struct pipe *pi, int indent);
 /*  really run the final data structures: */
 static int setup_redirects(struct child_prog *prog, int squirrel[]);
 static int run_list_real(struct pipe *pi);
+static void pseudo_exec_argv(char **argv) ATTRIBUTE_NORETURN;
 static void pseudo_exec(struct child_prog *child) ATTRIBUTE_NORETURN;
 static int run_pipe_real(struct pipe *pi);
 /*   extended glob support: */
@@ -387,6 +390,7 @@ static int checkjobs(struct pipe* fg_pipe);
 static int checkjobs_and_fg_shell(struct pipe* fg_pipe);
 static void insert_bg_job(struct pipe *pi);
 static void remove_bg_job(struct pipe *pi);
+static void delete_finished_bg_job(struct pipe *pi);
 /*     local variable support */
 static char **make_list_in(char **inp, char *name);
 static char *insert_var_value(char *inp);
@@ -503,18 +507,14 @@ static const char *set_cwd(void)
        return cwd;
 }
 
-// It seems ALL built-ins ever use *only* child->argv in child param.
-// Passing argv directly may make 'child->argv += n' modifications
-// unneeded on vfork codepaths.
-
 /* built-in 'eval' handler */
-static int builtin_eval(struct child_prog *child)
+static int builtin_eval(char **argv)
 {
        char *str = NULL;
        int rcode = EXIT_SUCCESS;
 
-       if (child->argv[1]) {
-               str = make_string(child->argv + 1);
+       if (argv[1]) {
+               str = make_string(argv + 1);
                parse_string_outer(str, FLAG_EXIT_FROM_LOOP |
                                        FLAG_PARSE_SEMICOLON);
                free(str);
@@ -524,13 +524,13 @@ static int builtin_eval(struct child_prog *child)
 }
 
 /* built-in 'cd <path>' handler */
-static int builtin_cd(struct child_prog *child)
+static int builtin_cd(char **argv)
 {
        char *newdir;
-       if (child->argv[1] == NULL)
+       if (argv[1] == NULL)
                newdir = getenv("HOME");
        else
-               newdir = child->argv[1];
+               newdir = argv[1];
        if (chdir(newdir)) {
                printf("cd: %s: %s\n", newdir, strerror(errno));
                return EXIT_FAILURE;
@@ -540,7 +540,7 @@ static int builtin_cd(struct child_prog *child)
 }
 
 /* built-in 'env' handler */
-static int builtin_env(struct child_prog *dummy ATTRIBUTE_UNUSED)
+static int builtin_env(char **argv ATTRIBUTE_UNUSED)
 {
 /* TODO: call env applet's code instead */
        char **e = environ;
@@ -553,37 +553,36 @@ static int builtin_env(struct child_prog *dummy ATTRIBUTE_UNUSED)
 }
 
 /* built-in 'exec' handler */
-static int builtin_exec(struct child_prog *child)
+static int builtin_exec(char **argv)
 {
-       if (child->argv[1] == NULL)
+       if (argv[1] == NULL)
                return EXIT_SUCCESS;   /* Really? */
-       child->argv++;
-       pseudo_exec(child);
+       pseudo_exec_argv(argv + 1);
        /* never returns */
 }
 
 /* built-in 'exit' handler */
-static int builtin_exit(struct child_prog *child)
+static int builtin_exit(char **argv)
 {
 // TODO: bash does it ONLY on top-level sh exit (+interacive only?)
        //puts("exit"); /* bash does it */
 
-       if (child->argv[1] == NULL)
+       if (argv[1] == NULL)
                hush_exit(last_return_code);
        /* mimic bash: exit 123abc == exit 255 + error msg */
        xfunc_error_retval = 255;
        /* bash: exit -2 == exit 254, no error msg */
-       hush_exit(xatoi(child->argv[1]));
+       hush_exit(xatoi(argv[1]));
 }
 
 /* built-in 'export VAR=value' handler */
-static int builtin_export(struct child_prog *child)
+static int builtin_export(char **argv)
 {
        int res = 0;
-       char *name = child->argv[1];
+       char *name = argv[1];
 
        if (name == NULL) {
-               return builtin_env(child);
+               return builtin_env(argv);
        }
 
        name = strdup(name);
@@ -624,7 +623,7 @@ static int builtin_export(struct child_prog *child)
 }
 
 /* built-in 'fg' and 'bg' handler */
-static int builtin_fg_bg(struct child_prog *child)
+static int builtin_fg_bg(char **argv)
 {
        int i, jobnum;
        struct pipe *pi;
@@ -632,17 +631,17 @@ static int builtin_fg_bg(struct child_prog *child)
        if (!interactive_fd)
                return EXIT_FAILURE;
        /* If they gave us no args, assume they want the last backgrounded task */
-       if (!child->argv[1]) {
+       if (!argv[1]) {
                for (pi = job_list; pi; pi = pi->next) {
                        if (pi->jobid == last_jobid) {
                                goto found;
                        }
                }
-               bb_error_msg("%s: no current job", child->argv[0]);
+               bb_error_msg("%s: no current job", argv[0]);
                return EXIT_FAILURE;
        }
-       if (sscanf(child->argv[1], "%%%d", &jobnum) != 1) {
-               bb_error_msg("%s: bad argument '%s'", child->argv[0], child->argv[1]);
+       if (sscanf(argv[1], "%%%d", &jobnum) != 1) {
+               bb_error_msg("%s: bad argument '%s'", argv[0], argv[1]);
                return EXIT_FAILURE;
        }
        for (pi = job_list; pi; pi = pi->next) {
@@ -650,37 +649,42 @@ static int builtin_fg_bg(struct child_prog *child)
                        goto found;
                }
        }
-       bb_error_msg("%s: %d: no such job", child->argv[0], jobnum);
+       bb_error_msg("%s: %d: no such job", argv[0], jobnum);
        return EXIT_FAILURE;
  found:
        // TODO: bash prints a string representation
        // of job being foregrounded (like "sleep 1 | cat")
-       if (*child->argv[0] == 'f') {
+       if (*argv[0] == 'f') {
                /* Put the job into the foreground.  */
                tcsetpgrp(interactive_fd, pi->pgrp);
        }
 
        /* Restart the processes in the job */
-       for (i = 0; i < pi->num_progs; i++)
+       debug_jobs_printf("reviving %d procs, pgrp %d\n", pi->num_progs, pi->pgrp);
+       for (i = 0; i < pi->num_progs; i++) {
+               debug_jobs_printf("reviving pid %d\n", pi->progs[i].pid);
                pi->progs[i].is_stopped = 0;
+       }
+       pi->stopped_progs = 0;
 
        i = kill(- pi->pgrp, SIGCONT);
-       pi->stopped_progs = 0;
        if (i < 0) {
                if (errno == ESRCH) {
-                       remove_bg_job(pi);
+                       delete_finished_bg_job(pi);
                } else {
                        bb_perror_msg("kill (SIGCONT)");
                }
        }
 
-       if (*child->argv[0] == 'f')
+       if (*argv[0] == 'f') {
+               remove_bg_job(pi);
                return checkjobs_and_fg_shell(pi);
+       }
        return EXIT_SUCCESS;
 }
 
 /* built-in 'help' handler */
-static int builtin_help(struct child_prog *dummy ATTRIBUTE_UNUSED)
+static int builtin_help(char **argv ATTRIBUTE_UNUSED)
 {
        const struct built_in_command *x;
 
@@ -696,7 +700,7 @@ static int builtin_help(struct child_prog *dummy ATTRIBUTE_UNUSED)
 }
 
 /* built-in 'jobs' handler */
-static int builtin_jobs(struct child_prog *child ATTRIBUTE_UNUSED)
+static int builtin_jobs(char **argv ATTRIBUTE_UNUSED)
 {
        struct pipe *job;
        const char *status_string;
@@ -713,18 +717,18 @@ static int builtin_jobs(struct child_prog *child ATTRIBUTE_UNUSED)
 }
 
 /* built-in 'pwd' handler */
-static int builtin_pwd(struct child_prog *dummy ATTRIBUTE_UNUSED)
+static int builtin_pwd(char **argv ATTRIBUTE_UNUSED)
 {
        puts(set_cwd());
        return EXIT_SUCCESS;
 }
 
 /* built-in 'read VAR' handler */
-static int builtin_read(struct child_prog *child)
+static int builtin_read(char **argv)
 {
        int res;
 
-       if (child->argv[1]) {
+       if (argv[1]) {
                char string[BUFSIZ];
                char *var = NULL;
 
@@ -732,9 +736,9 @@ static int builtin_read(struct child_prog *child)
                /* read string */
                fgets(string, sizeof(string), stdin);
                chomp(string);
-               var = malloc(strlen(child->argv[1]) + strlen(string) + 2);
+               var = malloc(strlen(argv[1]) + strlen(string) + 2);
                if (var) {
-                       sprintf(var, "%s=%s", child->argv[1], string);
+                       sprintf(var, "%s=%s", argv[1], string);
                        res = set_local_var(var, 0);
                } else
                        res = -1;
@@ -748,9 +752,9 @@ static int builtin_read(struct child_prog *child)
 }
 
 /* built-in 'set VAR=value' handler */
-static int builtin_set(struct child_prog *child)
+static int builtin_set(char **argv)
 {
-       char *temp = child->argv[1];
+       char *temp = argv[1];
        struct variables *e;
 
        if (temp == NULL)
@@ -764,11 +768,11 @@ static int builtin_set(struct child_prog *child)
 
 
 /* Built-in 'shift' handler */
-static int builtin_shift(struct child_prog *child)
+static int builtin_shift(char **argv)
 {
        int n = 1;
-       if (child->argv[1]) {
-               n = atoi(child->argv[1]);
+       if (argv[1]) {
+               n = atoi(argv[1]);
        }
        if (n >= 0 && n < global_argc) {
                /* XXX This probably breaks $0 */
@@ -780,25 +784,25 @@ static int builtin_shift(struct child_prog *child)
 }
 
 /* Built-in '.' handler (read-in and execute commands from file) */
-static int builtin_source(struct child_prog *child)
+static int builtin_source(char **argv)
 {
        FILE *input;
        int status;
 
-       if (child->argv[1] == NULL)
+       if (argv[1] == NULL)
                return EXIT_FAILURE;
 
        /* XXX search through $PATH is missing */
-       input = fopen(child->argv[1], "r");
+       input = fopen(argv[1], "r");
        if (!input) {
-               bb_error_msg("cannot open '%s'", child->argv[1]);
+               bb_error_msg("cannot open '%s'", argv[1]);
                return EXIT_FAILURE;
        }
 
        /* Now run the file */
        /* XXX argv and argc are broken; need to save old global_argv
         * (pointer only is OK!) on this stack frame,
-        * set global_argv=child->argv+1, recurse, and restore. */
+        * set global_argv=argv+1, recurse, and restore. */
        mark_open(fileno(input));
        status = parse_file_outer(input);
        mark_closed(fileno(input));
@@ -806,10 +810,10 @@ static int builtin_source(struct child_prog *child)
        return status;
 }
 
-static int builtin_umask(struct child_prog *child)
+static int builtin_umask(char **argv)
 {
        mode_t new_umask;
-       const char *arg = child->argv[1];
+       const char *arg = argv[1];
        char *end;
        if (arg) {
                new_umask = strtoul(arg, &end, 8);
@@ -825,16 +829,16 @@ static int builtin_umask(struct child_prog *child)
 }
 
 /* built-in 'unset VAR' handler */
-static int builtin_unset(struct child_prog *child)
+static int builtin_unset(char **argv)
 {
        /* bash returned already true */
-       unset_local_var(child->argv[1]);
+       unset_local_var(argv[1]);
        return EXIT_SUCCESS;
 }
 
-static int builtin_not_written(struct child_prog *child)
+static int builtin_not_written(char **argv)
 {
-       printf("builtin_%s not written\n", child->argv[0]);
+       printf("builtin_%s not written\n", argv[0]);
        return EXIT_FAILURE;
 }
 
@@ -1132,68 +1136,71 @@ static void restore_redirects(int squirrel[])
 /* XXX no exit() here.  If you don't exec, use _exit instead.
  * The at_exit handlers apparently confuse the calling process,
  * in particular stdin handling.  Not sure why? -- because of vfork! (vda) */
-static void pseudo_exec(struct child_prog *child)
+static void pseudo_exec_argv(char **argv)
 {
        int i, rcode;
        char *p;
        const struct built_in_command *x;
 
-       if (child->argv) {
-               for (i = 0; is_assignment(child->argv[i]); i++) {
-                       debug_printf("pid %d environment modification: %s\n",
-                                       getpid(), child->argv[i]);
-               // FIXME: vfork case??
-                       p = insert_var_value(child->argv[i]);
-                       putenv(strdup(p));
-                       if (p != child->argv[i])
-                               free(p);
-               }
-               child->argv += i;  /* XXX this hack isn't so horrible, since we are about
-                                       to exit, and therefore don't need to keep data
-                                       structures consistent for free() use. */
-               // FIXME: ...unless we have _vforked_! Think NOMMU!
-
-               /* If a variable is assigned in a forest, and nobody listens,
-                * was it ever really set?
-                */
-               if (child->argv[0] == NULL) {
-                       _exit(EXIT_SUCCESS);
-               }
-
-               /*
-                * Check if the command matches any of the builtins.
-                * Depending on context, this might be redundant.  But it's
-                * easier to waste a few CPU cycles than it is to figure out
-                * if this is one of those cases.
-                */
-               for (x = bltins; x->cmd; x++) {
-                       if (strcmp(child->argv[0], x->cmd) == 0) {
-                               debug_printf("builtin exec %s\n", child->argv[0]);
-                               rcode = x->function(child);
-                               fflush(stdout);
-                               _exit(rcode);
-                       }
-               }
+       for (i = 0; is_assignment(argv[i]); i++) {
+               debug_printf("pid %d environment modification: %s\n",
+                               getpid(), argv[i]);
+       // FIXME: vfork case??
+               p = insert_var_value(argv[i]);
+               putenv(strdup(p));
+               if (p != argv[i])
+                       free(p);
+       }
+       argv += i;
+       /* If a variable is assigned in a forest, and nobody listens,
+        * was it ever really set?
+        */
+       if (argv[0] == NULL) {
+               _exit(EXIT_SUCCESS);
+       }
 
-               /* Check if the command matches any busybox internal commands
-                * ("applets") here.
-                * FIXME: This feature is not 100% safe, since
-                * BusyBox is not fully reentrant, so we have no guarantee the things
-                * from the .bss are still zeroed, or that things from .data are still
-                * at their defaults.  We could exec ourself from /proc/self/exe, but I
-                * really dislike relying on /proc for things.  We could exec ourself
-                * from global_argv[0], but if we are in a chroot, we may not be able
-                * to find ourself... */
+       /*
+        * Check if the command matches any of the builtins.
+        * Depending on context, this might be redundant.  But it's
+        * easier to waste a few CPU cycles than it is to figure out
+        * if this is one of those cases.
+        */
+       for (x = bltins; x->cmd; x++) {
+               if (strcmp(argv[0], x->cmd) == 0) {
+                       debug_printf("builtin exec %s\n", argv[0]);
+                       rcode = x->function(argv);
+                       fflush(stdout);
+                       _exit(rcode);
+               }
+       }
+
+       /* Check if the command matches any busybox internal commands
+        * ("applets") here.
+        * FIXME: This feature is not 100% safe, since
+        * BusyBox is not fully reentrant, so we have no guarantee the things
+        * from the .bss are still zeroed, or that things from .data are still
+        * at their defaults.  We could exec ourself from /proc/self/exe, but I
+        * really dislike relying on /proc for things.  We could exec ourself
+        * from global_argv[0], but if we are in a chroot, we may not be able
+        * to find ourself... */
 #if ENABLE_FEATURE_SH_STANDALONE
-               debug_printf("running applet %s\n", child->argv[0]);
-               run_applet_and_exit(child->argv[0], child->argv);
+       debug_printf("running applet %s\n", argv[0]);
+       run_applet_and_exit(argv[0], argv);
 // is it ok that run_applet_and_exit() does exit(), not _exit()?
 // NB: IIRC on NOMMU we are after _vfork_, not fork!
 #endif
-               debug_printf("exec of %s\n", child->argv[0]);
-               execvp(child->argv[0], child->argv);
-               bb_perror_msg("cannot exec '%s'", child->argv[0]);
-               _exit(1);
+       debug_printf("exec of %s\n", argv[0]);
+       execvp(argv[0], argv);
+       bb_perror_msg("cannot exec '%s'", argv[0]);
+       _exit(1);
+}
+
+static void pseudo_exec(struct child_prog *child)
+{
+       int rcode;
+
+       if (child->argv) {
+               pseudo_exec_argv(child->argv);
        }
 
        if (child->group) {
@@ -1233,13 +1240,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->next = NULL;
-       thejob->running_progs = thejob->num_progs;
-       thejob->stopped_progs = 0;
+       /*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)
@@ -1254,7 +1265,6 @@ static void insert_bg_job(struct pipe *pi)
        last_jobid = thejob->jobid;
 }
 
-/* remove a backgrounded job */
 static void remove_bg_job(struct pipe *pi)
 {
        struct pipe *prev_pipe;
@@ -1271,7 +1281,12 @@ static void remove_bg_job(struct pipe *pi)
                last_jobid = job_list->jobid;
        else
                last_jobid = 0;
+}
 
+/* remove a backgrounded job */
+static void delete_finished_bg_job(struct pipe *pi)
+{
+       remove_bg_job(pi);
        pi->stopped_progs = 0;
        free_pipe(pi, 0);
        free(pi);
@@ -1289,36 +1304,67 @@ static int checkjobs(struct pipe* fg_pipe)
        int rcode = 0;
 
        attributes = WUNTRACED;
-//WUNTRACED?? huh, what will happed on Ctrl-Z? fg waiting code
-//doesn't seem to be ready for stopped children! (only exiting ones)...
        if (fg_pipe == NULL) {
                attributes |= WNOHANG;
        }
 
+/* Do we do this right?
+ * bash-3.00# sleep 20 | false
+ * <Ctrl-Z pressed>
+ * [3]+  Stopped          sleep 20 | false
+ * bash-3.00# echo $?
+ * 1   <========== bg pipe is not fully done, but exitcode is already known!
+ */
+
  wait_more:
        while ((childpid = waitpid(-1, &status, attributes)) > 0) {
+               const int dead = WIFEXITED(status) || WIFSIGNALED(status);
+
+#ifdef DEBUG_SHELL_JOBS
+               if (WIFSTOPPED(status))
+                       debug_jobs_printf("pid %d stopped by sig %d (exitcode %d)\n",
+                                       childpid, WSTOPSIG(status), WEXITSTATUS(status));
+               if (WIFSIGNALED(status))
+                       debug_jobs_printf("pid %d killed by sig %d (exitcode %d)\n",
+                                       childpid, WTERMSIG(status), WEXITSTATUS(status));
+               if (WIFEXITED(status))
+                       debug_jobs_printf("pid %d exited, exitcode %d\n",
+                                       childpid, WEXITSTATUS(status));
+#endif
                /* Were we asked to wait for fg pipe? */
                if (fg_pipe) {
                        int i;
                        for (i = 0; i < fg_pipe->num_progs; i++) {
+                               debug_jobs_printf("check pid %d\n", fg_pipe->progs[i].pid);
                                if (fg_pipe->progs[i].pid == childpid) {
                                        /* printf("process %d exit %d\n", i, WEXITSTATUS(status)); */
-                                       fg_pipe->progs[i].pid = 0;
-                                       if (i == fg_pipe->num_progs-1)
-                                               /* last process gives overall exitstatus */
-                                               rcode = WEXITSTATUS(status);
-                                       if (--fg_pipe->running_progs <= 0)
-                                               /* All processes in fg pipe have exited */
+                                       if (dead) {
+                                               fg_pipe->progs[i].pid = 0;
+                                               fg_pipe->running_progs--;
+                                               if (i == fg_pipe->num_progs-1)
+                                                       /* last process gives overall exitstatus */
+                                                       rcode = WEXITSTATUS(status);
+                                       } else {
+                                               fg_pipe->progs[i].is_stopped = 1;
+                                               fg_pipe->stopped_progs++;
+                                       }
+                                       debug_jobs_printf("fg_pipe: running_progs %d stopped_progs %d\n",
+                                                       fg_pipe->running_progs, fg_pipe->stopped_progs);
+                                       if (fg_pipe->running_progs - fg_pipe->stopped_progs <= 0) {
+                                               /* All processes in fg pipe have exited/stopped */
+                                               if (fg_pipe->running_progs)
+                                                       insert_bg_job(fg_pipe);
                                                return rcode;
+                                       }
                                        /* There are still running processes in the fg pipe */
                                        goto wait_more;
                                }
                        }
+                       /* fall through to searching process in bg pipes */
                }
 
                /* We asked to wait for bg or orphaned children */
                /* No need to remember exitcode in this case */
-
                for (pi = job_list; pi; pi = pi->next) {
                        prognum = 0;
                        while (prognum < pi->num_progs) {
@@ -1330,18 +1376,17 @@ static int checkjobs(struct pipe* fg_pipe)
 
                /* Happens when shell is used as init process (init=/bin/sh) */
                debug_printf("checkjobs: pid %d was not in our list!\n", childpid);
-               continue;
+               goto wait_more;
 
  found_pi_and_prognum:
-               if (WIFEXITED(status) || WIFSIGNALED(status)) {
+               if (dead) {
                        /* child exited */
-                       pi->running_progs--;
                        pi->progs[prognum].pid = 0;
-
+                       pi->running_progs--;
                        if (!pi->running_progs) {
                                printf(JOB_STATUS_FORMAT, pi->jobid,
                                                        "Done", pi->text);
-                               remove_bg_job(pi);
+                               delete_finished_bg_job(pi);
                        }
                } else {
                        /* child stopped */
@@ -1350,7 +1395,9 @@ static int checkjobs(struct pipe* fg_pipe)
                }
        }
 
-       if (childpid == -1 && errno != ECHILD)
+       /* wait found no children or failed */
+
+       if (childpid && errno != ECHILD)
                bb_perror_msg("waitpid");
 
        /* move the shell to the foreground */
@@ -1402,6 +1449,8 @@ static int run_pipe_real(struct pipe *pi)
 
        nextin = 0;
        pi->pgrp = -1;
+       pi->running_progs = 0;
+       pi->stopped_progs = 0;
 
        /* Check if this is a simple builtin (not part of a pipe).
         * Builtins within pipes have to fork anyway, and are handled in
@@ -1418,12 +1467,14 @@ static int run_pipe_real(struct pipe *pi)
                return rcode;
        }
 
-       if (single_fg && pi->progs[0].argv != NULL) {
-               for (i = 0; is_assignment(child->argv[i]); i++)
+       if (single_fg && child->argv != NULL) {
+               char **argv = child->argv;
+
+               for (i = 0; is_assignment(argv[i]); i++)
                        continue;
-               if (i != 0 && child->argv[i] == NULL) {
+               if (i != 0 && argv[i] == NULL) {
                        /* assignments, but no command: set the local environment */
-                       for (i = 0; child->argv[i] != NULL; i++) {
+                       for (i = 0; argv[i] != NULL; i++) {
                                /* Ok, this case is tricky.  We have to decide if this is a
                                 * local variable, or an already exported variable.  If it is
                                 * already exported, we have to export the new value.  If it is
@@ -1432,7 +1483,7 @@ static int run_pipe_real(struct pipe *pi)
                                 * variable. */
                                int export_me = 0;
                                char *name, *value;
-                               name = xstrdup(child->argv[i]);
+                               name = xstrdup(argv[i]);
                                debug_printf("Local environment set: %s\n", name);
                                value = strchr(name, '=');
                                if (value)
@@ -1441,17 +1492,17 @@ static int run_pipe_real(struct pipe *pi)
                                        export_me = 1;
                                }
                                free(name);
-                               p = insert_var_value(child->argv[i]);
+                               p = insert_var_value(argv[i]);
                                set_local_var(p, export_me);
-                               if (p != child->argv[i])
+                               if (p != argv[i])
                                        free(p);
                        }
                        return EXIT_SUCCESS;   /* don't worry about errors in set_local_var() yet */
                }
-               for (i = 0; is_assignment(child->argv[i]); i++) {
-                       p = insert_var_value(child->argv[i]);
+               for (i = 0; is_assignment(argv[i]); i++) {
+                       p = insert_var_value(argv[i]);
                        putenv(strdup(p));
-                       if (p != child->argv[i]) {
+                       if (p != argv[i]) {
                                child->sp--;
                                free(p);
                        }
@@ -1459,27 +1510,25 @@ static int run_pipe_real(struct pipe *pi)
                if (child->sp) {
                        char *str;
 
-                       str = make_string(child->argv + i);
+                       str = make_string(argv + i);
                        parse_string_outer(str, FLAG_EXIT_FROM_LOOP | FLAG_REPARSING);
                        free(str);
                        return last_return_code;
                }
                for (x = bltins; x->cmd; x++) {
-                       if (strcmp(child->argv[i], x->cmd) == 0) {
-                               if (x->function == builtin_exec && child->argv[i+1] == NULL) {
+                       if (strcmp(argv[i], x->cmd) == 0) {
+                               if (x->function == builtin_exec && argv[i+1] == NULL) {
                                        debug_printf("magic exec\n");
                                        setup_redirects(child, NULL);
                                        return EXIT_SUCCESS;
                                }
-                               debug_printf("builtin inline %s\n", child->argv[0]);
+                               debug_printf("builtin inline %s\n", argv[0]);
                                /* XXX setup_redirects acts on file descriptors, not FILEs.
                                 * This is perfect for work that comes after exec().
                                 * Is it really safe for inline use?  Experimentally,
                                 * things seem to work with glibc. */
                                setup_redirects(child, squirrel);
-                               child->argv += i;  /* XXX horrible hack */
-                               rcode = x->function(child);
-                               child->argv -= i;  /* XXX restore hack so free() can work right */
+                               rcode = x->function(argv + i);
                                restore_redirects(squirrel);
                                return rcode;
                        }
@@ -1488,10 +1537,10 @@ static int run_pipe_real(struct pipe *pi)
                {
 // FIXME: applet runs like part of shell - for example, it ignores
 // SIGINT! Try to Ctrl-C out of "rm -i"... doesn't work
-                       const struct bb_applet *a = find_applet_by_name(child->argv[i]);
+                       const struct bb_applet *a = find_applet_by_name(argv[i]);
                        if (a && a->nofork) {
                                setup_redirects(child, squirrel);
-                               rcode = run_nofork_applet(a, child->argv + i);
+                               rcode = run_nofork_applet(a, argv + i);
                                restore_redirects(squirrel);
                                return rcode;
                        }
@@ -1503,7 +1552,6 @@ static int run_pipe_real(struct pipe *pi)
         * for initial child code after fork */
        set_jobctrl_sighandler(SIG_IGN);
 
-       pi->running_progs = 0;
        for (i = 0; i < pi->num_progs; i++) {
                child = &(pi->progs[i]);