ash: starting second round of cleanups. #1
authorDenis Vlasenko <vda.linux@googlemail.com>
Fri, 23 Feb 2007 21:08:58 +0000 (21:08 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Fri, 23 Feb 2007 21:08:58 +0000 (21:08 -0000)
shell/ash.c

index 0e039322bc0ce556b1bf212f16e896b1088c116a..664a6fa16f3c9d1790a02882f5d43dbe985b78bf 100644 (file)
@@ -408,6 +408,39 @@ out2str(const char *p)
 
 /* ============ Parsing structures */
 
+/* control characters in argument strings */
+#define CTLESC '\201'           /* escape next character */
+#define CTLVAR '\202'           /* variable defn */
+#define CTLENDVAR '\203'
+#define CTLBACKQ '\204'
+#define CTLQUOTE 01             /* ored with CTLBACKQ code if in quotes */
+/*      CTLBACKQ | CTLQUOTE == '\205' */
+#define CTLARI  '\206'          /* arithmetic expression */
+#define CTLENDARI '\207'
+#define CTLQUOTEMARK '\210'
+
+/* variable substitution byte (follows CTLVAR) */
+#define VSTYPE  0x0f            /* type of variable substitution */
+#define VSNUL   0x10            /* colon--treat the empty string as unset */
+#define VSQUOTE 0x80            /* inside double quotes--suppress splitting */
+
+/* values of VSTYPE field */
+#define VSNORMAL        0x1             /* normal variable:  $var or ${var} */
+#define VSMINUS         0x2             /* ${var-text} */
+#define VSPLUS          0x3             /* ${var+text} */
+#define VSQUESTION      0x4             /* ${var?message} */
+#define VSASSIGN        0x5             /* ${var=text} */
+#define VSTRIMRIGHT     0x6             /* ${var%pattern} */
+#define VSTRIMRIGHTMAX  0x7             /* ${var%%pattern} */
+#define VSTRIMLEFT      0x8             /* ${var#pattern} */
+#define VSTRIMLEFTMAX   0x9             /* ${var##pattern} */
+#define VSLENGTH        0xa             /* ${#var} */
+
+/* values of checkkwd variable */
+#define CHKALIAS        0x1
+#define CHKKWD          0x2
+#define CHKNL           0x4
+
 #define NCMD 0
 #define NPIPE 1
 #define NREDIR 2
@@ -551,6 +584,16 @@ struct funcnode {
        union node n;
 };
 
+/*
+ * Free a parse tree.
+ */
+static void
+freefunc(struct funcnode *f)
+{
+       if (f && --f->count < 0)
+               free(f);
+}
+
 
 /* ============ Debugging output */
 
@@ -886,11 +929,11 @@ showtree(union node *n)
 #endif /* DEBUG */
 
 
-/* ============ Parser data
- *
+/* ============ Parser data */
+
+/*
  * ash_vmsg() needs parsefile->fd, hence parsefile definition is moved up.
  */
-
 struct strlist {
        struct strlist *next;
        char *text;
@@ -1431,7 +1474,7 @@ single_quote(const char *s)
 }
 
 
-/* ============ ... */
+/* ============ nextopt */
 
 static char **argptr;                  /* argument list for builtin commands */
 static char *optionarg;                /* set by nextopt (like getopt) */
@@ -2348,47 +2391,7 @@ pwdcmd(int argc, char **argv)
 }
 
 
-/* ============ Unsorted yet */
-
-
-/*      parser.h     */
-
-/* control characters in argument strings */
-#define CTL_FIRST '\201'        /* first 'special' character */
-#define CTLESC '\201'           /* escape next character */
-#define CTLVAR '\202'           /* variable defn */
-#define CTLENDVAR '\203'
-#define CTLBACKQ '\204'
-#define CTLQUOTE 01             /* ored with CTLBACKQ code if in quotes */
-/*      CTLBACKQ | CTLQUOTE == '\205' */
-#define CTLARI  '\206'          /* arithmetic expression */
-#define CTLENDARI '\207'
-#define CTLQUOTEMARK '\210'
-#define CTL_LAST '\210'         /* last 'special' character */
-
-/* variable substitution byte (follows CTLVAR) */
-#define VSTYPE  0x0f            /* type of variable substitution */
-#define VSNUL   0x10            /* colon--treat the empty string as unset */
-#define VSQUOTE 0x80            /* inside double quotes--suppress splitting */
-
-/* values of VSTYPE field */
-#define VSNORMAL        0x1             /* normal variable:  $var or ${var} */
-#define VSMINUS         0x2             /* ${var-text} */
-#define VSPLUS          0x3             /* ${var+text} */
-#define VSQUESTION      0x4             /* ${var?message} */
-#define VSASSIGN        0x5             /* ${var=text} */
-#define VSTRIMRIGHT     0x6             /* ${var%pattern} */
-#define VSTRIMRIGHTMAX  0x7             /* ${var%%pattern} */
-#define VSTRIMLEFT      0x8             /* ${var#pattern} */
-#define VSTRIMLEFTMAX   0x9             /* ${var##pattern} */
-#define VSLENGTH        0xa             /* ${#var} */
-
-/* values of checkkwd variable */
-#define CHKALIAS        0x1
-#define CHKKWD          0x2
-#define CHKNL           0x4
-
-#define IBUFSIZ (BUFSIZ + 1)
+/* ============ ... */
 
 /*
  * NEOF is returned by parsecmd when it encounters an end of file.  It
@@ -2404,6 +2407,7 @@ static int parselleft;                  /* copy of parsefile->lleft */
 /* next character in input buffer */
 static char *parsenextc;                /* copy of parsefile->nextc */
 
+#define IBUFSIZ (BUFSIZ + 1)
 #define basebuf bb_common_bufsiz1       /* buffer for top level input file */
 
 static int tokpushback;                 /* last token pushed back */
@@ -2431,98 +2435,6 @@ static const char dolatstr[] = { CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' };
 
 #define xlikely(x)       __builtin_expect((x),1)
 
-#define TEOF 0
-#define TNL 1
-#define TREDIR 2
-#define TWORD 3
-#define TSEMI 4
-#define TBACKGND 5
-#define TAND 6
-#define TOR 7
-#define TPIPE 8
-#define TLP 9
-#define TRP 10
-#define TENDCASE 11
-#define TENDBQUOTE 12
-#define TNOT 13
-#define TCASE 14
-#define TDO 15
-#define TDONE 16
-#define TELIF 17
-#define TELSE 18
-#define TESAC 19
-#define TFI 20
-#define TFOR 21
-#define TIF 22
-#define TIN 23
-#define TTHEN 24
-#define TUNTIL 25
-#define TWHILE 26
-#define TBEGIN 27
-#define TEND 28
-
-/* first char is indicating which tokens mark the end of a list */
-static const char *const tokname_array[] = {
-       "\1end of file",
-       "\0newline",
-       "\0redirection",
-       "\0word",
-       "\0;",
-       "\0&",
-       "\0&&",
-       "\0||",
-       "\0|",
-       "\0(",
-       "\1)",
-       "\1;;",
-       "\1`",
-#define KWDOFFSET 13
-       /* the following are keywords */
-       "\0!",
-       "\0case",
-       "\1do",
-       "\1done",
-       "\1elif",
-       "\1else",
-       "\1esac",
-       "\1fi",
-       "\0for",
-       "\0if",
-       "\0in",
-       "\1then",
-       "\0until",
-       "\0while",
-       "\0{",
-       "\1}",
-};
-
-static const char *
-tokname(int tok)
-{
-       static char buf[16];
-
-       if (tok >= TSEMI)
-               buf[0] = '"';
-       sprintf(buf + (tok >= TSEMI), "%s%c",
-                       tokname_array[tok] + 1, (tok >= TSEMI ? '"' : 0));
-       return buf;
-}
-
-/* Wrapper around strcmp for qsort/bsearch/... */
-static int
-pstrcmp(const void *a, const void *b)
-{
-       return strcmp((const char *) a, (*(const char *const *) b) + 1);
-}
-
-static const char *const *
-findkwd(const char *s)
-{
-       return bsearch(s, tokname_array + KWDOFFSET,
-                       (sizeof(tokname_array) / sizeof(const char *)) - KWDOFFSET,
-                       sizeof(const char *), pstrcmp);
-}
-
 /* Syntax classes */
 #define CWORD 0                 /* character is nothing special */
 #define CNL 1                   /* newline character */
@@ -2966,131 +2878,40 @@ static const char syntax_index_table[258] = {
 #endif  /* USE_SIT_FUNCTION */
 
 
-/*      alias.c      */
-
-#define ATABSIZE 39
+/*      exec.h    */
 
-static int funcblocksize;          /* size of structures in function */
-static int funcstringsize;         /* size of strings in node */
-static void *funcblock;            /* block to allocate function from */
-static char *funcstring;           /* block to allocate strings from */
+#if ENABLE_ASH_MATH_SUPPORT_64
+typedef int64_t arith_t;
+#define arith_t_type long long
+#else
+typedef long arith_t;
+#define arith_t_type long
+#endif
 
-static const short nodesize[26] = {
-       SHELL_ALIGN(sizeof(struct ncmd)),
-       SHELL_ALIGN(sizeof(struct npipe)),
-       SHELL_ALIGN(sizeof(struct nredir)),
-       SHELL_ALIGN(sizeof(struct nredir)),
-       SHELL_ALIGN(sizeof(struct nredir)),
-       SHELL_ALIGN(sizeof(struct nbinary)),
-       SHELL_ALIGN(sizeof(struct nbinary)),
-       SHELL_ALIGN(sizeof(struct nbinary)),
-       SHELL_ALIGN(sizeof(struct nif)),
-       SHELL_ALIGN(sizeof(struct nbinary)),
-       SHELL_ALIGN(sizeof(struct nbinary)),
-       SHELL_ALIGN(sizeof(struct nfor)),
-       SHELL_ALIGN(sizeof(struct ncase)),
-       SHELL_ALIGN(sizeof(struct nclist)),
-       SHELL_ALIGN(sizeof(struct narg)),
-       SHELL_ALIGN(sizeof(struct narg)),
-       SHELL_ALIGN(sizeof(struct nfile)),
-       SHELL_ALIGN(sizeof(struct nfile)),
-       SHELL_ALIGN(sizeof(struct nfile)),
-       SHELL_ALIGN(sizeof(struct nfile)),
-       SHELL_ALIGN(sizeof(struct nfile)),
-       SHELL_ALIGN(sizeof(struct ndup)),
-       SHELL_ALIGN(sizeof(struct ndup)),
-       SHELL_ALIGN(sizeof(struct nhere)),
-       SHELL_ALIGN(sizeof(struct nhere)),
-       SHELL_ALIGN(sizeof(struct nnot)),
-};
+#if ENABLE_ASH_MATH_SUPPORT
+static arith_t dash_arith(const char *);
+static arith_t arith(const char *expr, int *perrcode);
+#endif
 
-static void calcsize(union node *);
-static void sizenodelist(struct nodelist *);
-static union node *copynode(union node *);
-static struct nodelist *copynodelist(struct nodelist *);
-static char *nodeckstrdup(char *);
+#if ENABLE_ASH_RANDOM_SUPPORT
+static unsigned long rseed;
+# ifndef DYNAMIC_VAR
+#  define DYNAMIC_VAR
+# endif
+#endif
 
-static int evalskip;                   /* set if we are skipping commands */
-static int skipcount;           /* number of levels to skip */
-static int funcnest;                   /* depth of function calls */
 
-/* reasons for skipping commands (see comment on breakcmd routine) */
-#define SKIPBREAK      (1 << 0)
-#define SKIPCONT       (1 << 1)
-#define SKIPFUNC       (1 << 2)
-#define SKIPFILE       (1 << 3)
-#define SKIPEVAL       (1 << 4)
+/*      jobs.h    */
 
+/* Mode argument to forkshell.  Don't change FORK_FG or FORK_BG. */
+#define FORK_FG 0
+#define FORK_BG 1
+#define FORK_NOJOB 2
 
-/*      exec.h    */
-
-/* values of cmdtype */
-#define CMDUNKNOWN      -1      /* no entry in table for command */
-#define CMDNORMAL       0       /* command is an executable program */
-#define CMDFUNCTION     1       /* command is a shell function */
-#define CMDBUILTIN      2       /* command is a shell builtin */
-
-struct builtincmd {
-       const char *name;
-       int (*builtin)(int, char **);
-       /* unsigned flags; */
-};
-
-struct cmdentry {
-       int cmdtype;
-       union param {
-               int index;
-               const struct builtincmd *cmd;
-               struct funcnode *func;
-       } u;
-};
-
-/* action to find_command() */
-#define DO_ERR          0x01    /* prints errors */
-#define DO_ABS          0x02    /* checks absolute paths */
-#define DO_NOFUNC       0x04    /* don't return shell functions, for command */
-#define DO_ALTPATH      0x08    /* using alternate path */
-#define DO_ALTBLTIN     0x20    /* %builtin in alt. path */
-
-static void shellexec(char **, const char *, int) ATTRIBUTE_NORETURN;
-static char *padvance(const char **, const char *);
-static void find_command(char *, struct cmdentry *, int, const char *);
-static struct builtincmd *find_builtin(const char *);
-static void defun(char *, union node *);
-static void unsetfunc(const char *);
-
-#if ENABLE_ASH_MATH_SUPPORT_64
-typedef int64_t arith_t;
-#define arith_t_type long long
-#else
-typedef long arith_t;
-#define arith_t_type long
-#endif
-
-#if ENABLE_ASH_MATH_SUPPORT
-static arith_t dash_arith(const char *);
-static arith_t arith(const char *expr, int *perrcode);
-#endif
-
-#if ENABLE_ASH_RANDOM_SUPPORT
-static unsigned long rseed;
-# ifndef DYNAMIC_VAR
-#  define DYNAMIC_VAR
-# endif
-#endif
-
-
-/*      jobs.h    */
-
-/* Mode argument to forkshell.  Don't change FORK_FG or FORK_BG. */
-#define FORK_FG 0
-#define FORK_BG 1
-#define FORK_NOJOB 2
-
-/* mode flags for showjob(s) */
-#define SHOW_PGID       0x01    /* only show pgid - for jobs -p */
-#define SHOW_PID        0x04    /* include process pid */
-#define SHOW_CHANGED    0x08    /* only jobs whose state has changed */
+/* mode flags for showjob(s) */
+#define SHOW_PGID       0x01    /* only show pgid - for jobs -p */
+#define SHOW_PID        0x04    /* include process pid */
+#define SHOW_CHANGED    0x08    /* only jobs whose state has changed */
 
 
 /*
@@ -3222,6 +3043,8 @@ static int is_safe_applet(char *name)
 #define ALIASINUSE 1
 #define ALIASDEAD  2
 
+#define ATABSIZE 39
+
 struct alias {
        struct alias *next;
        char *name;
@@ -4899,1314 +4722,1630 @@ casematch(union node *pattern, char *val)
 }
 
 
-/* ============ eval.c */
+/* ============ find_command */
 
-/* flags in argument to evaltree */
-#define EV_EXIT 01              /* exit after evaluating tree */
-#define EV_TESTED 02            /* exit status is checked; ignore -e flag */
-#define EV_BACKCMD 04           /* command executing within back quotes */
+struct builtincmd {
+       const char *name;
+       int (*builtin)(int, char **);
+       /* unsigned flags; */
+};
+#define IS_BUILTIN_SPECIAL(b) ((b)->name[0] & 1)
+#define IS_BUILTIN_REGULAR(b) ((b)->name[0] & 2)
+#define IS_BUILTIN_ASSIGN(b) ((b)->name[0] & 4)
 
-/* forward declarations - evaluation is fairly recursive business... */
-static void evalloop(union node *, int);
-static void evalfor(union node *, int);
-static void evalcase(union node *, int);
-static void evalsubshell(union node *, int);
-static void expredir(union node *);
-static void evalpipe(union node *, int);
-static void evalcommand(union node *, int);
-static int evalbltin(const struct builtincmd *, int, char **);
-static int evalfun(struct funcnode *, int, char **, int);
-static void prehash(union node *);
+struct cmdentry {
+       int cmdtype;
+       union param {
+               int index;
+               const struct builtincmd *cmd;
+               struct funcnode *func;
+       } u;
+};
+/* values of cmdtype */
+#define CMDUNKNOWN      -1      /* no entry in table for command */
+#define CMDNORMAL       0       /* command is an executable program */
+#define CMDFUNCTION     1       /* command is a shell function */
+#define CMDBUILTIN      2       /* command is a shell builtin */
+
+/* action to find_command() */
+#define DO_ERR          0x01    /* prints errors */
+#define DO_ABS          0x02    /* checks absolute paths */
+#define DO_NOFUNC       0x04    /* don't return shell functions, for command */
+#define DO_ALTPATH      0x08    /* using alternate path */
+#define DO_ALTBLTIN     0x20    /* %builtin in alt. path */
+
+static void find_command(char *, struct cmdentry *, int, const char *);
+
+
+/* ============ Hashing commands */
 
 /*
- * Evaluate a parse tree.  The value is left in the global variable
- * exitstatus.
+ * When commands are first encountered, they are entered in a hash table.
+ * This ensures that a full path search will not have to be done for them
+ * on each invocation.
+ *
+ * We should investigate converting to a linear search, even though that
+ * would make the command name "hash" a misnomer.
  */
-static void
-evaltree(union node *n, int flags)
-{
-       int checkexit = 0;
-       void (*evalfn)(union node *, int);
-       unsigned isor;
-       int status;
-       if (n == NULL) {
-               TRACE(("evaltree(NULL) called\n"));
-               goto out;
-       }
-       TRACE(("pid %d, evaltree(%p: %d, %d) called\n",
-                       getpid(), n, n->type, flags));
-       switch (n->type) {
-       default:
-#if DEBUG
-               out1fmt("Node type = %d\n", n->type);
-               fflush(stdout);
-               break;
-#endif
-       case NNOT:
-               evaltree(n->nnot.com, EV_TESTED);
-               status = !exitstatus;
-               goto setstatus;
-       case NREDIR:
-               expredir(n->nredir.redirect);
-               status = redirectsafe(n->nredir.redirect, REDIR_PUSH);
-               if (!status) {
-                       evaltree(n->nredir.n, flags & EV_TESTED);
-                       status = exitstatus;
-               }
-               popredir(0);
-               goto setstatus;
-       case NCMD:
-               evalfn = evalcommand;
- checkexit:
-               if (eflag && !(flags & EV_TESTED))
-                       checkexit = ~0;
-               goto calleval;
-       case NFOR:
-               evalfn = evalfor;
-               goto calleval;
-       case NWHILE:
-       case NUNTIL:
-               evalfn = evalloop;
-               goto calleval;
-       case NSUBSHELL:
-       case NBACKGND:
-               evalfn = evalsubshell;
-               goto calleval;
-       case NPIPE:
-               evalfn = evalpipe;
-               goto checkexit;
-       case NCASE:
-               evalfn = evalcase;
-               goto calleval;
-       case NAND:
-       case NOR:
-       case NSEMI:
-#if NAND + 1 != NOR
-#error NAND + 1 != NOR
-#endif
-#if NOR + 1 != NSEMI
-#error NOR + 1 != NSEMI
-#endif
-               isor = n->type - NAND;
-               evaltree(
-                       n->nbinary.ch1,
-                       (flags | ((isor >> 1) - 1)) & EV_TESTED
-               );
-               if (!exitstatus == isor)
-                       break;
-               if (!evalskip) {
-                       n = n->nbinary.ch2;
- evaln:
-                       evalfn = evaltree;
- calleval:
-                       evalfn(n, flags);
-                       break;
-               }
-               break;
-       case NIF:
-               evaltree(n->nif.test, EV_TESTED);
-               if (evalskip)
-                       break;
-               if (exitstatus == 0) {
-                       n = n->nif.ifpart;
-                       goto evaln;
-               } else if (n->nif.elsepart) {
-                       n = n->nif.elsepart;
-                       goto evaln;
-               }
-               goto success;
-       case NDEFUN:
-               defun(n->narg.text, n->narg.next);
- success:
-               status = 0;
- setstatus:
-               exitstatus = status;
-               break;
-       }
- out:
-       if ((checkexit & exitstatus))
-               evalskip |= SKIPEVAL;
-       else if (pendingsigs && dotrap())
-               goto exexit;
 
-       if (flags & EV_EXIT) {
- exexit:
-               raise_exception(EXEXIT);
-       }
-}
+#define CMDTABLESIZE 31         /* should be prime */
+#define ARB 1                   /* actual size determined at run time */
 
-#if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3)
-static
-#endif
-void evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__));
+struct tblentry {
+       struct tblentry *next;  /* next entry in hash chain */
+       union param param;      /* definition of builtin function */
+       short cmdtype;          /* index identifying command */
+       char rehash;            /* if set, cd done since entry created */
+       char cmdname[ARB];      /* name of command */
+};
 
-static int loopnest;            /* current loop nesting level */
+static struct tblentry *cmdtable[CMDTABLESIZE];
+static int builtinloc = -1;             /* index in path of %builtin, or -1 */
 
 static void
-evalloop(union node *n, int flags)
+tryexec(char *cmd, char **argv, char **envp)
 {
-       int status;
-
-       loopnest++;
-       status = 0;
-       flags &= EV_TESTED;
-       for (;;) {
-               int i;
+       int repeated = 0;
+       struct BB_applet *a;
+       int argc = 0;
+       char **c;
 
-               evaltree(n->nbinary.ch1, EV_TESTED);
-               if (evalskip) {
- skipping:
-                       if (evalskip == SKIPCONT && --skipcount <= 0) {
-                               evalskip = 0;
-                               continue;
-                       }
-                       if (evalskip == SKIPBREAK && --skipcount <= 0)
-                               evalskip = 0;
-                       break;
+       if (strchr(cmd, '/') == NULL
+        && (a = find_applet_by_name(cmd)) != NULL
+        && is_safe_applet(cmd)
+       ) {
+               c = argv;
+               while (*c != NULL) {
+                       c++; argc++;
                }
-               i = exitstatus;
-               if (n->type != NWHILE)
-                       i = !i;
-               if (i != 0)
-                       break;
-               evaltree(n->nbinary.ch2, flags);
-               status = exitstatus;
-               if (evalskip)
-                       goto skipping;
+               applet_name = cmd;
+               exit(a->main(argc, argv));
        }
-       loopnest--;
-       exitstatus = status;
-}
-
-static void
-evalfor(union node *n, int flags)
-{
-       struct arglist arglist;
-       union node *argp;
-       struct strlist *sp;
-       struct stackmark smark;
-
-       setstackmark(&smark);
-       arglist.lastp = &arglist.list;
-       for (argp = n->nfor.args; argp; argp = argp->narg.next) {
-               expandarg(argp, &arglist, EXP_FULL | EXP_TILDE | EXP_RECORD);
-               /* XXX */
-               if (evalskip)
-                       goto out;
+#if ENABLE_FEATURE_SH_STANDALONE_SHELL
+       if (find_applet_by_name(cmd) != NULL) {
+               /* re-exec ourselves with the new arguments */
+               execve(CONFIG_BUSYBOX_EXEC_PATH, argv, envp);
+               /* If they called chroot or otherwise made the binary no longer
+                * executable, fall through */
        }
-       *arglist.lastp = NULL;
+#endif
 
-       exitstatus = 0;
-       loopnest++;
-       flags &= EV_TESTED;
-       for (sp = arglist.list; sp; sp = sp->next) {
-               setvar(n->nfor.var, sp->text, 0);
-               evaltree(n->nfor.body, flags);
-               if (evalskip) {
-                       if (evalskip == SKIPCONT && --skipcount <= 0) {
-                               evalskip = 0;
-                               continue;
-                       }
-                       if (evalskip == SKIPBREAK && --skipcount <= 0)
-                               evalskip = 0;
-                       break;
-               }
+ repeat:
+#ifdef SYSV
+       do {
+               execve(cmd, argv, envp);
+       } while (errno == EINTR);
+#else
+       execve(cmd, argv, envp);
+#endif
+       if (repeated++) {
+               free(argv);
+       } else if (errno == ENOEXEC) {
+               char **ap;
+               char **new;
+
+               for (ap = argv; *ap; ap++)
+                       ;
+               ap = new = ckmalloc((ap - argv + 2) * sizeof(char *));
+               ap[1] = cmd;
+               *ap = cmd = (char *)DEFAULT_SHELL;
+               ap += 2;
+               argv++;
+               while ((*ap++ = *argv++))
+                       ;
+               argv = new;
+               goto repeat;
        }
-       loopnest--;
- out:
-       popstackmark(&smark);
 }
 
+/*
+ * Exec a program.  Never returns.  If you change this routine, you may
+ * have to change the find_command routine as well.
+ */
+#define environment() listvars(VEXPORT, VUNSET, 0)
+static void shellexec(char **, const char *, int) ATTRIBUTE_NORETURN;
 static void
-evalcase(union node *n, int flags)
+shellexec(char **argv, const char *path, int idx)
 {
-       union node *cp;
-       union node *patp;
-       struct arglist arglist;
-       struct stackmark smark;
+       char *cmdname;
+       int e;
+       char **envp;
+       int exerrno;
 
-       setstackmark(&smark);
-       arglist.lastp = &arglist.list;
-       expandarg(n->ncase.expr, &arglist, EXP_TILDE);
-       exitstatus = 0;
-       for (cp = n->ncase.cases; cp && evalskip == 0; cp = cp->nclist.next) {
-               for (patp = cp->nclist.pattern; patp; patp = patp->narg.next) {
-                       if (casematch(patp, arglist.list->text)) {
-                               if (evalskip == 0) {
-                                       evaltree(cp->nclist.body, flags);
-                               }
-                               goto out;
+       clearredir(1);
+       envp = environment();
+       if (strchr(argv[0], '/') || is_safe_applet(argv[0])
+#if ENABLE_FEATURE_SH_STANDALONE_SHELL
+        || find_applet_by_name(argv[0])
+#endif
+       ) {
+               tryexec(argv[0], argv, envp);
+               e = errno;
+       } else {
+               e = ENOENT;
+               while ((cmdname = padvance(&path, argv[0])) != NULL) {
+                       if (--idx < 0 && pathopt == NULL) {
+                               tryexec(cmdname, argv, envp);
+                               if (errno != ENOENT && errno != ENOTDIR)
+                                       e = errno;
                        }
+                       stunalloc(cmdname);
                }
        }
- out:
-       popstackmark(&smark);
+
+       /* Map to POSIX errors */
+       switch (e) {
+       case EACCES:
+               exerrno = 126;
+               break;
+       case ENOENT:
+               exerrno = 127;
+               break;
+       default:
+               exerrno = 2;
+               break;
+       }
+       exitstatus = exerrno;
+       TRACE(("shellexec failed for %s, errno %d, suppressint %d\n",
+               argv[0], e, suppressint ));
+       ash_msg_and_raise(EXEXEC, "%s: %s", argv[0], errmsg(e, "not found"));
+       /* NOTREACHED */
 }
 
-/*
- * Kick off a subshell to evaluate a tree.
- */
 static void
-evalsubshell(union node *n, int flags)
+printentry(struct tblentry *cmdp)
 {
-       struct job *jp;
-       int backgnd = (n->type == NBACKGND);
-       int status;
+       int idx;
+       const char *path;
+       char *name;
 
-       expredir(n->nredir.redirect);
-       if (!backgnd && flags & EV_EXIT && !trap[0])
-               goto nofork;
-       INT_OFF;
-       jp = makejob(n, 1);
-       if (forkshell(jp, n, backgnd) == 0) {
-               INT_ON;
-               flags |= EV_EXIT;
-               if (backgnd)
-                       flags &=~ EV_TESTED;
- nofork:
-               redirect(n->nredir.redirect, 0);
-               evaltreenr(n->nredir.n, flags);
-               /* never returns */
-       }
-       status = 0;
-       if (! backgnd)
-               status = waitforjob(jp);
-       exitstatus = status;
-       INT_ON;
+       idx = cmdp->param.index;
+       path = pathval();
+       do {
+               name = padvance(&path, cmdp->cmdname);
+               stunalloc(name);
+       } while (--idx >= 0);
+       out1fmt("%s%s\n", name, (cmdp->rehash ? "*" : nullstr));
 }
 
 /*
- * Compute the names of the files in a redirection list.
+ * Clear out command entries.  The argument specifies the first entry in
+ * PATH which has changed.
  */
 static void
-expredir(union node *n)
+clearcmdentry(int firstchange)
 {
-       union node *redir;
-
-       for (redir = n; redir; redir = redir->nfile.next) {
-               struct arglist fn;
+       struct tblentry **tblp;
+       struct tblentry **pp;
+       struct tblentry *cmdp;
 
-               memset(&fn, 0, sizeof(fn));
-               fn.lastp = &fn.list;
-               switch (redir->type) {
-               case NFROMTO:
-               case NFROM:
-               case NTO:
-               case NCLOBBER:
-               case NAPPEND:
-                       expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR);
-                       redir->nfile.expfname = fn.list->text;
-                       break;
-               case NFROMFD:
-               case NTOFD:
-                       if (redir->ndup.vname) {
-                               expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE);
-                               if (fn.list == NULL)
-                                       ash_msg_and_raise_error("redir error");
-                               fixredir(redir, fn.list->text, 1);
+       INT_OFF;
+       for (tblp = cmdtable; tblp < &cmdtable[CMDTABLESIZE]; tblp++) {
+               pp = tblp;
+               while ((cmdp = *pp) != NULL) {
+                       if ((cmdp->cmdtype == CMDNORMAL &&
+                            cmdp->param.index >= firstchange)
+                        || (cmdp->cmdtype == CMDBUILTIN &&
+                            builtinloc >= firstchange)
+                       ) {
+                               *pp = cmdp->next;
+                               free(cmdp);
+                       } else {
+                               pp = &cmdp->next;
                        }
-                       break;
                }
        }
+       INT_ON;
 }
 
 /*
- * Evaluate a pipeline.  All the processes in the pipeline are children
- * of the process creating the pipeline.  (This differs from some versions
- * of the shell, which make the last process in a pipeline the parent
- * of all the rest.)
+ * Locate a command in the command hash table.  If "add" is nonzero,
+ * add the command to the table if it is not already present.  The
+ * variable "lastcmdentry" is set to point to the address of the link
+ * pointing to the entry, so that delete_cmd_entry can delete the
+ * entry.
+ *
+ * Interrupts must be off if called with add != 0.
  */
-static void
-evalpipe(union node *n, int flags)
+static struct tblentry **lastcmdentry;
+
+static struct tblentry *
+cmdlookup(const char *name, int add)
 {
-       struct job *jp;
-       struct nodelist *lp;
-       int pipelen;
-       int prevfd;
-       int pip[2];
+       unsigned int hashval;
+       const char *p;
+       struct tblentry *cmdp;
+       struct tblentry **pp;
+
+       p = name;
+       hashval = (unsigned char)*p << 4;
+       while (*p)
+               hashval += (unsigned char)*p++;
+       hashval &= 0x7FFF;
+       pp = &cmdtable[hashval % CMDTABLESIZE];
+       for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
+               if (strcmp(cmdp->cmdname, name) == 0)
+                       break;
+               pp = &cmdp->next;
+       }
+       if (add && cmdp == NULL) {
+               cmdp = *pp = ckmalloc(sizeof(struct tblentry) - ARB
+                                       + strlen(name) + 1);
+               cmdp->next = NULL;
+               cmdp->cmdtype = CMDUNKNOWN;
+               strcpy(cmdp->cmdname, name);
+       }
+       lastcmdentry = pp;
+       return cmdp;
+}
+
+/*
+ * Delete the command entry returned on the last lookup.
+ */
+static void
+delete_cmd_entry(void)
+{
+       struct tblentry *cmdp;
 
-       TRACE(("evalpipe(0x%lx) called\n", (long)n));
-       pipelen = 0;
-       for (lp = n->npipe.cmdlist; lp; lp = lp->next)
-               pipelen++;
-       flags |= EV_EXIT;
        INT_OFF;
-       jp = makejob(n, pipelen);
-       prevfd = -1;
-       for (lp = n->npipe.cmdlist; lp; lp = lp->next) {
-               prehash(lp->n);
-               pip[1] = -1;
-               if (lp->next) {
-                       if (pipe(pip) < 0) {
-                               close(prevfd);
-                               ash_msg_and_raise_error("Pipe call failed");
-                       }
-               }
-               if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) {
-                       INT_ON;
-                       if (pip[1] >= 0) {
-                               close(pip[0]);
-                       }
-                       if (prevfd > 0) {
-                               dup2(prevfd, 0);
-                               close(prevfd);
-                       }
-                       if (pip[1] > 1) {
-                               dup2(pip[1], 1);
-                               close(pip[1]);
+       cmdp = *lastcmdentry;
+       *lastcmdentry = cmdp->next;
+       if (cmdp->cmdtype == CMDFUNCTION)
+               freefunc(cmdp->param.func);
+       free(cmdp);
+       INT_ON;
+}
+
+/*
+ * Add a new command entry, replacing any existing command entry for
+ * the same name - except special builtins.
+ */
+static void
+addcmdentry(char *name, struct cmdentry *entry)
+{
+       struct tblentry *cmdp;
+
+       cmdp = cmdlookup(name, 1);
+       if (cmdp->cmdtype == CMDFUNCTION) {
+               freefunc(cmdp->param.func);
+       }
+       cmdp->cmdtype = entry->cmdtype;
+       cmdp->param = entry->u;
+       cmdp->rehash = 0;
+}
+
+static int
+hashcmd(int argc, char **argv)
+{
+       struct tblentry **pp;
+       struct tblentry *cmdp;
+       int c;
+       struct cmdentry entry;
+       char *name;
+
+       while ((c = nextopt("r")) != '\0') {
+               clearcmdentry(0);
+               return 0;
+       }
+       if (*argptr == NULL) {
+               for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) {
+                       for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
+                               if (cmdp->cmdtype == CMDNORMAL)
+                                       printentry(cmdp);
                        }
-                       evaltreenr(lp->n, flags);
-                       /* never returns */
                }
-               if (prevfd >= 0)
-                       close(prevfd);
-               prevfd = pip[0];
-               close(pip[1]);
+               return 0;
        }
-       if (n->npipe.backgnd == 0) {
-               exitstatus = waitforjob(jp);
-               TRACE(("evalpipe:  job done exit status %d\n", exitstatus));
+       c = 0;
+       while ((name = *argptr) != NULL) {
+               cmdp = cmdlookup(name, 0);
+               if (cmdp != NULL
+                && (cmdp->cmdtype == CMDNORMAL
+                    || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0)))
+                       delete_cmd_entry();
+               find_command(name, &entry, DO_ERR, pathval());
+               if (entry.cmdtype == CMDUNKNOWN)
+                       c = 1;
+               argptr++;
        }
-       INT_ON;
+       return c;
 }
 
-#if ENABLE_ASH_CMDCMD
-static char **
-parse_command_args(char **argv, const char **path)
+/*
+ * Called when a cd is done.  Marks all commands so the next time they
+ * are executed they will be rehashed.
+ */
+static void
+hashcd(void)
 {
-       char *cp, c;
+       struct tblentry **pp;
+       struct tblentry *cmdp;
+
+       for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) {
+               for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
+                       if (cmdp->cmdtype == CMDNORMAL || (
+                               cmdp->cmdtype == CMDBUILTIN &&
+                               !(IS_BUILTIN_REGULAR(cmdp->param.cmd)) &&
+                               builtinloc > 0
+                       ))
+                               cmdp->rehash = 1;
+               }
+       }
+}
+
+/*
+ * Fix command hash table when PATH changed.
+ * Called before PATH is changed.  The argument is the new value of PATH;
+ * pathval() still returns the old value at this point.
+ * Called with interrupts off.
+ */
+static void
+changepath(const char *newval)
+{
+       const char *old, *new;
+       int idx;
+       int firstchange;
+       int idx_bltin;
 
+       old = pathval();
+       new = newval;
+       firstchange = 9999;     /* assume no change */
+       idx = 0;
+       idx_bltin = -1;
        for (;;) {
-               cp = *++argv;
-               if (!cp)
-                       return 0;
-               if (*cp++ != '-')
-                       break;
-               c = *cp++;
-               if (!c)
-                       break;
-               if (c == '-' && !*cp) {
-                       argv++;
+               if (*old != *new) {
+                       firstchange = idx;
+                       if ((*old == '\0' && *new == ':')
+                        || (*old == ':' && *new == '\0'))
+                               firstchange++;
+                       old = new;      /* ignore subsequent differences */
+               }
+               if (*new == '\0')
                        break;
+               if (*new == '%' && idx_bltin < 0 && prefix(new + 1, "builtin"))
+                       idx_bltin = idx;
+               if (*new == ':') {
+                       idx++;
                }
-               do {
-                       switch (c) {
-                       case 'p':
-                               *path = defpath;
-                               break;
-                       default:
-                               /* run 'typecmd' for other options */
-                               return 0;
-                       }
-                       c = *cp++;
-               } while (c);
+               new++, old++;
        }
-       return argv;
+       if (builtinloc < 0 && idx_bltin >= 0)
+               builtinloc = idx_bltin;             /* zap builtins */
+       if (builtinloc >= 0 && idx_bltin < 0)
+               firstchange = 0;
+       clearcmdentry(firstchange);
+       builtinloc = idx_bltin;
 }
-#endif
 
-/* Forward declarations for builtintab[] */
-#if JOBS
-static int fg_bgcmd(int, char **);
-#endif
-static int breakcmd(int, char **);
-#if ENABLE_ASH_CMDCMD
-static int commandcmd(int, char **);
-#endif
-static int dotcmd(int, char **);
-static int evalcmd(int, char **);
-#if ENABLE_ASH_BUILTIN_ECHO
-static int echocmd(int, char **);
-#endif
-#if ENABLE_ASH_BUILTIN_TEST
-static int testcmd(int, char **);
-#endif
-static int execcmd(int, char **);
-static int exitcmd(int, char **);
-static int exportcmd(int, char **);
-static int falsecmd(int, char **);
-#if ENABLE_ASH_GETOPTS
-static int getoptscmd(int, char **);
-#endif
-static int hashcmd(int, char **);
-#if !ENABLE_FEATURE_SH_EXTRA_QUIET
-static int helpcmd(int argc, char **argv);
-#endif
-#if JOBS
-static int jobscmd(int, char **);
-#endif
-#if ENABLE_ASH_MATH_SUPPORT
-static int letcmd(int, char **);
-#endif
-static int localcmd(int, char **);
-static int pwdcmd(int, char **);
-static int readcmd(int, char **);
-static int returncmd(int, char **);
-static int setcmd(int, char **);
-static int shiftcmd(int, char **);
-static int timescmd(int, char **);
-static int trapcmd(int, char **);
-static int truecmd(int, char **);
-static int typecmd(int, char **);
-static int umaskcmd(int, char **);
-static int unsetcmd(int, char **);
-static int waitcmd(int, char **);
-static int ulimitcmd(int, char **);
-#if JOBS
-static int killcmd(int, char **);
-#endif
-
-#define BUILTIN_NOSPEC  "0"
-#define BUILTIN_SPECIAL "1"
-#define BUILTIN_REGULAR "2"
-#define BUILTIN_SPEC_REG "3"
-#define BUILTIN_ASSIGN  "4"
-#define BUILTIN_SPEC_ASSG  "5"
-#define BUILTIN_REG_ASSG   "6"
-#define BUILTIN_SPEC_REG_ASSG   "7"
-
-#define IS_BUILTIN_SPECIAL(b) ((b)->name[0] & 1)
-#define IS_BUILTIN_REGULAR(b) ((b)->name[0] & 2)
-#define IS_BUILTIN_ASSIGN(b) ((b)->name[0] & 4)
+#define TEOF 0
+#define TNL 1
+#define TREDIR 2
+#define TWORD 3
+#define TSEMI 4
+#define TBACKGND 5
+#define TAND 6
+#define TOR 7
+#define TPIPE 8
+#define TLP 9
+#define TRP 10
+#define TENDCASE 11
+#define TENDBQUOTE 12
+#define TNOT 13
+#define TCASE 14
+#define TDO 15
+#define TDONE 16
+#define TELIF 17
+#define TELSE 18
+#define TESAC 19
+#define TFI 20
+#define TFOR 21
+#define TIF 22
+#define TIN 23
+#define TTHEN 24
+#define TUNTIL 25
+#define TWHILE 26
+#define TBEGIN 27
+#define TEND 28
 
-/* make sure to keep these in proper order since it is searched via bsearch() */
-static const struct builtincmd builtintab[] = {
-       { BUILTIN_SPEC_REG      ".", dotcmd },
-       { BUILTIN_SPEC_REG      ":", truecmd },
-#if ENABLE_ASH_BUILTIN_TEST
-       { BUILTIN_REGULAR       "[", testcmd },
-       { BUILTIN_REGULAR       "[[", testcmd },
-#endif
-#if ENABLE_ASH_ALIAS
-       { BUILTIN_REG_ASSG      "alias", aliascmd },
-#endif
-#if JOBS
-       { BUILTIN_REGULAR       "bg", fg_bgcmd },
-#endif
-       { BUILTIN_SPEC_REG      "break", breakcmd },
-       { BUILTIN_REGULAR       "cd", cdcmd },
-       { BUILTIN_NOSPEC        "chdir", cdcmd },
-#if ENABLE_ASH_CMDCMD
-       { BUILTIN_REGULAR       "command", commandcmd },
-#endif
-       { BUILTIN_SPEC_REG      "continue", breakcmd },
-#if ENABLE_ASH_BUILTIN_ECHO
-       { BUILTIN_REGULAR       "echo", echocmd },
-#endif
-       { BUILTIN_SPEC_REG      "eval", evalcmd },
-       { BUILTIN_SPEC_REG      "exec", execcmd },
-       { BUILTIN_SPEC_REG      "exit", exitcmd },
-       { BUILTIN_SPEC_REG_ASSG "export", exportcmd },
-       { BUILTIN_REGULAR       "false", falsecmd },
-#if JOBS
-       { BUILTIN_REGULAR       "fg", fg_bgcmd },
-#endif
-#if ENABLE_ASH_GETOPTS
-       { BUILTIN_REGULAR       "getopts", getoptscmd },
-#endif
-       { BUILTIN_NOSPEC        "hash", hashcmd },
-#if !ENABLE_FEATURE_SH_EXTRA_QUIET
-       { BUILTIN_NOSPEC        "help", helpcmd },
-#endif
-#if JOBS
-       { BUILTIN_REGULAR       "jobs", jobscmd },
-       { BUILTIN_REGULAR       "kill", killcmd },
-#endif
-#if ENABLE_ASH_MATH_SUPPORT
-       { BUILTIN_NOSPEC        "let", letcmd },
-#endif
-       { BUILTIN_ASSIGN        "local", localcmd },
-       { BUILTIN_NOSPEC        "pwd", pwdcmd },
-       { BUILTIN_REGULAR       "read", readcmd },
-       { BUILTIN_SPEC_REG_ASSG "readonly", exportcmd },
-       { BUILTIN_SPEC_REG      "return", returncmd },
-       { BUILTIN_SPEC_REG      "set", setcmd },
-       { BUILTIN_SPEC_REG      "shift", shiftcmd },
-       { BUILTIN_SPEC_REG      "source", dotcmd },
-#if ENABLE_ASH_BUILTIN_TEST
-       { BUILTIN_REGULAR       "test", testcmd },
-#endif
-       { BUILTIN_SPEC_REG      "times", timescmd },
-       { BUILTIN_SPEC_REG      "trap", trapcmd },
-       { BUILTIN_REGULAR       "true", truecmd },
-       { BUILTIN_NOSPEC        "type", typecmd },
-       { BUILTIN_NOSPEC        "ulimit", ulimitcmd },
-       { BUILTIN_REGULAR       "umask", umaskcmd },
-#if ENABLE_ASH_ALIAS
-       { BUILTIN_REGULAR       "unalias", unaliascmd },
-#endif
-       { BUILTIN_SPEC_REG      "unset", unsetcmd },
-       { BUILTIN_REGULAR       "wait", waitcmd },
+/* first char is indicating which tokens mark the end of a list */
+static const char *const tokname_array[] = {
+       "\1end of file",
+       "\0newline",
+       "\0redirection",
+       "\0word",
+       "\0;",
+       "\0&",
+       "\0&&",
+       "\0||",
+       "\0|",
+       "\0(",
+       "\1)",
+       "\1;;",
+       "\1`",
+#define KWDOFFSET 13
+       /* the following are keywords */
+       "\0!",
+       "\0case",
+       "\1do",
+       "\1done",
+       "\1elif",
+       "\1else",
+       "\1esac",
+       "\1fi",
+       "\0for",
+       "\0if",
+       "\0in",
+       "\1then",
+       "\0until",
+       "\0while",
+       "\0{",
+       "\1}",
 };
 
-#define NUMBUILTINS (sizeof(builtintab) / sizeof(builtintab[0]))
+static const char *
+tokname(int tok)
+{
+       static char buf[16];
 
-#define COMMANDCMD (builtintab + 5 + \
-       2 * ENABLE_ASH_BUILTIN_TEST + \
-       ENABLE_ASH_ALIAS + \
-       ENABLE_ASH_JOB_CONTROL)
-#define EXECCMD (builtintab + 7 + \
-       2 * ENABLE_ASH_BUILTIN_TEST + \
-       ENABLE_ASH_ALIAS + \
-       ENABLE_ASH_JOB_CONTROL + \
-       ENABLE_ASH_CMDCMD + \
-       ENABLE_ASH_BUILTIN_ECHO)
+       if (tok >= TSEMI)
+               buf[0] = '"';
+       sprintf(buf + (tok >= TSEMI), "%s%c",
+                       tokname_array[tok] + 1, (tok >= TSEMI ? '"' : 0));
+       return buf;
+}
 
-/*
- * Execute a simple command.
- */
-static int back_exitstatus; /* exit status of backquoted command */
+/* Wrapper around strcmp for qsort/bsearch/... */
 static int
-isassignment(const char *p)
+pstrcmp(const void *a, const void *b)
 {
-       const char *q = endofname(p);
-       if (p == q)
-               return 0;
-       return *q == '=';
+       return strcmp((const char *) a, (*(const char *const *) b) + 1);
 }
-static int
-bltincmd(int argc, char **argv)
+
+static const char *const *
+findkwd(const char *s)
 {
-       /* Preserve exitstatus of a previous possible redirection
-        * as POSIX mandates */
-       return back_exitstatus;
+       return bsearch(s, tokname_array + KWDOFFSET,
+                       (sizeof(tokname_array) / sizeof(const char *)) - KWDOFFSET,
+                       sizeof(const char *), pstrcmp);
 }
-static void
-evalcommand(union node *cmd, int flags)
-{
-       static const struct builtincmd bltin = {
-               "\0\0", bltincmd
-       };
-       struct stackmark smark;
-       union node *argp;
-       struct arglist arglist;
-       struct arglist varlist;
-       char **argv;
-       int argc;
-       const struct strlist *sp;
-       struct cmdentry cmdentry;
-       struct job *jp;
-       char *lastarg;
-       const char *path;
-       int spclbltin;
-       int cmd_is_exec;
-       int status;
-       char **nargv;
-       struct builtincmd *bcmd;
-       int pseudovarflag = 0;
 
-       /* First expand the arguments. */
-       TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
-       setstackmark(&smark);
-       back_exitstatus = 0;
-
-       cmdentry.cmdtype = CMDBUILTIN;
-       cmdentry.u.cmd = &bltin;
-       varlist.lastp = &varlist.list;
-       *varlist.lastp = NULL;
-       arglist.lastp = &arglist.list;
-       *arglist.lastp = NULL;
+/*
+ * Locate and print what a word is...
+ */
+#if ENABLE_ASH_CMDCMD
+static int
+describe_command(char *command, int describe_command_verbose)
+#else
+#define describe_command_verbose 1
+static int
+describe_command(char *command)
+#endif
+{
+       struct cmdentry entry;
+       struct tblentry *cmdp;
+#if ENABLE_ASH_ALIAS
+       const struct alias *ap;
+#endif
+       const char *path = pathval();
 
-       argc = 0;
-       if (cmd->ncmd.args) {
-               bcmd = find_builtin(cmd->ncmd.args->narg.text);
-               pseudovarflag = bcmd && IS_BUILTIN_ASSIGN(bcmd);
+       if (describe_command_verbose) {
+               out1str(command);
        }
 
-       for (argp = cmd->ncmd.args; argp; argp = argp->narg.next) {
-               struct strlist **spp;
-
-               spp = arglist.lastp;
-               if (pseudovarflag && isassignment(argp->narg.text))
-                       expandarg(argp, &arglist, EXP_VARTILDE);
-               else
-                       expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
+       /* First look at the keywords */
+       if (findkwd(command)) {
+               out1str(describe_command_verbose ? " is a shell keyword" : command);
+               goto out;
+       }
 
-               for (sp = *spp; sp; sp = sp->next)
-                       argc++;
+#if ENABLE_ASH_ALIAS
+       /* Then look at the aliases */
+       ap = lookupalias(command, 0);
+       if (ap != NULL) {
+               if (describe_command_verbose) {
+                       out1fmt(" is an alias for %s", ap->val);
+               } else {
+                       out1str("alias ");
+                       printalias(ap);
+                       return 0;
+               }
+               goto out;
+       }
+#endif
+       /* Then check if it is a tracked alias */
+       cmdp = cmdlookup(command, 0);
+       if (cmdp != NULL) {
+               entry.cmdtype = cmdp->cmdtype;
+               entry.u = cmdp->param;
+       } else {
+               /* Finally use brute force */
+               find_command(command, &entry, DO_ABS, path);
        }
 
-       argv = nargv = stalloc(sizeof(char *) * (argc + 1));
-       for (sp = arglist.list; sp; sp = sp->next) {
-               TRACE(("evalcommand arg: %s\n", sp->text));
-               *nargv++ = sp->text;
+       switch (entry.cmdtype) {
+       case CMDNORMAL: {
+               int j = entry.u.index;
+               char *p;
+               if (j == -1) {
+                       p = command;
+               } else {
+                       do {
+                               p = padvance(&path, command);
+                               stunalloc(p);
+                       } while (--j >= 0);
+               }
+               if (describe_command_verbose) {
+                       out1fmt(" is%s %s",
+                               (cmdp ? " a tracked alias for" : nullstr), p
+                       );
+               } else {
+                       out1str(p);
+               }
+               break;
        }
-       *nargv = NULL;
 
-       lastarg = NULL;
-       if (iflag && funcnest == 0 && argc > 0)
-               lastarg = nargv[-1];
+       case CMDFUNCTION:
+               if (describe_command_verbose) {
+                       out1str(" is a shell function");
+               } else {
+                       out1str(command);
+               }
+               break;
 
-       preverrout_fd = 2;
-       expredir(cmd->ncmd.redirect);
-       status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH|REDIR_SAVEFD2);
+       case CMDBUILTIN:
+               if (describe_command_verbose) {
+                       out1fmt(" is a %sshell builtin",
+                               IS_BUILTIN_SPECIAL(entry.u.cmd) ?
+                                       "special " : nullstr
+                       );
+               } else {
+                       out1str(command);
+               }
+               break;
 
-       path = vpath.text;
-       for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) {
-               struct strlist **spp;
-               char *p;
+       default:
+               if (describe_command_verbose) {
+                       out1str(": not found\n");
+               }
+               return 127;
+       }
+ out:
+       outstr("\n", stdout);
+       return 0;
+}
 
-               spp = varlist.lastp;
-               expandarg(argp, &varlist, EXP_VARTILDE);
+static int
+typecmd(int argc, char **argv)
+{
+       int i;
+       int err = 0;
 
-               /*
-                * Modify the command lookup path, if a PATH= assignment
-                * is present
-                */
-               p = (*spp)->text;
-               if (varequal(p, path))
-                       path = p;
+       for (i = 1; i < argc; i++) {
+#if ENABLE_ASH_CMDCMD
+               err |= describe_command(argv[i], 1);
+#else
+               err |= describe_command(argv[i]);
+#endif
        }
+       return err;
+}
 
-       /* Print the command if xflag is set. */
-       if (xflag) {
-               int n;
-               const char *p = " %s";
+#if ENABLE_ASH_CMDCMD
+static int
+commandcmd(int argc, char **argv)
+{
+       int c;
+       enum {
+               VERIFY_BRIEF = 1,
+               VERIFY_VERBOSE = 2,
+       } verify = 0;
 
-               p++;
-               dprintf(preverrout_fd, p, expandstr(ps4val()));
+       while ((c = nextopt("pvV")) != '\0')
+               if (c == 'V')
+                       verify |= VERIFY_VERBOSE;
+               else if (c == 'v')
+                       verify |= VERIFY_BRIEF;
+#if DEBUG
+               else if (c != 'p')
+                       abort();
+#endif
+       if (verify)
+               return describe_command(*argptr, verify - VERIFY_BRIEF);
 
-               sp = varlist.list;
-               for (n = 0; n < 2; n++) {
-                       while (sp) {
-                               dprintf(preverrout_fd, p, sp->text);
-                               sp = sp->next;
-                               if (*p == '%') {
-                                       p--;
-                               }
-                       }
-                       sp = arglist.list;
-               }
-               full_write(preverrout_fd, "\n", 1);
-       }
+       return 0;
+}
+#endif
 
-       cmd_is_exec = 0;
-       spclbltin = -1;
 
-       /* Now locate the command. */
-       if (argc) {
-               const char *oldpath;
-               int cmd_flag = DO_ERR;
+/* ============ eval.c */
 
-               path += 5;
-               oldpath = path;
-               for (;;) {
-                       find_command(argv[0], &cmdentry, cmd_flag, path);
-                       if (cmdentry.cmdtype == CMDUNKNOWN) {
-                               status = 127;
-                               flush_stderr();
-                               goto bail;
-                       }
+static int funcblocksize;          /* size of structures in function */
+static int funcstringsize;         /* size of strings in node */
+static void *funcblock;            /* block to allocate function from */
+static char *funcstring;           /* block to allocate strings from */
 
-                       /* implement bltin and command here */
-                       if (cmdentry.cmdtype != CMDBUILTIN)
-                               break;
-                       if (spclbltin < 0)
-                               spclbltin = IS_BUILTIN_SPECIAL(cmdentry.u.cmd);
-                       if (cmdentry.u.cmd == EXECCMD)
-                               cmd_is_exec++;
-#if ENABLE_ASH_CMDCMD
-                       if (cmdentry.u.cmd == COMMANDCMD) {
-                               path = oldpath;
-                               nargv = parse_command_args(argv, &path);
-                               if (!nargv)
-                                       break;
-                               argc -= nargv - argv;
-                               argv = nargv;
-                               cmd_flag |= DO_NOFUNC;
-                       } else
-#endif
-                               break;
-               }
-       }
+/* flags in argument to evaltree */
+#define EV_EXIT 01              /* exit after evaluating tree */
+#define EV_TESTED 02            /* exit status is checked; ignore -e flag */
+#define EV_BACKCMD 04           /* command executing within back quotes */
+
+static const short nodesize[26] = {
+       SHELL_ALIGN(sizeof(struct ncmd)),
+       SHELL_ALIGN(sizeof(struct npipe)),
+       SHELL_ALIGN(sizeof(struct nredir)),
+       SHELL_ALIGN(sizeof(struct nredir)),
+       SHELL_ALIGN(sizeof(struct nredir)),
+       SHELL_ALIGN(sizeof(struct nbinary)),
+       SHELL_ALIGN(sizeof(struct nbinary)),
+       SHELL_ALIGN(sizeof(struct nbinary)),
+       SHELL_ALIGN(sizeof(struct nif)),
+       SHELL_ALIGN(sizeof(struct nbinary)),
+       SHELL_ALIGN(sizeof(struct nbinary)),
+       SHELL_ALIGN(sizeof(struct nfor)),
+       SHELL_ALIGN(sizeof(struct ncase)),
+       SHELL_ALIGN(sizeof(struct nclist)),
+       SHELL_ALIGN(sizeof(struct narg)),
+       SHELL_ALIGN(sizeof(struct narg)),
+       SHELL_ALIGN(sizeof(struct nfile)),
+       SHELL_ALIGN(sizeof(struct nfile)),
+       SHELL_ALIGN(sizeof(struct nfile)),
+       SHELL_ALIGN(sizeof(struct nfile)),
+       SHELL_ALIGN(sizeof(struct nfile)),
+       SHELL_ALIGN(sizeof(struct ndup)),
+       SHELL_ALIGN(sizeof(struct ndup)),
+       SHELL_ALIGN(sizeof(struct nhere)),
+       SHELL_ALIGN(sizeof(struct nhere)),
+       SHELL_ALIGN(sizeof(struct nnot)),
+};
 
-       if (status) {
-               /* We have a redirection error. */
-               if (spclbltin > 0)
-                       raise_exception(EXERROR);
- bail:
-               exitstatus = status;
-               goto out;
+static void calcsize(union node *n);
+
+static void
+sizenodelist(struct nodelist *lp)
+{
+       while (lp) {
+               funcblocksize += SHELL_ALIGN(sizeof(struct nodelist));
+               calcsize(lp->n);
+               lp = lp->next;
        }
+}
 
-       /* Execute the command. */
-       switch (cmdentry.cmdtype) {
-       default:
-               /* Fork off a child process if necessary. */
-               if (!(flags & EV_EXIT) || trap[0]) {
-                       INT_OFF;
-                       jp = makejob(cmd, 1);
-                       if (forkshell(jp, cmd, FORK_FG) != 0) {
-                               exitstatus = waitforjob(jp);
-                               INT_ON;
-                               break;
-                       }
-                       FORCE_INT_ON;
-               }
-               listsetvar(varlist.list, VEXPORT|VSTACK);
-               shellexec(argv, path, cmdentry.u.index);
-               /* NOTREACHED */
+static void
+calcsize(union node *n)
+{
+       if (n == NULL)
+               return;
+       funcblocksize += nodesize[n->type];
+       switch (n->type) {
+       case NCMD:
+               calcsize(n->ncmd.redirect);
+               calcsize(n->ncmd.args);
+               calcsize(n->ncmd.assign);
+               break;
+       case NPIPE:
+               sizenodelist(n->npipe.cmdlist);
+               break;
+       case NREDIR:
+       case NBACKGND:
+       case NSUBSHELL:
+               calcsize(n->nredir.redirect);
+               calcsize(n->nredir.n);
+               break;
+       case NAND:
+       case NOR:
+       case NSEMI:
+       case NWHILE:
+       case NUNTIL:
+               calcsize(n->nbinary.ch2);
+               calcsize(n->nbinary.ch1);
+               break;
+       case NIF:
+               calcsize(n->nif.elsepart);
+               calcsize(n->nif.ifpart);
+               calcsize(n->nif.test);
+               break;
+       case NFOR:
+               funcstringsize += strlen(n->nfor.var) + 1;
+               calcsize(n->nfor.body);
+               calcsize(n->nfor.args);
+               break;
+       case NCASE:
+               calcsize(n->ncase.cases);
+               calcsize(n->ncase.expr);
+               break;
+       case NCLIST:
+               calcsize(n->nclist.body);
+               calcsize(n->nclist.pattern);
+               calcsize(n->nclist.next);
+               break;
+       case NDEFUN:
+       case NARG:
+               sizenodelist(n->narg.backquote);
+               funcstringsize += strlen(n->narg.text) + 1;
+               calcsize(n->narg.next);
+               break;
+       case NTO:
+       case NCLOBBER:
+       case NFROM:
+       case NFROMTO:
+       case NAPPEND:
+               calcsize(n->nfile.fname);
+               calcsize(n->nfile.next);
+               break;
+       case NTOFD:
+       case NFROMFD:
+               calcsize(n->ndup.vname);
+               calcsize(n->ndup.next);
+       break;
+       case NHERE:
+       case NXHERE:
+               calcsize(n->nhere.doc);
+               calcsize(n->nhere.next);
+               break;
+       case NNOT:
+               calcsize(n->nnot.com);
+               break;
+       };
+}
 
-       case CMDBUILTIN:
-               cmdenviron = varlist.list;
-               if (cmdenviron) {
-                       struct strlist *list = cmdenviron;
-                       int i = VNOSET;
-                       if (spclbltin > 0 || argc == 0) {
-                               i = 0;
-                               if (cmd_is_exec && argc > 1)
-                                       i = VEXPORT;
-                       }
-                       listsetvar(list, i);
-               }
-               if (evalbltin(cmdentry.u.cmd, argc, argv)) {
-                       int exit_status;
-                       int i, j;
+static char *
+nodeckstrdup(char *s)
+{
+       char *rtn = funcstring;
 
-                       i = exception;
-                       if (i == EXEXIT)
-                               goto raise;
+       strcpy(funcstring, s);
+       funcstring += strlen(s) + 1;
+       return rtn;
+}
 
-                       exit_status = 2;
-                       j = 0;
-                       if (i == EXINT)
-                               j = SIGINT;
-                       if (i == EXSIG)
-                               j = pendingsigs;
-                       if (j)
-                               exit_status = j + 128;
-                       exitstatus = exit_status;
+static union node *copynode(union node *);
 
-                       if (i == EXINT || spclbltin > 0) {
- raise:
-                               longjmp(exception_handler->loc, 1);
-                       }
-                       FORCE_INT_ON;
-               }
-               break;
+static struct nodelist *
+copynodelist(struct nodelist *lp)
+{
+       struct nodelist *start;
+       struct nodelist **lpp;
 
-       case CMDFUNCTION:
-               listsetvar(varlist.list, 0);
-               if (evalfun(cmdentry.u.func, argc, argv, flags))
-                       goto raise;
-               break;
+       lpp = &start;
+       while (lp) {
+               *lpp = funcblock;
+               funcblock = (char *) funcblock + SHELL_ALIGN(sizeof(struct nodelist));
+               (*lpp)->n = copynode(lp->n);
+               lp = lp->next;
+               lpp = &(*lpp)->next;
        }
-
- out:
-       popredir(cmd_is_exec);
-       if (lastarg)
-               /* dsl: I think this is intended to be used to support
-                * '_' in 'vi' command mode during line editing...
-                * However I implemented that within libedit itself.
-                */
-               setvar("_", lastarg, 0);
-       popstackmark(&smark);
+       *lpp = NULL;
+       return start;
 }
 
-static int
-evalbltin(const struct builtincmd *cmd, int argc, char **argv)
+static union node *
+copynode(union node *n)
 {
-       char *volatile savecmdname;
-       struct jmploc *volatile savehandler;
-       struct jmploc jmploc;
-       int i;
+       union node *new;
 
-       savecmdname = commandname;
-       i = setjmp(jmploc.loc);
-       if (i)
-               goto cmddone;
-       savehandler = exception_handler;
-       exception_handler = &jmploc;
-       commandname = argv[0];
-       argptr = argv + 1;
-       optptr = NULL;                  /* initialize nextopt */
-       exitstatus = (*cmd->builtin)(argc, argv);
-       flush_stdout_stderr();
- cmddone:
-       exitstatus |= ferror(stdout);
-       clearerr(stdout);
-       commandname = savecmdname;
-       exsig = 0;
-       exception_handler = savehandler;
+       if (n == NULL)
+               return NULL;
+       new = funcblock;
+       funcblock = (char *) funcblock + nodesize[n->type];
 
-       return i;
+       switch (n->type) {
+       case NCMD:
+               new->ncmd.redirect = copynode(n->ncmd.redirect);
+               new->ncmd.args = copynode(n->ncmd.args);
+               new->ncmd.assign = copynode(n->ncmd.assign);
+               break;
+       case NPIPE:
+               new->npipe.cmdlist = copynodelist(n->npipe.cmdlist);
+               new->npipe.backgnd = n->npipe.backgnd;
+               break;
+       case NREDIR:
+       case NBACKGND:
+       case NSUBSHELL:
+               new->nredir.redirect = copynode(n->nredir.redirect);
+               new->nredir.n = copynode(n->nredir.n);
+               break;
+       case NAND:
+       case NOR:
+       case NSEMI:
+       case NWHILE:
+       case NUNTIL:
+               new->nbinary.ch2 = copynode(n->nbinary.ch2);
+               new->nbinary.ch1 = copynode(n->nbinary.ch1);
+               break;
+       case NIF:
+               new->nif.elsepart = copynode(n->nif.elsepart);
+               new->nif.ifpart = copynode(n->nif.ifpart);
+               new->nif.test = copynode(n->nif.test);
+               break;
+       case NFOR:
+               new->nfor.var = nodeckstrdup(n->nfor.var);
+               new->nfor.body = copynode(n->nfor.body);
+               new->nfor.args = copynode(n->nfor.args);
+               break;
+       case NCASE:
+               new->ncase.cases = copynode(n->ncase.cases);
+               new->ncase.expr = copynode(n->ncase.expr);
+               break;
+       case NCLIST:
+               new->nclist.body = copynode(n->nclist.body);
+               new->nclist.pattern = copynode(n->nclist.pattern);
+               new->nclist.next = copynode(n->nclist.next);
+               break;
+       case NDEFUN:
+       case NARG:
+               new->narg.backquote = copynodelist(n->narg.backquote);
+               new->narg.text = nodeckstrdup(n->narg.text);
+               new->narg.next = copynode(n->narg.next);
+               break;
+       case NTO:
+       case NCLOBBER:
+       case NFROM:
+       case NFROMTO:
+       case NAPPEND:
+               new->nfile.fname = copynode(n->nfile.fname);
+               new->nfile.fd = n->nfile.fd;
+               new->nfile.next = copynode(n->nfile.next);
+               break;
+       case NTOFD:
+       case NFROMFD:
+               new->ndup.vname = copynode(n->ndup.vname);
+               new->ndup.dupfd = n->ndup.dupfd;
+               new->ndup.fd = n->ndup.fd;
+               new->ndup.next = copynode(n->ndup.next);
+               break;
+       case NHERE:
+       case NXHERE:
+               new->nhere.doc = copynode(n->nhere.doc);
+               new->nhere.fd = n->nhere.fd;
+               new->nhere.next = copynode(n->nhere.next);
+               break;
+       case NNOT:
+               new->nnot.com = copynode(n->nnot.com);
+               break;
+       };
+       new->type = n->type;
+       return new;
 }
 
-static struct localvar *localvars;
-
 /*
- * Called after a function returns.
- * Interrupts must be off.
+ * Make a copy of a parse tree.
  */
-static void
-poplocalvars(void)
+static struct funcnode *
+copyfunc(union node *n)
 {
-       struct localvar *lvp;
-       struct var *vp;
+       struct funcnode *f;
+       size_t blocksize;
 
-       while ((lvp = localvars) != NULL) {
-               localvars = lvp->next;
-               vp = lvp->vp;
-               TRACE(("poplocalvar %s", vp ? vp->text : "-"));
-               if (vp == NULL) {       /* $- saved */
-                       memcpy(optlist, lvp->text, sizeof(optlist));
-                       free((char*)lvp->text);
-                       optschanged();
-               } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) {
-                       unsetvar(vp->text);
-               } else {
-                       if (vp->func)
-                               (*vp->func)(strchrnul(lvp->text, '=') + 1);
-                       if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
-                               free((char*)vp->text);
-                       vp->flags = lvp->flags;
-                       vp->text = lvp->text;
-               }
-               free(lvp);
-       }
+       funcblocksize = offsetof(struct funcnode, n);
+       funcstringsize = 0;
+       calcsize(n);
+       blocksize = funcblocksize;
+       f = ckmalloc(blocksize + funcstringsize);
+       funcblock = (char *) f + offsetof(struct funcnode, n);
+       funcstring = (char *) f + blocksize;
+       copynode(n);
+       f->count = 0;
+       return f;
 }
 
 /*
- * Free a parse tree.
+ * Define a shell function.
  */
 static void
-freefunc(struct funcnode *f)
-{
-       if (f && --f->count < 0)
-               free(f);
-}
-
-static int
-evalfun(struct funcnode *func, int argc, char **argv, int flags)
+defun(char *name, union node *func)
 {
-       volatile struct shparam saveparam;
-       struct localvar *volatile savelocalvars;
-       struct jmploc *volatile savehandler;
-       struct jmploc jmploc;
-       int e;
+       struct cmdentry entry;
 
-       saveparam = shellparam;
-       savelocalvars = localvars;
-       e = setjmp(jmploc.loc);
-       if (e) {
-               goto funcdone;
-       }
-       INT_OFF;
-       savehandler = exception_handler;
-       exception_handler = &jmploc;
-       localvars = NULL;
-       shellparam.malloc = 0;
-       func->count++;
-       funcnest++;
-       INT_ON;
-       shellparam.nparam = argc - 1;
-       shellparam.p = argv + 1;
-#if ENABLE_ASH_GETOPTS
-       shellparam.optind = 1;
-       shellparam.optoff = -1;
-#endif
-       evaltree(&func->n, flags & EV_TESTED);
-funcdone:
        INT_OFF;
-       funcnest--;
-       freefunc(func);
-       poplocalvars();
-       localvars = savelocalvars;
-       freeparam(&shellparam);
-       shellparam = saveparam;
-       exception_handler = savehandler;
+       entry.cmdtype = CMDFUNCTION;
+       entry.u.func = copyfunc(func);
+       addcmdentry(name, &entry);
        INT_ON;
-       evalskip &= ~SKIPFUNC;
-       return e;
 }
 
+static int evalskip;            /* set if we are skipping commands */
+/* reasons for skipping commands (see comment on breakcmd routine) */
+#define SKIPBREAK      (1 << 0)
+#define SKIPCONT       (1 << 1)
+#define SKIPFUNC       (1 << 2)
+#define SKIPFILE       (1 << 3)
+#define SKIPEVAL       (1 << 4)
+static int skipcount;           /* number of levels to skip */
+static int funcnest;            /* depth of function calls */
 
-static int
-goodname(const char *p)
-{
-       return !*endofname(p);
-}
-
+/* forward declarations - evaluation is fairly recursive business... */
+static void evalloop(union node *, int);
+static void evalfor(union node *, int);
+static void evalcase(union node *, int);
+static void evalsubshell(union node *, int);
+static void expredir(union node *);
+static void evalpipe(union node *, int);
+static void evalcommand(union node *, int);
+static int evalbltin(const struct builtincmd *, int, char **);
+static void prehash(union node *);
 
 /*
- * Search for a command.  This is called before we fork so that the
- * location of the command will be available in the parent as well as
- * the child.  The check for "goodname" is an overly conservative
- * check that the name will not be subject to expansion.
+ * Evaluate a parse tree.  The value is left in the global variable
+ * exitstatus.
  */
 static void
-prehash(union node *n)
+evaltree(union node *n, int flags)
 {
-       struct cmdentry entry;
+       int checkexit = 0;
+       void (*evalfn)(union node *, int);
+       unsigned isor;
+       int status;
+       if (n == NULL) {
+               TRACE(("evaltree(NULL) called\n"));
+               goto out;
+       }
+       TRACE(("pid %d, evaltree(%p: %d, %d) called\n",
+                       getpid(), n, n->type, flags));
+       switch (n->type) {
+       default:
+#if DEBUG
+               out1fmt("Node type = %d\n", n->type);
+               fflush(stdout);
+               break;
+#endif
+       case NNOT:
+               evaltree(n->nnot.com, EV_TESTED);
+               status = !exitstatus;
+               goto setstatus;
+       case NREDIR:
+               expredir(n->nredir.redirect);
+               status = redirectsafe(n->nredir.redirect, REDIR_PUSH);
+               if (!status) {
+                       evaltree(n->nredir.n, flags & EV_TESTED);
+                       status = exitstatus;
+               }
+               popredir(0);
+               goto setstatus;
+       case NCMD:
+               evalfn = evalcommand;
+ checkexit:
+               if (eflag && !(flags & EV_TESTED))
+                       checkexit = ~0;
+               goto calleval;
+       case NFOR:
+               evalfn = evalfor;
+               goto calleval;
+       case NWHILE:
+       case NUNTIL:
+               evalfn = evalloop;
+               goto calleval;
+       case NSUBSHELL:
+       case NBACKGND:
+               evalfn = evalsubshell;
+               goto calleval;
+       case NPIPE:
+               evalfn = evalpipe;
+               goto checkexit;
+       case NCASE:
+               evalfn = evalcase;
+               goto calleval;
+       case NAND:
+       case NOR:
+       case NSEMI:
+#if NAND + 1 != NOR
+#error NAND + 1 != NOR
+#endif
+#if NOR + 1 != NSEMI
+#error NOR + 1 != NSEMI
+#endif
+               isor = n->type - NAND;
+               evaltree(
+                       n->nbinary.ch1,
+                       (flags | ((isor >> 1) - 1)) & EV_TESTED
+               );
+               if (!exitstatus == isor)
+                       break;
+               if (!evalskip) {
+                       n = n->nbinary.ch2;
+ evaln:
+                       evalfn = evaltree;
+ calleval:
+                       evalfn(n, flags);
+                       break;
+               }
+               break;
+       case NIF:
+               evaltree(n->nif.test, EV_TESTED);
+               if (evalskip)
+                       break;
+               if (exitstatus == 0) {
+                       n = n->nif.ifpart;
+                       goto evaln;
+               } else if (n->nif.elsepart) {
+                       n = n->nif.elsepart;
+                       goto evaln;
+               }
+               goto success;
+       case NDEFUN:
+               defun(n->narg.text, n->narg.next);
+ success:
+               status = 0;
+ setstatus:
+               exitstatus = status;
+               break;
+       }
+ out:
+       if ((checkexit & exitstatus))
+               evalskip |= SKIPEVAL;
+       else if (pendingsigs && dotrap())
+               goto exexit;
 
-       if (n->type == NCMD && n->ncmd.args && goodname(n->ncmd.args->narg.text))
-               find_command(n->ncmd.args->narg.text, &entry, 0, pathval());
+       if (flags & EV_EXIT) {
+ exexit:
+               raise_exception(EXEXIT);
+       }
 }
 
+#if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3)
+static
+#endif
+void evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__));
 
-/*
- * Builtin commands.  Builtin commands whose functions are closely
- * tied to evaluation are implemented here.
- */
-
-/*
- * Handle break and continue commands.  Break, continue, and return are
- * all handled by setting the evalskip flag.  The evaluation routines
- * above all check this flag, and if it is set they start skipping
- * commands rather than executing them.  The variable skipcount is
- * the number of loops to break/continue, or the number of function
- * levels to return.  (The latter is always 1.)  It should probably
- * be an error to break out of more loops than exist, but it isn't
- * in the standard shell so we don't make it one here.
- */
+static int loopnest;            /* current loop nesting level */
 
-static int
-breakcmd(int argc, char **argv)
+static void
+evalloop(union node *n, int flags)
 {
-       int n = argc > 1 ? number(argv[1]) : 1;
+       int status;
 
-       if (n <= 0)
-               ash_msg_and_raise_error(illnum, argv[1]);
-       if (n > loopnest)
-               n = loopnest;
-       if (n > 0) {
-               evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK;
-               skipcount = n;
+       loopnest++;
+       status = 0;
+       flags &= EV_TESTED;
+       for (;;) {
+               int i;
+
+               evaltree(n->nbinary.ch1, EV_TESTED);
+               if (evalskip) {
+ skipping:
+                       if (evalskip == SKIPCONT && --skipcount <= 0) {
+                               evalskip = 0;
+                               continue;
+                       }
+                       if (evalskip == SKIPBREAK && --skipcount <= 0)
+                               evalskip = 0;
+                       break;
+               }
+               i = exitstatus;
+               if (n->type != NWHILE)
+                       i = !i;
+               if (i != 0)
+                       break;
+               evaltree(n->nbinary.ch2, flags);
+               status = exitstatus;
+               if (evalskip)
+                       goto skipping;
        }
-       return 0;
+       loopnest--;
+       exitstatus = status;
 }
 
-/*
- * The return command.
- */
-static int
-returncmd(int argc, char **argv)
+static void
+evalfor(union node *n, int flags)
 {
-       /*
-        * If called outside a function, do what ksh does;
-        * skip the rest of the file.
-        */
-       evalskip = funcnest ? SKIPFUNC : SKIPFILE;
-       return argv[1] ? number(argv[1]) : exitstatus;
-}
+       struct arglist arglist;
+       union node *argp;
+       struct strlist *sp;
+       struct stackmark smark;
 
-static int
-falsecmd(int argc, char **argv)
-{
-       return 1;
-}
+       setstackmark(&smark);
+       arglist.lastp = &arglist.list;
+       for (argp = n->nfor.args; argp; argp = argp->narg.next) {
+               expandarg(argp, &arglist, EXP_FULL | EXP_TILDE | EXP_RECORD);
+               /* XXX */
+               if (evalskip)
+                       goto out;
+       }
+       *arglist.lastp = NULL;
 
-static int
-truecmd(int argc, char **argv)
-{
-       return 0;
+       exitstatus = 0;
+       loopnest++;
+       flags &= EV_TESTED;
+       for (sp = arglist.list; sp; sp = sp->next) {
+               setvar(n->nfor.var, sp->text, 0);
+               evaltree(n->nfor.body, flags);
+               if (evalskip) {
+                       if (evalskip == SKIPCONT && --skipcount <= 0) {
+                               evalskip = 0;
+                               continue;
+                       }
+                       if (evalskip == SKIPBREAK && --skipcount <= 0)
+                               evalskip = 0;
+                       break;
+               }
+       }
+       loopnest--;
+ out:
+       popstackmark(&smark);
 }
 
-static int
-execcmd(int argc, char **argv)
+static void
+evalcase(union node *n, int flags)
 {
-       if (argc > 1) {
-               iflag = 0;              /* exit on error */
-               mflag = 0;
-               optschanged();
-               shellexec(argv + 1, pathval(), 0);
+       union node *cp;
+       union node *patp;
+       struct arglist arglist;
+       struct stackmark smark;
+
+       setstackmark(&smark);
+       arglist.lastp = &arglist.list;
+       expandarg(n->ncase.expr, &arglist, EXP_TILDE);
+       exitstatus = 0;
+       for (cp = n->ncase.cases; cp && evalskip == 0; cp = cp->nclist.next) {
+               for (patp = cp->nclist.pattern; patp; patp = patp->narg.next) {
+                       if (casematch(patp, arglist.list->text)) {
+                               if (evalskip == 0) {
+                                       evaltree(cp->nclist.body, flags);
+                               }
+                               goto out;
+                       }
+               }
        }
-       return 0;
+ out:
+       popstackmark(&smark);
 }
 
-
-/* ============ Executing commands */
-
 /*
- * When commands are first encountered, they are entered in a hash table.
- * This ensures that a full path search will not have to be done for them
- * on each invocation.
- *
- * We should investigate converting to a linear search, even though that
- * would make the command name "hash" a misnomer.
+ * Kick off a subshell to evaluate a tree.
  */
-
-#define CMDTABLESIZE 31         /* should be prime */
-#define ARB 1                   /* actual size determined at run time */
-
-struct tblentry {
-       struct tblentry *next;  /* next entry in hash chain */
-       union param param;      /* definition of builtin function */
-       short cmdtype;          /* index identifying command */
-       char rehash;            /* if set, cd done since entry created */
-       char cmdname[ARB];      /* name of command */
-};
-
-static struct tblentry *cmdtable[CMDTABLESIZE];
-static int builtinloc = -1;             /* index in path of %builtin, or -1 */
-
 static void
-tryexec(char *cmd, char **argv, char **envp)
+evalsubshell(union node *n, int flags)
 {
-       int repeated = 0;
-       struct BB_applet *a;
-       int argc = 0;
-       char **c;
-
-       if (strchr(cmd, '/') == NULL
-        && (a = find_applet_by_name(cmd)) != NULL
-        && is_safe_applet(cmd)
-       ) {
-               c = argv;
-               while (*c != NULL) {
-                       c++; argc++;
-               }
-               applet_name = cmd;
-               exit(a->main(argc, argv));
-       }
-#if ENABLE_FEATURE_SH_STANDALONE_SHELL
-       if (find_applet_by_name(cmd) != NULL) {
-               /* re-exec ourselves with the new arguments */
-               execve(CONFIG_BUSYBOX_EXEC_PATH, argv, envp);
-               /* If they called chroot or otherwise made the binary no longer
-                * executable, fall through */
-       }
-#endif
-
- repeat:
-#ifdef SYSV
-       do {
-               execve(cmd, argv, envp);
-       } while (errno == EINTR);
-#else
-       execve(cmd, argv, envp);
-#endif
-       if (repeated++) {
-               free(argv);
-       } else if (errno == ENOEXEC) {
-               char **ap;
-               char **new;
+       struct job *jp;
+       int backgnd = (n->type == NBACKGND);
+       int status;
 
-               for (ap = argv; *ap; ap++)
-                       ;
-               ap = new = ckmalloc((ap - argv + 2) * sizeof(char *));
-               ap[1] = cmd;
-               *ap = cmd = (char *)DEFAULT_SHELL;
-               ap += 2;
-               argv++;
-               while ((*ap++ = *argv++))
-                       ;
-               argv = new;
-               goto repeat;
+       expredir(n->nredir.redirect);
+       if (!backgnd && flags & EV_EXIT && !trap[0])
+               goto nofork;
+       INT_OFF;
+       jp = makejob(n, 1);
+       if (forkshell(jp, n, backgnd) == 0) {
+               INT_ON;
+               flags |= EV_EXIT;
+               if (backgnd)
+                       flags &=~ EV_TESTED;
+ nofork:
+               redirect(n->nredir.redirect, 0);
+               evaltreenr(n->nredir.n, flags);
+               /* never returns */
        }
+       status = 0;
+       if (! backgnd)
+               status = waitforjob(jp);
+       exitstatus = status;
+       INT_ON;
 }
 
 /*
- * Exec a program.  Never returns.  If you change this routine, you may
- * have to change the find_command routine as well.
+ * Compute the names of the files in a redirection list.
  */
-#define environment() listvars(VEXPORT, VUNSET, 0)
-static void shellexec(char **, const char *, int) ATTRIBUTE_NORETURN;
 static void
-shellexec(char **argv, const char *path, int idx)
+expredir(union node *n)
 {
-       char *cmdname;
-       int e;
-       char **envp;
-       int exerrno;
+       union node *redir;
 
-       clearredir(1);
-       envp = environment();
-       if (strchr(argv[0], '/') || is_safe_applet(argv[0])
-#if ENABLE_FEATURE_SH_STANDALONE_SHELL
-        || find_applet_by_name(argv[0])
-#endif
-       ) {
-               tryexec(argv[0], argv, envp);
-               e = errno;
-       } else {
-               e = ENOENT;
-               while ((cmdname = padvance(&path, argv[0])) != NULL) {
-                       if (--idx < 0 && pathopt == NULL) {
-                               tryexec(cmdname, argv, envp);
-                               if (errno != ENOENT && errno != ENOTDIR)
-                                       e = errno;
+       for (redir = n; redir; redir = redir->nfile.next) {
+               struct arglist fn;
+
+               memset(&fn, 0, sizeof(fn));
+               fn.lastp = &fn.list;
+               switch (redir->type) {
+               case NFROMTO:
+               case NFROM:
+               case NTO:
+               case NCLOBBER:
+               case NAPPEND:
+                       expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR);
+                       redir->nfile.expfname = fn.list->text;
+                       break;
+               case NFROMFD:
+               case NTOFD:
+                       if (redir->ndup.vname) {
+                               expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE);
+                               if (fn.list == NULL)
+                                       ash_msg_and_raise_error("redir error");
+                               fixredir(redir, fn.list->text, 1);
                        }
-                       stunalloc(cmdname);
+                       break;
                }
        }
-
-       /* Map to POSIX errors */
-       switch (e) {
-       case EACCES:
-               exerrno = 126;
-               break;
-       case ENOENT:
-               exerrno = 127;
-               break;
-       default:
-               exerrno = 2;
-               break;
-       }
-       exitstatus = exerrno;
-       TRACE(("shellexec failed for %s, errno %d, suppressint %d\n",
-               argv[0], e, suppressint ));
-       ash_msg_and_raise(EXEXEC, "%s: %s", argv[0], errmsg(e, "not found"));
-       /* NOTREACHED */
-}
-
-static void
-printentry(struct tblentry *cmdp)
-{
-       int idx;
-       const char *path;
-       char *name;
-
-       idx = cmdp->param.index;
-       path = pathval();
-       do {
-               name = padvance(&path, cmdp->cmdname);
-               stunalloc(name);
-       } while (--idx >= 0);
-       out1fmt("%s%s\n", name, (cmdp->rehash ? "*" : nullstr));
 }
 
 /*
- * Clear out command entries.  The argument specifies the first entry in
- * PATH which has changed.
+ * Evaluate a pipeline.  All the processes in the pipeline are children
+ * of the process creating the pipeline.  (This differs from some versions
+ * of the shell, which make the last process in a pipeline the parent
+ * of all the rest.)
  */
 static void
-clearcmdentry(int firstchange)
+evalpipe(union node *n, int flags)
 {
-       struct tblentry **tblp;
-       struct tblentry **pp;
-       struct tblentry *cmdp;
+       struct job *jp;
+       struct nodelist *lp;
+       int pipelen;
+       int prevfd;
+       int pip[2];
 
+       TRACE(("evalpipe(0x%lx) called\n", (long)n));
+       pipelen = 0;
+       for (lp = n->npipe.cmdlist; lp; lp = lp->next)
+               pipelen++;
+       flags |= EV_EXIT;
        INT_OFF;
-       for (tblp = cmdtable; tblp < &cmdtable[CMDTABLESIZE]; tblp++) {
-               pp = tblp;
-               while ((cmdp = *pp) != NULL) {
-                       if ((cmdp->cmdtype == CMDNORMAL &&
-                            cmdp->param.index >= firstchange)
-                        || (cmdp->cmdtype == CMDBUILTIN &&
-                            builtinloc >= firstchange)
-                       ) {
-                               *pp = cmdp->next;
-                               free(cmdp);
-                       } else {
-                               pp = &cmdp->next;
+       jp = makejob(n, pipelen);
+       prevfd = -1;
+       for (lp = n->npipe.cmdlist; lp; lp = lp->next) {
+               prehash(lp->n);
+               pip[1] = -1;
+               if (lp->next) {
+                       if (pipe(pip) < 0) {
+                               close(prevfd);
+                               ash_msg_and_raise_error("Pipe call failed");
+                       }
+               }
+               if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) {
+                       INT_ON;
+                       if (pip[1] >= 0) {
+                               close(pip[0]);
+                       }
+                       if (prevfd > 0) {
+                               dup2(prevfd, 0);
+                               close(prevfd);
+                       }
+                       if (pip[1] > 1) {
+                               dup2(pip[1], 1);
+                               close(pip[1]);
                        }
+                       evaltreenr(lp->n, flags);
+                       /* never returns */
                }
+               if (prevfd >= 0)
+                       close(prevfd);
+               prevfd = pip[0];
+               close(pip[1]);
+       }
+       if (n->npipe.backgnd == 0) {
+               exitstatus = waitforjob(jp);
+               TRACE(("evalpipe:  job done exit status %d\n", exitstatus));
        }
        INT_ON;
 }
 
+static struct localvar *localvars;
+
 /*
- * Locate a command in the command hash table.  If "add" is nonzero,
- * add the command to the table if it is not already present.  The
- * variable "lastcmdentry" is set to point to the address of the link
- * pointing to the entry, so that delete_cmd_entry can delete the
- * entry.
- *
- * Interrupts must be off if called with add != 0.
+ * Called after a function returns.
+ * Interrupts must be off.
  */
-static struct tblentry **lastcmdentry;
+static void
+poplocalvars(void)
+{
+       struct localvar *lvp;
+       struct var *vp;
 
-static struct tblentry *
-cmdlookup(const char *name, int add)
+       while ((lvp = localvars) != NULL) {
+               localvars = lvp->next;
+               vp = lvp->vp;
+               TRACE(("poplocalvar %s", vp ? vp->text : "-"));
+               if (vp == NULL) {       /* $- saved */
+                       memcpy(optlist, lvp->text, sizeof(optlist));
+                       free((char*)lvp->text);
+                       optschanged();
+               } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) {
+                       unsetvar(vp->text);
+               } else {
+                       if (vp->func)
+                               (*vp->func)(strchrnul(lvp->text, '=') + 1);
+                       if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
+                               free((char*)vp->text);
+                       vp->flags = lvp->flags;
+                       vp->text = lvp->text;
+               }
+               free(lvp);
+       }
+}
+
+static int
+evalfun(struct funcnode *func, int argc, char **argv, int flags)
 {
-       unsigned int hashval;
-       const char *p;
-       struct tblentry *cmdp;
-       struct tblentry **pp;
+       volatile struct shparam saveparam;
+       struct localvar *volatile savelocalvars;
+       struct jmploc *volatile savehandler;
+       struct jmploc jmploc;
+       int e;
 
-       p = name;
-       hashval = (unsigned char)*p << 4;
-       while (*p)
-               hashval += (unsigned char)*p++;
-       hashval &= 0x7FFF;
-       pp = &cmdtable[hashval % CMDTABLESIZE];
-       for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
-               if (strcmp(cmdp->cmdname, name) == 0)
+       saveparam = shellparam;
+       savelocalvars = localvars;
+       e = setjmp(jmploc.loc);
+       if (e) {
+               goto funcdone;
+       }
+       INT_OFF;
+       savehandler = exception_handler;
+       exception_handler = &jmploc;
+       localvars = NULL;
+       shellparam.malloc = 0;
+       func->count++;
+       funcnest++;
+       INT_ON;
+       shellparam.nparam = argc - 1;
+       shellparam.p = argv + 1;
+#if ENABLE_ASH_GETOPTS
+       shellparam.optind = 1;
+       shellparam.optoff = -1;
+#endif
+       evaltree(&func->n, flags & EV_TESTED);
+funcdone:
+       INT_OFF;
+       funcnest--;
+       freefunc(func);
+       poplocalvars();
+       localvars = savelocalvars;
+       freeparam(&shellparam);
+       shellparam = saveparam;
+       exception_handler = savehandler;
+       INT_ON;
+       evalskip &= ~SKIPFUNC;
+       return e;
+}
+
+#if ENABLE_ASH_CMDCMD
+static char **
+parse_command_args(char **argv, const char **path)
+{
+       char *cp, c;
+
+       for (;;) {
+               cp = *++argv;
+               if (!cp)
+                       return 0;
+               if (*cp++ != '-')
                        break;
-               pp = &cmdp->next;
-       }
-       if (add && cmdp == NULL) {
-               cmdp = *pp = ckmalloc(sizeof(struct tblentry) - ARB
-                                       + strlen(name) + 1);
-               cmdp->next = NULL;
-               cmdp->cmdtype = CMDUNKNOWN;
-               strcpy(cmdp->cmdname, name);
+               c = *cp++;
+               if (!c)
+                       break;
+               if (c == '-' && !*cp) {
+                       argv++;
+                       break;
+               }
+               do {
+                       switch (c) {
+                       case 'p':
+                               *path = defpath;
+                               break;
+                       default:
+                               /* run 'typecmd' for other options */
+                               return 0;
+                       }
+                       c = *cp++;
+               } while (c);
        }
-       lastcmdentry = pp;
-       return cmdp;
+       return argv;
 }
+#endif
 
 /*
- * Delete the command entry returned on the last lookup.
+ * Make a variable a local variable.  When a variable is made local, it's
+ * value and flags are saved in a localvar structure.  The saved values
+ * will be restored when the shell function returns.  We handle the name
+ * "-" as a special case.
  */
 static void
-delete_cmd_entry(void)
+mklocal(char *name)
 {
-       struct tblentry *cmdp;
+       struct localvar *lvp;
+       struct var **vpp;
+       struct var *vp;
 
        INT_OFF;
-       cmdp = *lastcmdentry;
-       *lastcmdentry = cmdp->next;
-       if (cmdp->cmdtype == CMDFUNCTION)
-               freefunc(cmdp->param.func);
-       free(cmdp);
+       lvp = ckmalloc(sizeof(struct localvar));
+       if (LONE_DASH(name)) {
+               char *p;
+               p = ckmalloc(sizeof(optlist));
+               lvp->text = memcpy(p, optlist, sizeof(optlist));
+               vp = NULL;
+       } else {
+               char *eq;
+
+               vpp = hashvar(name);
+               vp = *findvar(vpp, name);
+               eq = strchr(name, '=');
+               if (vp == NULL) {
+                       if (eq)
+                               setvareq(name, VSTRFIXED);
+                       else
+                               setvar(name, NULL, VSTRFIXED);
+                       vp = *vpp;      /* the new variable */
+                       lvp->flags = VUNSET;
+               } else {
+                       lvp->text = vp->text;
+                       lvp->flags = vp->flags;
+                       vp->flags |= VSTRFIXED|VTEXTFIXED;
+                       if (eq)
+                               setvareq(name, 0);
+               }
+       }
+       lvp->vp = vp;
+       lvp->next = localvars;
+       localvars = lvp;
        INT_ON;
 }
 
 /*
- * Add a new command entry, replacing any existing command entry for
- * the same name - except special builtins.
+ * The "local" command.
  */
-static void
-addcmdentry(char *name, struct cmdentry *entry)
+static int
+localcmd(int argc, char **argv)
 {
-       struct tblentry *cmdp;
+       char *name;
 
-       cmdp = cmdlookup(name, 1);
-       if (cmdp->cmdtype == CMDFUNCTION) {
-               freefunc(cmdp->param.func);
+       argv = argptr;
+       while ((name = *argv++) != NULL) {
+               mklocal(name);
        }
-       cmdp->cmdtype = entry->cmdtype;
-       cmdp->param = entry->u;
-       cmdp->rehash = 0;
+       return 0;
 }
 
-static int
-hashcmd(int argc, char **argv)
-{
-       struct tblentry **pp;
-       struct tblentry *cmdp;
-       int c;
-       struct cmdentry entry;
-       char *name;
+/* Forward declarations for builtintab[] */
+#if JOBS
+static int fg_bgcmd(int, char **);
+#endif
+static int breakcmd(int, char **);
+#if ENABLE_ASH_CMDCMD
+static int commandcmd(int, char **);
+#endif
+static int dotcmd(int, char **);
+static int evalcmd(int, char **);
+#if ENABLE_ASH_BUILTIN_ECHO
+static int echocmd(int, char **);
+#endif
+#if ENABLE_ASH_BUILTIN_TEST
+static int testcmd(int, char **);
+#endif
+static int execcmd(int, char **);
+static int exitcmd(int, char **);
+static int exportcmd(int, char **);
+static int falsecmd(int, char **);
+#if ENABLE_ASH_GETOPTS
+static int getoptscmd(int, char **);
+#endif
+static int hashcmd(int, char **);
+#if !ENABLE_FEATURE_SH_EXTRA_QUIET
+static int helpcmd(int argc, char **argv);
+#endif
+#if JOBS
+static int jobscmd(int, char **);
+#endif
+#if ENABLE_ASH_MATH_SUPPORT
+static int letcmd(int, char **);
+#endif
+static int pwdcmd(int, char **);
+static int readcmd(int, char **);
+static int returncmd(int, char **);
+static int setcmd(int, char **);
+static int shiftcmd(int, char **);
+static int timescmd(int, char **);
+static int trapcmd(int, char **);
+static int truecmd(int, char **);
+static int typecmd(int, char **);
+static int umaskcmd(int, char **);
+static int unsetcmd(int, char **);
+static int waitcmd(int, char **);
+static int ulimitcmd(int, char **);
+#if JOBS
+static int killcmd(int, char **);
+#endif
 
-       while ((c = nextopt("r")) != '\0') {
-               clearcmdentry(0);
-               return 0;
-       }
-       if (*argptr == NULL) {
-               for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) {
-                       for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
-                               if (cmdp->cmdtype == CMDNORMAL)
-                                       printentry(cmdp);
-                       }
-               }
-               return 0;
-       }
-       c = 0;
-       while ((name = *argptr) != NULL) {
-               cmdp = cmdlookup(name, 0);
-               if (cmdp != NULL
-                && (cmdp->cmdtype == CMDNORMAL
-                    || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0)))
-                       delete_cmd_entry();
-               find_command(name, &entry, DO_ERR, pathval());
-               if (entry.cmdtype == CMDUNKNOWN)
-                       c = 1;
-               argptr++;
-       }
-       return c;
+#define BUILTIN_NOSPEC          "0"
+#define BUILTIN_SPECIAL         "1"
+#define BUILTIN_REGULAR         "2"
+#define BUILTIN_SPEC_REG        "3"
+#define BUILTIN_ASSIGN          "4"
+#define BUILTIN_SPEC_ASSG       "5"
+#define BUILTIN_REG_ASSG        "6"
+#define BUILTIN_SPEC_REG_ASSG   "7"
+
+/* make sure to keep these in proper order since it is searched via bsearch() */
+static const struct builtincmd builtintab[] = {
+       { BUILTIN_SPEC_REG      ".", dotcmd },
+       { BUILTIN_SPEC_REG      ":", truecmd },
+#if ENABLE_ASH_BUILTIN_TEST
+       { BUILTIN_REGULAR       "[", testcmd },
+       { BUILTIN_REGULAR       "[[", testcmd },
+#endif
+#if ENABLE_ASH_ALIAS
+       { BUILTIN_REG_ASSG      "alias", aliascmd },
+#endif
+#if JOBS
+       { BUILTIN_REGULAR       "bg", fg_bgcmd },
+#endif
+       { BUILTIN_SPEC_REG      "break", breakcmd },
+       { BUILTIN_REGULAR       "cd", cdcmd },
+       { BUILTIN_NOSPEC        "chdir", cdcmd },
+#if ENABLE_ASH_CMDCMD
+       { BUILTIN_REGULAR       "command", commandcmd },
+#endif
+       { BUILTIN_SPEC_REG      "continue", breakcmd },
+#if ENABLE_ASH_BUILTIN_ECHO
+       { BUILTIN_REGULAR       "echo", echocmd },
+#endif
+       { BUILTIN_SPEC_REG      "eval", evalcmd },
+       { BUILTIN_SPEC_REG      "exec", execcmd },
+       { BUILTIN_SPEC_REG      "exit", exitcmd },
+       { BUILTIN_SPEC_REG_ASSG "export", exportcmd },
+       { BUILTIN_REGULAR       "false", falsecmd },
+#if JOBS
+       { BUILTIN_REGULAR       "fg", fg_bgcmd },
+#endif
+#if ENABLE_ASH_GETOPTS
+       { BUILTIN_REGULAR       "getopts", getoptscmd },
+#endif
+       { BUILTIN_NOSPEC        "hash", hashcmd },
+#if !ENABLE_FEATURE_SH_EXTRA_QUIET
+       { BUILTIN_NOSPEC        "help", helpcmd },
+#endif
+#if JOBS
+       { BUILTIN_REGULAR       "jobs", jobscmd },
+       { BUILTIN_REGULAR       "kill", killcmd },
+#endif
+#if ENABLE_ASH_MATH_SUPPORT
+       { BUILTIN_NOSPEC        "let", letcmd },
+#endif
+       { BUILTIN_ASSIGN        "local", localcmd },
+       { BUILTIN_NOSPEC        "pwd", pwdcmd },
+       { BUILTIN_REGULAR       "read", readcmd },
+       { BUILTIN_SPEC_REG_ASSG "readonly", exportcmd },
+       { BUILTIN_SPEC_REG      "return", returncmd },
+       { BUILTIN_SPEC_REG      "set", setcmd },
+       { BUILTIN_SPEC_REG      "shift", shiftcmd },
+       { BUILTIN_SPEC_REG      "source", dotcmd },
+#if ENABLE_ASH_BUILTIN_TEST
+       { BUILTIN_REGULAR       "test", testcmd },
+#endif
+       { BUILTIN_SPEC_REG      "times", timescmd },
+       { BUILTIN_SPEC_REG      "trap", trapcmd },
+       { BUILTIN_REGULAR       "true", truecmd },
+       { BUILTIN_NOSPEC        "type", typecmd },
+       { BUILTIN_NOSPEC        "ulimit", ulimitcmd },
+       { BUILTIN_REGULAR       "umask", umaskcmd },
+#if ENABLE_ASH_ALIAS
+       { BUILTIN_REGULAR       "unalias", unaliascmd },
+#endif
+       { BUILTIN_SPEC_REG      "unset", unsetcmd },
+       { BUILTIN_REGULAR       "wait", waitcmd },
+};
+
+#define NUMBUILTINS (sizeof(builtintab) / sizeof(builtintab[0]))
+
+#define COMMANDCMD (builtintab + 5 + \
+       2 * ENABLE_ASH_BUILTIN_TEST + \
+       ENABLE_ASH_ALIAS + \
+       ENABLE_ASH_JOB_CONTROL)
+#define EXECCMD (builtintab + 7 + \
+       2 * ENABLE_ASH_BUILTIN_TEST + \
+       ENABLE_ASH_ALIAS + \
+       ENABLE_ASH_JOB_CONTROL + \
+       ENABLE_ASH_CMDCMD + \
+       ENABLE_ASH_BUILTIN_ECHO)
+
+/*
+ * Search the table of builtin commands.
+ */
+static struct builtincmd *
+find_builtin(const char *name)
+{
+       struct builtincmd *bp;
+
+       bp = bsearch(
+               name, builtintab, NUMBUILTINS, sizeof(builtintab[0]),
+               pstrcmp
+       );
+       return bp;
 }
 
 /*
@@ -6394,283 +6533,377 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path)
 }
 
 /*
- * Search the table of builtin commands.
+ * Execute a simple command.
  */
-static struct builtincmd *
-find_builtin(const char *name)
+static int back_exitstatus; /* exit status of backquoted command */
+static int
+isassignment(const char *p)
 {
-       struct builtincmd *bp;
-
-       bp = bsearch(
-               name, builtintab, NUMBUILTINS, sizeof(builtintab[0]),
-               pstrcmp
-       );
-       return bp;
+       const char *q = endofname(p);
+       if (p == q)
+               return 0;
+       return *q == '=';
+}
+static int
+bltincmd(int argc, char **argv)
+{
+       /* Preserve exitstatus of a previous possible redirection
+        * as POSIX mandates */
+       return back_exitstatus;
 }
-
-/*
- * Called when a cd is done.  Marks all commands so the next time they
- * are executed they will be rehashed.
- */
 static void
-hashcd(void)
+evalcommand(union node *cmd, int flags)
 {
-       struct tblentry **pp;
-       struct tblentry *cmdp;
+       static const struct builtincmd bltin = {
+               "\0\0", bltincmd
+       };
+       struct stackmark smark;
+       union node *argp;
+       struct arglist arglist;
+       struct arglist varlist;
+       char **argv;
+       int argc;
+       const struct strlist *sp;
+       struct cmdentry cmdentry;
+       struct job *jp;
+       char *lastarg;
+       const char *path;
+       int spclbltin;
+       int cmd_is_exec;
+       int status;
+       char **nargv;
+       struct builtincmd *bcmd;
+       int pseudovarflag = 0;
+
+       /* First expand the arguments. */
+       TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
+       setstackmark(&smark);
+       back_exitstatus = 0;
+
+       cmdentry.cmdtype = CMDBUILTIN;
+       cmdentry.u.cmd = &bltin;
+       varlist.lastp = &varlist.list;
+       *varlist.lastp = NULL;
+       arglist.lastp = &arglist.list;
+       *arglist.lastp = NULL;
+
+       argc = 0;
+       if (cmd->ncmd.args) {
+               bcmd = find_builtin(cmd->ncmd.args->narg.text);
+               pseudovarflag = bcmd && IS_BUILTIN_ASSIGN(bcmd);
+       }
+
+       for (argp = cmd->ncmd.args; argp; argp = argp->narg.next) {
+               struct strlist **spp;
+
+               spp = arglist.lastp;
+               if (pseudovarflag && isassignment(argp->narg.text))
+                       expandarg(argp, &arglist, EXP_VARTILDE);
+               else
+                       expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
+
+               for (sp = *spp; sp; sp = sp->next)
+                       argc++;
+       }
+
+       argv = nargv = stalloc(sizeof(char *) * (argc + 1));
+       for (sp = arglist.list; sp; sp = sp->next) {
+               TRACE(("evalcommand arg: %s\n", sp->text));
+               *nargv++ = sp->text;
+       }
+       *nargv = NULL;
+
+       lastarg = NULL;
+       if (iflag && funcnest == 0 && argc > 0)
+               lastarg = nargv[-1];
+
+       preverrout_fd = 2;
+       expredir(cmd->ncmd.redirect);
+       status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH|REDIR_SAVEFD2);
+
+       path = vpath.text;
+       for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) {
+               struct strlist **spp;
+               char *p;
+
+               spp = varlist.lastp;
+               expandarg(argp, &varlist, EXP_VARTILDE);
+
+               /*
+                * Modify the command lookup path, if a PATH= assignment
+                * is present
+                */
+               p = (*spp)->text;
+               if (varequal(p, path))
+                       path = p;
+       }
+
+       /* Print the command if xflag is set. */
+       if (xflag) {
+               int n;
+               const char *p = " %s";
+
+               p++;
+               dprintf(preverrout_fd, p, expandstr(ps4val()));
+
+               sp = varlist.list;
+               for (n = 0; n < 2; n++) {
+                       while (sp) {
+                               dprintf(preverrout_fd, p, sp->text);
+                               sp = sp->next;
+                               if (*p == '%') {
+                                       p--;
+                               }
+                       }
+                       sp = arglist.list;
+               }
+               full_write(preverrout_fd, "\n", 1);
+       }
+
+       cmd_is_exec = 0;
+       spclbltin = -1;
+
+       /* Now locate the command. */
+       if (argc) {
+               const char *oldpath;
+               int cmd_flag = DO_ERR;
+
+               path += 5;
+               oldpath = path;
+               for (;;) {
+                       find_command(argv[0], &cmdentry, cmd_flag, path);
+                       if (cmdentry.cmdtype == CMDUNKNOWN) {
+                               status = 127;
+                               flush_stderr();
+                               goto bail;
+                       }
+
+                       /* implement bltin and command here */
+                       if (cmdentry.cmdtype != CMDBUILTIN)
+                               break;
+                       if (spclbltin < 0)
+                               spclbltin = IS_BUILTIN_SPECIAL(cmdentry.u.cmd);
+                       if (cmdentry.u.cmd == EXECCMD)
+                               cmd_is_exec++;
+#if ENABLE_ASH_CMDCMD
+                       if (cmdentry.u.cmd == COMMANDCMD) {
+                               path = oldpath;
+                               nargv = parse_command_args(argv, &path);
+                               if (!nargv)
+                                       break;
+                               argc -= nargv - argv;
+                               argv = nargv;
+                               cmd_flag |= DO_NOFUNC;
+                       } else
+#endif
+                               break;
+               }
+       }
+
+       if (status) {
+               /* We have a redirection error. */
+               if (spclbltin > 0)
+                       raise_exception(EXERROR);
+ bail:
+               exitstatus = status;
+               goto out;
+       }
+
+       /* Execute the command. */
+       switch (cmdentry.cmdtype) {
+       default:
+               /* Fork off a child process if necessary. */
+               if (!(flags & EV_EXIT) || trap[0]) {
+                       INT_OFF;
+                       jp = makejob(cmd, 1);
+                       if (forkshell(jp, cmd, FORK_FG) != 0) {
+                               exitstatus = waitforjob(jp);
+                               INT_ON;
+                               break;
+                       }
+                       FORCE_INT_ON;
+               }
+               listsetvar(varlist.list, VEXPORT|VSTACK);
+               shellexec(argv, path, cmdentry.u.index);
+               /* NOTREACHED */
+
+       case CMDBUILTIN:
+               cmdenviron = varlist.list;
+               if (cmdenviron) {
+                       struct strlist *list = cmdenviron;
+                       int i = VNOSET;
+                       if (spclbltin > 0 || argc == 0) {
+                               i = 0;
+                               if (cmd_is_exec && argc > 1)
+                                       i = VEXPORT;
+                       }
+                       listsetvar(list, i);
+               }
+               if (evalbltin(cmdentry.u.cmd, argc, argv)) {
+                       int exit_status;
+                       int i, j;
+
+                       i = exception;
+                       if (i == EXEXIT)
+                               goto raise;
+
+                       exit_status = 2;
+                       j = 0;
+                       if (i == EXINT)
+                               j = SIGINT;
+                       if (i == EXSIG)
+                               j = pendingsigs;
+                       if (j)
+                               exit_status = j + 128;
+                       exitstatus = exit_status;
 
-       for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) {
-               for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
-                       if (cmdp->cmdtype == CMDNORMAL || (
-                               cmdp->cmdtype == CMDBUILTIN &&
-                               !(IS_BUILTIN_REGULAR(cmdp->param.cmd)) &&
-                               builtinloc > 0
-                       ))
-                               cmdp->rehash = 1;
+                       if (i == EXINT || spclbltin > 0) {
+ raise:
+                               longjmp(exception_handler->loc, 1);
+                       }
+                       FORCE_INT_ON;
                }
+               break;
+
+       case CMDFUNCTION:
+               listsetvar(varlist.list, 0);
+               if (evalfun(cmdentry.u.func, argc, argv, flags))
+                       goto raise;
+               break;
        }
+
+ out:
+       popredir(cmd_is_exec);
+       if (lastarg)
+               /* dsl: I think this is intended to be used to support
+                * '_' in 'vi' command mode during line editing...
+                * However I implemented that within libedit itself.
+                */
+               setvar("_", lastarg, 0);
+       popstackmark(&smark);
 }
 
-/*
- * Fix command hash table when PATH changed.
- * Called before PATH is changed.  The argument is the new value of PATH;
- * pathval() still returns the old value at this point.
- * Called with interrupts off.
- */
-static void
-changepath(const char *newval)
+static int
+evalbltin(const struct builtincmd *cmd, int argc, char **argv)
 {
-       const char *old, *new;
-       int idx;
-       int firstchange;
-       int idx_bltin;
+       char *volatile savecmdname;
+       struct jmploc *volatile savehandler;
+       struct jmploc jmploc;
+       int i;
 
-       old = pathval();
-       new = newval;
-       firstchange = 9999;     /* assume no change */
-       idx = 0;
-       idx_bltin = -1;
-       for (;;) {
-               if (*old != *new) {
-                       firstchange = idx;
-                       if ((*old == '\0' && *new == ':')
-                        || (*old == ':' && *new == '\0'))
-                               firstchange++;
-                       old = new;      /* ignore subsequent differences */
-               }
-               if (*new == '\0')
-                       break;
-               if (*new == '%' && idx_bltin < 0 && prefix(new + 1, "builtin"))
-                       idx_bltin = idx;
-               if (*new == ':') {
-                       idx++;
-               }
-               new++, old++;
-       }
-       if (builtinloc < 0 && idx_bltin >= 0)
-               builtinloc = idx_bltin;             /* zap builtins */
-       if (builtinloc >= 0 && idx_bltin < 0)
-               firstchange = 0;
-       clearcmdentry(firstchange);
-       builtinloc = idx_bltin;
-}
+       savecmdname = commandname;
+       i = setjmp(jmploc.loc);
+       if (i)
+               goto cmddone;
+       savehandler = exception_handler;
+       exception_handler = &jmploc;
+       commandname = argv[0];
+       argptr = argv + 1;
+       optptr = NULL;                  /* initialize nextopt */
+       exitstatus = (*cmd->builtin)(argc, argv);
+       flush_stdout_stderr();
+ cmddone:
+       exitstatus |= ferror(stdout);
+       clearerr(stdout);
+       commandname = savecmdname;
+       exsig = 0;
+       exception_handler = savehandler;
 
+       return i;
+}
 
-/*
- * Make a copy of a parse tree.
- */
-static struct funcnode *
-copyfunc(union node *n)
+static int
+goodname(const char *p)
 {
-       struct funcnode *f;
-       size_t blocksize;
-
-       funcblocksize = offsetof(struct funcnode, n);
-       funcstringsize = 0;
-       calcsize(n);
-       blocksize = funcblocksize;
-       f = ckmalloc(blocksize + funcstringsize);
-       funcblock = (char *) f + offsetof(struct funcnode, n);
-       funcstring = (char *) f + blocksize;
-       copynode(n);
-       f->count = 0;
-       return f;
+       return !*endofname(p);
 }
 
+
 /*
- * Define a shell function.
+ * Search for a command.  This is called before we fork so that the
+ * location of the command will be available in the parent as well as
+ * the child.  The check for "goodname" is an overly conservative
+ * check that the name will not be subject to expansion.
  */
 static void
-defun(char *name, union node *func)
+prehash(union node *n)
 {
        struct cmdentry entry;
 
-       INT_OFF;
-       entry.cmdtype = CMDFUNCTION;
-       entry.u.func = copyfunc(func);
-       addcmdentry(name, &entry);
-       INT_ON;
+       if (n->type == NCMD && n->ncmd.args && goodname(n->ncmd.args->narg.text))
+               find_command(n->ncmd.args->narg.text, &entry, 0, pathval());
 }
 
+
 /*
- * Delete a function if it exists.
+ * Builtin commands.  Builtin commands whose functions are closely
+ * tied to evaluation are implemented here.
  */
-static void
-unsetfunc(const char *name)
-{
-       struct tblentry *cmdp;
-
-       cmdp = cmdlookup(name, 0);
-       if (cmdp!= NULL && cmdp->cmdtype == CMDFUNCTION)
-               delete_cmd_entry();
-}
-
 
 /*
- * Locate and print what a word is...
+ * Handle break and continue commands.  Break, continue, and return are
+ * all handled by setting the evalskip flag.  The evaluation routines
+ * above all check this flag, and if it is set they start skipping
+ * commands rather than executing them.  The variable skipcount is
+ * the number of loops to break/continue, or the number of function
+ * levels to return.  (The latter is always 1.)  It should probably
+ * be an error to break out of more loops than exist, but it isn't
+ * in the standard shell so we don't make it one here.
  */
-#if ENABLE_ASH_CMDCMD
-static int
-describe_command(char *command, int describe_command_verbose)
-#else
-#define describe_command_verbose 1
+
 static int
-describe_command(char *command)
-#endif
+breakcmd(int argc, char **argv)
 {
-       struct cmdentry entry;
-       struct tblentry *cmdp;
-#if ENABLE_ASH_ALIAS
-       const struct alias *ap;
-#endif
-       const char *path = pathval();
-
-       if (describe_command_verbose) {
-               out1str(command);
-       }
-
-       /* First look at the keywords */
-       if (findkwd(command)) {
-               out1str(describe_command_verbose ? " is a shell keyword" : command);
-               goto out;
-       }
-
-#if ENABLE_ASH_ALIAS
-       /* Then look at the aliases */
-       ap = lookupalias(command, 0);
-       if (ap != NULL) {
-               if (describe_command_verbose) {
-                       out1fmt(" is an alias for %s", ap->val);
-               } else {
-                       out1str("alias ");
-                       printalias(ap);
-                       return 0;
-               }
-               goto out;
-       }
-#endif
-       /* Then check if it is a tracked alias */
-       cmdp = cmdlookup(command, 0);
-       if (cmdp != NULL) {
-               entry.cmdtype = cmdp->cmdtype;
-               entry.u = cmdp->param;
-       } else {
-               /* Finally use brute force */
-               find_command(command, &entry, DO_ABS, path);
-       }
-
-       switch (entry.cmdtype) {
-       case CMDNORMAL: {
-               int j = entry.u.index;
-               char *p;
-               if (j == -1) {
-                       p = command;
-               } else {
-                       do {
-                               p = padvance(&path, command);
-                               stunalloc(p);
-                       } while (--j >= 0);
-               }
-               if (describe_command_verbose) {
-                       out1fmt(" is%s %s",
-                               (cmdp ? " a tracked alias for" : nullstr), p
-                       );
-               } else {
-                       out1str(p);
-               }
-               break;
-       }
-
-       case CMDFUNCTION:
-               if (describe_command_verbose) {
-                       out1str(" is a shell function");
-               } else {
-                       out1str(command);
-               }
-               break;
-
-       case CMDBUILTIN:
-               if (describe_command_verbose) {
-                       out1fmt(" is a %sshell builtin",
-                               IS_BUILTIN_SPECIAL(entry.u.cmd) ?
-                                       "special " : nullstr
-                       );
-               } else {
-                       out1str(command);
-               }
-               break;
+       int n = argc > 1 ? number(argv[1]) : 1;
 
-       default:
-               if (describe_command_verbose) {
-                       out1str(": not found\n");
-               }
-               return 127;
+       if (n <= 0)
+               ash_msg_and_raise_error(illnum, argv[1]);
+       if (n > loopnest)
+               n = loopnest;
+       if (n > 0) {
+               evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK;
+               skipcount = n;
        }
- out:
-       outstr("\n", stdout);
        return 0;
 }
 
+/*
+ * The return command.
+ */
 static int
-typecmd(int argc, char **argv)
+returncmd(int argc, char **argv)
 {
-       int i;
-       int err = 0;
-
-       for (i = 1; i < argc; i++) {
-#if ENABLE_ASH_CMDCMD
-               err |= describe_command(argv[i], 1);
-#else
-               err |= describe_command(argv[i]);
-#endif
-       }
-       return err;
+       /*
+        * If called outside a function, do what ksh does;
+        * skip the rest of the file.
+        */
+       evalskip = funcnest ? SKIPFUNC : SKIPFILE;
+       return argv[1] ? number(argv[1]) : exitstatus;
 }
 
-#if ENABLE_ASH_CMDCMD
 static int
-commandcmd(int argc, char **argv)
+falsecmd(int argc, char **argv)
 {
-       int c;
-       enum {
-               VERIFY_BRIEF = 1,
-               VERIFY_VERBOSE = 2,
-       } verify = 0;
+       return 1;
+}
 
-       while ((c = nextopt("pvV")) != '\0')
-               if (c == 'V')
-                       verify |= VERIFY_VERBOSE;
-               else if (c == 'v')
-                       verify |= VERIFY_BRIEF;
-#if DEBUG
-               else if (c != 'p')
-                       abort();
-#endif
-       if (verify)
-               return describe_command(*argptr, verify - VERIFY_BRIEF);
+static int
+truecmd(int argc, char **argv)
+{
+       return 0;
+}
 
+static int
+execcmd(int argc, char **argv)
+{
+       if (argc > 1) {
+               iflag = 0;              /* exit on error */
+               mflag = 0;
+               optschanged();
+               shellexec(argv + 1, pathval(), 0);
+       }
        return 0;
 }
-#endif
 
 
 /* ============ input.c
@@ -8437,314 +8670,109 @@ stoppedjobs(void)
                job_warning = 2;
                retval++;
        }
- out:
-       return retval;
-}
-
-
-/* ============ mail.c
- *
- * Routines to check for mail.
- */
-#if ENABLE_ASH_MAIL
-
-#define MAXMBOXES 10
-
-/* times of mailboxes */
-static time_t mailtime[MAXMBOXES];
-/* Set if MAIL or MAILPATH is changed. */
-static int mail_var_path_changed;
-
-/*
- * Print appropriate message(s) if mail has arrived.
- * If mail_var_path_changed is set,
- * then the value of MAIL has mail_var_path_changed,
- * so we just update the values.
- */
-static void
-chkmail(void)
-{
-       const char *mpath;
-       char *p;
-       char *q;
-       time_t *mtp;
-       struct stackmark smark;
-       struct stat statb;
-
-       setstackmark(&smark);
-       mpath = mpathset() ? mpathval() : mailval();
-       for (mtp = mailtime; mtp < mailtime + MAXMBOXES; mtp++) {
-               p = padvance(&mpath, nullstr);
-               if (p == NULL)
-                       break;
-               if (*p == '\0')
-                       continue;
-               for (q = p; *q; q++);
-#if DEBUG
-               if (q[-1] != '/')
-                       abort();
-#endif
-               q[-1] = '\0';                   /* delete trailing '/' */
-               if (stat(p, &statb) < 0) {
-                       *mtp = 0;
-                       continue;
-               }
-               if (!mail_var_path_changed && statb.st_mtime != *mtp) {
-                       fprintf(
-                               stderr, snlfmt,
-                               pathopt ? pathopt : "you have mail"
-                       );
-               }
-               *mtp = statb.st_mtime;
-       }
-       mail_var_path_changed = 0;
-       popstackmark(&smark);
-}
-
-static void
-changemail(const char *val)
-{
-       mail_var_path_changed++;
-}
-#endif /* ASH_MAIL */
-
-
-/* ============ ??? */
-
-/*
- * Take commands from a file.  To be compatible we should do a path
- * search for the file, which is necessary to find sub-commands.
- */
-static char *
-find_dot_file(char *name)
-{
-       char *fullname;
-       const char *path = pathval();
-       struct stat statb;
-
-       /* don't try this for absolute or relative paths */
-       if (strchr(name, '/'))
-               return name;
-
-       while ((fullname = padvance(&path, name)) != NULL) {
-               if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) {
-                       /*
-                        * Don't bother freeing here, since it will
-                        * be freed by the caller.
-                        */
-                       return fullname;
-               }
-               stunalloc(fullname);
-       }
-
-       /* not found in the PATH */
-       ash_msg_and_raise_error("%s: not found", name);
-       /* NOTREACHED */
-}
-
-static void
-calcsize(union node *n)
-{
-       if (n == NULL)
-               return;
-       funcblocksize += nodesize[n->type];
-       switch (n->type) {
-       case NCMD:
-               calcsize(n->ncmd.redirect);
-               calcsize(n->ncmd.args);
-               calcsize(n->ncmd.assign);
-               break;
-       case NPIPE:
-               sizenodelist(n->npipe.cmdlist);
-               break;
-       case NREDIR:
-       case NBACKGND:
-       case NSUBSHELL:
-               calcsize(n->nredir.redirect);
-               calcsize(n->nredir.n);
-               break;
-       case NAND:
-       case NOR:
-       case NSEMI:
-       case NWHILE:
-       case NUNTIL:
-               calcsize(n->nbinary.ch2);
-               calcsize(n->nbinary.ch1);
-               break;
-       case NIF:
-               calcsize(n->nif.elsepart);
-               calcsize(n->nif.ifpart);
-               calcsize(n->nif.test);
-               break;
-       case NFOR:
-               funcstringsize += strlen(n->nfor.var) + 1;
-               calcsize(n->nfor.body);
-               calcsize(n->nfor.args);
-               break;
-       case NCASE:
-               calcsize(n->ncase.cases);
-               calcsize(n->ncase.expr);
-               break;
-       case NCLIST:
-               calcsize(n->nclist.body);
-               calcsize(n->nclist.pattern);
-               calcsize(n->nclist.next);
-               break;
-       case NDEFUN:
-       case NARG:
-               sizenodelist(n->narg.backquote);
-               funcstringsize += strlen(n->narg.text) + 1;
-               calcsize(n->narg.next);
-               break;
-       case NTO:
-       case NCLOBBER:
-       case NFROM:
-       case NFROMTO:
-       case NAPPEND:
-               calcsize(n->nfile.fname);
-               calcsize(n->nfile.next);
-               break;
-       case NTOFD:
-       case NFROMFD:
-               calcsize(n->ndup.vname);
-               calcsize(n->ndup.next);
-       break;
-       case NHERE:
-       case NXHERE:
-               calcsize(n->nhere.doc);
-               calcsize(n->nhere.next);
-               break;
-       case NNOT:
-               calcsize(n->nnot.com);
-               break;
-       };
-}
-
-static void
-sizenodelist(struct nodelist *lp)
-{
-       while (lp) {
-               funcblocksize += SHELL_ALIGN(sizeof(struct nodelist));
-               calcsize(lp->n);
-               lp = lp->next;
-       }
+ out:
+       return retval;
 }
 
-static union node *
-copynode(union node *n)
-{
-       union node *new;
 
-       if (n == NULL)
-               return NULL;
-       new = funcblock;
-       funcblock = (char *) funcblock + nodesize[n->type];
+/* ============ mail.c
+ *
+ * Routines to check for mail.
+ */
+#if ENABLE_ASH_MAIL
 
-       switch (n->type) {
-       case NCMD:
-               new->ncmd.redirect = copynode(n->ncmd.redirect);
-               new->ncmd.args = copynode(n->ncmd.args);
-               new->ncmd.assign = copynode(n->ncmd.assign);
-               break;
-       case NPIPE:
-               new->npipe.cmdlist = copynodelist(n->npipe.cmdlist);
-               new->npipe.backgnd = n->npipe.backgnd;
-               break;
-       case NREDIR:
-       case NBACKGND:
-       case NSUBSHELL:
-               new->nredir.redirect = copynode(n->nredir.redirect);
-               new->nredir.n = copynode(n->nredir.n);
-               break;
-       case NAND:
-       case NOR:
-       case NSEMI:
-       case NWHILE:
-       case NUNTIL:
-               new->nbinary.ch2 = copynode(n->nbinary.ch2);
-               new->nbinary.ch1 = copynode(n->nbinary.ch1);
-               break;
-       case NIF:
-               new->nif.elsepart = copynode(n->nif.elsepart);
-               new->nif.ifpart = copynode(n->nif.ifpart);
-               new->nif.test = copynode(n->nif.test);
-               break;
-       case NFOR:
-               new->nfor.var = nodeckstrdup(n->nfor.var);
-               new->nfor.body = copynode(n->nfor.body);
-               new->nfor.args = copynode(n->nfor.args);
-               break;
-       case NCASE:
-               new->ncase.cases = copynode(n->ncase.cases);
-               new->ncase.expr = copynode(n->ncase.expr);
-               break;
-       case NCLIST:
-               new->nclist.body = copynode(n->nclist.body);
-               new->nclist.pattern = copynode(n->nclist.pattern);
-               new->nclist.next = copynode(n->nclist.next);
-               break;
-       case NDEFUN:
-       case NARG:
-               new->narg.backquote = copynodelist(n->narg.backquote);
-               new->narg.text = nodeckstrdup(n->narg.text);
-               new->narg.next = copynode(n->narg.next);
-               break;
-       case NTO:
-       case NCLOBBER:
-       case NFROM:
-       case NFROMTO:
-       case NAPPEND:
-               new->nfile.fname = copynode(n->nfile.fname);
-               new->nfile.fd = n->nfile.fd;
-               new->nfile.next = copynode(n->nfile.next);
-               break;
-       case NTOFD:
-       case NFROMFD:
-               new->ndup.vname = copynode(n->ndup.vname);
-               new->ndup.dupfd = n->ndup.dupfd;
-               new->ndup.fd = n->ndup.fd;
-               new->ndup.next = copynode(n->ndup.next);
-               break;
-       case NHERE:
-       case NXHERE:
-               new->nhere.doc = copynode(n->nhere.doc);
-               new->nhere.fd = n->nhere.fd;
-               new->nhere.next = copynode(n->nhere.next);
-               break;
-       case NNOT:
-               new->nnot.com = copynode(n->nnot.com);
-               break;
-       };
-       new->type = n->type;
-       return new;
-}
+#define MAXMBOXES 10
 
-static struct nodelist *
-copynodelist(struct nodelist *lp)
+/* times of mailboxes */
+static time_t mailtime[MAXMBOXES];
+/* Set if MAIL or MAILPATH is changed. */
+static int mail_var_path_changed;
+
+/*
+ * Print appropriate message(s) if mail has arrived.
+ * If mail_var_path_changed is set,
+ * then the value of MAIL has mail_var_path_changed,
+ * so we just update the values.
+ */
+static void
+chkmail(void)
 {
-       struct nodelist *start;
-       struct nodelist **lpp;
+       const char *mpath;
+       char *p;
+       char *q;
+       time_t *mtp;
+       struct stackmark smark;
+       struct stat statb;
 
-       lpp = &start;
-       while (lp) {
-               *lpp = funcblock;
-               funcblock = (char *) funcblock + SHELL_ALIGN(sizeof(struct nodelist));
-               (*lpp)->n = copynode(lp->n);
-               lp = lp->next;
-               lpp = &(*lpp)->next;
+       setstackmark(&smark);
+       mpath = mpathset() ? mpathval() : mailval();
+       for (mtp = mailtime; mtp < mailtime + MAXMBOXES; mtp++) {
+               p = padvance(&mpath, nullstr);
+               if (p == NULL)
+                       break;
+               if (*p == '\0')
+                       continue;
+               for (q = p; *q; q++);
+#if DEBUG
+               if (q[-1] != '/')
+                       abort();
+#endif
+               q[-1] = '\0';                   /* delete trailing '/' */
+               if (stat(p, &statb) < 0) {
+                       *mtp = 0;
+                       continue;
+               }
+               if (!mail_var_path_changed && statb.st_mtime != *mtp) {
+                       fprintf(
+                               stderr, snlfmt,
+                               pathopt ? pathopt : "you have mail"
+                       );
+               }
+               *mtp = statb.st_mtime;
        }
-       *lpp = NULL;
-       return start;
+       mail_var_path_changed = 0;
+       popstackmark(&smark);
+}
+
+static void
+changemail(const char *val)
+{
+       mail_var_path_changed++;
 }
+#endif /* ASH_MAIL */
 
+
+/* ============ ??? */
+
+/*
+ * Take commands from a file.  To be compatible we should do a path
+ * search for the file, which is necessary to find sub-commands.
+ */
 static char *
-nodeckstrdup(char *s)
+find_dot_file(char *name)
 {
-       char *rtn = funcstring;
+       char *fullname;
+       const char *path = pathval();
+       struct stat statb;
 
-       strcpy(funcstring, s);
-       funcstring += strlen(s) + 1;
-       return rtn;
+       /* don't try this for absolute or relative paths */
+       if (strchr(name, '/'))
+               return name;
+
+       while ((fullname = padvance(&path, name)) != NULL) {
+               if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) {
+                       /*
+                        * Don't bother freeing here, since it will
+                        * be freed by the caller.
+                        */
+                       return fullname;
+               }
+               stunalloc(fullname);
+       }
+
+       /* not found in the PATH */
+       ash_msg_and_raise_error("%s: not found", name);
+       /* NOTREACHED */
 }
 
 /*
@@ -8819,7 +8847,6 @@ minus_o(char *name, int val)
                                optlist[i] ? "on" : "off");
 }
 
-
 static void
 setoption(int flag, int val)
 {
@@ -11450,65 +11477,16 @@ exportcmd(int argc, char **argv)
 }
 
 /*
- * Make a variable a local variable.  When a variable is made local, it's
- * value and flags are saved in a localvar structure.  The saved values
- * will be restored when the shell function returns.  We handle the name
- * "-" as a special case.
+ * Delete a function if it exists.
  */
 static void
-mklocal(char *name)
-{
-       struct localvar *lvp;
-       struct var **vpp;
-       struct var *vp;
-
-       INT_OFF;
-       lvp = ckmalloc(sizeof(struct localvar));
-       if (LONE_DASH(name)) {
-               char *p;
-               p = ckmalloc(sizeof(optlist));
-               lvp->text = memcpy(p, optlist, sizeof(optlist));
-               vp = NULL;
-       } else {
-               char *eq;
-
-               vpp = hashvar(name);
-               vp = *findvar(vpp, name);
-               eq = strchr(name, '=');
-               if (vp == NULL) {
-                       if (eq)
-                               setvareq(name, VSTRFIXED);
-                       else
-                               setvar(name, NULL, VSTRFIXED);
-                       vp = *vpp;      /* the new variable */
-                       lvp->flags = VUNSET;
-               } else {
-                       lvp->text = vp->text;
-                       lvp->flags = vp->flags;
-                       vp->flags |= VSTRFIXED|VTEXTFIXED;
-                       if (eq)
-                               setvareq(name, 0);
-               }
-       }
-       lvp->vp = vp;
-       lvp->next = localvars;
-       localvars = lvp;
-       INT_ON;
-}
-
-/*
- * The "local" command.
- */
-static int
-localcmd(int argc, char **argv)
+unsetfunc(const char *name)
 {
-       char *name;
+       struct tblentry *cmdp;
 
-       argv = argptr;
-       while ((name = *argv++) != NULL) {
-               mklocal(name);
-       }
-       return 0;
+       cmdp = cmdlookup(name, 0);
+       if (cmdp!= NULL && cmdp->cmdtype == CMDFUNCTION)
+               delete_cmd_entry();
 }
 
 /*