forgot about avn add... :(
[oweals/busybox.git] / shell / lash.c
index 16401225edf0ea4ac09ed52babb007ab44415f06..4067bc6bc853f2bb6622a4658477286bc3101df3 100644 (file)
@@ -2,27 +2,13 @@
 /*
  * lash -- the BusyBox Lame-Ass SHell
  *
- * Copyright (C) 1999,2000 by Lineo, inc. and Erik Andersen
- * Copyright (C) 1999,2000,2001 by Erik Andersen <andersee@debian.org>
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
  *
  * Based in part on ladsh.c by Michael K. Johnson and Erik W. Troan, which is
  * under the following liberal license: "We have placed this source code in the
  * public domain. Use it in any project, free or commercial."
  *
- * 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
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
  */
 
 /* This shell's parsing engine is officially at a dead-end.  Future
 //#define DEBUG_SHELL
 
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <ctype.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <sys/wait.h>
-#include <unistd.h>
-#include <getopt.h>
-#include <termios.h>
 #include "busybox.h"
+#include <getopt.h>
 #include "cmdedit.h"
 
 #ifdef CONFIG_LOCALE_SUPPORT
 #include <glob.h>
 #define expand_t       glob_t
 
+/* Always enable for the moment... */
+#define CONFIG_LASH_PIPE_N_REDIRECTS
+#define CONFIG_LASH_JOB_CONTROL
 
 static const int MAX_READ = 128;       /* size of input buffer for `read' builtin */
 #define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n"
 
 
+#ifdef CONFIG_LASH_PIPE_N_REDIRECTS
 enum redir_type { REDIRECT_INPUT, REDIRECT_OVERWRITE,
        REDIRECT_APPEND
 };
+#endif
 
-static const unsigned int DEFAULT_CONTEXT=0x1;
-static const unsigned int IF_TRUE_CONTEXT=0x2;
-static const unsigned int IF_FALSE_CONTEXT=0x4;
-static const unsigned int THEN_EXP_CONTEXT=0x8;
-static const unsigned int ELSE_EXP_CONTEXT=0x10;
-
-
-struct jobset {
-       struct job *head;                       /* head of list of running jobs */
-       struct job *fg;                         /* current foreground job */
+enum {
+       DEFAULT_CONTEXT = 0x1,
+       IF_TRUE_CONTEXT = 0x2,
+       IF_FALSE_CONTEXT = 0x4,
+       THEN_EXP_CONTEXT = 0x8,
+       ELSE_EXP_CONTEXT = 0x10
 };
 
+#define LASH_OPT_DONE (1)
+#define LASH_OPT_SAW_QUOTE (2)
+
+#ifdef CONFIG_LASH_PIPE_N_REDIRECTS
 struct redir_struct {
        enum redir_type type;   /* type of redirection */
        int fd;                                         /* file descriptor being redirected */
        char *filename;                         /* file to redirect fd to */
 };
+#endif
 
 struct child_prog {
        pid_t pid;                                      /* 0 if exited */
        char **argv;                            /* program name and arguments */
        int num_redirects;                      /* elements in redirection array */
-       struct redir_struct *redirects; /* I/O redirects */
        int is_stopped;                         /* is the program currently running? */
        struct job *family;                     /* pointer back to the child's parent job */
+#ifdef CONFIG_LASH_PIPE_N_REDIRECTS
+       struct redir_struct *redirects; /* I/O redirects */
+#endif
+};
+
+struct jobset {
+       struct job *head;                       /* head of list of running jobs */
+       struct job *fg;                         /* current foreground job */
 };
 
 struct job {
@@ -113,11 +101,6 @@ struct built_in_command {
        int (*function) (struct child_prog *);  /* function ptr */
 };
 
-struct close_me {
-       int fd;
-       struct close_me *next;
-};
-
 /* function prototypes for builtins */
 static int builtin_cd(struct child_prog *cmd);
 static int builtin_exec(struct child_prog *cmd);
@@ -133,15 +116,12 @@ static int builtin_read(struct child_prog *cmd);
 
 
 /* function prototypes for shell stuff */
-static void mark_open(int fd);
-static void mark_closed(int fd);
-static void close_all(void);
 static void checkjobs(struct jobset *job_list);
 static void remove_job(struct jobset *j_list, struct job *job);
 static int get_command(FILE * source, char *command);
 static int parse_command(char **command_ptr, struct job *job, int *inbg);
 static int run_command(struct job *newjob, int inbg, int outpipe[2]);
-static int pseudo_exec(struct child_prog *cmd) __attribute__ ((noreturn));
+static int pseudo_exec(struct child_prog *cmd) ATTRIBUTE_NORETURN;
 static int busy_loop(FILE * input);
 
 
@@ -177,16 +157,15 @@ static int shell_context;  /* Type prompt trigger (PS1 or PS2) */
 
 /* Globals that are static to this file */
 static const char *cwd;
-static char *local_pending_command = NULL;
+static char *local_pending_command;
 static struct jobset job_list = { NULL, NULL };
 static int argc;
 static char **argv;
-static struct close_me *close_me_head;
+static llist_t *close_me_list;
 static int last_return_code;
 static int last_bg_pid;
 static unsigned int last_jobid;
 static int shell_terminal;
-static pid_t shell_pgrp;
 static char *PS1;
 static char *PS2 = "> ";
 
@@ -200,7 +179,7 @@ static inline void debug_printf(const char *format, ...)
        va_end(args);
 }
 #else
-static inline void debug_printf(const char *format, ...) { }
+static inline void debug_printf(const char ATTRIBUTE_UNUSED *format, ...) { }
 #endif
 
 /*
@@ -241,12 +220,12 @@ static int builtin_cd(struct child_prog *child)
        else
                newdir = child->argv[1];
        if (chdir(newdir)) {
-               printf("cd: %s: %m\n", newdir);
+               bb_perror_msg("cd: %s", newdir);
                return EXIT_FAILURE;
        }
        cwd = xgetcwd((char *)cwd);
        if (!cwd)
-               cwd = unknown;
+               cwd = bb_msg_unknown;
        return EXIT_SUCCESS;
 }
 
@@ -256,7 +235,7 @@ static int builtin_exec(struct child_prog *child)
        if (child->argv[1] == NULL)
                return EXIT_SUCCESS;   /* Really? */
        child->argv++;
-       close_all();
+       while(close_me_list) close((long)llist_pop(&close_me_list));
        pseudo_exec(child);
        /* never returns */
 }
@@ -284,12 +263,12 @@ static int builtin_fg_bg(struct child_prog *child)
                        }
                }
                if (!job) {
-                       error_msg("%s: no current job", child->argv[0]);
+                       bb_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]);
+                       bb_error_msg(bb_msg_invalid_arg, child->argv[1], child->argv[0]);
                        return EXIT_FAILURE;
                }
                for (job = child->family->job_list->head; job; job = job->next) {
@@ -298,7 +277,7 @@ static int builtin_fg_bg(struct child_prog *child)
                        }
                }
                if (!job) {
-                       error_msg("%s: %d: no such job", child->argv[0], jobnum);
+                       bb_error_msg("%s: %d: no such job", child->argv[0], jobnum);
                        return EXIT_FAILURE;
                }
        }
@@ -320,7 +299,7 @@ static int builtin_fg_bg(struct child_prog *child)
                if (i == ESRCH) {
                        remove_job(&job_list, job);
                } else {
-                       perror_msg("kill (SIGCONT)");
+                       bb_perror_msg("kill (SIGCONT)");
                }
        }
 
@@ -328,12 +307,12 @@ static int builtin_fg_bg(struct child_prog *child)
 }
 
 /* built-in 'help' handler */
-static int builtin_help(struct child_prog *dummy)
+static int builtin_help(struct child_prog ATTRIBUTE_UNUSED *dummy)
 {
        struct built_in_command *x;
 
-       printf("\nBuilt-in commands:\n");
-       printf("-------------------\n");
+       printf("\nBuilt-in commands:\n"
+                  "-------------------\n");
        for (x = bltins; x->cmd; x++) {
                if (x->descr==NULL)
                        continue;
@@ -344,7 +323,7 @@ static int builtin_help(struct child_prog *dummy)
                        continue;
                printf("%s\t%s\n", x->cmd, x->descr);
        }
-       printf("\n\n");
+       putchar('\n');
        return EXIT_SUCCESS;
 }
 
@@ -367,11 +346,11 @@ static int builtin_jobs(struct child_prog *child)
 
 
 /* built-in 'pwd' handler */
-static int builtin_pwd(struct child_prog *dummy)
+static int builtin_pwd(struct child_prog ATTRIBUTE_UNUSED *dummy)
 {
        cwd = xgetcwd((char *)cwd);
        if (!cwd)
-               cwd = unknown;
+               cwd = bb_msg_unknown;
        puts(cwd);
        return EXIT_SUCCESS;
 }
@@ -391,7 +370,7 @@ static int builtin_export(struct child_prog *child)
        }
        res = putenv(v);
        if (res)
-               fprintf(stderr, "export: %m\n");
+               bb_perror_msg("export");
 #ifdef CONFIG_FEATURE_SH_FANCY_PROMPT
        if (strncmp(v, "PS1=", 4)==0)
                PS1 = getenv("PS1");
@@ -410,7 +389,7 @@ static int builtin_export(struct child_prog *child)
 /* built-in 'read VAR' handler */
 static int builtin_read(struct child_prog *child)
 {
-       int res = 0, len, newlen;
+       int res = 0, len;
        char *s;
        char string[MAX_READ];
 
@@ -421,19 +400,19 @@ static int builtin_read(struct child_prog *child)
                string[len++] = '=';
                string[len]   = '\0';
                fgets(&string[len], sizeof(string) - len, stdin);       /* read string */
-               newlen = strlen(string);
-               if(newlen > len)
-                       string[--newlen] = '\0';        /* chomp trailing newline */
+               res = strlen(string);
+               if (res > len)
+                       string[--res] = '\0';   /* chomp trailing newline */
                /*
                ** string should now contain "VAR=<value>"
                ** copy it (putenv() won't do that, so we must make sure
                ** the string resides in a static buffer!)
                */
                res = -1;
-               if((s = strdup(string)))
+               if ((s = strdup(string)))
                        res = putenv(s);
                if (res)
-                       fprintf(stderr, "read: %m\n");
+                       bb_perror_msg("read");
        }
        else
                fgets(string, sizeof(string), stdin);
@@ -446,23 +425,17 @@ static int builtin_source(struct child_prog *child)
 {
        FILE *input;
        int status;
-       int fd;
-
-       if (child->argv[1] == NULL)
-               return EXIT_FAILURE;
 
-       input = fopen(child->argv[1], "r");
+       input = fopen_or_warn(child->argv[1], "r");
        if (!input) {
-               printf( "Couldn't open file '%s'\n", child->argv[1]);
                return EXIT_FAILURE;
        }
 
-       fd=fileno(input);
-       mark_open(fd);
+       llist_add_to(&close_me_list, (void *)(long)fileno(input));
        /* Now run the file */
        status = busy_loop(input);
        fclose(input);
-       mark_closed(fd);
+       llist_pop(&close_me_list);
        return (status);
 }
 
@@ -470,43 +443,14 @@ static int builtin_source(struct child_prog *child)
 static int builtin_unset(struct child_prog *child)
 {
        if (child->argv[1] == NULL) {
-               printf( "unset: parameter required.\n");
+               printf(bb_msg_requires_arg, "unset");
                return EXIT_FAILURE;
        }
        unsetenv(child->argv[1]);
        return EXIT_SUCCESS;
 }
 
-static void mark_open(int fd)
-{
-       struct close_me *new = xmalloc(sizeof(struct close_me));
-       new->fd = fd;
-       new->next = close_me_head;
-       close_me_head = new;
-}
-
-static void mark_closed(int fd)
-{
-       struct close_me *tmp;
-       if (close_me_head == NULL || close_me_head->fd != fd)
-               error_msg_and_die("corrupt close_me");
-       tmp = close_me_head;
-       close_me_head = close_me_head->next;
-       free(tmp);
-}
-
-static void close_all()
-{
-       struct close_me *c, *tmp;
-       for (c=close_me_head; c; c=tmp) {
-               close(c->fd);
-               tmp=c->next;
-               free(c);
-       }
-       close_me_head = NULL;
-}
-
-
+#ifdef CONFIG_LASH_JOB_CONTROL
 /* free up all memory from a job */
 static void free_job(struct job *cmd)
 {
@@ -515,15 +459,14 @@ static void free_job(struct job *cmd)
 
        for (i = 0; i < cmd->num_progs; i++) {
                free(cmd->progs[i].argv);
+#ifdef CONFIG_LASH_PIPE_N_REDIRECTS
                if (cmd->progs[i].redirects)
                        free(cmd->progs[i].redirects);
+#endif
        }
-       if (cmd->progs)
-               free(cmd->progs);
-       if (cmd->text)
-               free(cmd->text);
-       if (cmd->cmdbuf)
-               free(cmd->cmdbuf);
+       free(cmd->progs);
+       free(cmd->text);
+       free(cmd->cmdbuf);
        keep = cmd->job_list;
        memset(cmd, 0, sizeof(struct job));
        cmd->job_list = keep;
@@ -552,7 +495,7 @@ static void remove_job(struct jobset *j_list, struct job *job)
        free(job);
 }
 
-/* Checks to see if any background processes have exited -- if they 
+/* 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(struct jobset *j_list)
 {
@@ -588,23 +531,25 @@ static void checkjobs(struct jobset *j_list)
                        /* child stopped */
                        job->stopped_progs++;
                        job->progs[prognum].is_stopped = 1;
-
-#if 0
-                       /* Printing this stuff is a pain, since it tends to
-                        * overwrite the prompt an inconveinient moments.  So
-                        * don't do that.  */
-                       if (job->stopped_progs == job->num_progs) {
-                               printf(JOB_STATUS_FORMAT, job->jobid, "Stopped",
-                                          job->text);
-                       }
-#endif 
                }
        }
 
        if (childpid == -1 && errno != ECHILD)
-               perror_msg("waitpid");
+               bb_perror_msg("waitpid");
+}
+#else
+static void checkjobs(struct jobset *j_list)
+{
+}
+static void free_job(struct job *cmd)
+{
+}
+static void remove_job(struct jobset *j_list, struct job *job)
+{
 }
+#endif
 
+#ifdef CONFIG_LASH_PIPE_N_REDIRECTS
 /* squirrel != NULL means we squirrel away copies of stdin, stdout,
  * and stderr if they are redirected. */
 static int setup_redirects(struct child_prog *prog, int squirrel[])
@@ -631,13 +576,14 @@ static int setup_redirects(struct child_prog *prog, int squirrel[])
                if (openfd < 0) {
                        /* this could get lost if stderr has been redirected, but
                           bash and ash both lose it as well (though zsh doesn't!) */
-                       perror_msg("error opening %s", redir->filename);
+                       bb_perror_msg("error opening %s", redir->filename);
                        return 1;
                }
 
                if (openfd != redir->fd) {
                        if (squirrel && redir->fd < 3) {
                                squirrel[redir->fd] = dup(redir->fd);
+                               fcntl (squirrel[redir->fd], F_SETFD, FD_CLOEXEC);
                        }
                        dup2(openfd, redir->fd);
                        close(openfd);
@@ -660,6 +606,15 @@ static void restore_redirects(int squirrel[])
                }
        }
 }
+#else
+static inline int setup_redirects(struct child_prog *prog, int squirrel[])
+{
+       return 0;
+}
+static inline void restore_redirects(int squirrel[])
+{
+}
+#endif
 
 static inline void cmdedit_set_initial_prompt(void)
 {
@@ -669,7 +624,7 @@ static inline void cmdedit_set_initial_prompt(void)
        PS1 = getenv("PS1");
        if(PS1==0)
                PS1 = "\\w \\$ ";
-#endif 
+#endif
 }
 
 static inline void setup_prompt_string(char **prompt_str)
@@ -677,17 +632,16 @@ static inline void setup_prompt_string(char **prompt_str)
 #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
        /* Set up the prompt */
        if (shell_context == 0) {
-               if (PS1)
-                       free(PS1);
+               free(PS1);
                PS1=xmalloc(strlen(cwd)+4);
-               sprintf(PS1, "%s %s", cwd, ( geteuid() != 0 ) ?  "$ ":"# ");
+               sprintf(PS1, "%s %c ", cwd, ( geteuid() != 0 ) ? '$': '#');
                *prompt_str = PS1;
        } else {
                *prompt_str = PS2;
        }
 #else
        *prompt_str = (shell_context==0)? PS1 : PS2;
-#endif 
+#endif
 }
 
 static int get_command(FILE * source, char *command)
@@ -724,64 +678,36 @@ static int get_command(FILE * source, char *command)
 
        if (!fgets(command, BUFSIZ - 2, source)) {
                if (source == stdin)
-                       printf("\n");
+                       puts("");
                return 1;
        }
 
        return 0;
 }
 
-static char* itoa(register int i)
-{
-       static char a[7]; /* Max 7 ints */
-       register char *b = a + sizeof(a) - 1;
-       int   sign = (i < 0);
-
-       if (sign)
-               i = -i;
-       *b = 0;
-       do
-       {
-               *--b = '0' + (i % 10);
-               i /= 10;
-       }
-       while (i);
-       if (sign)
-               *--b = '-';
-       return b;
-}
-
-char * strsep_space( char *string, int * ix)
+static char * strsep_space( char *string, int * ix)
 {
-       char *token, *begin;
-
-       begin = string;
-
        /* Short circuit the trivial case */
        if ( !string || ! string[*ix])
                return NULL;
 
        /* Find the end of the token. */
-       while( string && string[*ix] && !isspace(string[*ix]) ) {
+       while (string[*ix] && !isspace(string[*ix]) ) {
                (*ix)++;
        }
 
-       /* Find the end of any whitespace trailing behind 
+       /* Find the end of any whitespace trailing behind
         * the token and let that be part of the token */
-       while( string && string[*ix] && isspace(string[*ix]) ) {
+       while (string[*ix] && (isspace)(string[*ix]) ) {
                (*ix)++;
        }
 
-       if (! string && *ix==0) {
+       if (!*ix) {
                /* Nothing useful was found */
                return NULL;
        }
 
-       token = xmalloc(*ix+1);
-       token[*ix] = '\0';
-       strncpy(token, string,  *ix); 
-
-       return token;
+       return xstrndup(string, *ix);
 }
 
 static int expand_arguments(char *command)
@@ -790,14 +716,14 @@ static int expand_arguments(char *command)
        expand_t expand_result;
        char *tmpcmd, *cmd, *cmd_copy;
        char *src, *dst, *var;
-       const char *out_of_space = "out of space during expansion";
+       const char * const out_of_space = "out of space during expansion";
        int flags = GLOB_NOCHECK
 #ifdef GLOB_BRACE
                | GLOB_BRACE
-#endif 
+#endif
 #ifdef GLOB_TILDE
                | GLOB_TILDE
-#endif 
+#endif
                ;
 
        /* get rid of the terminating \n */
@@ -807,7 +733,7 @@ static int expand_arguments(char *command)
        while( command && command[ix]) {
                if (command[ix] == '\\') {
                        const char *tmp = command+ix+1;
-                       command[ix] = process_escape_sequence(  &tmp );
+                       command[ix] = bb_process_escape_sequence(  &tmp );
                        memmove(command+ix + 1, tmp, strlen(tmp)+1);
                }
                ix++;
@@ -822,7 +748,7 @@ static int expand_arguments(char *command)
         * we write stuff into the original (in a minute) */
        cmd = cmd_copy = xstrdup(command);
        *command = '\0';
-       for (ix = 0, tmpcmd = cmd; 
+       for (ix = 0, tmpcmd = cmd;
                        (tmpcmd = strsep_space(cmd, &ix)) != NULL; cmd += ix, ix=0) {
                if (*tmpcmd == '\0')
                        break;
@@ -833,13 +759,13 @@ static int expand_arguments(char *command)
                if (retval == GLOB_NOSPACE) {
                        /* Mem may have been allocated... */
                        globfree (&expand_result);
-                       error_msg(out_of_space);
+                       bb_error_msg(out_of_space);
                        return FALSE;
                } else if (retval != 0) {
                        /* Some other error.  GLOB_NOMATCH shouldn't
-                        * happen because of the GLOB_NOCHECK flag in 
+                        * happen because of the GLOB_NOCHECK flag in
                         * the glob call. */
-                       error_msg("syntax error");
+                       bb_error_msg("syntax error");
                        return FALSE;
                } else {
                        /* Convert from char** (one word per string) to a simple char*,
@@ -847,7 +773,7 @@ static int expand_arguments(char *command)
                        for (i=0; i < expand_result.gl_pathc; i++) {
                                length=strlen(expand_result.gl_pathv[i]);
                                if (total_length+length+1 >= BUFSIZ) {
-                                       error_msg(out_of_space);
+                                       bb_error_msg(out_of_space);
                                        return FALSE;
                                }
                                strcat(command+total_length, " ");
@@ -861,7 +787,7 @@ static int expand_arguments(char *command)
        free(cmd_copy);
        trim(command);
 
-       /* Now do the shell variable substitutions which 
+       /* Now do the shell variable substitutions which
         * wordexp can't do for us, namely $? and $! */
        src = command;
        while((dst = strchr(src,'$')) != NULL){
@@ -890,7 +816,7 @@ static int expand_arguments(char *command)
                        case '0':case '1':case '2':case '3':case '4':
                        case '5':case '6':case '7':case '8':case '9':
                                {
-                                       int ixx=*(dst + 1)-48;
+                                       int ixx=*(dst+1)-48+1;
                                        if (ixx >= argc) {
                                                var='\0';
                                        } else {
@@ -901,7 +827,7 @@ static int expand_arguments(char *command)
 
                }
                if (var) {
-                       /* a single character construction was found, and 
+                       /* a single character construction was found, and
                         * already handled in the case statement */
                        src=dst+2;
                } else {
@@ -915,7 +841,7 @@ static int expand_arguments(char *command)
                                num_skip_chars=1;
                        } else {
                                src=dst+1;
-                               while(isalnum(*src) || *src=='_') src++;
+                               while((isalnum)(*src) || *src=='_') src++;
                        }
                        if (src == NULL) {
                                src = dst+dstlen;
@@ -934,7 +860,7 @@ static int expand_arguments(char *command)
                        int subst_len = strlen(var);
                        int trail_len = strlen(src);
                        if (dst+subst_len+trail_len >= command+BUFSIZ) {
-                               error_msg(out_of_space);
+                               bb_error_msg(out_of_space);
                                return FALSE;
                        }
                        /* Move stuff to the end of the string to accommodate
@@ -951,25 +877,26 @@ static int expand_arguments(char *command)
 
 /* Return cmd->num_progs as 0 if no command is present (e.g. an empty
    line). If a valid command is found, command_ptr is set to point to
-   the beginning of the next command (if the original command had more 
-   then one job associated with it) or NULL if no more commands are 
+   the beginning of the next command (if the original command had more
+   then one job associated with it) or NULL if no more commands are
    present. */
 static int parse_command(char **command_ptr, struct job *job, int *inbg)
 {
        char *command;
        char *return_command = NULL;
-       char *src, *buf, *chptr;
-       int argc_l = 0;
-       int done = 0;
+       char *src, *buf;
+       int argc_l;
+       int flag;
        int argv_alloced;
-       int i, saw_quote = 0;
        char quote = '\0';
-       int count;
        struct child_prog *prog;
+#ifdef CONFIG_LASH_PIPE_N_REDIRECTS
+       int i;
+       char *chptr;
+#endif
 
        /* skip leading white space */
-       while (**command_ptr && isspace(**command_ptr))
-               (*command_ptr)++;
+       *command_ptr = skip_whitespace(*command_ptr);
 
        /* this handles empty lines or leading '#' characters */
        if (!**command_ptr || (**command_ptr == '#')) {
@@ -981,36 +908,39 @@ static int parse_command(char **command_ptr, struct job *job, int *inbg)
        job->num_progs = 1;
        job->progs = xmalloc(sizeof(*job->progs));
 
-       /* We set the argv elements to point inside of this string. The 
+       /* We set the argv elements to point inside of this string. The
           memory is freed by free_job(). Allocate twice the original
           length in case we need to quote every single character.
 
-          Getting clean memory relieves us of the task of NULL 
-          terminating things and makes the rest of this look a bit 
+          Getting clean memory relieves us of the task of NULL
+          terminating things and makes the rest of this look a bit
           cleaner (though it is, admittedly, a tad less efficient) */
-       job->cmdbuf = command = xcalloc(2*strlen(*command_ptr) + 1, sizeof(char));
+       job->cmdbuf = command = xzalloc(2*strlen(*command_ptr) + 1);
        job->text = NULL;
 
        prog = job->progs;
        prog->num_redirects = 0;
-       prog->redirects = NULL;
        prog->is_stopped = 0;
        prog->family = job;
+#ifdef CONFIG_LASH_PIPE_N_REDIRECTS
+       prog->redirects = NULL;
+#endif
 
        argv_alloced = 5;
        prog->argv = xmalloc(sizeof(*prog->argv) * argv_alloced);
        prog->argv[0] = job->cmdbuf;
 
+       flag = argc_l = 0;
        buf = command;
        src = *command_ptr;
-       while (*src && !done) {
+       while (*src && !(flag & LASH_OPT_DONE)) {
                if (quote == *src) {
                        quote = '\0';
                } else if (quote) {
                        if (*src == '\\') {
                                src++;
                                if (!*src) {
-                                       error_msg("character expected after \\");
+                                       bb_error_msg("character expected after \\");
                                        free_job(job);
                                        return 1;
                                }
@@ -1024,7 +954,7 @@ static int parse_command(char **command_ptr, struct job *job, int *inbg)
                                           *src == ']') *buf++ = '\\';
                        *buf++ = *src;
                } else if (isspace(*src)) {
-                       if (*prog->argv[argc_l] || saw_quote) {
+                       if (*prog->argv[argc_l] || flag & LASH_OPT_SAW_QUOTE) {
                                buf++, argc_l++;
                                /* +1 here leaves room for the NULL which ends argv */
                                if ((argc_l + 1) == argv_alloced) {
@@ -1034,23 +964,24 @@ static int parse_command(char **command_ptr, struct job *job, int *inbg)
                                                                                  argv_alloced);
                                }
                                prog->argv[argc_l] = buf;
-                               saw_quote = 0;
+                               flag ^= LASH_OPT_SAW_QUOTE;
                        }
                } else
                        switch (*src) {
                        case '"':
                        case '\'':
                                quote = *src;
-                               saw_quote = 1;
+                               flag |= LASH_OPT_SAW_QUOTE;
                                break;
 
                        case '#':                       /* comment */
                                if (*(src-1)== '$')
                                        *buf++ = *src;
                                else
-                                       done = 1;
+                                       flag |= LASH_OPT_DONE;
                                break;
 
+#ifdef CONFIG_LASH_PIPE_N_REDIRECTS
                        case '>':                       /* redirects */
                        case '<':
                                i = prog->num_redirects++;
@@ -1060,7 +991,7 @@ static int parse_command(char **command_ptr, struct job *job, int *inbg)
 
                                prog->redirects[i].fd = -1;
                                if (buf != prog->argv[argc_l]) {
-                                       /* the stuff before this character may be the file number 
+                                       /* the stuff before this character may be the file number
                                           being redirected */
                                        prog->redirects[i].fd =
                                                strtol(prog->argv[argc_l], &chptr, 10);
@@ -1090,11 +1021,10 @@ static int parse_command(char **command_ptr, struct job *job, int *inbg)
 
                                /* This isn't POSIX sh compliant. Oh well. */
                                chptr = src;
-                               while (isspace(*chptr))
-                                       chptr++;
+                               chptr = skip_whitespace(chptr);
 
                                if (!*chptr) {
-                                       error_msg("file name expected after %c", *(src-1));
+                                       bb_error_msg("file name expected after %c", *(src-1));
                                        free_job(job);
                                        job->num_progs=0;
                                        return 1;
@@ -1110,13 +1040,10 @@ static int parse_command(char **command_ptr, struct job *job, int *inbg)
 
                        case '|':                       /* pipe */
                                /* finish this command */
-                               if (*prog->argv[argc_l] || saw_quote)
+                               if (*prog->argv[argc_l] || flag & LASH_OPT_SAW_QUOTE)
                                        argc_l++;
                                if (!argc_l) {
-                                       error_msg("empty command in pipe");
-                                       free_job(job);
-                                       job->num_progs=0;
-                                       return 1;
+                                       goto empty_command_in_pipe;
                                }
                                prog->argv[argc_l] = NULL;
 
@@ -1136,11 +1063,11 @@ static int parse_command(char **command_ptr, struct job *job, int *inbg)
                                prog->argv[0] = ++buf;
 
                                src++;
-                               while (*src && isspace(*src))
-                                       src++;
+                               src = skip_whitespace(src);
 
                                if (!*src) {
-                                       error_msg("empty command in pipe");
+empty_command_in_pipe:
+                                       bb_error_msg("empty command in pipe");
                                        free_job(job);
                                        job->num_progs=0;
                                        return 1;
@@ -1148,18 +1075,22 @@ static int parse_command(char **command_ptr, struct job *job, int *inbg)
                                src--;                  /* we'll ++ it at the end of the loop */
 
                                break;
+#endif
 
+#ifdef CONFIG_LASH_JOB_CONTROL
                        case '&':                       /* background */
                                *inbg = 1;
+                               /* fallthrough */
+#endif
                        case ';':                       /* multiple commands */
-                               done = 1;
+                               flag |= LASH_OPT_DONE;
                                return_command = *command_ptr + (src - *command_ptr) + 1;
                                break;
 
                        case '\\':
                                src++;
                                if (!*src) {
-                                       error_msg("character expected after \\");
+                                       bb_error_msg("character expected after \\");
                                        free_job(job);
                                        return 1;
                                }
@@ -1173,7 +1104,7 @@ static int parse_command(char **command_ptr, struct job *job, int *inbg)
                src++;
        }
 
-       if (*prog->argv[argc_l] || saw_quote) {
+       if (*prog->argv[argc_l] || flag & LASH_OPT_SAW_QUOTE) {
                argc_l++;
        }
        if (!argc_l) {
@@ -1183,18 +1114,14 @@ static int parse_command(char **command_ptr, struct job *job, int *inbg)
        prog->argv[argc_l] = NULL;
 
        if (!return_command) {
-               job->text = xmalloc(strlen(*command_ptr) + 1);
-               strcpy(job->text, *command_ptr);
+               job->text = xstrdup(*command_ptr);
        } else {
                /* This leaves any trailing spaces, which is a bit sloppy */
-               count = return_command - *command_ptr;
-               job->text = xmalloc(count + 1);
-               strncpy(job->text, *command_ptr, count);
-               job->text[count] = '\0';
+               job->text = xstrndup(*command_ptr, return_command - *command_ptr);
        }
 
        *command_ptr = return_command;
-       
+
        return 0;
 }
 
@@ -1203,9 +1130,6 @@ static int parse_command(char **command_ptr, struct job *job, int *inbg)
 static int pseudo_exec(struct child_prog *child)
 {
        struct built_in_command *x;
-#ifdef CONFIG_FEATURE_SH_STANDALONE_SHELL
-       char *name;
-#endif
 
        /* Check if the command matches any of the non-forking builtins.
         * Depending on context, this might be redundant.  But it's
@@ -1214,7 +1138,7 @@ static int pseudo_exec(struct child_prog *child)
         */
        for (x = bltins; x->cmd; x++) {
                if (strcmp(child->argv[0], x->cmd) == 0 ) {
-                       exit(x->function(child));
+                       _exit(x->function(child));
                }
        }
 
@@ -1222,14 +1146,14 @@ static int pseudo_exec(struct child_prog *child)
        for (x = bltins_forking; x->cmd; x++) {
                if (strcmp(child->argv[0], x->cmd) == 0) {
                        applet_name=x->cmd;
-                       exit (x->function(child));
+                       _exit (x->function(child));
                }
        }
-#ifdef CONFIG_FEATURE_SH_STANDALONE_SHELL
+
        /* Check if the command matches any busybox internal
         * commands ("applets") here.  Following discussions from
         * November 2000 on busybox@busybox.net, don't use
-        * get_last_path_component().  This way explicit (with
+        * bb_get_last_path_component().  This way explicit (with
         * slashes) filenames will never be interpreted as an
         * applet, just like with builtins.  This way the user can
         * override an applet with an explicit filename reference.
@@ -1237,28 +1161,22 @@ static int pseudo_exec(struct child_prog *child)
         * /bin/foo invocation will fork and exec /bin/foo, even if
         * /bin/foo is a symlink to busybox.
         */
-       name = child->argv[0];
 
-#ifdef CONFIG_FEATURE_SH_APPLETS_ALWAYS_WIN
-       /* If you enable CONFIG_FEATURE_SH_APPLETS_ALWAYS_WIN, then
-        * if you run /bin/cat, it will use BusyBox cat even if 
-        * /bin/cat exists on the filesystem and is _not_ busybox.
-        * Some systems want this, others do not.  Choose wisely.  :-)
-        */
-       name = get_last_path_component(name);
-#endif
+       if (ENABLE_FEATURE_SH_STANDALONE_SHELL) {
+               char **argv_l = child->argv;
+               int argc_l;
 
-       {
-           char** argv_l=child->argv;
-           int argc_l;
-           for(argc_l=0;*argv_l!=NULL; argv_l++, argc_l++);
-           optind = 1;
-           run_applet_by_name(name, argc_l, child->argv);
+               for(argc_l=0; *argv_l; argv_l++, argc_l++);
+               optind = 1;
+               run_applet_by_name(child->argv[0], argc_l, child->argv);
        }
-#endif
 
        execvp(child->argv[0], child->argv);
-       perror_msg_and_die("%s", child->argv[0]);
+
+       /* Do not use bb_perror_msg_and_die() here, since we must not
+        * call exit() but should call _exit() instead */
+       bb_perror_msg("%s", child->argv[0]);
+       _exit(EXIT_FAILURE);
 }
 
 static void insert_job(struct job *newjob, int inbg)
@@ -1286,8 +1204,9 @@ static void insert_job(struct job *newjob, int inbg)
        thejob->running_progs = thejob->num_progs;
        thejob->stopped_progs = 0;
 
+#ifdef CONFIG_LASH_JOB_CONTROL
        if (inbg) {
-               /* we don't wait for background thejobs to return -- append it 
+               /* 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", thejob->jobid,
                           newjob->progs[newjob->num_progs - 1].pid);
@@ -1297,10 +1216,10 @@ static void insert_job(struct job *newjob, int inbg)
                newjob->job_list->fg = thejob;
 
                /* move the new process group into the foreground */
-               /* suppress messages when run from /linuxrc mag@sysgo.de */
-               if (tcsetpgrp(shell_terminal, newjob->pgrp) && errno != ENOTTY)
-                       perror_msg("tcsetpgrp");
+               /* Ignore errors since child could have already exited */
+               tcsetpgrp(shell_terminal, newjob->pgrp);
        }
+#endif
 }
 
 static int run_command(struct job *newjob, int inbg, int outpipe[2])
@@ -1317,7 +1236,7 @@ static int run_command(struct job *newjob, int inbg, int outpipe[2])
                child = & (newjob->progs[i]);
 
                if ((i + 1) < newjob->num_progs) {
-                       if (pipe(pipefds)<0) perror_msg_and_die("pipe");
+                       if (pipe(pipefds)<0) bb_perror_msg_and_die("pipe");
                        nextout = pipefds[1];
                } else {
                        if (outpipe[1]!=-1) {
@@ -1335,10 +1254,16 @@ static int run_command(struct job *newjob, int inbg, int outpipe[2])
                 * is doomed to failure, and doesn't work on bash, either.
                 */
                if (newjob->num_progs == 1) {
+                       /* Check if the command sets an environment variable. */
+                       if (strchr(child->argv[0], '=') != NULL) {
+                               child->argv[1] = child->argv[0];
+                               return builtin_export(child);
+                       }
+
                        for (x = bltins; x->cmd; x++) {
                                if (strcmp(child->argv[0], x->cmd) == 0 ) {
-                                       int squirrel[] = {-1, -1, -1};
                                        int rcode;
+                                       int squirrel[] = {-1, -1, -1};
                                        setup_redirects(child, squirrel);
                                        rcode = x->function(child);
                                        restore_redirects(squirrel);
@@ -1347,10 +1272,10 @@ static int run_command(struct job *newjob, int inbg, int outpipe[2])
                        }
                }
 
-#if !defined(__UCLIBC__) || defined(__UCLIBC_HAS_MMU__)
-               if (!(child->pid = fork())) 
+#if !defined(__UCLIBC__) || defined(__ARCH_HAS_MMU__)
+               if (!(child->pid = fork()))
 #else
-               if (!(child->pid = vfork())) 
+               if (!(child->pid = vfork()))
 #endif
                {
                        /* Set the handling for job control signals back to the default.  */
@@ -1361,7 +1286,8 @@ static int run_command(struct job *newjob, int inbg, int outpipe[2])
                        signal(SIGTTOU, SIG_DFL);
                        signal(SIGCHLD, SIG_DFL);
 
-                       close_all();
+                       /* Close all open filehandles. */
+                       while(close_me_list) close((long)llist_pop(&close_me_list));
 
                        if (outpipe[1]!=-1) {
                                close(outpipe[0]);
@@ -1395,7 +1321,7 @@ static int run_command(struct job *newjob, int inbg, int outpipe[2])
                if (nextout != 1)
                        close(nextout);
 
-               /* If there isn't another process, nextin is garbage 
+               /* If there isn't another process, nextin is garbage
                   but it doesn't matter */
                nextin = pipefds[0];
        }
@@ -1412,17 +1338,18 @@ static int busy_loop(FILE * input)
        char *command;
        char *next_command = NULL;
        struct job newjob;
-       pid_t  parent_pgrp;
        int i;
-       int inbg;
+       int inbg = 0;
        int status;
-       newjob.job_list = &job_list;
-       newjob.job_context = DEFAULT_CONTEXT;
-
+#ifdef CONFIG_LASH_JOB_CONTROL
+       pid_t  parent_pgrp;
        /* save current owner of TTY so we can restore it on exit */
        parent_pgrp = tcgetpgrp(shell_terminal);
+#endif
+       newjob.job_list = &job_list;
+       newjob.job_context = DEFAULT_CONTEXT;
 
-       command = (char *) xcalloc(BUFSIZ, sizeof(char));
+       command = xzalloc(BUFSIZ);
 
        while (1) {
                if (!job_list.fg) {
@@ -1439,7 +1366,7 @@ static int busy_loop(FILE * input)
 
                        if (! expand_arguments(next_command)) {
                                free(command);
-                               command = (char *) xcalloc(BUFSIZ, sizeof(char));
+                               command = xzalloc(BUFSIZ);
                                next_command = NULL;
                                continue;
                        }
@@ -1447,13 +1374,13 @@ static int busy_loop(FILE * input)
                        if (!parse_command(&next_command, &newjob, &inbg) &&
                                newjob.num_progs) {
                                int pipefds[2] = {-1,-1};
-                               debug_printf( "job=%p fed to run_command by busy_loop()'\n", 
+                               debug_printf( "job=%p fed to run_command by busy_loop()'\n",
                                                &newjob);
                                run_command(&newjob, inbg, pipefds);
                        }
                        else {
                                free(command);
-                               command = (char *) xcalloc(BUFSIZ, sizeof(char));
+                               command = (char *) xzalloc(BUFSIZ);
                                next_command = NULL;
                        }
                } else {
@@ -1462,8 +1389,11 @@ static int busy_loop(FILE * input)
                        while (!job_list.fg->progs[i].pid ||
                                   job_list.fg->progs[i].is_stopped == 1) i++;
 
-                       if (waitpid(job_list.fg->progs[i].pid, &status, WUNTRACED)<0)
-                               perror_msg_and_die("waitpid(%d)",job_list.fg->progs[i].pid);
+                       if (waitpid(job_list.fg->progs[i].pid, &status, WUNTRACED)<0) {
+                               if (errno != ECHILD) {
+                                       bb_perror_msg_and_die("waitpid(%d)",job_list.fg->progs[i].pid);
+                               }
+                       }
 
                        if (WIFEXITED(status) || WIFSIGNALED(status)) {
                                /* the child exited */
@@ -1477,7 +1407,9 @@ static int busy_loop(FILE * input)
                                        remove_job(&job_list, job_list.fg);
                                        job_list.fg = NULL;
                                }
-                       } else {
+                       }
+#ifdef CONFIG_LASH_JOB_CONTROL
+                       else {
                                /* the child was stopped */
                                job_list.fg->stopped_progs++;
                                job_list.fg->progs[i].is_stopped = 1;
@@ -1493,28 +1425,30 @@ static int busy_loop(FILE * input)
                                /* move the shell to the foreground */
                                /* suppress messages when run from /linuxrc mag@sysgo.de */
                                if (tcsetpgrp(shell_terminal, getpgrp()) && errno != ENOTTY)
-                                       perror_msg("tcsetpgrp"); 
+                                       bb_perror_msg("tcsetpgrp");
                        }
+#endif
                }
        }
        free(command);
 
+#ifdef CONFIG_LASH_JOB_CONTROL
        /* return controlling TTY back to parent process group before exiting */
-       if (tcsetpgrp(shell_terminal, parent_pgrp))
-               perror_msg("tcsetpgrp");
+       if (tcsetpgrp(shell_terminal, parent_pgrp) && errno != ENOTTY)
+               bb_perror_msg("tcsetpgrp");
+#endif
 
        /* return exit status if called with "-c" */
        if (input == NULL && WIFEXITED(status))
                return WEXITSTATUS(status);
-       
+
        return 0;
 }
 
-
 #ifdef CONFIG_FEATURE_CLEAN_UP
-void free_memory(void)
+static void free_memory(void)
 {
-       if (cwd && cwd!=unknown) {
+       if (cwd && cwd!=bb_msg_unknown) {
                free((char*)cwd);
        }
        if (local_pending_command)
@@ -1524,15 +1458,19 @@ void free_memory(void)
                remove_job(&job_list, job_list.fg);
        }
 }
+#else
+void free_memory(void);
 #endif
 
+#ifdef CONFIG_LASH_JOB_CONTROL
 /* Make sure we have a controlling tty.  If we get started under a job
  * aware app (like bash for example), make sure we are now in charge so
  * we don't fight over who gets the foreground */
-static void setup_job_control()
+static void setup_job_control(void)
 {
        int status;
-       
+       pid_t shell_pgrp;
+
        /* Loop until we are in the foreground.  */
        while ((status = tcgetpgrp (shell_terminal)) >= 0) {
                if (status == (shell_pgrp = getpgrp ())) {
@@ -1552,23 +1490,27 @@ static void setup_job_control()
        /* Put ourselves in our own process group.  */
        setsid();
        shell_pgrp = getpid ();
-       setpgid (shell_pgrp, shell_pgrp);
+       setpgid(shell_pgrp, shell_pgrp);
 
        /* Grab control of the terminal.  */
        tcsetpgrp(shell_terminal, shell_pgrp);
 }
+#else
+static inline void setup_job_control(void)
+{
+}
+#endif
 
 int lash_main(int argc_l, char **argv_l)
 {
-       int opt, interactive=FALSE;
+       unsigned opt;
        FILE *input = stdin;
        argc = argc_l;
        argv = argv_l;
 
        /* These variables need re-initializing when recursing */
        last_jobid = 0;
-       local_pending_command = NULL;
-       close_me_head = NULL;
+       close_me_list = NULL;
        job_list.head = NULL;
        job_list.fg = NULL;
        last_return_code=1;
@@ -1577,31 +1519,21 @@ int lash_main(int argc_l, char **argv_l)
                FILE *prof_input;
                prof_input = fopen("/etc/profile", "r");
                if (prof_input) {
-                       int tmp_fd = fileno(prof_input);
-                       mark_open(tmp_fd);      
+                       llist_add_to(&close_me_list, (void *)(long)fileno(prof_input));
                        /* Now run the file */
                        busy_loop(prof_input);
-                       fclose(prof_input);
-                       mark_closed(tmp_fd);
+                       fclose_if_not_stdin(prof_input);
+                       llist_pop(&close_me_list);
                }
        }
 
-       while ((opt = getopt(argc_l, argv_l, "cxi")) > 0) {
-               switch (opt) {
-                       case 'c':
-                               input = NULL;
-                               if (local_pending_command != 0)
-                                       error_msg_and_die("multiple -c arguments");
-                               local_pending_command = xstrdup(argv[optind]);
-                               optind++;
-                               argv = argv+optind;
-                               break;
-                       case 'i':
-                               interactive = TRUE;
-                               break;
-                       default:
-                               show_usage();
-               }
+       opt = getopt32(argc_l, argv_l, "+ic:", &local_pending_command);
+#define LASH_OPT_i (1<<0)
+#define LASH_OPT_c (1<<2)
+       if (opt & LASH_OPT_c) {
+               input = NULL;
+               optind++;
+               argv += optind;
        }
        /* A shell is interactive if the `-i' flag was given, or if all of
         * the following conditions are met:
@@ -1611,37 +1543,34 @@ int lash_main(int argc_l, char **argv_l)
         *    standard output is a terminal
         *    Refer to Posix.2, the description of the `sh' utility. */
        if (argv[optind]==NULL && input==stdin &&
-                       isatty(fileno(stdin)) && isatty(fileno(stdout))) {
-               interactive=TRUE;
+                       isatty(STDIN_FILENO) && isatty(STDOUT_FILENO))
+       {
+               opt |= LASH_OPT_i;
        }
        setup_job_control();
-       if (interactive==TRUE) {
-               //printf( "optind=%d  argv[optind]='%s'\n", optind, argv[optind]);
+       if (opt & LASH_OPT_i) {
                /* Looks like they want an interactive shell */
-#ifndef CONFIG_FEATURE_SH_EXTRA_QUIET 
-               printf( "\n\n" BB_BANNER " Built-in shell (lash)\n");
-               printf( "Enter 'help' for a list of built-in commands.\n\n");
-#endif
-       } else if (local_pending_command==NULL) {
+               if (!ENABLE_FEATURE_SH_EXTRA_QUIET) {
+                       printf("\n\n%s Built-in shell (lash)\n"
+                                       "Enter 'help' for a list of built-in commands.\n\n",
+                                       BB_BANNER);
+               }
+       } else if (!local_pending_command && argv[optind]) {
                //printf( "optind=%d  argv[optind]='%s'\n", optind, argv[optind]);
                input = xfopen(argv[optind], "r");
-               mark_open(fileno(input));  /* be lazy, never mark this closed */
+               /* be lazy, never mark this closed */
+               llist_add_to(&close_me_list, (void *)(long)fileno(input));
        }
 
        /* initialize the cwd -- this is never freed...*/
        cwd = xgetcwd(0);
        if (!cwd)
-               cwd = unknown;
+               cwd = bb_msg_unknown;
 
-#ifdef CONFIG_FEATURE_CLEAN_UP
-       atexit(free_memory);
-#endif
+       if (ENABLE_FEATURE_CLEAN_UP) atexit(free_memory);
+
+       if (ENABLE_FEATURE_COMMAND_EDITING) cmdedit_set_initial_prompt();
+       else PS1 = NULL;
 
-#ifdef CONFIG_FEATURE_COMMAND_EDITING
-       cmdedit_set_initial_prompt();
-#else
-       PS1 = NULL;
-#endif
-       
        return (busy_loop(input));
 }