Larry's variant on Evin Robertson's fix to what I messed up
[oweals/busybox.git] / shell / hush.c
index cec2549be7e6d4af25ca3c291c04ad102db313da..b8e4a55c26bf8914114306ccce6ffd2a1b51f112 100644 (file)
  *      fancy forms of Parameter Expansion
  *      Arithmetic Expansion
  *      <(list) and >(list) Process Substitution
- *      reserved words: if, then, elif, else, fi, while, until, for,
- *                      do, done, case
+ *      reserved words: case, esac, function
  *      Here Documents ( << word )
  *      Functions
  * Major bugs:
  *      job handling woefully incomplete and buggy
  *      reserved word execution woefully incomplete and buggy
- *      incomplete reserved word sequence doesn't request more lines of input
  * to-do:
  *      port selected bugfixes from post-0.49 busybox lash
  *      finish implementing reserved words
@@ -73,7 +71,6 @@
  *      more testing, especially quoting rules and redirection
  *      maybe change map[] to use 2-bit entries
  *      (eventually) remove all the printf's
- *      more integration with BusyBox: prompts, cmdedit, applets
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -216,7 +213,6 @@ struct pipe {
        int job_context;                        /* bitmask defining current context */
        pipe_style followup;            /* PIPE_BG, PIPE_SEQ, PIPE_OR, PIPE_AND */
        reserved_style r_mode;          /* supports if, for, while, until */
-       struct jobset *job_list;
 };
 
 struct jobset {
@@ -247,7 +243,7 @@ static int fake_mode=0;
 static int interactive=0;
 static struct close_me *close_me_head = NULL;
 static char *cwd;
-/* static struct jobset job_list = { NULL, NULL }; */
+static struct jobset *job_list;
 static unsigned int last_bg_pid=0;
 static char *PS1;
 static char *PS2 = "> ";
@@ -380,6 +376,11 @@ static int parse_stream(o_string *dest, struct p_context *ctx, struct in_str *in
 static int parse_stream_outer(struct in_str *inp);
 static int parse_string_outer(const char *s);
 static int parse_file_outer(FILE *f);
+/*   job management: */
+static void checkjobs();
+static void insert_bg_job(struct pipe *pi);
+static void remove_bg_job(struct pipe *pi);
+static void free_pipe(struct pipe *pi);
 
 /* Table of built-in functions.  They can be forked or not, depending on
  * context: within pipes, they fork.  As simple commands, they do not.
@@ -448,7 +449,7 @@ static int builtin_exec(struct child_prog *child)
 static int builtin_exit(struct child_prog *child)
 {
        if (child->argv[1] == NULL)
-               exit(EXIT_SUCCESS);
+               exit(last_return_code);
        exit (atoi(child->argv[1]));
 }
 
@@ -469,48 +470,53 @@ static int builtin_export(struct child_prog *child)
 /* built-in 'fg' and 'bg' handler */
 static int builtin_fg_bg(struct child_prog *child)
 {
-       int i, jobNum;
-       struct pipe *job=NULL;
-       
-       if (!child->argv[1] || child->argv[2]) {
-               error_msg("%s: exactly one argument is expected\n",
-                               child->argv[0]);
-               return EXIT_FAILURE;
-       }
-
-       if (sscanf(child->argv[1], "%%%d", &jobNum) != 1) {
-               error_msg("%s: bad argument '%s'\n",
-                               child->argv[0], child->argv[1]);
-               return EXIT_FAILURE;
-       }
+       int i, jobnum;
+       struct pipe *pi=NULL;
 
-       for (job = child->family->job_list->head; job; job = job->next) {
-               if (job->jobid == jobNum) {
-                       break;
+       /* If they gave us no args, assume they want the last backgrounded task */
+       if (!child->argv[1]) {
+               for (pi = job_list->head; pi; pi = pi->next) {
+                       if (pi->progs && pi->progs->pid == last_bg_pid) {
+                               break;
+                       }
+               }
+               if (!pi) {
+                       error_msg("%s: no current job", child->argv[0]);
+                       return EXIT_FAILURE;
+               }
+       } else {
+               if (sscanf(child->argv[1], "%%%d", &jobnum) != 1) {
+                       error_msg("%s: bad argument '%s'", child->argv[0], child->argv[1]);
+                       return EXIT_FAILURE;
                }
-       }
 
-       if (!job) {
-               error_msg("%s: unknown job %d\n",
-                               child->argv[0], jobNum);
-               return EXIT_FAILURE;
+               for (pi = job_list->head; pi; pi = pi->next) {
+                       if (pi->jobid == jobnum) {
+                               break;
+                       }
+               }
+               if (!pi) {
+                       error_msg("%s: %d: no such job", child->argv[0], jobnum);
+                       return EXIT_FAILURE;
+               }
        }
-
        if (*child->argv[0] == 'f') {
                /* Make this job the foreground job */
+               signal(SIGTTOU, SIG_IGN);
                /* suppress messages when run from /linuxrc mag@sysgo.de */
-               if (tcsetpgrp(0, job->pgrp) && errno != ENOTTY)
+               if (tcsetpgrp(0, pi->pgrp) && errno != ENOTTY)
                        perror_msg("tcsetpgrp"); 
-               child->family->job_list->fg = job;
+               signal(SIGTTOU, SIG_DFL);
+               job_list->fg = pi;
        }
 
        /* Restart the processes in the job */
-       for (i = 0; i < job->num_progs; i++)
-               job->progs[i].is_stopped = 0;
+       for (i = 0; i < pi->num_progs; i++)
+               pi->progs[i].is_stopped = 0;
 
-       kill(-job->pgrp, SIGCONT);
+       kill(-pi->pgrp, SIGCONT);
 
-       job->stopped_progs = 0;
+       pi->stopped_progs = 0;
        return EXIT_SUCCESS;
 }
 
@@ -536,7 +542,7 @@ static int builtin_jobs(struct child_prog *child)
        struct pipe *job;
        char *status_string;
 
-       for (job = child->family->job_list->head; job; job = job->next) {
+       for (job = job_list->head; job; job = job->next) {
                if (job->running_progs == job->stopped_progs)
                        status_string = "Stopped";
                else
@@ -780,7 +786,7 @@ static inline void setup_prompt_string(int promptmode, char **prompt_str)
 static void get_user_input(struct in_str *i)
 {
        char *prompt_str;
-       static char the_command[MAX_LINE];
+       static char the_command[BUFSIZ];
 
        setup_prompt_string(i->promptmode, &prompt_str);
 #ifdef BB_FEATURE_COMMAND_EDITING
@@ -817,12 +823,14 @@ static int file_get(struct in_str *i)
                if (i->__promptme && interactive && i->file == stdin) {
                        get_user_input(i);
                        i->promptmode=2;
+                       i->__promptme = 0;
+                       if (i->p && *i->p) {
+                               ch=*i->p++;
+                       }
+               } else {
+                       ch = fgetc(i->file);
                }
-               i->__promptme = 0;
 
-               if (i->p && *i->p) {
-                       ch=*i->p++;
-               }
                debug_printf("b_getch: got a %d\n", ch);
        }
        if (ch == '\n') i->__promptme=1;
@@ -837,9 +845,10 @@ static int file_peek(struct in_str *i)
        if (i->p && *i->p) {
                return *i->p;
        } else {
-               static char buffer;
-               buffer = fgetc(i->file);
-               i->p = &buffer;
+               static char buffer[2];
+               buffer[0] = fgetc(i->file);
+               buffer[1] = '\0';
+               i->p = buffer;
                debug_printf("b_peek: got a %d\n", *i->p);
                return *i->p; 
        }
@@ -977,6 +986,41 @@ static void pseudo_exec(struct child_prog *child)
                                exit(x->function(child));
                        }
                }
+
+               /* 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... */ 
+#ifdef BB_FEATURE_SH_STANDALONE_SHELL
+               {
+                       int argc_l;
+                       char** argv_l=child->argv;
+                       char *name = child->argv[0];
+
+#ifdef BB_FEATURE_SH_APPLETS_ALWAYS_WIN
+                       /* Following discussions from November 2000 on the busybox mailing
+                        * list, the default configuration, (without
+                        * get_last_path_component()) lets the user force use of an
+                        * external command by specifying the full (with slashes) filename.
+                        * If you enable BB_FEATURE_SH_APPLETS_ALWAYS_WIN, then applets
+                        * _aways_ override external commands, so if you want to run
+                        * /bin/cat, it will use BusyBox cat even if /bin/cat exists on the
+                        * filesystem and is _not_ busybox.  Some systems may want this,
+                        * most do not.  */
+                       name = get_last_path_component(name);
+#endif
+                       /* Count argc for use in a second... */
+                       for(argc_l=0;*argv_l!=NULL; argv_l++, argc_l++);
+                       optind = 1;
+                       debug_printf("running applet %s\n", name);
+                       run_applet_by_name(name, argc_l, child->argv);
+               }
+#endif
                debug_printf("exec of %s\n",child->argv[0]);
                execvp(child->argv[0],child->argv);
                perror("execvp");
@@ -995,6 +1039,122 @@ static void pseudo_exec(struct child_prog *child)
        }
 }
 
+static void insert_bg_job(struct pipe *pi)
+{
+       struct pipe *thejob;
+
+       /* Linear search for the ID of the job to use */
+       pi->jobid = 1;
+       for (thejob = job_list->head; thejob; thejob = thejob->next)
+               if (thejob->jobid >= pi->jobid)
+                       pi->jobid = thejob->jobid + 1;
+
+       /* add thejob to the list of running jobs */
+       if (!job_list->head) {
+               thejob = job_list->head = xmalloc(sizeof(*thejob));
+       } else {
+               for (thejob = job_list->head; thejob->next; thejob = thejob->next) /* nothing */;
+               thejob->next = xmalloc(sizeof(*thejob));
+               thejob = thejob->next;
+       }
+
+       /* physically copy the struct job */
+       memcpy(thejob, pi, sizeof(struct pipe));
+       thejob->next = NULL;
+       thejob->running_progs = thejob->num_progs;
+       thejob->stopped_progs = 0;
+
+       /* we don't wait for background thejobs to return -- append it 
+          to the list of backgrounded thejobs and leave it alone */
+       printf("[%d] %d\n", pi->jobid, pi->pgrp);
+       last_bg_pid = pi->pgrp;
+}
+
+/* remove a backgrounded job from a jobset */
+static void remove_bg_job(struct pipe *pi)
+{
+       struct pipe *prev_pipe;
+
+       free_pipe(pi);
+       if (pi == job_list->head) {
+               job_list->head = pi->next;
+       } else {
+               prev_pipe = job_list->head;
+               while (prev_pipe->next != pi)
+                       prev_pipe = prev_pipe->next;
+               prev_pipe->next = pi->next;
+       }
+
+       free(pi);
+}
+
+/* free up all memory from a pipe */
+static void free_pipe(struct pipe *pi)
+{
+       int i;
+
+       for (i = 0; i < pi->num_progs; i++) {
+               free(pi->progs[i].argv);
+               if (pi->progs[i].redirects)
+                       free(pi->progs[i].redirects);
+       }
+       if (pi->progs)
+               free(pi->progs);
+       if (pi->text)
+               free(pi->text);
+       if (pi->cmdbuf)
+               free(pi->cmdbuf);
+       memset(pi, 0, sizeof(struct pipe));
+}
+
+
+/* Checks to see if any background processes have exited -- if they 
+   have, figure out why and see if a job has completed */
+static void checkjobs()
+{
+       int status;
+       int prognum = 0;
+       struct pipe *pi;
+       pid_t childpid;
+
+       while ((childpid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
+               for (pi = job_list->head; pi; pi = pi->next) {
+                       prognum = 0;
+                       while (prognum < pi->num_progs &&
+                                  pi->progs[prognum].pid != childpid) prognum++;
+                       if (prognum < pi->num_progs)
+                               break;
+               }
+
+               if (WIFEXITED(status) || WIFSIGNALED(status)) {
+                       /* child exited */
+                       pi->running_progs--;
+                       pi->progs[prognum].pid = 0;
+
+                       if (!pi->running_progs) {
+                               printf(JOB_STATUS_FORMAT, pi->jobid, "Done", pi->text);
+                               remove_bg_job(pi);
+                       }
+               } else {
+                       /* child stopped */
+                       pi->stopped_progs++;
+                       pi->progs[prognum].is_stopped = 1;
+
+                       if (pi->stopped_progs == pi->num_progs) {
+                               printf(JOB_STATUS_FORMAT, pi->jobid, "Stopped",
+                                               pi->text);
+                       }
+               }
+       }
+
+       if (childpid == -1 && errno != ECHILD)
+               perror_msg("waitpid");
+
+       /* move the shell to the foreground */
+       if (tcsetpgrp(0, getpgrp()) && errno != ENOTTY)
+               perror_msg("tcsetpgrp"); 
+}
+
 /* run_pipe_real() starts all the jobs, but doesn't wait for anything
  * to finish.  See pipe_wait().
  *
@@ -1014,14 +1174,27 @@ static void pseudo_exec(struct child_prog *child)
 static int run_pipe_real(struct pipe *pi)
 {
        int i;
+       int ctty;
        int nextin, nextout;
        int pipefds[2];                         /* pipefds[0] is for reading */
        struct child_prog *child;
        struct built_in_command *x;
 
+       ctty = -1;
        nextin = 0;
        pi->pgrp = 0;
 
+       /* Check if we are supposed to run in the foreground */
+       if (interactive && pi->followup!=PIPE_BG) {
+               if ((pi->pgrp = tcgetpgrp(ctty = 2)) < 0
+                               && (pi->pgrp = tcgetpgrp(ctty = 0)) < 0
+                               && (pi->pgrp = tcgetpgrp(ctty = 1)) < 0)
+                       return errno = ENOTTY, -1;
+
+               if (pi->pgrp < 0 && pi->pgrp != getpgrp())
+                       return errno = EPERM, -1;
+       }
+
        /* Check if this is a simple builtin (not part of a pipe).
         * Builtins within pipes have to fork anyway, and are handled in
         * pseudo_exec.  "echo foo | read bar" doesn't work on bash, either.
@@ -1070,6 +1243,9 @@ static int run_pipe_real(struct pipe *pi)
 
                /* XXX test for failed fork()? */
                if (!(child->pid = fork())) {
+
+                       signal(SIGTTOU, SIG_DFL);
+                       
                        close_all();
 
                        if (nextin != 0) {
@@ -1087,22 +1263,33 @@ static int run_pipe_real(struct pipe *pi)
                        /* Like bash, explicit redirects override pipes,
                         * and the pipe fd is available for dup'ing. */
                        setup_redirects(child,NULL);
+                       
+                       if (pi->followup!=PIPE_BG) {
+                               /* Put our child in the process group whose leader is the
+                                * first process in this pipe. */
+                               if (pi->pgrp < 0) {
+                                       pi->pgrp = child->pid;
+                               }
+                               /* Don't check for errors.  The child may be dead already,
+                                * in which case setpgid returns error code EACCES. */
+                               if (setpgid(0, pi->pgrp) == 0) {
+                                       signal(SIGTTOU, SIG_IGN);
+                                       tcsetpgrp(ctty, pi->pgrp);
+                                       signal(SIGTTOU, SIG_DFL);
+                               }
+                       }
 
                        pseudo_exec(child);
                }
-               if (interactive) {
-                       /* Put our child in the process group whose leader is the
-                        * first process in this pipe. */
-                       if (pi->pgrp==0) {
-                               pi->pgrp = child->pid;
-                       }
-                       /* Don't check for errors.  The child may be dead already,
-                        * in which case setpgid returns error code EACCES. */
-                       setpgid(child->pid, pi->pgrp);
+               /* Put our child in the process group whose leader is the
+                * first process in this pipe. */
+               if (pi->pgrp < 0) {
+                       pi->pgrp = child->pid;
                }
-               /* In the non-interactive case, do nothing.  Leave the children
-                * with the process group that they inherited from us. */
-       
+               /* Don't check for errors.  The child may be dead already,
+                * in which case setpgid returns error code EACCES. */
+               setpgid(child->pid, pi->pgrp);
+
                if (nextin != 0)
                        close(nextin);
                if (nextout != 1)
@@ -1119,15 +1306,17 @@ static int run_list_real(struct pipe *pi)
 {
        int rcode=0;
        int if_code=0, next_if_code=0;  /* need double-buffer to handle elif */
-       reserved_style rmode=RES_NONE;
+       reserved_style rmode, skip_more_in_this_rmode=RES_XXXX;
        for (;pi;pi=pi->next) {
                rmode = pi->r_mode;
-               debug_printf("rmode=%d  if_code=%d  next_if_code=%d\n", rmode, if_code, next_if_code);
+               debug_printf("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) continue;
+               skip_more_in_this_rmode = RES_XXXX;
                if (rmode == RES_THEN || rmode == RES_ELSE) if_code = next_if_code;
                if (rmode == RES_THEN &&  if_code) continue;
                if (rmode == RES_ELSE && !if_code) continue;
                if (rmode == RES_ELIF && !if_code) continue;
-               if (pi->num_progs == 0) break;
+               if (pi->num_progs == 0) continue;
                rcode = run_pipe_real(pi);
                if (rcode!=-1) {
                        /* We only ran a builtin: rcode was set by the return value
@@ -1136,22 +1325,22 @@ static int run_list_real(struct pipe *pi)
                        /* XXX check bash's behavior with nontrivial pipes */
                        /* XXX compute jobid */
                        /* XXX what does bash do with attempts to background builtins? */
-                       printf("[%d] %d\n", pi->jobid, pi->pgrp);
-                       last_bg_pid = pi->pgrp;
+                       insert_bg_job(pi);
                        rcode = EXIT_SUCCESS;
                } else {
+
                        if (interactive) {
                                /* move the new process group into the foreground */
                                /* suppress messages when run from /linuxrc mag@sysgo.de */
-                               signal(SIGTTIN, SIG_IGN);
-                               signal(SIGTTOU, SIG_IGN);
+                               //signal(SIGTTIN, SIG_IGN);
+                               //signal(SIGTTOU, SIG_IGN);
                                if (tcsetpgrp(0, pi->pgrp) && errno != ENOTTY)
                                        perror_msg("tcsetpgrp");
                                rcode = pipe_wait(pi);
-                               if (tcsetpgrp(0, getpid()) && errno != ENOTTY)
+                               if (tcsetpgrp(0, getpgrp()) && errno != ENOTTY)
                                        perror_msg("tcsetpgrp");
-                               signal(SIGTTIN, SIG_DFL);
-                               signal(SIGTTOU, SIG_DFL);
+                               //signal(SIGTTIN, SIG_DFL);
+                               //signal(SIGTTOU, SIG_DFL);
                        } else {
                                rcode = pipe_wait(pi);
                        }
@@ -1161,8 +1350,10 @@ static int run_list_real(struct pipe *pi)
                        next_if_code=rcode;  /* can be overwritten a number of times */
                if ( (rcode==EXIT_SUCCESS && pi->followup==PIPE_OR) ||
                     (rcode!=EXIT_SUCCESS && pi->followup==PIPE_AND) )
-                       return rcode;  /* XXX broken if list is part of if/then/else */
+                       skip_more_in_this_rmode=rmode;
+                       /* return rcode; */ /* XXX broken if list is part of if/then/else */
        }
+       checkjobs();
        return rcode;
 }
 
@@ -1469,7 +1660,6 @@ int reserved_word(o_string *dest, struct p_context *ctx)
                                old->child->group = ctx->list_head;
                                *ctx = *old;   /* physical copy */
                                free(old);
-                               ctx->w=RES_NONE;
                        }
                        b_reset (dest);
                        return 1;
@@ -1786,6 +1976,7 @@ static int handle_dollar(o_string *dest, struct p_context *ctx, struct in_str *i
                        lookup_param(dest, ctx, &alt);
                        break;
                case '(':
+                       b_getch(input);
                        process_command_subs(dest, ctx, input, ')');
                        break;
                case '*':
@@ -1842,14 +2033,16 @@ int parse_stream(o_string *dest, struct p_context *ctx,
                        ch,ch,m,dest->quote);
                if (m==0 || ((m==1 || m==2) && dest->quote)) {
                        b_addqchr(dest, ch, dest->quote);
-               } else if (ch == end_trigger && !dest->quote && ctx->w==RES_NONE) {
-                       debug_printf("leaving parse_stream\n");
-                       return 0;
-               } else if (m==2 && !dest->quote) {  /* IFS */
-                       done_word(dest, ctx);
-                       if (ch=='\n') done_pipe(ctx,PIPE_SEQ);
+               } else {
+                       if (m==2) {  /* unquoted IFS */
+                               done_word(dest, ctx);
+                               /* If we aren't performing a substitution, treat a newline as a
+                                * command separator.  */
+                               if (end_trigger != '\0' && ch=='\n')
+                                       done_pipe(ctx,PIPE_SEQ);
+                       }
                        if (ch == end_trigger && !dest->quote && ctx->w==RES_NONE) {
-                               debug_printf("leaving parse_stream (stupid duplication)\n");
+                               debug_printf("leaving parse_stream\n");
                                return 0;
                        }
 #if 0
@@ -1860,7 +2053,7 @@ int parse_stream(o_string *dest, struct p_context *ctx,
                                initialize_context(ctx);
                        }
 #endif
-               } else switch (ch) {
+                       if (m!=2) switch (ch) {
                case '#':
                        if (dest->length == 0 && !dest->quote) {
                                while(ch=b_peek(input),ch!=EOF && ch!='\n') { b_getch(input); }
@@ -1962,6 +2155,7 @@ int parse_stream(o_string *dest, struct p_context *ctx,
                default:
                        syntax();   /* this is really an internal logic error */
                        return 1;
+                       }
                }
        }
        /* complain if quote?  No, maybe we just finished a command substitution
@@ -2039,11 +2233,19 @@ int shell_main(int argc, char **argv)
 {
        int opt;
        FILE *input;
+       struct jobset joblist_end = { NULL, NULL };
+       job_list = &joblist_end;
+
+       last_return_code=EXIT_SUCCESS;
 
        /* XXX what should these be while sourcing /etc/profile? */
        global_argc = argc;
        global_argv = argv;
 
+       /* don't pay any attention to this signal; it just confuses 
+          things and isn't really meant for shells anyway */
+       signal(SIGTTOU, SIG_IGN);
+
        if (argv[0] && argv[0][0] == '-') {
                debug_printf("\nsourcing /etc/profile\n");
                input = xfopen("/etc/profile", "r");
@@ -2069,7 +2271,7 @@ int shell_main(int argc, char **argv)
                                        global_argv = argv+optind;
                                        global_argc = argc-optind;
                                        opt = parse_string_outer(optarg);
-                                       exit(opt);
+                                       goto final_return;
                                }
                                break;
                        case 'i':
@@ -2095,13 +2297,14 @@ int shell_main(int argc, char **argv)
                        isatty(fileno(stdin)) && isatty(fileno(stdout))) {
                interactive++;
        }
-       
+
+       debug_printf("\ninteractive=%d\n", interactive);
        if (interactive) {
                /* Looks like they want an interactive shell */
                fprintf(stdout, "\nhush -- the humble shell v0.01 (testing)\n\n");
-               exit(parse_file_outer(stdin));
+               opt=parse_file_outer(stdin);
+               goto final_return;
        }
-       debug_printf("\ninteractive=%d\n", interactive);
 
        debug_printf("\nrunning script '%s'\n", argv[optind]);
        global_argv = argv+optind;
@@ -2113,5 +2316,6 @@ int shell_main(int argc, char **argv)
        fclose(input.file);
 #endif
 
-       return(opt);
+final_return:
+       return(opt?opt:last_return_code);
 }