ash: cleanup part 2
authorDenis Vlasenko <vda.linux@googlemail.com>
Fri, 23 Feb 2007 01:04:22 +0000 (01:04 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Fri, 23 Feb 2007 01:04:22 +0000 (01:04 -0000)
shell/ash.c

index 379e8ab7f6257cc2774a5c18730bcb7a7870e40d..731b07996805f3a9daf1407e55491b879ca4331c 100644 (file)
 #error "Do not even bother, ash will not run on uClinux"
 #endif
 
+#if DEBUG
+#define TRACE(param)    trace param
+#define TRACEV(param)   tracev param
+#else
+#define TRACE(param)
+#define TRACEV(param)
+#endif
 
 #ifdef __GLIBC__
 /* glibc sucks */
@@ -141,6 +148,11 @@ static char optlist[NOPTS];
 
 /* ============ Misc data */
 
+static char nullstr[1];                /* zero length string */
+static const char homestr[] = "HOME";
+static const char snlfmt[] = "%s\n";
+static const char illnum[] = "Illegal number: %s";
+
 static int isloginsh;
 /* pid of main shell */
 static int rootpid;
@@ -380,6 +392,11 @@ out2str(const char *p)
  * ash_vmsg() needs parsefile->fd, hence parsefile definition is moved up.
  */
 
+struct strlist {
+       struct strlist *next;
+       char *text;
+};
+
 struct strpush {
        struct strpush *prev;   /* preceding string on stack */
        char *prevstring;
@@ -623,6 +640,16 @@ stunalloc(void *p)
        stacknxt = p;
 }
 
+/*
+ * Like strdup but works with the ash stack.
+ */
+static char *
+ststrdup(const char *p)
+{
+       size_t len = strlen(p) + 1;
+       return memcpy(stalloc(len), p, len);
+}
+
 static void
 setstackmark(struct stackmark *mark)
 {
@@ -785,584 +812,1602 @@ stack_putstr(const char *s, char *p)
        return stack_nputstr(s, strlen(s), p);
 }
 
+static char *
+_STPUTC(int c, char *p)
+{
+       if (p == sstrend)
+               p = growstackstr();
+       *p++ = c;
+       return p;
+}
 
-/* ============ Unsorted yet */
-
-
-static void setpwd(const char *, int);
-
-/*      expand.h     */
+#define STARTSTACKSTR(p) ((p) = stackblock())
+#define STPUTC(c, p) ((p) = _STPUTC((c), (p)))
+#define CHECKSTRSPACE(n, p) \
+       ({ \
+               char *q = (p); \
+               size_t l = (n); \
+               size_t m = sstrend - q; \
+               if (l > m) \
+                       (p) = makestrspace(l, q); \
+               0; \
+       })
+#define USTPUTC(c, p)           (*p++ = (c))
+#define STACKSTRNUL(p)          ((p) == sstrend ? (p = growstackstr(), *p = '\0') : (*p = '\0'))
+#define STUNPUTC(p)             (--p)
+#define STTOPC(p)               p[-1]
+#define STADJUST(amount, p)     (p += (amount))
 
-struct strlist {
-       struct strlist *next;
-       char *text;
-};
+#define grabstackstr(p)         stalloc((char *)(p) - (char *)stackblock())
+#define ungrabstackstr(s, p)    stunalloc((s))
+#define stackstrend()           ((void *)sstrend)
 
 
-struct arglist {
-       struct strlist *list;
-       struct strlist **lastp;
-};
+/* ============ String helpers */
 
 /*
- * expandarg() flags
+ * prefix -- see if pfx is a prefix of string.
  */
-#define EXP_FULL        0x1     /* perform word splitting & file globbing */
-#define EXP_TILDE       0x2     /* do normal tilde expansion */
-#define EXP_VARTILDE    0x4     /* expand tildes in an assignment */
-#define EXP_REDIR       0x8     /* file glob for a redirection (1 match only) */
-#define EXP_CASE        0x10    /* keeps quotes around for CASE pattern */
-#define EXP_RECORD      0x20    /* need to record arguments for ifs breakup */
-#define EXP_VARTILDE2   0x40    /* expand tildes after colons only */
-#define EXP_WORD        0x80    /* expand word in parameter expansion */
-#define EXP_QWORD       0x100   /* expand word in quoted parameter expansion */
-
-
-union node;
-static void expandarg(union node *, struct arglist *, int);
-#define rmescapes(p) _rmescapes((p), 0)
-static char *_rmescapes(char *, int);
-static int casematch(union node *, char *);
-
-#if ENABLE_ASH_MATH_SUPPORT
-static void expari(int);
-#endif
-
-/*      eval.h       */
-
+static char *
+prefix(const char *string, const char *pfx)
+{
+       while (*pfx) {
+               if (*pfx++ != *string++)
+                       return 0;
+       }
+       return (char *) string;
+}
 
+/*
+ * Check for a valid number.  This should be elsewhere.
+ */
+static int
+is_number(const char *p)
+{
+       do {
+               if (!isdigit(*p))
+                       return 0;
+       } while (*++p != '\0');
+       return 1;
+}
 
-struct backcmd {                /* result of evalbackcmd */
-       int fd;                 /* file descriptor to read from */
-       char *buf;              /* buffer */
-       int nleft;              /* number of chars in buffer */
-       struct job *jp;         /* job structure for command */
-};
+/*
+ * Convert a string of digits to an integer, printing an error message on
+ * failure.
+ */
+static int
+number(const char *s)
+{
+       if (!is_number(s))
+               ash_msg_and_raise_error(illnum, s);
+       return atoi(s);
+}
 
 /*
- * This file was generated by the mknodes program.
+ * Produce a possibly single quoted string suitable as input to the shell.
+ * The return string is allocated on the stack.
  */
+static char *
+single_quote(const char *s)
+{
+       char *p;
 
-#define NCMD 0
-#define NPIPE 1
-#define NREDIR 2
-#define NBACKGND 3
-#define NSUBSHELL 4
-#define NAND 5
-#define NOR 6
-#define NSEMI 7
-#define NIF 8
-#define NWHILE 9
-#define NUNTIL 10
-#define NFOR 11
-#define NCASE 12
-#define NCLIST 13
-#define NDEFUN 14
-#define NARG 15
-#define NTO 16
-#define NCLOBBER 17
-#define NFROM 18
-#define NFROMTO 19
-#define NAPPEND 20
-#define NTOFD 21
-#define NFROMFD 22
-#define NHERE 23
-#define NXHERE 24
-#define NNOT 25
+       STARTSTACKSTR(p);
 
+       do {
+               char *q;
+               size_t len;
 
-struct ncmd {
-       int type;
-       union node *assign;
-       union node *args;
-       union node *redirect;
-};
+               len = strchrnul(s, '\'') - s;
 
-struct npipe {
-       int type;
-       int backgnd;
-       struct nodelist *cmdlist;
-};
+               q = p = makestrspace(len + 3, p);
 
-struct nredir {
-       int type;
-       union node *n;
-       union node *redirect;
-};
+               *q++ = '\'';
+               q = memcpy(q, s, len) + len;
+               *q++ = '\'';
+               s += len;
 
-struct nbinary {
-       int type;
-       union node *ch1;
-       union node *ch2;
-};
+               STADJUST(q - p, p);
 
-struct nif {
-       int type;
-       union node *test;
-       union node *ifpart;
-       union node *elsepart;
-};
+               len = strspn(s, "'");
+               if (!len)
+                       break;
 
-struct nfor {
-       int type;
-       union node *args;
-       union node *body;
-       char *var;
-};
+               q = p = makestrspace(len + 3, p);
 
-struct ncase {
-       int type;
-       union node *expr;
-       union node *cases;
-};
+               *q++ = '"';
+               q = memcpy(q, s, len) + len;
+               *q++ = '"';
+               s += len;
 
-struct nclist {
-       int type;
-       union node *next;
-       union node *pattern;
-       union node *body;
-};
+               STADJUST(q - p, p);
+       } while (*s);
 
-struct narg {
-       int type;
-       union node *next;
-       char *text;
-       struct nodelist *backquote;
-};
+       USTPUTC(0, p);
 
-struct nfile {
-       int type;
-       union node *next;
-       int fd;
-       union node *fname;
-       char *expfname;
-};
+       return stackblock();
+}
 
-struct ndup {
-       int type;
-       union node *next;
-       int fd;
-       int dupfd;
-       union node *vname;
-};
 
-struct nhere {
-       int type;
-       union node *next;
-       int fd;
-       union node *doc;
-};
+/* ============ ... */
 
-struct nnot {
-       int type;
-       union node *com;
-};
+static char **argptr;                  /* argument list for builtin commands */
+static char *optionarg;                /* set by nextopt (like getopt) */
+static char *optptr;                   /* used by nextopt */
 
-union node {
-       int type;
-       struct ncmd ncmd;
-       struct npipe npipe;
-       struct nredir nredir;
-       struct nbinary nbinary;
-       struct nif nif;
-       struct nfor nfor;
-       struct ncase ncase;
-       struct nclist nclist;
-       struct narg narg;
-       struct nfile nfile;
-       struct ndup ndup;
-       struct nhere nhere;
-       struct nnot nnot;
-};
+/*
+ * XXX - should get rid of.  have all builtins use getopt(3).  the
+ * library getopt must have the BSD extension static variable "optreset"
+ * otherwise it can't be used within the shell safely.
+ *
+ * Standard option processing (a la getopt) for builtin routines.  The
+ * only argument that is passed to nextopt is the option string; the
+ * other arguments are unnecessary.  It return the character, or '\0' on
+ * end of input.
+ */
+static int
+nextopt(const char *optstring)
+{
+       char *p;
+       const char *q;
+       char c;
 
-struct nodelist {
-       struct nodelist *next;
-       union node *n;
-};
+       p = optptr;
+       if (p == NULL || *p == '\0') {
+               p = *argptr;
+               if (p == NULL || *p != '-' || *++p == '\0')
+                       return '\0';
+               argptr++;
+               if (LONE_DASH(p))        /* check for "--" */
+                       return '\0';
+       }
+       c = *p++;
+       for (q = optstring; *q != c; ) {
+               if (*q == '\0')
+                       ash_msg_and_raise_error("Illegal option -%c", c);
+               if (*++q == ':')
+                       q++;
+       }
+       if (*++q == ':') {
+               if (*p == '\0' && (p = *argptr++) == NULL)
+                       ash_msg_and_raise_error("No arg for -%c option", c);
+               optionarg = p;
+               p = NULL;
+       }
+       optptr = p;
+       return c;
+}
 
-struct funcnode {
-       int count;
-       union node n;
-};
 
+/* ============ Variables */
 
-static void freefunc(struct funcnode *);
-/*      parser.h     */
+/* flags */
+#define VEXPORT         0x01    /* variable is exported */
+#define VREADONLY       0x02    /* variable cannot be modified */
+#define VSTRFIXED       0x04    /* variable struct is statically allocated */
+#define VTEXTFIXED      0x08    /* text is statically allocated */
+#define VSTACK          0x10    /* text is allocated on the stack */
+#define VUNSET          0x20    /* the variable is not set */
+#define VNOFUNC         0x40    /* don't call the callback function */
+#define VNOSET          0x80    /* do not set variable - just readonly test */
+#define VNOSAVE         0x100   /* when text is on the heap before setvareq */
+#ifdef DYNAMIC_VAR
+# define VDYNAMIC        0x200   /* dynamic variable */
+# else
+# define VDYNAMIC        0
+#endif
 
-/* 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 */
+#if ENABLE_LOCALE_SUPPORT
+static void change_lc_all(const char *value);
+static void change_lc_ctype(const char *value);
+#endif
 
-/* 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 */
+static const char defpathvar[] = "PATH=/usr/local/bin:/usr/bin:/sbin:/bin";
+#ifdef IFS_BROKEN
+static const char defifsvar[] = "IFS= \t\n";
+#define defifs (defifsvar + 4)
+#else
+static const char defifs[] = " \t\n";
+#endif
 
-/* 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} */
+struct var {
+       struct var *next;               /* next entry in hash list */
+       int flags;                      /* flags are defined above */
+       const char *text;               /* name=value */
+       void (*func)(const char *);     /* function to be called when  */
+                                       /* the variable gets set/unset */
+};
 
-/* values of checkkwd variable */
-#define CHKALIAS        0x1
-#define CHKKWD          0x2
-#define CHKNL           0x4
+struct localvar {
+       struct localvar *next;          /* next local variable in list */
+       struct var *vp;                 /* the variable that was made local */
+       int flags;                      /* saved flags */
+       const char *text;               /* saved text */
+};
 
-#define IBUFSIZ (BUFSIZ + 1)
+/* Forward decls for varinit[] */
+#if ENABLE_ASH_MAIL
+static void chkmail(void);
+static void changemail(const char *);
+#endif
+static void changepath(const char *);
+#if ENABLE_ASH_GETOPTS
+static void getoptsreset(const char *);
+#endif
+#if ENABLE_ASH_RANDOM_SUPPORT
+static void change_random(const char *);
+#endif
+
+static struct var varinit[] = {
+#ifdef IFS_BROKEN
+       { 0,    VSTRFIXED|VTEXTFIXED,           defifsvar,      0 },
+#else
+       { 0,    VSTRFIXED|VTEXTFIXED|VUNSET,    "IFS\0",        0 },
+#endif
+
+#if ENABLE_ASH_MAIL
+       { 0,    VSTRFIXED|VTEXTFIXED|VUNSET,    "MAIL\0",       changemail },
+       { 0,    VSTRFIXED|VTEXTFIXED|VUNSET,    "MAILPATH\0",   changemail },
+#endif
+
+       { 0,    VSTRFIXED|VTEXTFIXED,           defpathvar,     changepath },
+       { 0,    VSTRFIXED|VTEXTFIXED,           "PS1=$ ",       0          },
+       { 0,    VSTRFIXED|VTEXTFIXED,           "PS2=> ",       0          },
+       { 0,    VSTRFIXED|VTEXTFIXED,           "PS4=+ ",       0          },
+#if ENABLE_ASH_GETOPTS
+       { 0,    VSTRFIXED|VTEXTFIXED,           "OPTIND=1",     getoptsreset },
+#endif
+#if ENABLE_ASH_RANDOM_SUPPORT
+       {0, VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM\0", change_random },
+#endif
+#if ENABLE_LOCALE_SUPPORT
+       {0, VSTRFIXED | VTEXTFIXED | VUNSET, "LC_ALL\0", change_lc_all },
+       {0, VSTRFIXED | VTEXTFIXED | VUNSET, "LC_CTYPE\0", change_lc_ctype },
+#endif
+#if ENABLE_FEATURE_EDITING_SAVEHISTORY
+       {0, VSTRFIXED | VTEXTFIXED | VUNSET, "HISTFILE\0", NULL },
+#endif
+};
+
+#define vifs varinit[0]
+#if ENABLE_ASH_MAIL
+#define vmail (&vifs)[1]
+#define vmpath (&vmail)[1]
+#else
+#define vmpath vifs
+#endif
+#define vpath (&vmpath)[1]
+#define vps1 (&vpath)[1]
+#define vps2 (&vps1)[1]
+#define vps4 (&vps2)[1]
+#define voptind (&vps4)[1]
+#if ENABLE_ASH_GETOPTS
+#define vrandom (&voptind)[1]
+#else
+#define vrandom (&vps4)[1]
+#endif
+#define defpath (defpathvar + 5)
 
 /*
- * NEOF is returned by parsecmd when it encounters an end of file.  It
- * must be distinct from NULL, so we use the address of a variable that
- * happens to be handy.
+ * The following macros access the values of the above variables.
+ * They have to skip over the name.  They return the null string
+ * for unset variables.
  */
-static int plinno = 1;                  /* input line number */
+#define ifsval()        (vifs.text + 4)
+#define ifsset()        ((vifs.flags & VUNSET) == 0)
+#define mailval()       (vmail.text + 5)
+#define mpathval()      (vmpath.text + 9)
+#define pathval()       (vpath.text + 5)
+#define ps1val()        (vps1.text + 4)
+#define ps2val()        (vps2.text + 4)
+#define ps4val()        (vps4.text + 4)
+#define optindval()     (voptind.text + 7)
 
-/* number of characters left in input buffer */
-static int parsenleft;                  /* copy of parsefile->nleft */
-static int parselleft;                  /* copy of parsefile->lleft */
+#define mpathset()      ((vmpath.flags & VUNSET) == 0)
 
-/* next character in input buffer */
-static char *parsenextc;                /* copy of parsefile->nextc */
+static struct var **hashvar(const char *);
 
-#define basebuf bb_common_bufsiz1       /* buffer for top level input file */
+static int loopnest;            /* current loop nesting level */
 
+/*
+ * The parsefile structure pointed to by the global variable parsefile
+ * contains information about the current file being read.
+ */
+struct redirtab {
+       struct redirtab *next;
+       int renamed[10];
+       int nullredirs;
+};
 
-static int tokpushback;                 /* last token pushed back */
-#define NEOF ((union node *)&tokpushback)
-static int parsebackquote;             /* nonzero if we are inside backquotes */
-static int doprompt;                   /* if set, prompt the user */
-static int needprompt;                 /* true if interactive and at start of line */
-static int lasttoken;                  /* last token read */
-static char *wordtext;                 /* text of last word returned by readtoken */
-static int checkkwd;
-static struct nodelist *backquotelist;
-static union node *redirnode;
-static struct heredoc *heredoc;
-static int quoteflag;                  /* set if (part of) last token was quoted */
+static struct redirtab *redirlist;
+static int nullredirs;
 
-static void fixredir(union node *, const char *, int);
-static char *endofname(const char *);
+extern char **environ;
 
-/*      shell.h   */
+static int preverrout_fd;   /* save fd2 before print debug if xflag is set. */
 
-static char nullstr[1];                /* zero length string */
-static const char spcstr[] = " ";
-static const char snlfmt[] = "%s\n";
-static const char dolatstr[] = { CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' };
-static const char illnum[] = "Illegal number: %s";
-static const char homestr[] = "HOME";
-
-#if DEBUG
-#define TRACE(param)    trace param
-#define TRACEV(param)   tracev param
-#else
-#define TRACE(param)
-#define TRACEV(param)
+struct shparam {
+       int nparam;             /* # of positional parameters (without $0) */
+       unsigned char malloc;   /* if parameter list dynamically allocated */
+       char **p;               /* parameter list */
+#if ENABLE_ASH_GETOPTS
+       int optind;             /* next parameter to be processed by getopts */
+       int optoff;             /* used by getopts */
 #endif
+};
 
-#if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96)
-#define __builtin_expect(x, expected_value) (x)
-#endif
+static struct shparam shellparam;      /* $@ current positional parameters */
 
-#define xlikely(x)       __builtin_expect((x),1)
+#define VTABSIZE 39
 
+static struct var *vartab[VTABSIZE];
 
-#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
+#if ENABLE_ASH_GETOPTS
+static void
+getoptsreset(const char *value)
+{
+       shellparam.optind = number(value);
+       shellparam.optoff = -1;
+}
+#endif
 
-/* 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 is_name(c)      ((c) == '_' || isalpha((unsigned char)(c)))
+#define is_in_name(c)   ((c) == '_' || isalnum((unsigned char)(c)))
 
-static const char *
-tokname(int tok)
+/*
+ * Return of a legal variable name (a letter or underscore followed by zero or
+ * more letters, underscores, and digits).
+ */
+static char *
+endofname(const char *name)
 {
-       static char buf[16];
+       char *p;
 
-       if (tok >= TSEMI)
-               buf[0] = '"';
-       sprintf(buf + (tok >= TSEMI), "%s%c",
-                       tokname_array[tok] + 1, (tok >= TSEMI ? '"' : 0));
-       return buf;
+       p = (char *) name;
+       if (!is_name(*p))
+               return p;
+       while (*++p) {
+               if (!is_in_name(*p))
+                       break;
+       }
+       return p;
 }
 
-/* Wrapper around strcmp for qsort/bsearch/... */
+/*
+ * Compares two strings up to the first = or '\0'.  The first
+ * string must be terminated by '='; the second may be terminated by
+ * either '=' or '\0'.
+ */
 static int
-pstrcmp(const void *a, const void *b)
+varcmp(const char *p, const char *q)
 {
-       return strcmp((const char *) a, (*(const char *const *) b) + 1);
+       int c, d;
+
+       while ((c = *p) == (d = *q)) {
+               if (!c || c == '=')
+                       goto out;
+               p++;
+               q++;
+       }
+       if (c == '=')
+               c = 0;
+       if (d == '=')
+               d = 0;
+ out:
+       return c - d;
 }
 
-static const char *const *
-findkwd(const char *s)
+static int
+varequal(const char *a, const char *b)
 {
-       return bsearch(s, tokname_array + KWDOFFSET,
-                       (sizeof(tokname_array) / sizeof(const char *)) - KWDOFFSET,
-                       sizeof(const char *), pstrcmp);
+       return !varcmp(a, b);
 }
 
-/* Syntax classes */
-#define CWORD 0                 /* character is nothing special */
-#define CNL 1                   /* newline character */
-#define CBACK 2                 /* a backslash character */
-#define CSQUOTE 3               /* single quote */
-#define CDQUOTE 4               /* double quote */
-#define CENDQUOTE 5             /* a terminating quote */
-#define CBQUOTE 6               /* backwards single quote */
-#define CVAR 7                  /* a dollar sign */
-#define CENDVAR 8               /* a '}' character */
-#define CLP 9                   /* a left paren in arithmetic */
-#define CRP 10                  /* a right paren in arithmetic */
-#define CENDFILE 11             /* end of file */
-#define CCTL 12                 /* like CWORD, except it must be escaped */
-#define CSPCL 13                /* these terminate a word */
-#define CIGN 14                 /* character should be ignored */
-
-#if ENABLE_ASH_ALIAS
-#define SYNBASE 130
-#define PEOF -130
-#define PEOA -129
-#define PEOA_OR_PEOF PEOA
-#else
-#define SYNBASE 129
-#define PEOF -129
-#define PEOA_OR_PEOF PEOF
-#endif
+/*
+ * Find the appropriate entry in the hash table from the name.
+ */
+static struct var **
+hashvar(const char *p)
+{
+       unsigned hashval;
 
-#define is_digit(c)     ((unsigned)((c) - '0') <= 9)
-#define is_name(c)      ((c) == '_' || isalpha((unsigned char)(c)))
-#define is_in_name(c)   ((c) == '_' || isalnum((unsigned char)(c)))
+       hashval = ((unsigned char) *p) << 4;
+       while (*p && *p != '=')
+               hashval += (unsigned char) *p++;
+       return &vartab[hashval % VTABSIZE];
+}
 
-/* C99 say: "char" declaration may be signed or unsigned default */
-#define SC2INT(chr2may_be_negative_int) (int)((signed char)chr2may_be_negative_int)
+static int
+vpcmp(const void *a, const void *b)
+{
+       return varcmp(*(const char **)a, *(const char **)b);
+}
 
 /*
- * is_special(c) evaluates to 1 for c in "!#$*-0123456789?@"; 0 otherwise
- * (assuming ascii char codes, as the original implementation did)
+ * This routine initializes the builtin variables.
  */
-#define is_special(c) \
-       ( (((unsigned int)c) - 33 < 32) \
-                        && ((0xc1ff920dUL >> (((unsigned int)c) - 33)) & 1))
+static void
+initvar(void)
+{
+       struct var *vp;
+       struct var *end;
+       struct var **vpp;
 
-#define digit_val(c)    ((c) - '0')
+       /*
+        * PS1 depends on uid
+        */
+#if ENABLE_FEATURE_EDITING && ENABLE_FEATURE_EDITING_FANCY_PROMPT
+       vps1.text = "PS1=\\w \\$ ";
+#else
+       if (!geteuid())
+               vps1.text = "PS1=# ";
+#endif
+       vp = varinit;
+       end = vp + sizeof(varinit) / sizeof(varinit[0]);
+       do {
+               vpp = hashvar(vp->text);
+               vp->next = *vpp;
+               *vpp = vp;
+       } while (++vp < end);
+}
+
+static struct var **
+findvar(struct var **vpp, const char *name)
+{
+       for (; *vpp; vpp = &(*vpp)->next) {
+               if (varequal((*vpp)->text, name)) {
+                       break;
+               }
+       }
+       return vpp;
+}
 
 /*
- * This file was generated by the mksyntax program.
+ * Find the value of a variable.  Returns NULL if not set.
  */
+static char *
+lookupvar(const char *name)
+{
+       struct var *v;
 
-#if ENABLE_ASH_OPTIMIZE_FOR_SIZE
-#define USE_SIT_FUNCTION
+       v = *findvar(hashvar(name), name);
+       if (v) {
+#ifdef DYNAMIC_VAR
+       /*
+        * Dynamic variables are implemented roughly the same way they are
+        * in bash. Namely, they're "special" so long as they aren't unset.
+        * As soon as they're unset, they're no longer dynamic, and dynamic
+        * lookup will no longer happen at that point. -- PFM.
+        */
+               if ((v->flags & VDYNAMIC))
+                       (*v->func)(NULL);
 #endif
+               if (!(v->flags & VUNSET))
+                       return strchrnul(v->text, '=') + 1;
+       }
+       return NULL;
+}
 
-/* number syntax index */
-#define  BASESYNTAX  0  /* not in quotes */
-#define  DQSYNTAX    1  /* in double quotes */
-#define  SQSYNTAX    2  /* in single quotes */
-#define  ARISYNTAX   3  /* in arithmetic */
-
-#if ENABLE_ASH_MATH_SUPPORT
-static const char S_I_T[][4] = {
-#if ENABLE_ASH_ALIAS
-       {CSPCL, CIGN, CIGN, CIGN},              /* 0, PEOA */
-#endif
-       {CSPCL, CWORD, CWORD, CWORD},           /* 1, ' ' */
-       {CNL, CNL, CNL, CNL},                   /* 2, \n */
-       {CWORD, CCTL, CCTL, CWORD},             /* 3, !*-/:=?[]~ */
-       {CDQUOTE, CENDQUOTE, CWORD, CWORD},     /* 4, '"' */
-       {CVAR, CVAR, CWORD, CVAR},              /* 5, $ */
-       {CSQUOTE, CWORD, CENDQUOTE, CWORD},     /* 6, "'" */
-       {CSPCL, CWORD, CWORD, CLP},             /* 7, ( */
-       {CSPCL, CWORD, CWORD, CRP},             /* 8, ) */
-       {CBACK, CBACK, CCTL, CBACK},            /* 9, \ */
-       {CBQUOTE, CBQUOTE, CWORD, CBQUOTE},     /* 10, ` */
-       {CENDVAR, CENDVAR, CWORD, CENDVAR},     /* 11, } */
-#ifndef USE_SIT_FUNCTION
-       {CENDFILE, CENDFILE, CENDFILE, CENDFILE},       /* 12, PEOF */
-       {CWORD, CWORD, CWORD, CWORD},           /* 13, 0-9A-Za-z */
-       {CCTL, CCTL, CCTL, CCTL}                /* 14, CTLESC ... */
-#endif
-};
-#else
-static const char S_I_T[][3] = {
-#if ENABLE_ASH_ALIAS
-       {CSPCL, CIGN, CIGN},                    /* 0, PEOA */
-#endif
-       {CSPCL, CWORD, CWORD},                  /* 1, ' ' */
-       {CNL, CNL, CNL},                        /* 2, \n */
-       {CWORD, CCTL, CCTL},                    /* 3, !*-/:=?[]~ */
-       {CDQUOTE, CENDQUOTE, CWORD},            /* 4, '"' */
-       {CVAR, CVAR, CWORD},                    /* 5, $ */
-       {CSQUOTE, CWORD, CENDQUOTE},            /* 6, "'" */
-       {CSPCL, CWORD, CWORD},                  /* 7, ( */
-       {CSPCL, CWORD, CWORD},                  /* 8, ) */
-       {CBACK, CBACK, CCTL},                   /* 9, \ */
-       {CBQUOTE, CBQUOTE, CWORD},              /* 10, ` */
-       {CENDVAR, CENDVAR, CWORD},              /* 11, } */
-#ifndef USE_SIT_FUNCTION
-       {CENDFILE, CENDFILE, CENDFILE},         /* 12, PEOF */
-       {CWORD, CWORD, CWORD},                  /* 13, 0-9A-Za-z */
-       {CCTL, CCTL, CCTL}                      /* 14, CTLESC ... */
-#endif
-};
-#endif /* ASH_MATH_SUPPORT */
+/*
+ * Search the environment of a builtin command.
+ */
+static char *
+bltinlookup(const char *name)
+{
+       struct strlist *sp;
 
-#ifdef USE_SIT_FUNCTION
+       for (sp = cmdenviron; sp; sp = sp->next) {
+               if (varequal(sp->text, name))
+                       return strchrnul(sp->text, '=') + 1;
+       }
+       return lookupvar(name);
+}
 
-#define U_C(c) ((unsigned char)(c))
+/*
+ * Same as setvar except that the variable and value are passed in
+ * the first argument as name=value.  Since the first argument will
+ * be actually stored in the table, it should not be a string that
+ * will go away.
+ * Called with interrupts off.
+ */
+static void
+setvareq(char *s, int flags)
+{
+       struct var *vp, **vpp;
+
+       vpp = hashvar(s);
+       flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1));
+       vp = *findvar(vpp, s);
+       if (vp) {
+               if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) {
+                       const char *n;
+
+                       if (flags & VNOSAVE)
+                               free(s);
+                       n = vp->text;
+                       ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n);
+               }
+
+               if (flags & VNOSET)
+                       return;
+
+               if (vp->func && (flags & VNOFUNC) == 0)
+                       (*vp->func)(strchrnul(s, '=') + 1);
+
+               if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
+                       free((char*)vp->text);
+
+               flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET);
+       } else {
+               if (flags & VNOSET)
+                       return;
+               /* not found */
+               vp = ckmalloc(sizeof(*vp));
+               vp->next = *vpp;
+               vp->func = NULL;
+               *vpp = vp;
+       }
+       if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE)))
+               s = ckstrdup(s);
+       vp->text = s;
+       vp->flags = flags;
+}
+
+/*
+ * Set the value of a variable.  The flags argument is ored with the
+ * flags of the variable.  If val is NULL, the variable is unset.
+ */
+static void
+setvar(const char *name, const char *val, int flags)
+{
+       char *p, *q;
+       size_t namelen;
+       char *nameeq;
+       size_t vallen;
+
+       q = endofname(name);
+       p = strchrnul(q, '=');
+       namelen = p - name;
+       if (!namelen || p != q)
+               ash_msg_and_raise_error("%.*s: bad variable name", namelen, name);
+       vallen = 0;
+       if (val == NULL) {
+               flags |= VUNSET;
+       } else {
+               vallen = strlen(val);
+       }
+       INT_OFF;
+       nameeq = ckmalloc(namelen + vallen + 2);
+       p = memcpy(nameeq, name, namelen) + namelen;
+       if (val) {
+               *p++ = '=';
+               p = memcpy(p, val, vallen) + vallen;
+       }
+       *p = '\0';
+       setvareq(nameeq, flags | VNOSAVE);
+       INT_ON;
+}
 
+#if ENABLE_ASH_GETOPTS
+/*
+ * Safe version of setvar, returns 1 on success 0 on failure.
+ */
 static int
-SIT(int c, int syntax)
+setvarsafe(const char *name, const char *val, int flags)
 {
-       static const char spec_symbls[] = "\t\n !\"$&'()*-/:;<=>?[\\]`|}~";
-#if ENABLE_ASH_ALIAS
-       static const char syntax_index_table[] = {
-               1, 2, 1, 3, 4, 5, 1, 6,         /* "\t\n !\"$&'" */
-               7, 8, 3, 3, 3, 3, 1, 1,         /* "()*-/:;<" */
-               3, 1, 3, 3, 9, 3, 10, 1,        /* "=>?[\\]`|" */
-               11, 3                           /* "}~" */
-       };
-#else
-       static const char syntax_index_table[] = {
-               0, 1, 0, 2, 3, 4, 0, 5,         /* "\t\n !\"$&'" */
-               6, 7, 2, 2, 2, 2, 0, 0,         /* "()*-/:;<" */
-               2, 0, 2, 2, 8, 2, 9, 0,         /* "=>?[\\]`|" */
-               10, 2                           /* "}~" */
-       };
-#endif
-       const char *s;
-       int indx;
+       int err;
+       volatile int saveint;
+       struct jmploc *volatile savehandler = exception_handler;
+       struct jmploc jmploc;
 
-       if (c == PEOF)          /* 2^8+2 */
-               return CENDFILE;
-#if ENABLE_ASH_ALIAS
-       if (c == PEOA)          /* 2^8+1 */
-               indx = 0;
-       else
-#endif
-       if (U_C(c) >= U_C(CTLESC) && U_C(c) <= U_C(CTLQUOTEMARK))
-               return CCTL;
+       SAVE_INT(saveint);
+       if (setjmp(jmploc.loc))
+               err = 1;
        else {
-               s = strchr(spec_symbls, c);
-               if (s == NULL || *s == '\0')
-                       return CWORD;
-               indx = syntax_index_table[(s - spec_symbls)];
+               exception_handler = &jmploc;
+               setvar(name, val, flags);
+               err = 0;
        }
-       return S_I_T[indx][syntax];
+       exception_handler = savehandler;
+       RESTORE_INT(saveint);
+       return err;
 }
+#endif
 
-#else   /* USE_SIT_FUNCTION */
-
-#define SIT(c, syntax) S_I_T[(int)syntax_index_table[((int)c)+SYNBASE]][syntax]
+/*
+ * Unset the specified variable.
+ */
+static int
+unsetvar(const char *s)
+{
+       struct var **vpp;
+       struct var *vp;
+       int retval;
 
-#if ENABLE_ASH_ALIAS
-#define CSPCL_CIGN_CIGN_CIGN                           0
-#define CSPCL_CWORD_CWORD_CWORD                        1
-#define CNL_CNL_CNL_CNL                                2
-#define CWORD_CCTL_CCTL_CWORD                          3
-#define CDQUOTE_CENDQUOTE_CWORD_CWORD                  4
-#define CVAR_CVAR_CWORD_CVAR                           5
-#define CSQUOTE_CWORD_CENDQUOTE_CWORD                  6
-#define CSPCL_CWORD_CWORD_CLP                          7
-#define CSPCL_CWORD_CWORD_CRP                          8
-#define CBACK_CBACK_CCTL_CBACK                         9
-#define CBQUOTE_CBQUOTE_CWORD_CBQUOTE                 10
-#define CENDVAR_CENDVAR_CWORD_CENDVAR                 11
-#define CENDFILE_CENDFILE_CENDFILE_CENDFILE           12
-#define CWORD_CWORD_CWORD_CWORD                       13
-#define CCTL_CCTL_CCTL_CCTL                           14
-#else
-#define CSPCL_CWORD_CWORD_CWORD                        0
-#define CNL_CNL_CNL_CNL                                1
-#define CWORD_CCTL_CCTL_CWORD                          2
-#define CDQUOTE_CENDQUOTE_CWORD_CWORD                  3
-#define CVAR_CVAR_CWORD_CVAR                           4
-#define CSQUOTE_CWORD_CENDQUOTE_CWORD                  5
-#define CSPCL_CWORD_CWORD_CLP                          6
-#define CSPCL_CWORD_CWORD_CRP                          7
-#define CBACK_CBACK_CCTL_CBACK                         8
-#define CBQUOTE_CBQUOTE_CWORD_CBQUOTE                  9
-#define CENDVAR_CENDVAR_CWORD_CENDVAR                 10
-#define CENDFILE_CENDFILE_CENDFILE_CENDFILE           11
-#define CWORD_CWORD_CWORD_CWORD                       12
-#define CCTL_CCTL_CCTL_CCTL                           13
-#endif
+       vpp = findvar(hashvar(s), s);
+       vp = *vpp;
+       retval = 2;
+       if (vp) {
+               int flags = vp->flags;
 
-static const char syntax_index_table[258] = {
-       /* BASESYNTAX_DQSYNTAX_SQSYNTAX_ARISYNTAX */
+               retval = 1;
+               if (flags & VREADONLY)
+                       goto out;
+#ifdef DYNAMIC_VAR
+               vp->flags &= ~VDYNAMIC;
+#endif
+               if (flags & VUNSET)
+                       goto ok;
+               if ((flags & VSTRFIXED) == 0) {
+                       INT_OFF;
+                       if ((flags & (VTEXTFIXED|VSTACK)) == 0)
+                               free((char*)vp->text);
+                       *vpp = vp->next;
+                       free(vp);
+                       INT_ON;
+               } else {
+                       setvar(s, 0, 0);
+                       vp->flags &= ~VEXPORT;
+               }
+ ok:
+               retval = 0;
+       }
+ out:
+       return retval;
+}
+
+/*
+ * Process a linked list of variable assignments.
+ */
+static void
+listsetvar(struct strlist *list_set_var, int flags)
+{
+       struct strlist *lp = list_set_var;
+
+       if (!lp)
+               return;
+       INT_OFF;
+       do {
+               setvareq(lp->text, flags);
+       } while ((lp = lp->next));
+       INT_ON;
+}
+
+/*
+ * Generate a list of variables satisfying the given conditions.
+ */
+static char **
+listvars(int on, int off, char ***end)
+{
+       struct var **vpp;
+       struct var *vp;
+       char **ep;
+       int mask;
+
+       STARTSTACKSTR(ep);
+       vpp = vartab;
+       mask = on | off;
+       do {
+               for (vp = *vpp; vp; vp = vp->next) {
+                       if ((vp->flags & mask) == on) {
+                               if (ep == stackstrend())
+                                       ep = growstackstr();
+                               *ep++ = (char *) vp->text;
+                       }
+               }
+       } while (++vpp < vartab + VTABSIZE);
+       if (ep == stackstrend())
+               ep = growstackstr();
+       if (end)
+               *end = ep;
+       *ep++ = NULL;
+       return grabstackstr(ep);
+}
+
+
+/* ============ Path search helper
+ *
+ * The variable path (passed by reference) should be set to the start
+ * of the path before the first call; padvance will update
+ * this value as it proceeds.  Successive calls to padvance will return
+ * the possible path expansions in sequence.  If an option (indicated by
+ * a percent sign) appears in the path entry then the global variable
+ * pathopt will be set to point to it; otherwise pathopt will be set to
+ * NULL.
+ */
+static const char *pathopt;     /* set by padvance */
+
+static char *
+padvance(const char **path, const char *name)
+{
+       const char *p;
+       char *q;
+       const char *start;
+       size_t len;
+
+       if (*path == NULL)
+               return NULL;
+       start = *path;
+       for (p = start; *p && *p != ':' && *p != '%'; p++);
+       len = p - start + strlen(name) + 2;     /* "2" is for '/' and '\0' */
+       while (stackblocksize() < len)
+               growstackblock();
+       q = stackblock();
+       if (p != start) {
+               memcpy(q, start, p - start);
+               q += p - start;
+               *q++ = '/';
+       }
+       strcpy(q, name);
+       pathopt = NULL;
+       if (*p == '%') {
+               pathopt = ++p;
+               while (*p && *p != ':') p++;
+       }
+       if (*p == ':')
+               *path = p + 1;
+       else
+               *path = NULL;
+       return stalloc(len);
+}
+
+
+/* ============ Prompt */
+
+static int doprompt;                   /* if set, prompt the user */
+static int needprompt;                 /* true if interactive and at start of line */
+
+#if ENABLE_FEATURE_EDITING
+static line_input_t *line_input_state;
+static const char *cmdedit_prompt;
+static void
+putprompt(const char *s)
+{
+       if (ENABLE_ASH_EXPAND_PRMT) {
+               free((char*)cmdedit_prompt);
+               cmdedit_prompt = xstrdup(s);
+               return;
+       }
+       cmdedit_prompt = s;
+}
+#else
+static void
+putprompt(const char *s)
+{
+       out2str(s);
+}
+#endif
+
+#if ENABLE_ASH_EXPAND_PRMT
+/* expandstr() needs parsing machinery, so it is far away ahead... */
+static const char *expandstr(const char *ps);
+#else
+#define expandstr(s) s
+#endif
+
+static void
+setprompt(int whichprompt)
+{
+       const char *prompt;
+#if ENABLE_ASH_EXPAND_PRMT
+       struct stackmark smark;
+#endif
+
+       needprompt = 0;
+
+       switch (whichprompt) {
+       case 1:
+               prompt = ps1val();
+               break;
+       case 2:
+               prompt = ps2val();
+               break;
+       default:                        /* 0 */
+               prompt = nullstr;
+       }
+#if ENABLE_ASH_EXPAND_PRMT
+       setstackmark(&smark);
+       stalloc(stackblocksize());
+#endif
+       putprompt(expandstr(prompt));
+#if ENABLE_ASH_EXPAND_PRMT
+       popstackmark(&smark);
+#endif
+}
+
+
+/* ============ The cd and pwd commands */
+
+#define CD_PHYSICAL 1
+#define CD_PRINT 2
+
+static int docd(const char *, int);
+
+static char *curdir = nullstr;          /* current working directory */
+static char *physdir = nullstr;         /* physical working directory */
+
+static int
+cdopt(void)
+{
+       int flags = 0;
+       int i, j;
+
+       j = 'L';
+       while ((i = nextopt("LP"))) {
+               if (i != j) {
+                       flags ^= CD_PHYSICAL;
+                       j = i;
+               }
+       }
+
+       return flags;
+}
+
+/*
+ * Update curdir (the name of the current directory) in response to a
+ * cd command.
+ */
+static const char *
+updatepwd(const char *dir)
+{
+       char *new;
+       char *p;
+       char *cdcomppath;
+       const char *lim;
+
+       cdcomppath = ststrdup(dir);
+       STARTSTACKSTR(new);
+       if (*dir != '/') {
+               if (curdir == nullstr)
+                       return 0;
+               new = stack_putstr(curdir, new);
+       }
+       new = makestrspace(strlen(dir) + 2, new);
+       lim = stackblock() + 1;
+       if (*dir != '/') {
+               if (new[-1] != '/')
+                       USTPUTC('/', new);
+               if (new > lim && *lim == '/')
+                       lim++;
+       } else {
+               USTPUTC('/', new);
+               cdcomppath++;
+               if (dir[1] == '/' && dir[2] != '/') {
+                       USTPUTC('/', new);
+                       cdcomppath++;
+                       lim++;
+               }
+       }
+       p = strtok(cdcomppath, "/");
+       while (p) {
+               switch (*p) {
+               case '.':
+                       if (p[1] == '.' && p[2] == '\0') {
+                               while (new > lim) {
+                                       STUNPUTC(new);
+                                       if (new[-1] == '/')
+                                               break;
+                               }
+                               break;
+                       } else if (p[1] == '\0')
+                               break;
+                       /* fall through */
+               default:
+                       new = stack_putstr(p, new);
+                       USTPUTC('/', new);
+               }
+               p = strtok(0, "/");
+       }
+       if (new > lim)
+               STUNPUTC(new);
+       *new = 0;
+       return stackblock();
+}
+
+/*
+ * Find out what the current directory is. If we already know the current
+ * directory, this routine returns immediately.
+ */
+static char *
+getpwd(void)
+{
+       char *dir = getcwd(0, 0);
+       return dir ? dir : nullstr;
+}
+
+static void
+setpwd(const char *val, int setold)
+{
+       char *oldcur, *dir;
+
+       oldcur = dir = curdir;
+
+       if (setold) {
+               setvar("OLDPWD", oldcur, VEXPORT);
+       }
+       INT_OFF;
+       if (physdir != nullstr) {
+               if (physdir != oldcur)
+                       free(physdir);
+               physdir = nullstr;
+       }
+       if (oldcur == val || !val) {
+               char *s = getpwd();
+               physdir = s;
+               if (!val)
+                       dir = s;
+       } else
+               dir = ckstrdup(val);
+       if (oldcur != dir && oldcur != nullstr) {
+               free(oldcur);
+       }
+       curdir = dir;
+       INT_ON;
+       setvar("PWD", dir, VEXPORT);
+}
+
+static void hashcd(void);
+
+/*
+ * Actually do the chdir.  We also call hashcd to let the routines in exec.c
+ * know that the current directory has changed.
+ */
+static int
+docd(const char *dest, int flags)
+{
+       const char *dir = 0;
+       int err;
+
+       TRACE(("docd(\"%s\", %d) called\n", dest, flags));
+
+       INT_OFF;
+       if (!(flags & CD_PHYSICAL)) {
+               dir = updatepwd(dest);
+               if (dir)
+                       dest = dir;
+       }
+       err = chdir(dest);
+       if (err)
+               goto out;
+       setpwd(dir, 1);
+       hashcd();
+ out:
+       INT_ON;
+       return err;
+}
+
+static int
+cdcmd(int argc, char **argv)
+{
+       const char *dest;
+       const char *path;
+       const char *p;
+       char c;
+       struct stat statb;
+       int flags;
+
+       flags = cdopt();
+       dest = *argptr;
+       if (!dest)
+               dest = bltinlookup(homestr);
+       else if (LONE_DASH(dest)) {
+               dest = bltinlookup("OLDPWD");
+               flags |= CD_PRINT;
+       }
+       if (!dest)
+               dest = nullstr;
+       if (*dest == '/')
+               goto step7;
+       if (*dest == '.') {
+               c = dest[1];
+ dotdot:
+               switch (c) {
+               case '\0':
+               case '/':
+                       goto step6;
+               case '.':
+                       c = dest[2];
+                       if (c != '.')
+                               goto dotdot;
+               }
+       }
+       if (!*dest)
+               dest = ".";
+       path = bltinlookup("CDPATH");
+       if (!path) {
+ step6:
+ step7:
+               p = dest;
+               goto docd;
+       }
+       do {
+               c = *path;
+               p = padvance(&path, dest);
+               if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
+                       if (c && c != ':')
+                               flags |= CD_PRINT;
+ docd:
+                       if (!docd(p, flags))
+                               goto out;
+                       break;
+               }
+       } while (path);
+       ash_msg_and_raise_error("can't cd to %s", dest);
+       /* NOTREACHED */
+ out:
+       if (flags & CD_PRINT)
+               out1fmt(snlfmt, curdir);
+       return 0;
+}
+
+static int
+pwdcmd(int argc, char **argv)
+{
+       int flags;
+       const char *dir = curdir;
+
+       flags = cdopt();
+       if (flags) {
+               if (physdir == nullstr)
+                       setpwd(dir, 0);
+               dir = physdir;
+       }
+       out1fmt(snlfmt, dir);
+       return 0;
+}
+
+
+/* ============ Unsorted yet */
+
+
+/*      expand.h     */
+
+struct arglist {
+       struct strlist *list;
+       struct strlist **lastp;
+};
+
+/*
+ * expandarg() flags
+ */
+#define EXP_FULL        0x1     /* perform word splitting & file globbing */
+#define EXP_TILDE       0x2     /* do normal tilde expansion */
+#define EXP_VARTILDE    0x4     /* expand tildes in an assignment */
+#define EXP_REDIR       0x8     /* file glob for a redirection (1 match only) */
+#define EXP_CASE        0x10    /* keeps quotes around for CASE pattern */
+#define EXP_RECORD      0x20    /* need to record arguments for ifs breakup */
+#define EXP_VARTILDE2   0x40    /* expand tildes after colons only */
+#define EXP_WORD        0x80    /* expand word in parameter expansion */
+#define EXP_QWORD       0x100   /* expand word in quoted parameter expansion */
+
+
+union node;
+static void expandarg(union node *, struct arglist *, int);
+#define rmescapes(p) _rmescapes((p), 0)
+static char *_rmescapes(char *, int);
+static int casematch(union node *, char *);
+
+#if ENABLE_ASH_MATH_SUPPORT
+static void expari(int);
+#endif
+
+/*      eval.h       */
+
+
+
+struct backcmd {                /* result of evalbackcmd */
+       int fd;                 /* file descriptor to read from */
+       char *buf;              /* buffer */
+       int nleft;              /* number of chars in buffer */
+       struct job *jp;         /* job structure for command */
+};
+
+/*
+ * This file was generated by the mknodes program.
+ */
+
+#define NCMD 0
+#define NPIPE 1
+#define NREDIR 2
+#define NBACKGND 3
+#define NSUBSHELL 4
+#define NAND 5
+#define NOR 6
+#define NSEMI 7
+#define NIF 8
+#define NWHILE 9
+#define NUNTIL 10
+#define NFOR 11
+#define NCASE 12
+#define NCLIST 13
+#define NDEFUN 14
+#define NARG 15
+#define NTO 16
+#define NCLOBBER 17
+#define NFROM 18
+#define NFROMTO 19
+#define NAPPEND 20
+#define NTOFD 21
+#define NFROMFD 22
+#define NHERE 23
+#define NXHERE 24
+#define NNOT 25
+
+
+struct ncmd {
+       int type;
+       union node *assign;
+       union node *args;
+       union node *redirect;
+};
+
+struct npipe {
+       int type;
+       int backgnd;
+       struct nodelist *cmdlist;
+};
+
+struct nredir {
+       int type;
+       union node *n;
+       union node *redirect;
+};
+
+struct nbinary {
+       int type;
+       union node *ch1;
+       union node *ch2;
+};
+
+struct nif {
+       int type;
+       union node *test;
+       union node *ifpart;
+       union node *elsepart;
+};
+
+struct nfor {
+       int type;
+       union node *args;
+       union node *body;
+       char *var;
+};
+
+struct ncase {
+       int type;
+       union node *expr;
+       union node *cases;
+};
+
+struct nclist {
+       int type;
+       union node *next;
+       union node *pattern;
+       union node *body;
+};
+
+struct narg {
+       int type;
+       union node *next;
+       char *text;
+       struct nodelist *backquote;
+};
+
+struct nfile {
+       int type;
+       union node *next;
+       int fd;
+       union node *fname;
+       char *expfname;
+};
+
+struct ndup {
+       int type;
+       union node *next;
+       int fd;
+       int dupfd;
+       union node *vname;
+};
+
+struct nhere {
+       int type;
+       union node *next;
+       int fd;
+       union node *doc;
+};
+
+struct nnot {
+       int type;
+       union node *com;
+};
+
+union node {
+       int type;
+       struct ncmd ncmd;
+       struct npipe npipe;
+       struct nredir nredir;
+       struct nbinary nbinary;
+       struct nif nif;
+       struct nfor nfor;
+       struct ncase ncase;
+       struct nclist nclist;
+       struct narg narg;
+       struct nfile nfile;
+       struct ndup ndup;
+       struct nhere nhere;
+       struct nnot nnot;
+};
+
+struct nodelist {
+       struct nodelist *next;
+       union node *n;
+};
+
+struct funcnode {
+       int count;
+       union node n;
+};
+
+
+static void freefunc(struct funcnode *);
+/*      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
+ * must be distinct from NULL, so we use the address of a variable that
+ * happens to be handy.
+ */
+static int plinno = 1;                  /* input line number */
+
+/* number of characters left in input buffer */
+static int parsenleft;                  /* copy of parsefile->nleft */
+static int parselleft;                  /* copy of parsefile->lleft */
+
+/* next character in input buffer */
+static char *parsenextc;                /* copy of parsefile->nextc */
+
+#define basebuf bb_common_bufsiz1       /* buffer for top level input file */
+
+
+static int tokpushback;                 /* last token pushed back */
+#define NEOF ((union node *)&tokpushback)
+static int parsebackquote;             /* nonzero if we are inside backquotes */
+static int lasttoken;                  /* last token read */
+static char *wordtext;                 /* text of last word returned by readtoken */
+static int checkkwd;
+static struct nodelist *backquotelist;
+static union node *redirnode;
+static struct heredoc *heredoc;
+static int quoteflag;                  /* set if (part of) last token was quoted */
+
+static void fixredir(union node *, const char *, int);
+static char *endofname(const char *);
+
+/*      shell.h   */
+
+static const char spcstr[] = " ";
+static const char dolatstr[] = { CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' };
+
+#if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96)
+#define __builtin_expect(x, expected_value) (x)
+#endif
+
+#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 */
+#define CBACK 2                 /* a backslash character */
+#define CSQUOTE 3               /* single quote */
+#define CDQUOTE 4               /* double quote */
+#define CENDQUOTE 5             /* a terminating quote */
+#define CBQUOTE 6               /* backwards single quote */
+#define CVAR 7                  /* a dollar sign */
+#define CENDVAR 8               /* a '}' character */
+#define CLP 9                   /* a left paren in arithmetic */
+#define CRP 10                  /* a right paren in arithmetic */
+#define CENDFILE 11             /* end of file */
+#define CCTL 12                 /* like CWORD, except it must be escaped */
+#define CSPCL 13                /* these terminate a word */
+#define CIGN 14                 /* character should be ignored */
+
+#if ENABLE_ASH_ALIAS
+#define SYNBASE 130
+#define PEOF -130
+#define PEOA -129
+#define PEOA_OR_PEOF PEOA
+#else
+#define SYNBASE 129
+#define PEOF -129
+#define PEOA_OR_PEOF PEOF
+#endif
+
+/* C99 say: "char" declaration may be signed or unsigned default */
+#define SC2INT(chr2may_be_negative_int) (int)((signed char)chr2may_be_negative_int)
+
+/*
+ * is_special(c) evaluates to 1 for c in "!#$*-0123456789?@"; 0 otherwise
+ * (assuming ascii char codes, as the original implementation did)
+ */
+#define is_special(c) \
+       ( (((unsigned int)c) - 33 < 32) \
+                        && ((0xc1ff920dUL >> (((unsigned int)c) - 33)) & 1))
+
+#define digit_val(c)    ((c) - '0')
+
+/*
+ * This file was generated by the mksyntax program.
+ */
+
+#if ENABLE_ASH_OPTIMIZE_FOR_SIZE
+#define USE_SIT_FUNCTION
+#endif
+
+/* number syntax index */
+#define  BASESYNTAX  0  /* not in quotes */
+#define  DQSYNTAX    1  /* in double quotes */
+#define  SQSYNTAX    2  /* in single quotes */
+#define  ARISYNTAX   3  /* in arithmetic */
+
+#if ENABLE_ASH_MATH_SUPPORT
+static const char S_I_T[][4] = {
+#if ENABLE_ASH_ALIAS
+       {CSPCL, CIGN, CIGN, CIGN},              /* 0, PEOA */
+#endif
+       {CSPCL, CWORD, CWORD, CWORD},           /* 1, ' ' */
+       {CNL, CNL, CNL, CNL},                   /* 2, \n */
+       {CWORD, CCTL, CCTL, CWORD},             /* 3, !*-/:=?[]~ */
+       {CDQUOTE, CENDQUOTE, CWORD, CWORD},     /* 4, '"' */
+       {CVAR, CVAR, CWORD, CVAR},              /* 5, $ */
+       {CSQUOTE, CWORD, CENDQUOTE, CWORD},     /* 6, "'" */
+       {CSPCL, CWORD, CWORD, CLP},             /* 7, ( */
+       {CSPCL, CWORD, CWORD, CRP},             /* 8, ) */
+       {CBACK, CBACK, CCTL, CBACK},            /* 9, \ */
+       {CBQUOTE, CBQUOTE, CWORD, CBQUOTE},     /* 10, ` */
+       {CENDVAR, CENDVAR, CWORD, CENDVAR},     /* 11, } */
+#ifndef USE_SIT_FUNCTION
+       {CENDFILE, CENDFILE, CENDFILE, CENDFILE},       /* 12, PEOF */
+       {CWORD, CWORD, CWORD, CWORD},           /* 13, 0-9A-Za-z */
+       {CCTL, CCTL, CCTL, CCTL}                /* 14, CTLESC ... */
+#endif
+};
+#else
+static const char S_I_T[][3] = {
+#if ENABLE_ASH_ALIAS
+       {CSPCL, CIGN, CIGN},                    /* 0, PEOA */
+#endif
+       {CSPCL, CWORD, CWORD},                  /* 1, ' ' */
+       {CNL, CNL, CNL},                        /* 2, \n */
+       {CWORD, CCTL, CCTL},                    /* 3, !*-/:=?[]~ */
+       {CDQUOTE, CENDQUOTE, CWORD},            /* 4, '"' */
+       {CVAR, CVAR, CWORD},                    /* 5, $ */
+       {CSQUOTE, CWORD, CENDQUOTE},            /* 6, "'" */
+       {CSPCL, CWORD, CWORD},                  /* 7, ( */
+       {CSPCL, CWORD, CWORD},                  /* 8, ) */
+       {CBACK, CBACK, CCTL},                   /* 9, \ */
+       {CBQUOTE, CBQUOTE, CWORD},              /* 10, ` */
+       {CENDVAR, CENDVAR, CWORD},              /* 11, } */
+#ifndef USE_SIT_FUNCTION
+       {CENDFILE, CENDFILE, CENDFILE},         /* 12, PEOF */
+       {CWORD, CWORD, CWORD},                  /* 13, 0-9A-Za-z */
+       {CCTL, CCTL, CCTL}                      /* 14, CTLESC ... */
+#endif
+};
+#endif /* ASH_MATH_SUPPORT */
+
+#ifdef USE_SIT_FUNCTION
+
+#define U_C(c) ((unsigned char)(c))
+
+static int
+SIT(int c, int syntax)
+{
+       static const char spec_symbls[] = "\t\n !\"$&'()*-/:;<=>?[\\]`|}~";
+#if ENABLE_ASH_ALIAS
+       static const char syntax_index_table[] = {
+               1, 2, 1, 3, 4, 5, 1, 6,         /* "\t\n !\"$&'" */
+               7, 8, 3, 3, 3, 3, 1, 1,         /* "()*-/:;<" */
+               3, 1, 3, 3, 9, 3, 10, 1,        /* "=>?[\\]`|" */
+               11, 3                           /* "}~" */
+       };
+#else
+       static const char syntax_index_table[] = {
+               0, 1, 0, 2, 3, 4, 0, 5,         /* "\t\n !\"$&'" */
+               6, 7, 2, 2, 2, 2, 0, 0,         /* "()*-/:;<" */
+               2, 0, 2, 2, 8, 2, 9, 0,         /* "=>?[\\]`|" */
+               10, 2                           /* "}~" */
+       };
+#endif
+       const char *s;
+       int indx;
+
+       if (c == PEOF)          /* 2^8+2 */
+               return CENDFILE;
+#if ENABLE_ASH_ALIAS
+       if (c == PEOA)          /* 2^8+1 */
+               indx = 0;
+       else
+#endif
+       if (U_C(c) >= U_C(CTLESC) && U_C(c) <= U_C(CTLQUOTEMARK))
+               return CCTL;
+       else {
+               s = strchr(spec_symbls, c);
+               if (s == NULL || *s == '\0')
+                       return CWORD;
+               indx = syntax_index_table[(s - spec_symbls)];
+       }
+       return S_I_T[indx][syntax];
+}
+
+#else   /* USE_SIT_FUNCTION */
+
+#define SIT(c, syntax) S_I_T[(int)syntax_index_table[((int)c)+SYNBASE]][syntax]
+
+#if ENABLE_ASH_ALIAS
+#define CSPCL_CIGN_CIGN_CIGN                           0
+#define CSPCL_CWORD_CWORD_CWORD                        1
+#define CNL_CNL_CNL_CNL                                2
+#define CWORD_CCTL_CCTL_CWORD                          3
+#define CDQUOTE_CENDQUOTE_CWORD_CWORD                  4
+#define CVAR_CVAR_CWORD_CVAR                           5
+#define CSQUOTE_CWORD_CENDQUOTE_CWORD                  6
+#define CSPCL_CWORD_CWORD_CLP                          7
+#define CSPCL_CWORD_CWORD_CRP                          8
+#define CBACK_CBACK_CCTL_CBACK                         9
+#define CBQUOTE_CBQUOTE_CWORD_CBQUOTE                 10
+#define CENDVAR_CENDVAR_CWORD_CENDVAR                 11
+#define CENDFILE_CENDFILE_CENDFILE_CENDFILE           12
+#define CWORD_CWORD_CWORD_CWORD                       13
+#define CCTL_CCTL_CCTL_CCTL                           14
+#else
+#define CSPCL_CWORD_CWORD_CWORD                        0
+#define CNL_CNL_CNL_CNL                                1
+#define CWORD_CCTL_CCTL_CWORD                          2
+#define CDQUOTE_CENDQUOTE_CWORD_CWORD                  3
+#define CVAR_CVAR_CWORD_CVAR                           4
+#define CSQUOTE_CWORD_CENDQUOTE_CWORD                  5
+#define CSPCL_CWORD_CWORD_CLP                          6
+#define CSPCL_CWORD_CWORD_CRP                          7
+#define CBACK_CBACK_CCTL_CBACK                         8
+#define CBQUOTE_CBQUOTE_CWORD_CBQUOTE                  9
+#define CENDVAR_CENDVAR_CWORD_CENDVAR                 10
+#define CENDFILE_CENDFILE_CENDFILE_CENDFILE           11
+#define CWORD_CWORD_CWORD_CWORD                       12
+#define CCTL_CCTL_CCTL_CCTL                           13
+#endif
+
+static const char syntax_index_table[258] = {
+       /* BASESYNTAX_DQSYNTAX_SQSYNTAX_ARISYNTAX */
        /*   0  PEOF */      CENDFILE_CENDFILE_CENDFILE_CENDFILE,
 #if ENABLE_ASH_ALIAS
        /*   1  PEOA */      CSPCL_CIGN_CIGN_CIGN,
@@ -1743,13 +2788,6 @@ static int ulimitcmd(int, char **);
 static int killcmd(int, char **);
 #endif
 
-/*      mail.h        */
-
-#if ENABLE_ASH_MAIL
-static void chkmail(void);
-static void changemail(const char *);
-#endif
-
 /*      exec.h    */
 
 /* values of cmdtype */
@@ -1879,14 +2917,10 @@ struct cmdentry {
 #define DO_ALTPATH      0x08    /* using alternate path */
 #define DO_ALTBLTIN     0x20    /* %builtin in alt. path */
 
-static const char *pathopt;     /* set by padvance */
-
 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 hashcd(void);
-static void changepath(const char *);
 static void defun(char *, union node *);
 static void unsetfunc(const char *);
 
@@ -1905,222 +2939,11 @@ static arith_t arith(const char *expr, int *perrcode);
 
 #if ENABLE_ASH_RANDOM_SUPPORT
 static unsigned long rseed;
-static void change_random(const char *);
 # ifndef DYNAMIC_VAR
 #  define DYNAMIC_VAR
 # endif
 #endif
 
-/*      var.h     */
-
-/*
- * Shell variables.
- */
-
-#if ENABLE_ASH_GETOPTS
-static void getoptsreset(const char *);
-#endif
-
-/* flags */
-#define VEXPORT         0x01    /* variable is exported */
-#define VREADONLY       0x02    /* variable cannot be modified */
-#define VSTRFIXED       0x04    /* variable struct is statically allocated */
-#define VTEXTFIXED      0x08    /* text is statically allocated */
-#define VSTACK          0x10    /* text is allocated on the stack */
-#define VUNSET          0x20    /* the variable is not set */
-#define VNOFUNC         0x40    /* don't call the callback function */
-#define VNOSET          0x80    /* do not set variable - just readonly test */
-#define VNOSAVE         0x100   /* when text is on the heap before setvareq */
-#ifdef DYNAMIC_VAR
-# define VDYNAMIC        0x200   /* dynamic variable */
-# else
-# define VDYNAMIC        0
-#endif
-
-struct var {
-       struct var *next;               /* next entry in hash list */
-       int flags;                      /* flags are defined above */
-       const char *text;               /* name=value */
-       void (*func)(const char *);     /* function to be called when  */
-                                       /* the variable gets set/unset */
-};
-
-struct localvar {
-       struct localvar *next;          /* next local variable in list */
-       struct var *vp;                 /* the variable that was made local */
-       int flags;                      /* saved flags */
-       const char *text;               /* saved text */
-};
-
-
-static struct localvar *localvars;
-
-/*
- * Shell variables.
- */
-#if ENABLE_LOCALE_SUPPORT
-static void change_lc_all(const char *value);
-static void change_lc_ctype(const char *value);
-#endif
-
-
-#define VTABSIZE 39
-
-static const char defpathvar[] = "PATH=/usr/local/bin:/usr/bin:/sbin:/bin";
-#ifdef IFS_BROKEN
-static const char defifsvar[] = "IFS= \t\n";
-#define defifs (defifsvar + 4)
-#else
-static const char defifs[] = " \t\n";
-#endif
-
-
-static struct var varinit[] = {
-#ifdef IFS_BROKEN
-       { 0,    VSTRFIXED|VTEXTFIXED,           defifsvar,      0 },
-#else
-       { 0,    VSTRFIXED|VTEXTFIXED|VUNSET,    "IFS\0",        0 },
-#endif
-
-#if ENABLE_ASH_MAIL
-       { 0,    VSTRFIXED|VTEXTFIXED|VUNSET,    "MAIL\0",       changemail },
-       { 0,    VSTRFIXED|VTEXTFIXED|VUNSET,    "MAILPATH\0",   changemail },
-#endif
-
-       { 0,    VSTRFIXED|VTEXTFIXED,           defpathvar,     changepath },
-       { 0,    VSTRFIXED|VTEXTFIXED,           "PS1=$ ",       0          },
-       { 0,    VSTRFIXED|VTEXTFIXED,           "PS2=> ",       0          },
-       { 0,    VSTRFIXED|VTEXTFIXED,           "PS4=+ ",       0          },
-#if ENABLE_ASH_GETOPTS
-       { 0,    VSTRFIXED|VTEXTFIXED,           "OPTIND=1",     getoptsreset },
-#endif
-#if ENABLE_ASH_RANDOM_SUPPORT
-       {0, VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM\0", change_random },
-#endif
-#if ENABLE_LOCALE_SUPPORT
-       {0, VSTRFIXED | VTEXTFIXED | VUNSET, "LC_ALL\0", change_lc_all },
-       {0, VSTRFIXED | VTEXTFIXED | VUNSET, "LC_CTYPE\0", change_lc_ctype },
-#endif
-#if ENABLE_FEATURE_EDITING_SAVEHISTORY
-       {0, VSTRFIXED | VTEXTFIXED | VUNSET, "HISTFILE\0", NULL },
-#endif
-};
-
-#define vifs varinit[0]
-#if ENABLE_ASH_MAIL
-#define vmail (&vifs)[1]
-#define vmpath (&vmail)[1]
-#else
-#define vmpath vifs
-#endif
-#define vpath (&vmpath)[1]
-#define vps1 (&vpath)[1]
-#define vps2 (&vps1)[1]
-#define vps4 (&vps2)[1]
-#define voptind (&vps4)[1]
-#if ENABLE_ASH_GETOPTS
-#define vrandom (&voptind)[1]
-#else
-#define vrandom (&vps4)[1]
-#endif
-#define defpath (defpathvar + 5)
-
-/*
- * The following macros access the values of the above variables.
- * They have to skip over the name.  They return the null string
- * for unset variables.
- */
-
-#define ifsval()        (vifs.text + 4)
-#define ifsset()        ((vifs.flags & VUNSET) == 0)
-#define mailval()       (vmail.text + 5)
-#define mpathval()      (vmpath.text + 9)
-#define pathval()       (vpath.text + 5)
-#define ps1val()        (vps1.text + 4)
-#define ps2val()        (vps2.text + 4)
-#define ps4val()        (vps4.text + 4)
-#define optindval()     (voptind.text + 7)
-
-#define mpathset()      ((vmpath.flags & VUNSET) == 0)
-
-static void setvar(const char *, const char *, int);
-static void setvareq(char *, int);
-static void listsetvar(struct strlist *, int);
-static char *lookupvar(const char *);
-static char *bltinlookup(const char *);
-static char **listvars(int, int, char ***);
-#define environment() listvars(VEXPORT, VUNSET, 0)
-static int showvars(const char *, int, int);
-static void poplocalvars(void);
-static int unsetvar(const char *);
-#if ENABLE_ASH_GETOPTS
-static int setvarsafe(const char *, const char *, int);
-#endif
-static int varcmp(const char *, const char *);
-static struct var **hashvar(const char *);
-
-
-static int varequal(const char *a, const char *b)
-{
-       return !varcmp(a, b);
-}
-
-
-static int loopnest;            /* current loop nesting level */
-
-/*
- * The parsefile structure pointed to by the global variable parsefile
- * contains information about the current file being read.
- */
-
-
-struct redirtab {
-       struct redirtab *next;
-       int renamed[10];
-       int nullredirs;
-};
-
-static struct redirtab *redirlist;
-static int nullredirs;
-
-extern char **environ;
-
-
-static int preverrout_fd;   /* save fd2 before print debug if xflag is set. */
-
-
-/*
- * Initialization code.
- */
-
-/*
- * This routine initializes the builtin variables.
- */
-
-static void initvar(void)
-{
-       struct var *vp;
-       struct var *end;
-       struct var **vpp;
-
-       /*
-        * PS1 depends on uid
-        */
-#if ENABLE_FEATURE_EDITING && ENABLE_FEATURE_EDITING_FANCY_PROMPT
-       vps1.text = "PS1=\\w \\$ ";
-#else
-       if (!geteuid())
-               vps1.text = "PS1=# ";
-#endif
-       vp = varinit;
-       end = vp + sizeof(varinit) / sizeof(varinit[0]);
-       do {
-               vpp = hashvar(vp->text);
-               vp->next = *vpp;
-               *vpp = vp;
-       } while (++vp < end);
-}
-
 /* PEOF (the end of file marker) */
 
 enum {
@@ -2133,10 +2956,6 @@ enum {
  * and restores it when files are pushed and popped.  The user of this
  * package must set its value.
  */
-
-static int pgetc(void);
-static int pgetc2(void);
-static int preadbuffer(void);
 static void pungetc(void);
 static void pushstring(char *, void *);
 static void popstring(void);
@@ -2146,7 +2965,6 @@ static void popfile(void);
 static void popallfiles(void);
 static void closescript(void);
 
-
 /*      jobs.h    */
 
 
@@ -2218,36 +3036,7 @@ static void showjobs(FILE *, int);
 
 
 static void readcmdfile(char *);
-
-static char *_STPUTC(int c, char *p)
-{
-       if (p == sstrend)
-               p = growstackstr();
-       *p++ = c;
-       return p;
-}
-
-#define STARTSTACKSTR(p) ((p) = stackblock())
-#define STPUTC(c, p) ((p) = _STPUTC((c), (p)))
-#define CHECKSTRSPACE(n, p) \
-       ({ \
-               char *q = (p); \
-               size_t l = (n); \
-               size_t m = sstrend - q; \
-               if (l > m) \
-                       (p) = makestrspace(l, q); \
-               0; \
-       })
-#define USTPUTC(c, p)   (*p++ = (c))
-#define STACKSTRNUL(p)  ((p) == sstrend? (p = growstackstr(), *p = '\0') : (*p = '\0'))
-#define STUNPUTC(p)     (--p)
-#define STTOPC(p)       p[-1]
-#define STADJUST(amount, p)     (p += (amount))
-
-#define grabstackstr(p) stalloc((char *)(p) - (char *)stackblock())
-#define ungrabstackstr(s, p) stunalloc((s))
-#define stackstrend() ((void *)sstrend)
 
 /*      mystring.h   */
 
@@ -2258,29 +3047,12 @@ static char *prefix(const char *, const char *);
 static int number(const char *);
 static int is_number(const char *);
 static char *single_quote(const char *);
-static char *sstrdup(const char *);
 
 #define equal(s1, s2)   (strcmp(s1, s2) == 0)
 #define scopy(s1, s2)   ((void)strcpy(s2, s1))
 
 /*      options.h */
 
-struct shparam {
-       int nparam;             /* # of positional parameters (without $0) */
-       unsigned char malloc;   /* if parameter list dynamically allocated */
-       char **p;               /* parameter list */
-#if ENABLE_ASH_GETOPTS
-       int optind;             /* next parameter to be processed by getopts */
-       int optoff;             /* used by getopts */
-#endif
-};
-
-
-static struct shparam shellparam;      /* $@ current positional parameters */
-static char **argptr;                  /* argument list for builtin commands */
-static char *optionarg;                /* set by nextopt (like getopt) */
-static char *optptr;                   /* used by nextopt */
-
 static char *minusc;                   /* argument to -c option */
 
 
@@ -2550,250 +3322,6 @@ __lookupalias(const char *name) {
 }
 #endif /* ASH_ALIAS */
 
-
-/*      cd.c      */
-
-/*
- * The cd and pwd commands.
- */
-
-#define CD_PHYSICAL 1
-#define CD_PRINT 2
-
-static int docd(const char *, int);
-static int cdopt(void);
-
-static char *curdir = nullstr;          /* current working directory */
-static char *physdir = nullstr;         /* physical working directory */
-
-static int
-cdopt(void)
-{
-       int flags = 0;
-       int i, j;
-
-       j = 'L';
-       while ((i = nextopt("LP"))) {
-               if (i != j) {
-                       flags ^= CD_PHYSICAL;
-                       j = i;
-               }
-       }
-
-       return flags;
-}
-
-static int
-cdcmd(int argc, char **argv)
-{
-       const char *dest;
-       const char *path;
-       const char *p;
-       char c;
-       struct stat statb;
-       int flags;
-
-       flags = cdopt();
-       dest = *argptr;
-       if (!dest)
-               dest = bltinlookup(homestr);
-       else if (LONE_DASH(dest)) {
-               dest = bltinlookup("OLDPWD");
-               flags |= CD_PRINT;
-       }
-       if (!dest)
-               dest = nullstr;
-       if (*dest == '/')
-               goto step7;
-       if (*dest == '.') {
-               c = dest[1];
- dotdot:
-               switch (c) {
-               case '\0':
-               case '/':
-                       goto step6;
-               case '.':
-                       c = dest[2];
-                       if (c != '.')
-                               goto dotdot;
-               }
-       }
-       if (!*dest)
-               dest = ".";
-       path = bltinlookup("CDPATH");
-       if (!path) {
- step6:
- step7:
-               p = dest;
-               goto docd;
-       }
-       do {
-               c = *path;
-               p = padvance(&path, dest);
-               if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
-                       if (c && c != ':')
-                               flags |= CD_PRINT;
- docd:
-                       if (!docd(p, flags))
-                               goto out;
-                       break;
-               }
-       } while (path);
-       ash_msg_and_raise_error("can't cd to %s", dest);
-       /* NOTREACHED */
- out:
-       if (flags & CD_PRINT)
-               out1fmt(snlfmt, curdir);
-       return 0;
-}
-
-
-/*
- * Update curdir (the name of the current directory) in response to a
- * cd command.
- */
-static const char * updatepwd(const char *dir)
-{
-       char *new;
-       char *p;
-       char *cdcomppath;
-       const char *lim;
-
-       cdcomppath = sstrdup(dir);
-       STARTSTACKSTR(new);
-       if (*dir != '/') {
-               if (curdir == nullstr)
-                       return 0;
-               new = stack_putstr(curdir, new);
-       }
-       new = makestrspace(strlen(dir) + 2, new);
-       lim = stackblock() + 1;
-       if (*dir != '/') {
-               if (new[-1] != '/')
-                       USTPUTC('/', new);
-               if (new > lim && *lim == '/')
-                       lim++;
-       } else {
-               USTPUTC('/', new);
-               cdcomppath++;
-               if (dir[1] == '/' && dir[2] != '/') {
-                       USTPUTC('/', new);
-                       cdcomppath++;
-                       lim++;
-               }
-       }
-       p = strtok(cdcomppath, "/");
-       while (p) {
-               switch (*p) {
-               case '.':
-                       if (p[1] == '.' && p[2] == '\0') {
-                               while (new > lim) {
-                                       STUNPUTC(new);
-                                       if (new[-1] == '/')
-                                               break;
-                               }
-                               break;
-                       } else if (p[1] == '\0')
-                               break;
-                       /* fall through */
-               default:
-                       new = stack_putstr(p, new);
-                       USTPUTC('/', new);
-               }
-               p = strtok(0, "/");
-       }
-       if (new > lim)
-               STUNPUTC(new);
-       *new = 0;
-       return stackblock();
-}
-
-
-/*
- * Actually do the chdir.  We also call hashcd to let the routines in exec.c
- * know that the current directory has changed.
- */
-static int
-docd(const char *dest, int flags)
-{
-       const char *dir = 0;
-       int err;
-
-       TRACE(("docd(\"%s\", %d) called\n", dest, flags));
-
-       INT_OFF;
-       if (!(flags & CD_PHYSICAL)) {
-               dir = updatepwd(dest);
-               if (dir)
-                       dest = dir;
-       }
-       err = chdir(dest);
-       if (err)
-               goto out;
-       setpwd(dir, 1);
-       hashcd();
- out:
-       INT_ON;
-       return err;
-}
-
-/*
- * Find out what the current directory is. If we already know the current
- * directory, this routine returns immediately.
- */
-static char * getpwd(void)
-{
-       char *dir = getcwd(0, 0);
-       return dir ? dir : nullstr;
-}
-
-static int
-pwdcmd(int argc, char **argv)
-{
-       int flags;
-       const char *dir = curdir;
-
-       flags = cdopt();
-       if (flags) {
-               if (physdir == nullstr)
-                       setpwd(dir, 0);
-               dir = physdir;
-       }
-       out1fmt(snlfmt, dir);
-       return 0;
-}
-
-static void
-setpwd(const char *val, int setold)
-{
-       char *oldcur, *dir;
-
-       oldcur = dir = curdir;
-
-       if (setold) {
-               setvar("OLDPWD", oldcur, VEXPORT);
-       }
-       INT_OFF;
-       if (physdir != nullstr) {
-               if (physdir != oldcur)
-                       free(physdir);
-               physdir = nullstr;
-       }
-       if (oldcur == val || !val) {
-               char *s = getpwd();
-               physdir = s;
-               if (!val)
-                       dir = s;
-       } else
-               dir = ckstrdup(val);
-       if (oldcur != dir && oldcur != nullstr) {
-               free(oldcur);
-       }
-       curdir = dir;
-       INT_ON;
-       setvar("PWD", dir, VEXPORT);
-}
-
 /*      eval.c  */
 
 /*
@@ -3235,7 +3763,8 @@ evalbackcmd(union node *n, struct backcmd *result)
 }
 
 #if ENABLE_ASH_CMDCMD
-static char ** parse_command_args(char **argv, const char **path)
+static char **
+parse_command_args(char **argv, const char **path)
 {
        char *cp, c;
 
@@ -3275,12 +3804,6 @@ static int isassignment(const char *p)
        return *q == '=';
 }
 
-#if ENABLE_ASH_EXPAND_PRMT
-static const char *expandstr(const char *ps);
-#else
-#define expandstr(s) s
-#endif
-
 /*
  * Execute a simple command.
  */
@@ -3543,6 +4066,40 @@ evalbltin(const struct builtincmd *cmd, int argc, char **argv)
        return i;
 }
 
+static struct localvar *localvars;
+
+/*
+ * Called after a function returns.
+ * Interrupts must be off.
+ */
+static void
+poplocalvars(void)
+{
+       struct localvar *lvp;
+       struct var *vp;
+
+       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)
 {
@@ -3588,7 +4145,8 @@ funcdone:
 }
 
 
-static int goodname(const char *p)
+static int
+goodname(const char *p)
 {
        return !*endofname(p);
 }
@@ -3738,6 +4296,7 @@ static void delete_cmd_entry(void);
  * 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
 shellexec(char **argv, const char *path, int idx)
@@ -3845,50 +4404,6 @@ tryexec(char *cmd, char **argv, char **envp)
 }
 
 
-/*
- * Do a path search.  The variable path (passed by reference) should be
- * set to the start of the path before the first call; padvance will update
- * this value as it proceeds.  Successive calls to padvance will return
- * the possible path expansions in sequence.  If an option (indicated by
- * a percent sign) appears in the path entry then the global variable
- * pathopt will be set to point to it; otherwise pathopt will be set to
- * NULL.
- */
-static char *
-padvance(const char **path, const char *name)
-{
-       const char *p;
-       char *q;
-       const char *start;
-       size_t len;
-
-       if (*path == NULL)
-               return NULL;
-       start = *path;
-       for (p = start; *p && *p != ':' && *p != '%'; p++);
-       len = p - start + strlen(name) + 2;     /* "2" is for '/' and '\0' */
-       while (stackblocksize() < len)
-               growstackblock();
-       q = stackblock();
-       if (p != start) {
-               memcpy(q, start, p - start);
-               q += p - start;
-               *q++ = '/';
-       }
-       strcpy(q, name);
-       pathopt = NULL;
-       if (*p == '%') {
-               pathopt = ++p;
-               while (*p && *p != ':')  p++;
-       }
-       if (*p == ':')
-               *path = p + 1;
-       else
-               *path = NULL;
-       return stalloc(len);
-}
-
-
 /*** Command hashing code ***/
 
 static void
@@ -5652,7 +6167,7 @@ addfname(const char *name)
        struct strlist *sp;
 
        sp = stalloc(sizeof(*sp));
-       sp->text = sstrdup(name);
+       sp->text = ststrdup(name);
        *exparg.lastp = sp;
        exparg.lastp = &sp->next;
 }
@@ -5987,8 +6502,7 @@ static void pushfile(void);
  * Read a character from the script, returning PEOF on end of file.
  * Nul characters in the input are silently discarded.
  */
-
-
+static int preadbuffer(void);
 #define pgetc_as_macro()   (--parsenleft >= 0? SC2INT(*parsenextc++) : preadbuffer())
 
 #if ENABLE_ASH_OPTIMIZE_FOR_SIZE
@@ -6007,12 +6521,12 @@ pgetc(void)
 }
 #endif
 
-
 /*
  * Same as pgetc(), but ignores PEOA.
  */
 #if ENABLE_ASH_ALIAS
-static int pgetc2(void)
+static int
+pgetc2(void)
 {
        int c;
 
@@ -6022,7 +6536,8 @@ static int pgetc2(void)
        return c;
 }
 #else
-static int pgetc2(void)
+static int
+pgetc2(void)
 {
        return pgetc_macro();
 }
@@ -6031,8 +6546,8 @@ static int pgetc2(void)
 /*
  * Read a line from the script.
  */
-
-static char * pfgets(char *line, int len)
+static char *
+pfgets(char *line, int len)
 {
        char *p = line;
        int nleft = len;
@@ -6053,27 +6568,6 @@ static char * pfgets(char *line, int len)
        return line;
 }
 
-
-#if ENABLE_FEATURE_EDITING
-static line_input_t *line_input_state;
-//static SKIP_ASH_EXPAND_PRMT(const) char *cmdedit_prompt;
-static const char *cmdedit_prompt;
-static void putprompt(const char *s)
-{
-       if (ENABLE_ASH_EXPAND_PRMT) {
-               free((char*)cmdedit_prompt);
-               cmdedit_prompt = xstrdup(s);
-               return;
-       }
-       cmdedit_prompt = s;
-}
-#else
-static void putprompt(const char *s)
-{
-       out2str(s);
-}
-#endif
-
 #if ENABLE_FEATURE_EDITING_VI
 #define setvimode(on) do { \
        if (on) line_input_state->flags |= VI_MODE; \
@@ -6083,8 +6577,8 @@ static void putprompt(const char *s)
 #define setvimode(on) viflag = 0   /* forcibly keep the option off */
 #endif
 
-
-static int preadfd(void)
+static int
+preadfd(void)
 {
        int nr;
        char *buf =  parsefile->buf;
@@ -7838,187 +8332,80 @@ 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 */
-}
-
-/*      mystring.c   */
-
-/*
- * String functions.
- *
- *      number(s)               Convert a string of digits to an integer.
- *      is_number(s)            Return true if s is a string of digits.
- */
-
-/*
- * prefix -- see if pfx is a prefix of string.
- */
-static char *
-prefix(const char *string, const char *pfx)
-{
-       while (*pfx) {
-               if (*pfx++ != *string++)
-                       return 0;
-       }
-       return (char *) string;
-}
-
-
-/*
- * Convert a string of digits to an integer, printing an error message on
- * failure.
- */
-static int
-number(const char *s)
-{
-       if (!is_number(s))
-               ash_msg_and_raise_error(illnum, s);
-       return atoi(s);
-}
-
-
-/*
- * Check for a valid number.  This should be elsewhere.
- */
-static int
-is_number(const char *p)
-{
-       do {
-               if (!is_digit(*p))
-                       return 0;
-       } while (*++p != '\0');
-       return 1;
-}
-
-
-/*
- * Produce a possibly single quoted string suitable as input to the shell.
- * The return string is allocated on the stack.
- */
-static char *
-single_quote(const char *s)
-{
-       char *p;
-
-       STARTSTACKSTR(p);
-
-       do {
-               char *q;
-               size_t len;
-
-               len = strchrnul(s, '\'') - s;
-
-               q = p = makestrspace(len + 3, p);
-
-               *q++ = '\'';
-               q = memcpy(q, s, len) + len;
-               *q++ = '\'';
-               s += len;
-
-               STADJUST(q - p, p);
+       char *q;
+       time_t *mtp;
+       struct stackmark smark;
+       struct stat statb;
 
-               len = strspn(s, "'");
-               if (!len)
+       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);
+}
 
-               q = p = makestrspace(len + 3, p);
-
-               *q++ = '"';
-               q = memcpy(q, s, len) + len;
-               *q++ = '"';
-               s += len;
-
-               STADJUST(q - p, p);
-       } while (*s);
-
-       USTPUTC(0, p);
 
-       return stackblock();
+static void
+changemail(const char *val)
+{
+       mail_var_path_changed++;
 }
 
+#endif /* ASH_MAIL */
 
 /*
- * Like strdup but works with the ash stack.
+ * 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 *
-sstrdup(const char *p)
+find_dot_file(char *name)
 {
-       size_t len = strlen(p) + 1;
-       return memcpy(stalloc(len), p, len);
-}
+       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)
@@ -8199,949 +8586,614 @@ copynode(union node *n)
        return new;
 }
 
-
-static struct nodelist *
-copynodelist(struct nodelist *lp)
-{
-       struct nodelist *start;
-       struct nodelist **lpp;
-
-       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;
-       }
-       *lpp = NULL;
-       return start;
-}
-
-
-static char *
-nodeckstrdup(char *s)
-{
-       char *rtn = funcstring;
-
-       strcpy(funcstring, s);
-       funcstring += strlen(s) + 1;
-       return rtn;
-}
-
-
-/*
- * Free a parse tree.
- */
-static void
-freefunc(struct funcnode *f)
-{
-       if (f && --f->count < 0)
-               free(f);
-}
-
-
-static void setoption(int, int);
-
-
-static void
-optschanged(void)
-{
-#if DEBUG
-       opentrace();
-#endif
-       setinteractive(iflag);
-       setjobctl(mflag);
-       setvimode(viflag);
-}
-
-static void minus_o(char *name, int val)
-{
-       int i;
-
-       if (name) {
-               for (i = 0; i < NOPTS; i++) {
-                       if (equal(name, optnames(i))) {
-                               optlist[i] = val;
-                               return;
-                       }
-               }
-               ash_msg_and_raise_error("Illegal option -o %s", name);
-       }
-       out1str("Current option settings\n");
-       for (i = 0; i < NOPTS; i++)
-               out1fmt("%-16s%s\n", optnames(i),
-                               optlist[i] ? "on" : "off");
-}
-
-
-/*
- * Process shell options.  The global variable argptr contains a pointer
- * to the argument list; we advance it past the options.
- */
-static void
-options(int cmdline)
-{
-       char *p;
-       int val;
-       int c;
-
-       if (cmdline)
-               minusc = NULL;
-       while ((p = *argptr) != NULL) {
-               argptr++;
-               c = *p++;
-               if (c == '-') {
-                       val = 1;
-                       if (p[0] == '\0' || LONE_DASH(p)) {
-                               if (!cmdline) {
-                                       /* "-" means turn off -x and -v */
-                                       if (p[0] == '\0')
-                                               xflag = vflag = 0;
-                                       /* "--" means reset params */
-                                       else if (*argptr == NULL)
-                                               setparam(argptr);
-                               }
-                               break;    /* "-" or  "--" terminates options */
-                       }
-               } else if (c == '+') {
-                       val = 0;
-               } else {
-                       argptr--;
-                       break;
-               }
-               while ((c = *p++) != '\0') {
-                       if (c == 'c' && cmdline) {
-                               minusc = p;     /* command is after shell args*/
-                       } else if (c == 'o') {
-                               minus_o(*argptr, val);
-                               if (*argptr)
-                                       argptr++;
-                       } else if (cmdline && (c == '-')) {     // long options
-                               if (strcmp(p, "login") == 0)
-                                       isloginsh = 1;
-                               break;
-                       } else {
-                               setoption(c, val);
-                       }
-               }
-       }
-}
-
-
-static void
-setoption(int flag, int val)
-{
-       int i;
-
-       for (i = 0; i < NOPTS; i++) {
-               if (optletters(i) == flag) {
-                       optlist[i] = val;
-                       return;
-               }
-       }
-       ash_msg_and_raise_error("Illegal option -%c", flag);
-       /* NOTREACHED */
-}
-
-
-/*
- * Set the shell parameters.
- */
-static void
-setparam(char **argv)
-{
-       char **newparam;
-       char **ap;
-       int nparam;
-
-       for (nparam = 0; argv[nparam]; nparam++);
-       ap = newparam = ckmalloc((nparam + 1) * sizeof(*ap));
-       while (*argv) {
-               *ap++ = ckstrdup(*argv++);
-       }
-       *ap = NULL;
-       freeparam(&shellparam);
-       shellparam.malloc = 1;
-       shellparam.nparam = nparam;
-       shellparam.p = newparam;
-#if ENABLE_ASH_GETOPTS
-       shellparam.optind = 1;
-       shellparam.optoff = -1;
-#endif
-}
-
-
-/*
- * Free the list of positional parameters.
- */
-static void
-freeparam(volatile struct shparam *param)
+
+static struct nodelist *
+copynodelist(struct nodelist *lp)
 {
-       char **ap;
+       struct nodelist *start;
+       struct nodelist **lpp;
 
-       if (param->malloc) {
-               for (ap = param->p; *ap; ap++)
-                       free(*ap);
-               free(param->p);
+       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;
        }
+       *lpp = NULL;
+       return start;
 }
 
 
-/*
- * The shift builtin command.
- */
-static int
-shiftcmd(int argc, char **argv)
+static char *
+nodeckstrdup(char *s)
 {
-       int n;
-       char **ap1, **ap2;
+       char *rtn = funcstring;
 
-       n = 1;
-       if (argc > 1)
-               n = number(argv[1]);
-       if (n > shellparam.nparam)
-               ash_msg_and_raise_error("can't shift that many");
-       INT_OFF;
-       shellparam.nparam -= n;
-       for (ap1 = shellparam.p; --n >= 0; ap1++) {
-               if (shellparam.malloc)
-                       free(*ap1);
-       }
-       ap2 = shellparam.p;
-       while ((*ap2++ = *ap1++) != NULL);
-#if ENABLE_ASH_GETOPTS
-       shellparam.optind = 1;
-       shellparam.optoff = -1;
-#endif
-       INT_ON;
-       return 0;
+       strcpy(funcstring, s);
+       funcstring += strlen(s) + 1;
+       return rtn;
 }
 
 
 /*
- * The set command builtin.
+ * Free a parse tree.
  */
-static int
-setcmd(int argc, char **argv)
+static void
+freefunc(struct funcnode *f)
 {
-       if (argc == 1)
-               return showvars(nullstr, 0, VUNSET);
-       INT_OFF;
-       options(0);
-       optschanged();
-       if (*argptr != NULL) {
-               setparam(argptr);
-       }
-       INT_ON;
-       return 0;
+       if (f && --f->count < 0)
+               free(f);
 }
 
 
-#if ENABLE_ASH_GETOPTS
 static void
-getoptsreset(const char *value)
+optschanged(void)
 {
-       shellparam.optind = number(value);
-       shellparam.optoff = -1;
-}
+#if DEBUG
+       opentrace();
 #endif
-
-#if ENABLE_LOCALE_SUPPORT
-static void change_lc_all(const char *value)
-{
-       if (value && *value != '\0')
-               setlocale(LC_ALL, value);
-}
-
-static void change_lc_ctype(const char *value)
-{
-       if (value && *value != '\0')
-               setlocale(LC_CTYPE, value);
+       setinteractive(iflag);
+       setjobctl(mflag);
+       setvimode(viflag);
 }
 
-#endif
-
-#if ENABLE_ASH_RANDOM_SUPPORT
-/* Roughly copied from bash.. */
-static void change_random(const char *value)
+static void
+minus_o(char *name, int val)
 {
-       if (value == NULL) {
-               /* "get", generate */
-               char buf[16];
+       int i;
 
-               rseed = rseed * 1103515245 + 12345;
-               sprintf(buf, "%d", (unsigned int)((rseed & 32767)));
-               /* set without recursion */
-               setvar(vrandom.text, buf, VNOFUNC);
-               vrandom.flags &= ~VNOFUNC;
-       } else {
-               /* set/reset */
-               rseed = strtoul(value, (char **)NULL, 10);
+       if (name) {
+               for (i = 0; i < NOPTS; i++) {
+                       if (equal(name, optnames(i))) {
+                               optlist[i] = val;
+                               return;
+                       }
+               }
+               ash_msg_and_raise_error("Illegal option -o %s", name);
        }
+       out1str("Current option settings\n");
+       for (i = 0; i < NOPTS; i++)
+               out1fmt("%-16s%s\n", optnames(i),
+                               optlist[i] ? "on" : "off");
 }
-#endif
 
 
-#if ENABLE_ASH_GETOPTS
-static int
-getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *optoff)
+static void
+setoption(int flag, int val)
 {
-       char *p, *q;
-       char c = '?';
-       int done = 0;
-       int err = 0;
-       char s[12];
-       char **optnext;
-
-       if (*param_optind < 1)
-               return 1;
-       optnext = optfirst + *param_optind - 1;
-
-       if (*param_optind <= 1 || *optoff < 0 || strlen(optnext[-1]) < *optoff)
-               p = NULL;
-       else
-               p = optnext[-1] + *optoff;
-       if (p == NULL || *p == '\0') {
-               /* Current word is done, advance */
-               p = *optnext;
-               if (p == NULL || *p != '-' || *++p == '\0') {
- atend:
-                       p = NULL;
-                       done = 1;
-                       goto out;
-               }
-               optnext++;
-               if (LONE_DASH(p))        /* check for "--" */
-                       goto atend;
-       }
-
-       c = *p++;
-       for (q = optstr; *q != c; ) {
-               if (*q == '\0') {
-                       if (optstr[0] == ':') {
-                               s[0] = c;
-                               s[1] = '\0';
-                               err |= setvarsafe("OPTARG", s, 0);
-                       } else {
-                               fprintf(stderr, "Illegal option -%c\n", c);
-                               (void) unsetvar("OPTARG");
-                       }
-                       c = '?';
-                       goto out;
-               }
-               if (*++q == ':')
-                       q++;
-       }
+       int i;
 
-       if (*++q == ':') {
-               if (*p == '\0' && (p = *optnext) == NULL) {
-                       if (optstr[0] == ':') {
-                               s[0] = c;
-                               s[1] = '\0';
-                               err |= setvarsafe("OPTARG", s, 0);
-                               c = ':';
-                       } else {
-                               fprintf(stderr, "No arg for -%c option\n", c);
-                               (void) unsetvar("OPTARG");
-                               c = '?';
-                       }
-                       goto out;
+       for (i = 0; i < NOPTS; i++) {
+               if (optletters(i) == flag) {
+                       optlist[i] = val;
+                       return;
                }
-
-               if (p == *optnext)
-                       optnext++;
-               err |= setvarsafe("OPTARG", p, 0);
-               p = NULL;
-       } else
-               err |= setvarsafe("OPTARG", nullstr, 0);
- out:
-       *optoff = p ? p - *(optnext - 1) : -1;
-       *param_optind = optnext - optfirst + 1;
-       fmtstr(s, sizeof(s), "%d", *param_optind);
-       err |= setvarsafe("OPTIND", s, VNOFUNC);
-       s[0] = c;
-       s[1] = '\0';
-       err |= setvarsafe(optvar, s, 0);
-       if (err) {
-               *param_optind = 1;
-               *optoff = -1;
-               flush_stdout_stderr();
-               raise_exception(EXERROR);
        }
-       return done;
+       ash_msg_and_raise_error("Illegal option -%c", flag);
+       /* NOTREACHED */
 }
 
 
 /*
- * The getopts builtin.  Shellparam.optnext points to the next argument
- * to be processed.  Shellparam.optptr points to the next character to
- * be processed in the current argument.  If shellparam.optnext is NULL,
- * then it's the first time getopts has been called.
+ * Process shell options.  The global variable argptr contains a pointer
+ * to the argument list; we advance it past the options.
  */
-static int
-getoptscmd(int argc, char **argv)
+static void
+options(int cmdline)
 {
-       char **optbase;
+       char *p;
+       int val;
+       int c;
 
-       if (argc < 3)
-               ash_msg_and_raise_error("Usage: getopts optstring var [arg]");
-       else if (argc == 3) {
-               optbase = shellparam.p;
-               if (shellparam.optind > shellparam.nparam + 1) {
-                       shellparam.optind = 1;
-                       shellparam.optoff = -1;
+       if (cmdline)
+               minusc = NULL;
+       while ((p = *argptr) != NULL) {
+               argptr++;
+               c = *p++;
+               if (c == '-') {
+                       val = 1;
+                       if (p[0] == '\0' || LONE_DASH(p)) {
+                               if (!cmdline) {
+                                       /* "-" means turn off -x and -v */
+                                       if (p[0] == '\0')
+                                               xflag = vflag = 0;
+                                       /* "--" means reset params */
+                                       else if (*argptr == NULL)
+                                               setparam(argptr);
+                               }
+                               break;    /* "-" or  "--" terminates options */
+                       }
+               } else if (c == '+') {
+                       val = 0;
+               } else {
+                       argptr--;
+                       break;
                }
-       } else {
-               optbase = &argv[3];
-               if (shellparam.optind > argc - 2) {
-                       shellparam.optind = 1;
-                       shellparam.optoff = -1;
+               while ((c = *p++) != '\0') {
+                       if (c == 'c' && cmdline) {
+                               minusc = p;     /* command is after shell args*/
+                       } else if (c == 'o') {
+                               minus_o(*argptr, val);
+                               if (*argptr)
+                                       argptr++;
+                       } else if (cmdline && (c == '-')) {     // long options
+                               if (strcmp(p, "login") == 0)
+                                       isloginsh = 1;
+                               break;
+                       } else {
+                               setoption(c, val);
+                       }
                }
        }
-
-       return getopts(argv[1], argv[2], optbase, &shellparam.optind,
-                       &shellparam.optoff);
 }
-#endif /* ASH_GETOPTS */
+
 
 /*
- * XXX - should get rid of.  have all builtins use getopt(3).  the
- * library getopt must have the BSD extension static variable "optreset"
- * otherwise it can't be used within the shell safely.
- *
- * Standard option processing (a la getopt) for builtin routines.  The
- * only argument that is passed to nextopt is the option string; the
- * other arguments are unnecessary.  It return the character, or '\0' on
- * end of input.
+ * Set the shell parameters.
  */
-static int
-nextopt(const char *optstring)
+static void
+setparam(char **argv)
 {
-       char *p;
-       const char *q;
-       char c;
+       char **newparam;
+       char **ap;
+       int nparam;
 
-       p = optptr;
-       if (p == NULL || *p == '\0') {
-               p = *argptr;
-               if (p == NULL || *p != '-' || *++p == '\0')
-                       return '\0';
-               argptr++;
-               if (LONE_DASH(p))        /* check for "--" */
-                       return '\0';
-       }
-       c = *p++;
-       for (q = optstring; *q != c; ) {
-               if (*q == '\0')
-                       ash_msg_and_raise_error("Illegal option -%c", c);
-               if (*++q == ':')
-                       q++;
-       }
-       if (*++q == ':') {
-               if (*p == '\0' && (p = *argptr++) == NULL)
-                       ash_msg_and_raise_error("No arg for -%c option", c);
-               optionarg = p;
-               p = NULL;
+       for (nparam = 0; argv[nparam]; nparam++);
+       ap = newparam = ckmalloc((nparam + 1) * sizeof(*ap));
+       while (*argv) {
+               *ap++ = ckstrdup(*argv++);
        }
-       optptr = p;
-       return c;
+       *ap = NULL;
+       freeparam(&shellparam);
+       shellparam.malloc = 1;
+       shellparam.nparam = nparam;
+       shellparam.p = newparam;
+#if ENABLE_ASH_GETOPTS
+       shellparam.optind = 1;
+       shellparam.optoff = -1;
+#endif
 }
 
 
-/*      parser.c     */
-
-
 /*
- * Shell command parser.
+ * Free the list of positional parameters.
  */
-
-#define EOFMARKLEN 79
-
-struct heredoc {
-       struct heredoc *next;   /* next here document in list */
-       union node *here;               /* redirection node */
-       char *eofmark;          /* string indicating end of input */
-       int striptabs;          /* if set, strip leading tabs */
-};
-
-static struct heredoc *heredoclist;    /* list of here documents to read */
-
-static union node *list(int);
-static union node *andor(void);
-static union node *pipeline(void);
-static union node *command(void);
-static union node *simplecmd(void);
-static union node *makename(void);
-static void parsefname(void);
-static void parseheredoc(void);
-static char peektoken(void);
-static int readtoken(void);
-static int xxreadtoken(void);
-static int readtoken1(int firstc, int syntax, char *eofmark, int striptabs);
-static int noexpand(char *);
-static void setprompt(int);
-
-static void raise_error_syntax(const char *) ATTRIBUTE_NORETURN;
 static void
-raise_error_syntax(const char *msg)
+freeparam(volatile struct shparam *param)
 {
-       ash_msg_and_raise_error("Syntax error: %s", msg);
-       /* NOTREACHED */
+       char **ap;
+
+       if (param->malloc) {
+               for (ap = param->p; *ap; ap++)
+                       free(*ap);
+               free(param->p);
+       }
 }
 
+
 /*
- * Called when an unexpected token is read during the parse.  The argument
- * is the token that is expected, or -1 if more than one type of token can
- * occur at this point.
+ * The shift builtin command.
  */
-static void raise_error_unexpected_syntax(int) ATTRIBUTE_NORETURN;
-static void
-raise_error_unexpected_syntax(int token)
+static int
+shiftcmd(int argc, char **argv)
 {
-       char msg[64];
-       int l;
+       int n;
+       char **ap1, **ap2;
 
-       l = sprintf(msg, "%s unexpected", tokname(lasttoken));
-       if (token >= 0)
-               sprintf(msg + l, " (expecting %s)", tokname(token));
-       raise_error_syntax(msg);
-       /* NOTREACHED */
+       n = 1;
+       if (argc > 1)
+               n = number(argv[1]);
+       if (n > shellparam.nparam)
+               ash_msg_and_raise_error("can't shift that many");
+       INT_OFF;
+       shellparam.nparam -= n;
+       for (ap1 = shellparam.p; --n >= 0; ap1++) {
+               if (shellparam.malloc)
+                       free(*ap1);
+       }
+       ap2 = shellparam.p;
+       while ((*ap2++ = *ap1++) != NULL);
+#if ENABLE_ASH_GETOPTS
+       shellparam.optind = 1;
+       shellparam.optoff = -1;
+#endif
+       INT_ON;
+       return 0;
 }
 
+
 /*
- * Read and parse a command.  Returns NEOF on end of file.  (NULL is a
- * valid parse tree indicating a blank line.)
+ * POSIX requires that 'set' (but not export or readonly) output the
+ * variables in lexicographic order - by the locale's collating order (sigh).
+ * Maybe we could keep them in an ordered balanced binary tree
+ * instead of hashed lists.
+ * For now just roll 'em through qsort for printing...
  */
-static union node *
-parsecmd(int interact)
+static int
+showvars(const char *sep_prefix, int on, int off)
 {
-       int t;
+       const char *sep;
+       char **ep, **epend;
 
-       tokpushback = 0;
-       doprompt = interact;
-       if (doprompt)
-               setprompt(doprompt);
-       needprompt = 0;
-       t = readtoken();
-       if (t == TEOF)
-               return NEOF;
-       if (t == TNL)
-               return NULL;
-       tokpushback++;
-       return list(1);
-}
+       ep = listvars(on, off, &epend);
+       qsort(ep, epend - ep, sizeof(char *), vpcmp);
 
+       sep = *sep_prefix ? spcstr : sep_prefix;
 
-static union node *
-list(int nlflag)
-{
-       union node *n1, *n2, *n3;
-       int tok;
+       for (; ep < epend; ep++) {
+               const char *p;
+               const char *q;
 
-       checkkwd = CHKNL | CHKKWD | CHKALIAS;
-       if (nlflag == 2 && peektoken())
-               return NULL;
-       n1 = NULL;
-       for (;;) {
-               n2 = andor();
-               tok = readtoken();
-               if (tok == TBACKGND) {
-                       if (n2->type == NPIPE) {
-                               n2->npipe.backgnd = 1;
-                       } else {
-                               if (n2->type != NREDIR) {
-                                       n3 = stalloc(sizeof(struct nredir));
-                                       n3->nredir.n = n2;
-                                       n3->nredir.redirect = NULL;
-                                       n2 = n3;
-                               }
-                               n2->type = NBACKGND;
-                       }
-               }
-               if (n1 == NULL) {
-                       n1 = n2;
-               } else {
-                       n3 = stalloc(sizeof(struct nbinary));
-                       n3->type = NSEMI;
-                       n3->nbinary.ch1 = n1;
-                       n3->nbinary.ch2 = n2;
-                       n1 = n3;
-               }
-               switch (tok) {
-               case TBACKGND:
-               case TSEMI:
-                       tok = readtoken();
-                       /* fall through */
-               case TNL:
-                       if (tok == TNL) {
-                               parseheredoc();
-                               if (nlflag == 1)
-                                       return n1;
-                       } else {
-                               tokpushback++;
-                       }
-                       checkkwd = CHKNL | CHKKWD | CHKALIAS;
-                       if (peektoken())
-                               return n1;
-                       break;
-               case TEOF:
-                       if (heredoclist)
-                               parseheredoc();
-                       else
-                               pungetc();              /* push back EOF on input */
-                       return n1;
-               default:
-                       if (nlflag == 1)
-                               raise_error_unexpected_syntax(-1);
-                       tokpushback++;
-                       return n1;
-               }
+               p = strchrnul(*ep, '=');
+               q = nullstr;
+               if (*p)
+                       q = single_quote(++p);
+               out1fmt("%s%s%.*s%s\n", sep_prefix, sep, (int)(p - *ep), *ep, q);
+       }
+       return 0;
+}
+
+/*
+ * The set command builtin.
+ */
+static int
+setcmd(int argc, char **argv)
+{
+       if (argc == 1)
+               return showvars(nullstr, 0, VUNSET);
+       INT_OFF;
+       options(0);
+       optschanged();
+       if (*argptr != NULL) {
+               setparam(argptr);
        }
+       INT_ON;
+       return 0;
 }
 
 
-static union node *
-andor(void)
+#if ENABLE_LOCALE_SUPPORT
+static void change_lc_all(const char *value)
 {
-       union node *n1, *n2, *n3;
-       int t;
-
-       n1 = pipeline();
-       for (;;) {
-               t = readtoken();
-               if (t == TAND) {
-                       t = NAND;
-               } else if (t == TOR) {
-                       t = NOR;
-               } else {
-                       tokpushback++;
-                       return n1;
-               }
-               checkkwd = CHKNL | CHKKWD | CHKALIAS;
-               n2 = pipeline();
-               n3 = stalloc(sizeof(struct nbinary));
-               n3->type = t;
-               n3->nbinary.ch1 = n1;
-               n3->nbinary.ch2 = n2;
-               n1 = n3;
-       }
+       if (value && *value != '\0')
+               setlocale(LC_ALL, value);
 }
 
+static void change_lc_ctype(const char *value)
+{
+       if (value && *value != '\0')
+               setlocale(LC_CTYPE, value);
+}
+#endif
 
-static union node *
-pipeline(void)
+#if ENABLE_ASH_RANDOM_SUPPORT
+/* Roughly copied from bash.. */
+static void change_random(const char *value)
 {
-       union node *n1, *n2, *pipenode;
-       struct nodelist *lp, *prev;
-       int negate;
+       if (value == NULL) {
+               /* "get", generate */
+               char buf[16];
 
-       negate = 0;
-       TRACE(("pipeline: entered\n"));
-       if (readtoken() == TNOT) {
-               negate = !negate;
-               checkkwd = CHKKWD | CHKALIAS;
-       } else
-               tokpushback++;
-       n1 = command();
-       if (readtoken() == TPIPE) {
-               pipenode = stalloc(sizeof(struct npipe));
-               pipenode->type = NPIPE;
-               pipenode->npipe.backgnd = 0;
-               lp = stalloc(sizeof(struct nodelist));
-               pipenode->npipe.cmdlist = lp;
-               lp->n = n1;
-               do {
-                       prev = lp;
-                       lp = stalloc(sizeof(struct nodelist));
-                       checkkwd = CHKNL | CHKKWD | CHKALIAS;
-                       lp->n = command();
-                       prev->next = lp;
-               } while (readtoken() == TPIPE);
-               lp->next = NULL;
-               n1 = pipenode;
-       }
-       tokpushback++;
-       if (negate) {
-               n2 = stalloc(sizeof(struct nnot));
-               n2->type = NNOT;
-               n2->nnot.com = n1;
-               return n2;
+               rseed = rseed * 1103515245 + 12345;
+               sprintf(buf, "%d", (unsigned int)((rseed & 32767)));
+               /* set without recursion */
+               setvar(vrandom.text, buf, VNOFUNC);
+               vrandom.flags &= ~VNOFUNC;
+       } else {
+               /* set/reset */
+               rseed = strtoul(value, (char **)NULL, 10);
        }
-       return n1;
 }
+#endif
 
 
-static union node *
-command(void)
+#if ENABLE_ASH_GETOPTS
+static int
+getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *optoff)
 {
-       union node *n1, *n2;
-       union node *ap, **app;
-       union node *cp, **cpp;
-       union node *redir, **rpp;
-       union node **rpp2;
-       int t;
-
-       redir = NULL;
-       rpp2 = &redir;
+       char *p, *q;
+       char c = '?';
+       int done = 0;
+       int err = 0;
+       char s[12];
+       char **optnext;
 
-       switch (readtoken()) {
-       default:
-               raise_error_unexpected_syntax(-1);
-               /* NOTREACHED */
-       case TIF:
-               n1 = stalloc(sizeof(struct nif));
-               n1->type = NIF;
-               n1->nif.test = list(0);
-               if (readtoken() != TTHEN)
-                       raise_error_unexpected_syntax(TTHEN);
-               n1->nif.ifpart = list(0);
-               n2 = n1;
-               while (readtoken() == TELIF) {
-                       n2->nif.elsepart = stalloc(sizeof(struct nif));
-                       n2 = n2->nif.elsepart;
-                       n2->type = NIF;
-                       n2->nif.test = list(0);
-                       if (readtoken() != TTHEN)
-                               raise_error_unexpected_syntax(TTHEN);
-                       n2->nif.ifpart = list(0);
-               }
-               if (lasttoken == TELSE)
-                       n2->nif.elsepart = list(0);
-               else {
-                       n2->nif.elsepart = NULL;
-                       tokpushback++;
-               }
-               t = TFI;
-               break;
-       case TWHILE:
-       case TUNTIL: {
-               int got;
-               n1 = stalloc(sizeof(struct nbinary));
-               n1->type = (lasttoken == TWHILE)? NWHILE : NUNTIL;
-               n1->nbinary.ch1 = list(0);
-               if ((got=readtoken()) != TDO) {
-                       TRACE(("expecting DO got %s %s\n", tokname(got),
-                                       got == TWORD ? wordtext : ""));
-                       raise_error_unexpected_syntax(TDO);
-               }
-               n1->nbinary.ch2 = list(0);
-               t = TDONE;
-               break;
-       }
-       case TFOR:
-               if (readtoken() != TWORD || quoteflag || ! goodname(wordtext))
-                       raise_error_syntax("Bad for loop variable");
-               n1 = stalloc(sizeof(struct nfor));
-               n1->type = NFOR;
-               n1->nfor.var = wordtext;
-               checkkwd = CHKKWD | CHKALIAS;
-               if (readtoken() == TIN) {
-                       app = &ap;
-                       while (readtoken() == TWORD) {
-                               n2 = stalloc(sizeof(struct narg));
-                               n2->type = NARG;
-                               n2->narg.text = wordtext;
-                               n2->narg.backquote = backquotelist;
-                               *app = n2;
-                               app = &n2->narg.next;
-                       }
-                       *app = NULL;
-                       n1->nfor.args = ap;
-                       if (lasttoken != TNL && lasttoken != TSEMI)
-                               raise_error_unexpected_syntax(-1);
-               } else {
-                       n2 = stalloc(sizeof(struct narg));
-                       n2->type = NARG;
-                       n2->narg.text = (char *)dolatstr;
-                       n2->narg.backquote = NULL;
-                       n2->narg.next = NULL;
-                       n1->nfor.args = n2;
-                       /*
-                        * Newline or semicolon here is optional (but note
-                        * that the original Bourne shell only allowed NL).
-                        */
-                       if (lasttoken != TNL && lasttoken != TSEMI)
-                               tokpushback++;
-               }
-               checkkwd = CHKNL | CHKKWD | CHKALIAS;
-               if (readtoken() != TDO)
-                       raise_error_unexpected_syntax(TDO);
-               n1->nfor.body = list(0);
-               t = TDONE;
-               break;
-       case TCASE:
-               n1 = stalloc(sizeof(struct ncase));
-               n1->type = NCASE;
-               if (readtoken() != TWORD)
-                       raise_error_unexpected_syntax(TWORD);
-               n1->ncase.expr = n2 = stalloc(sizeof(struct narg));
-               n2->type = NARG;
-               n2->narg.text = wordtext;
-               n2->narg.backquote = backquotelist;
-               n2->narg.next = NULL;
-               do {
-                       checkkwd = CHKKWD | CHKALIAS;
-               } while (readtoken() == TNL);
-               if (lasttoken != TIN)
-                       raise_error_unexpected_syntax(TIN);
-               cpp = &n1->ncase.cases;
- next_case:
-               checkkwd = CHKNL | CHKKWD;
-               t = readtoken();
-               while (t != TESAC) {
-                       if (lasttoken == TLP)
-                               readtoken();
-                       *cpp = cp = stalloc(sizeof(struct nclist));
-                       cp->type = NCLIST;
-                       app = &cp->nclist.pattern;
-                       for (;;) {
-                               *app = ap = stalloc(sizeof(struct narg));
-                               ap->type = NARG;
-                               ap->narg.text = wordtext;
-                               ap->narg.backquote = backquotelist;
-                               if (readtoken() != TPIPE)
-                                       break;
-                               app = &ap->narg.next;
-                               readtoken();
-                       }
-                       ap->narg.next = NULL;
-                       if (lasttoken != TRP)
-                               raise_error_unexpected_syntax(TRP);
-                       cp->nclist.body = list(2);
+       if (*param_optind < 1)
+               return 1;
+       optnext = optfirst + *param_optind - 1;
 
-                       cpp = &cp->nclist.next;
+       if (*param_optind <= 1 || *optoff < 0 || strlen(optnext[-1]) < *optoff)
+               p = NULL;
+       else
+               p = optnext[-1] + *optoff;
+       if (p == NULL || *p == '\0') {
+               /* Current word is done, advance */
+               p = *optnext;
+               if (p == NULL || *p != '-' || *++p == '\0') {
+ atend:
+                       p = NULL;
+                       done = 1;
+                       goto out;
+               }
+               optnext++;
+               if (LONE_DASH(p))        /* check for "--" */
+                       goto atend;
+       }
 
-                       checkkwd = CHKNL | CHKKWD;
-                       t = readtoken();
-                       if (t != TESAC) {
-                               if (t != TENDCASE)
-                                       raise_error_unexpected_syntax(TENDCASE);
-                               goto next_case;
+       c = *p++;
+       for (q = optstr; *q != c; ) {
+               if (*q == '\0') {
+                       if (optstr[0] == ':') {
+                               s[0] = c;
+                               s[1] = '\0';
+                               err |= setvarsafe("OPTARG", s, 0);
+                       } else {
+                               fprintf(stderr, "Illegal option -%c\n", c);
+                               unsetvar("OPTARG");
                        }
+                       c = '?';
+                       goto out;
                }
-               *cpp = NULL;
-               goto redir;
-       case TLP:
-               n1 = stalloc(sizeof(struct nredir));
-               n1->type = NSUBSHELL;
-               n1->nredir.n = list(0);
-               n1->nredir.redirect = NULL;
-               t = TRP;
-               break;
-       case TBEGIN:
-               n1 = list(0);
-               t = TEND;
-               break;
-       case TWORD:
-       case TREDIR:
-               tokpushback++;
-               return simplecmd();
+               if (*++q == ':')
+                       q++;
        }
 
-       if (readtoken() != t)
-               raise_error_unexpected_syntax(t);
+       if (*++q == ':') {
+               if (*p == '\0' && (p = *optnext) == NULL) {
+                       if (optstr[0] == ':') {
+                               s[0] = c;
+                               s[1] = '\0';
+                               err |= setvarsafe("OPTARG", s, 0);
+                               c = ':';
+                       } else {
+                               fprintf(stderr, "No arg for -%c option\n", c);
+                               unsetvar("OPTARG");
+                               c = '?';
+                       }
+                       goto out;
+               }
 
- redir:
-       /* Now check for redirection which may follow command */
-       checkkwd = CHKKWD | CHKALIAS;
-       rpp = rpp2;
-       while (readtoken() == TREDIR) {
-               *rpp = n2 = redirnode;
-               rpp = &n2->nfile.next;
-               parsefname();
+               if (p == *optnext)
+                       optnext++;
+               err |= setvarsafe("OPTARG", p, 0);
+               p = NULL;
+       } else
+               err |= setvarsafe("OPTARG", nullstr, 0);
+ out:
+       *optoff = p ? p - *(optnext - 1) : -1;
+       *param_optind = optnext - optfirst + 1;
+       fmtstr(s, sizeof(s), "%d", *param_optind);
+       err |= setvarsafe("OPTIND", s, VNOFUNC);
+       s[0] = c;
+       s[1] = '\0';
+       err |= setvarsafe(optvar, s, 0);
+       if (err) {
+               *param_optind = 1;
+               *optoff = -1;
+               flush_stdout_stderr();
+               raise_exception(EXERROR);
        }
-       tokpushback++;
-       *rpp = NULL;
-       if (redir) {
-               if (n1->type != NSUBSHELL) {
-                       n2 = stalloc(sizeof(struct nredir));
-                       n2->type = NREDIR;
-                       n2->nredir.n = n1;
-                       n1 = n2;
+       return done;
+}
+
+
+/*
+ * The getopts builtin.  Shellparam.optnext points to the next argument
+ * to be processed.  Shellparam.optptr points to the next character to
+ * be processed in the current argument.  If shellparam.optnext is NULL,
+ * then it's the first time getopts has been called.
+ */
+static int
+getoptscmd(int argc, char **argv)
+{
+       char **optbase;
+
+       if (argc < 3)
+               ash_msg_and_raise_error("Usage: getopts optstring var [arg]");
+       if (argc == 3) {
+               optbase = shellparam.p;
+               if (shellparam.optind > shellparam.nparam + 1) {
+                       shellparam.optind = 1;
+                       shellparam.optoff = -1;
+               }
+       } else {
+               optbase = &argv[3];
+               if (shellparam.optind > argc - 2) {
+                       shellparam.optind = 1;
+                       shellparam.optoff = -1;
                }
-               n1->nredir.redirect = redir;
        }
-       return n1;
+
+       return getopts(argv[1], argv[2], optbase, &shellparam.optind,
+                       &shellparam.optoff);
 }
+#endif /* ASH_GETOPTS */
 
 
-static union node *
-simplecmd(void)
+/* ============ Shell parser */
+
+static void raise_error_syntax(const char *) ATTRIBUTE_NORETURN;
+static void
+raise_error_syntax(const char *msg)
 {
-       union node *args, **app;
-       union node *n = NULL;
-       union node *vars, **vpp;
-       union node **rpp, *redir;
-       int savecheckkwd;
+       ash_msg_and_raise_error("Syntax error: %s", msg);
+       /* NOTREACHED */
+}
 
-       args = NULL;
-       app = &args;
-       vars = NULL;
-       vpp = &vars;
-       redir = NULL;
-       rpp = &redir;
+/*
+ * Called when an unexpected token is read during the parse.  The argument
+ * is the token that is expected, or -1 if more than one type of token can
+ * occur at this point.
+ */
+static void raise_error_unexpected_syntax(int) ATTRIBUTE_NORETURN;
+static void
+raise_error_unexpected_syntax(int token)
+{
+       char msg[64];
+       int l;
 
-       savecheckkwd = CHKALIAS;
+       l = sprintf(msg, "%s unexpected", tokname(lasttoken));
+       if (token >= 0)
+               sprintf(msg + l, " (expecting %s)", tokname(token));
+       raise_error_syntax(msg);
+       /* NOTREACHED */
+}
+
+#define EOFMARKLEN 79
+
+struct heredoc {
+       struct heredoc *next;   /* next here document in list */
+       union node *here;               /* redirection node */
+       char *eofmark;          /* string indicating end of input */
+       int striptabs;          /* if set, strip leading tabs */
+};
+
+static struct heredoc *heredoclist;    /* list of here documents to read */
+
+/* parsing is heavily cross-recursive, need these forward decls */
+static union node *andor(void);
+static union node *pipeline(void);
+static union node *parse_command(void);
+static void parseheredoc(void);
+static char peektoken(void);
+static int readtoken(void);
+
+static union node *
+list(int nlflag)
+{
+       union node *n1, *n2, *n3;
+       int tok;
+
+       checkkwd = CHKNL | CHKKWD | CHKALIAS;
+       if (nlflag == 2 && peektoken())
+               return NULL;
+       n1 = NULL;
        for (;;) {
-               checkkwd = savecheckkwd;
-               switch (readtoken()) {
-               case TWORD:
-                       n = stalloc(sizeof(struct narg));
-                       n->type = NARG;
-                       n->narg.text = wordtext;
-                       n->narg.backquote = backquotelist;
-                       if (savecheckkwd && isassignment(wordtext)) {
-                               *vpp = n;
-                               vpp = &n->narg.next;
+               n2 = andor();
+               tok = readtoken();
+               if (tok == TBACKGND) {
+                       if (n2->type == NPIPE) {
+                               n2->npipe.backgnd = 1;
                        } else {
-                               *app = n;
-                               app = &n->narg.next;
-                               savecheckkwd = 0;
-                       }
-                       break;
-               case TREDIR:
-                       *rpp = n = redirnode;
-                       rpp = &n->nfile.next;
-                       parsefname();   /* read name of redirection file */
-                       break;
-               case TLP:
-                       if (args && app == &args->narg.next
-                        && !vars && !redir
-                       ) {
-                               struct builtincmd *bcmd;
-                               const char *name;
-
-                               /* We have a function */
-                               if (readtoken() != TRP)
-                                       raise_error_unexpected_syntax(TRP);
-                               name = n->narg.text;
-                               if (!goodname(name)
-                                || ((bcmd = find_builtin(name)) && IS_BUILTIN_SPECIAL(bcmd))
-                               ) {
-                                       raise_error_syntax("Bad function name");
+                               if (n2->type != NREDIR) {
+                                       n3 = stalloc(sizeof(struct nredir));
+                                       n3->nredir.n = n2;
+                                       n3->nredir.redirect = NULL;
+                                       n2 = n3;
                                }
-                               n->type = NDEFUN;
-                               checkkwd = CHKNL | CHKKWD | CHKALIAS;
-                               n->narg.next = command();
-                               return n;
+                               n2->type = NBACKGND;
+                       }
+               }
+               if (n1 == NULL) {
+                       n1 = n2;
+               } else {
+                       n3 = stalloc(sizeof(struct nbinary));
+                       n3->type = NSEMI;
+                       n3->nbinary.ch1 = n1;
+                       n3->nbinary.ch2 = n2;
+                       n1 = n3;
+               }
+               switch (tok) {
+               case TBACKGND:
+               case TSEMI:
+                       tok = readtoken();
+                       /* fall through */
+               case TNL:
+                       if (tok == TNL) {
+                               parseheredoc();
+                               if (nlflag == 1)
+                                       return n1;
+                       } else {
+                               tokpushback++;
                        }
-                       /* fall through */
+                       checkkwd = CHKNL | CHKKWD | CHKALIAS;
+                       if (peektoken())
+                               return n1;
+                       break;
+               case TEOF:
+                       if (heredoclist)
+                               parseheredoc();
+                       else
+                               pungetc();              /* push back EOF on input */
+                       return n1;
                default:
+                       if (nlflag == 1)
+                               raise_error_unexpected_syntax(-1);
                        tokpushback++;
-                       goto out;
+                       return n1;
                }
        }
- out:
-       *app = NULL;
-       *vpp = NULL;
-       *rpp = NULL;
-       n = stalloc(sizeof(struct ncmd));
-       n->type = NCMD;
-       n->ncmd.args = args;
-       n->ncmd.assign = vars;
-       n->ncmd.redirect = redir;
-       return n;
+}
+
+static union node *
+andor(void)
+{
+       union node *n1, *n2, *n3;
+       int t;
+
+       n1 = pipeline();
+       for (;;) {
+               t = readtoken();
+               if (t == TAND) {
+                       t = NAND;
+               } else if (t == TOR) {
+                       t = NOR;
+               } else {
+                       tokpushback++;
+                       return n1;
+               }
+               checkkwd = CHKNL | CHKKWD | CHKALIAS;
+               n2 = pipeline();
+               n3 = stalloc(sizeof(struct nbinary));
+               n3->type = t;
+               n3->nbinary.ch1 = n1;
+               n3->nbinary.ch2 = n2;
+               n1 = n3;
+       }
+}
+
+static union node *
+pipeline(void)
+{
+       union node *n1, *n2, *pipenode;
+       struct nodelist *lp, *prev;
+       int negate;
+
+       negate = 0;
+       TRACE(("pipeline: entered\n"));
+       if (readtoken() == TNOT) {
+               negate = !negate;
+               checkkwd = CHKKWD | CHKALIAS;
+       } else
+               tokpushback++;
+       n1 = parse_command();
+       if (readtoken() == TPIPE) {
+               pipenode = stalloc(sizeof(struct npipe));
+               pipenode->type = NPIPE;
+               pipenode->npipe.backgnd = 0;
+               lp = stalloc(sizeof(struct nodelist));
+               pipenode->npipe.cmdlist = lp;
+               lp->n = n1;
+               do {
+                       prev = lp;
+                       lp = stalloc(sizeof(struct nodelist));
+                       checkkwd = CHKNL | CHKKWD | CHKALIAS;
+                       lp->n = parse_command();
+                       prev->next = lp;
+               } while (readtoken() == TPIPE);
+               lp->next = NULL;
+               n1 = pipenode;
+       }
+       tokpushback++;
+       if (negate) {
+               n2 = stalloc(sizeof(struct nnot));
+               n2->type = NNOT;
+               n2->nnot.com = n1;
+               return n2;
+       }
+       return n1;
 }
 
 static union node *
@@ -9164,7 +9216,7 @@ fixredir(union node *n, const char *text, int err)
        if (!err)
                n->ndup.vname = NULL;
 
-       if (is_digit(text[0]) && text[1] == '\0')
+       if (isdigit(text[0]) && text[1] == '\0')
                n->ndup.dupfd = digit_val(text[0]);
        else if (LONE_DASH(text))
                n->ndup.dupfd = -1;
@@ -9175,6 +9227,27 @@ fixredir(union node *n, const char *text, int err)
        }
 }
 
+/*
+ * Returns true if the text contains nothing to expand (no dollar signs
+ * or backquotes).
+ */
+static int
+noexpand(char *text)
+{
+       char *p;
+       char c;
+
+       p = text;
+       while ((c = *p++) != '\0') {
+               if (c == CTLQUOTEMARK)
+                       continue;
+               if (c == CTLESC)
+                       p++;
+               else if (SIT(c, BASESYNTAX) == CCTL)
+                       return 0;
+       }
+       return 1;
+}
 
 static void
 parsefname(void)
@@ -9209,276 +9282,277 @@ parsefname(void)
        }
 }
 
-
-/*
- * Input any here documents.
- */
-static void
-parseheredoc(void)
-{
-       struct heredoc *here;
-       union node *n;
-
-       here = heredoclist;
-       heredoclist = 0;
-
-       while (here) {
-               if (needprompt) {
-                       setprompt(2);
-               }
-               readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX,
-                               here->eofmark, here->striptabs);
-               n = stalloc(sizeof(struct narg));
-               n->narg.type = NARG;
-               n->narg.next = NULL;
-               n->narg.text = wordtext;
-               n->narg.backquote = backquotelist;
-               here->here->nhere.doc = n;
-               here = here->next;
-       }
-}
-
-static char
-peektoken(void)
-{
-       int t;
-
-       t = readtoken();
-       tokpushback++;
-       return tokname_array[t][0];
-}
-
-static int
-readtoken(void)
+static union node *
+simplecmd(void)
 {
-       int t;
-#if DEBUG
-       int alreadyseen = tokpushback;
-#endif
-
-#if ENABLE_ASH_ALIAS
- top:
-#endif
-
-       t = xxreadtoken();
-
-       /*
-        * eat newlines
-        */
-       if (checkkwd & CHKNL) {
-               while (t == TNL) {
-                       parseheredoc();
-                       t = xxreadtoken();
-               }
-       }
-
-       if (t != TWORD || quoteflag) {
-               goto out;
-       }
+       union node *args, **app;
+       union node *n = NULL;
+       union node *vars, **vpp;
+       union node **rpp, *redir;
+       int savecheckkwd;
 
-       /*
-        * check for keywords
-        */
-       if (checkkwd & CHKKWD) {
-               const char *const *pp;
+       args = NULL;
+       app = &args;
+       vars = NULL;
+       vpp = &vars;
+       redir = NULL;
+       rpp = &redir;
 
-               pp = findkwd(wordtext);
-               if (pp) {
-                       lasttoken = t = pp - tokname_array;
-                       TRACE(("keyword %s recognized\n", tokname(t)));
-                       goto out;
-               }
-       }
+       savecheckkwd = CHKALIAS;
+       for (;;) {
+               checkkwd = savecheckkwd;
+               switch (readtoken()) {
+               case TWORD:
+                       n = stalloc(sizeof(struct narg));
+                       n->type = NARG;
+                       n->narg.text = wordtext;
+                       n->narg.backquote = backquotelist;
+                       if (savecheckkwd && isassignment(wordtext)) {
+                               *vpp = n;
+                               vpp = &n->narg.next;
+                       } else {
+                               *app = n;
+                               app = &n->narg.next;
+                               savecheckkwd = 0;
+                       }
+                       break;
+               case TREDIR:
+                       *rpp = n = redirnode;
+                       rpp = &n->nfile.next;
+                       parsefname();   /* read name of redirection file */
+                       break;
+               case TLP:
+                       if (args && app == &args->narg.next
+                        && !vars && !redir
+                       ) {
+                               struct builtincmd *bcmd;
+                               const char *name;
 
-       if (checkkwd & CHKALIAS) {
-#if ENABLE_ASH_ALIAS
-               struct alias *ap;
-               ap = lookupalias(wordtext, 1);
-               if (ap != NULL) {
-                       if (*ap->val) {
-                               pushstring(ap->val, ap);
+                               /* We have a function */
+                               if (readtoken() != TRP)
+                                       raise_error_unexpected_syntax(TRP);
+                               name = n->narg.text;
+                               if (!goodname(name)
+                                || ((bcmd = find_builtin(name)) && IS_BUILTIN_SPECIAL(bcmd))
+                               ) {
+                                       raise_error_syntax("Bad function name");
+                               }
+                               n->type = NDEFUN;
+                               checkkwd = CHKNL | CHKKWD | CHKALIAS;
+                               n->narg.next = parse_command();
+                               return n;
                        }
-                       goto top;
+                       /* fall through */
+               default:
+                       tokpushback++;
+                       goto out;
                }
-#endif
        }
  out:
-       checkkwd = 0;
-#if DEBUG
-       if (!alreadyseen)
-               TRACE(("token %s %s\n", tokname(t), t == TWORD ? wordtext : ""));
-       else
-               TRACE(("reread token %s %s\n", tokname(t), t == TWORD ? wordtext : ""));
-#endif
-       return t;
+       *app = NULL;
+       *vpp = NULL;
+       *rpp = NULL;
+       n = stalloc(sizeof(struct ncmd));
+       n->type = NCMD;
+       n->ncmd.args = args;
+       n->ncmd.assign = vars;
+       n->ncmd.redirect = redir;
+       return n;
 }
 
-
-/*
- * Read the next input token.
- * If the token is a word, we set backquotelist to the list of cmds in
- *      backquotes.  We set quoteflag to true if any part of the word was
- *      quoted.
- * If the token is TREDIR, then we set redirnode to a structure containing
- *      the redirection.
- * In all cases, the variable startlinno is set to the number of the line
- *      on which the token starts.
- *
- * [Change comment:  here documents and internal procedures]
- * [Readtoken shouldn't have any arguments.  Perhaps we should make the
- *  word parsing code into a separate routine.  In this case, readtoken
- *  doesn't need to have any internal procedures, but parseword does.
- *  We could also make parseoperator in essence the main routine, and
- *  have parseword (readtoken1?) handle both words and redirection.]
- */
-#define NEW_xxreadtoken
-#ifdef NEW_xxreadtoken
-/* singles must be first! */
-static const char xxreadtoken_chars[7] = { '\n', '(', ')', '&', '|', ';', 0 };
-
-static const char xxreadtoken_tokens[] = {
-       TNL, TLP, TRP,          /* only single occurrence allowed */
-       TBACKGND, TPIPE, TSEMI, /* if single occurrence */
-       TEOF,                   /* corresponds to trailing nul */
-       TAND, TOR, TENDCASE,    /* if double occurrence */
-};
-
-#define xxreadtoken_doubles \
-       (sizeof(xxreadtoken_tokens) - sizeof(xxreadtoken_chars))
-#define xxreadtoken_singles \
-       (sizeof(xxreadtoken_chars) - xxreadtoken_doubles - 1)
-
-static int xxreadtoken(void)
+static union node *
+parse_command(void)
 {
-       int c;
+       union node *n1, *n2;
+       union node *ap, **app;
+       union node *cp, **cpp;
+       union node *redir, **rpp;
+       union node **rpp2;
+       int t;
 
-       if (tokpushback) {
-               tokpushback = 0;
-               return lasttoken;
-       }
-       if (needprompt) {
-               setprompt(2);
-       }
-       startlinno = plinno;
-       for (;;) {                      /* until token or start of word found */
-               c = pgetc_macro();
+       redir = NULL;
+       rpp2 = &redir;
 
-               if ((c != ' ') && (c != '\t')
-#if ENABLE_ASH_ALIAS
-                && (c != PEOA)
-#endif
-               ) {
-                       if (c == '#') {
-                               while ((c = pgetc()) != '\n' && c != PEOF);
-                               pungetc();
-                       } else if (c == '\\') {
-                               if (pgetc() != '\n') {
-                                       pungetc();
-                                       goto READTOKEN1;
-                               }
-                               startlinno = ++plinno;
-                               if (doprompt)
-                                       setprompt(2);
-                       } else {
-                               const char *p
-                                       = xxreadtoken_chars + sizeof(xxreadtoken_chars) - 1;
+       switch (readtoken()) {
+       default:
+               raise_error_unexpected_syntax(-1);
+               /* NOTREACHED */
+       case TIF:
+               n1 = stalloc(sizeof(struct nif));
+               n1->type = NIF;
+               n1->nif.test = list(0);
+               if (readtoken() != TTHEN)
+                       raise_error_unexpected_syntax(TTHEN);
+               n1->nif.ifpart = list(0);
+               n2 = n1;
+               while (readtoken() == TELIF) {
+                       n2->nif.elsepart = stalloc(sizeof(struct nif));
+                       n2 = n2->nif.elsepart;
+                       n2->type = NIF;
+                       n2->nif.test = list(0);
+                       if (readtoken() != TTHEN)
+                               raise_error_unexpected_syntax(TTHEN);
+                       n2->nif.ifpart = list(0);
+               }
+               if (lasttoken == TELSE)
+                       n2->nif.elsepart = list(0);
+               else {
+                       n2->nif.elsepart = NULL;
+                       tokpushback++;
+               }
+               t = TFI;
+               break;
+       case TWHILE:
+       case TUNTIL: {
+               int got;
+               n1 = stalloc(sizeof(struct nbinary));
+               n1->type = (lasttoken == TWHILE) ? NWHILE : NUNTIL;
+               n1->nbinary.ch1 = list(0);
+               got = readtoken();
+               if (got != TDO) {
+                       TRACE(("expecting DO got %s %s\n", tokname(got),
+                                       got == TWORD ? wordtext : ""));
+                       raise_error_unexpected_syntax(TDO);
+               }
+               n1->nbinary.ch2 = list(0);
+               t = TDONE;
+               break;
+       }
+       case TFOR:
+               if (readtoken() != TWORD || quoteflag || ! goodname(wordtext))
+                       raise_error_syntax("Bad for loop variable");
+               n1 = stalloc(sizeof(struct nfor));
+               n1->type = NFOR;
+               n1->nfor.var = wordtext;
+               checkkwd = CHKKWD | CHKALIAS;
+               if (readtoken() == TIN) {
+                       app = &ap;
+                       while (readtoken() == TWORD) {
+                               n2 = stalloc(sizeof(struct narg));
+                               n2->type = NARG;
+                               n2->narg.text = wordtext;
+                               n2->narg.backquote = backquotelist;
+                               *app = n2;
+                               app = &n2->narg.next;
+                       }
+                       *app = NULL;
+                       n1->nfor.args = ap;
+                       if (lasttoken != TNL && lasttoken != TSEMI)
+                               raise_error_unexpected_syntax(-1);
+               } else {
+                       n2 = stalloc(sizeof(struct narg));
+                       n2->type = NARG;
+                       n2->narg.text = (char *)dolatstr;
+                       n2->narg.backquote = NULL;
+                       n2->narg.next = NULL;
+                       n1->nfor.args = n2;
+                       /*
+                        * Newline or semicolon here is optional (but note
+                        * that the original Bourne shell only allowed NL).
+                        */
+                       if (lasttoken != TNL && lasttoken != TSEMI)
+                               tokpushback++;
+               }
+               checkkwd = CHKNL | CHKKWD | CHKALIAS;
+               if (readtoken() != TDO)
+                       raise_error_unexpected_syntax(TDO);
+               n1->nfor.body = list(0);
+               t = TDONE;
+               break;
+       case TCASE:
+               n1 = stalloc(sizeof(struct ncase));
+               n1->type = NCASE;
+               if (readtoken() != TWORD)
+                       raise_error_unexpected_syntax(TWORD);
+               n1->ncase.expr = n2 = stalloc(sizeof(struct narg));
+               n2->type = NARG;
+               n2->narg.text = wordtext;
+               n2->narg.backquote = backquotelist;
+               n2->narg.next = NULL;
+               do {
+                       checkkwd = CHKKWD | CHKALIAS;
+               } while (readtoken() == TNL);
+               if (lasttoken != TIN)
+                       raise_error_unexpected_syntax(TIN);
+               cpp = &n1->ncase.cases;
+ next_case:
+               checkkwd = CHKNL | CHKKWD;
+               t = readtoken();
+               while (t != TESAC) {
+                       if (lasttoken == TLP)
+                               readtoken();
+                       *cpp = cp = stalloc(sizeof(struct nclist));
+                       cp->type = NCLIST;
+                       app = &cp->nclist.pattern;
+                       for (;;) {
+                               *app = ap = stalloc(sizeof(struct narg));
+                               ap->type = NARG;
+                               ap->narg.text = wordtext;
+                               ap->narg.backquote = backquotelist;
+                               if (readtoken() != TPIPE)
+                                       break;
+                               app = &ap->narg.next;
+                               readtoken();
+                       }
+                       ap->narg.next = NULL;
+                       if (lasttoken != TRP)
+                               raise_error_unexpected_syntax(TRP);
+                       cp->nclist.body = list(2);
 
-                               if (c != PEOF) {
-                                       if (c == '\n') {
-                                               plinno++;
-                                               needprompt = doprompt;
-                                       }
+                       cpp = &cp->nclist.next;
 
-                                       p = strchr(xxreadtoken_chars, c);
-                                       if (p == NULL) {
- READTOKEN1:
-                                               return readtoken1(c, BASESYNTAX, (char *) NULL, 0);
-                                       }
+                       checkkwd = CHKNL | CHKKWD;
+                       t = readtoken();
+                       if (t != TESAC) {
+                               if (t != TENDCASE)
+                                       raise_error_unexpected_syntax(TENDCASE);
+                               goto next_case;
+                       }
+               }
+               *cpp = NULL;
+               goto redir;
+       case TLP:
+               n1 = stalloc(sizeof(struct nredir));
+               n1->type = NSUBSHELL;
+               n1->nredir.n = list(0);
+               n1->nredir.redirect = NULL;
+               t = TRP;
+               break;
+       case TBEGIN:
+               n1 = list(0);
+               t = TEND;
+               break;
+       case TWORD:
+       case TREDIR:
+               tokpushback++;
+               return simplecmd();
+       }
 
-                                       if (p - xxreadtoken_chars >= xxreadtoken_singles) {
-                                               if (pgetc() == *p) {    /* double occurrence? */
-                                                       p += xxreadtoken_doubles + 1;
-                                               } else {
-                                                       pungetc();
-                                               }
-                                       }
-                               }
-                               return lasttoken = xxreadtoken_tokens[p - xxreadtoken_chars];
-                       }
-               }
-       } /* for */
-}
-#else
-#define RETURN(token)   return lasttoken = token
-static int
-xxreadtoken(void)
-{
-       int c;
+       if (readtoken() != t)
+               raise_error_unexpected_syntax(t);
 
-       if (tokpushback) {
-               tokpushback = 0;
-               return lasttoken;
-       }
-       if (needprompt) {
-               setprompt(2);
+ redir:
+       /* Now check for redirection which may follow command */
+       checkkwd = CHKKWD | CHKALIAS;
+       rpp = rpp2;
+       while (readtoken() == TREDIR) {
+               *rpp = n2 = redirnode;
+               rpp = &n2->nfile.next;
+               parsefname();
        }
-       startlinno = plinno;
-       for (;;) {      /* until token or start of word found */
-               c = pgetc_macro();
-               switch (c) {
-               case ' ': case '\t':
-#if ENABLE_ASH_ALIAS
-               case PEOA:
-#endif
-                       continue;
-               case '#':
-                       while ((c = pgetc()) != '\n' && c != PEOF);
-                       pungetc();
-                       continue;
-               case '\\':
-                       if (pgetc() == '\n') {
-                               startlinno = ++plinno;
-                               if (doprompt)
-                                       setprompt(2);
-                               continue;
-                       }
-                       pungetc();
-                       goto breakloop;
-               case '\n':
-                       plinno++;
-                       needprompt = doprompt;
-                       RETURN(TNL);
-               case PEOF:
-                       RETURN(TEOF);
-               case '&':
-                       if (pgetc() == '&')
-                               RETURN(TAND);
-                       pungetc();
-                       RETURN(TBACKGND);
-               case '|':
-                       if (pgetc() == '|')
-                               RETURN(TOR);
-                       pungetc();
-                       RETURN(TPIPE);
-               case ';':
-                       if (pgetc() == ';')
-                               RETURN(TENDCASE);
-                       pungetc();
-                       RETURN(TSEMI);
-               case '(':
-                       RETURN(TLP);
-               case ')':
-                       RETURN(TRP);
-               default:
-                       goto breakloop;
+       tokpushback++;
+       *rpp = NULL;
+       if (redir) {
+               if (n1->type != NSUBSHELL) {
+                       n2 = stalloc(sizeof(struct nredir));
+                       n2->type = NREDIR;
+                       n2->nredir.n = n1;
+                       n1 = n2;
                }
+               n1->nredir.redirect = redir;
        }
- breakloop:
-       return readtoken1(c, BASESYNTAX, (char *)NULL, 0);
-#undef RETURN
+       return n1;
 }
-#endif /* NEW_xxreadtoken */
-
 
 /*
  * If eofmark is NULL, read a word or a redirection symbol.  If eofmark
@@ -9696,7 +9770,7 @@ readtoken1(int firstc, int syntax, char *eofmark, int striptabs)
                if ((c == '>' || c == '<')
                 && quotef == 0
                 && len <= 2
-                && (*out == '\0' || is_digit(*out))) {
+                && (*out == '\0' || isdigit(*out))) {
                        PARSEREDIR();
                        return lasttoken = TREDIR;
                } else {
@@ -9864,11 +9938,11 @@ parsesub: {
                                STPUTC(c, out);
                                c = pgetc();
                        } while (c > PEOA_OR_PEOF && is_in_name(c));
-               } else if (is_digit(c)) {
+               } else if (isdigit(c)) {
                        do {
                                STPUTC(c, out);
                                c = pgetc();
-                       } while (is_digit(c));
+                       } while (isdigit(c));
                } else if (is_special(c)) {
                        USTPUTC(c, out);
                        c = pgetc();
@@ -10040,10 +10114,8 @@ parsebackq: {
 
        if (oldstyle)
                doprompt = saveprompt;
-       else {
-               if (readtoken() != TRP)
-                       raise_error_unexpected_syntax(TRP);
-       }
+       else if (readtoken() != TRP)
+               raise_error_unexpected_syntax(TRP);
 
        (*nlpp)->n = n;
        if (oldstyle) {
@@ -10072,1611 +10144,1572 @@ parsebackq: {
        else
                USTPUTC(CTLBACKQ, out);
        if (oldstyle)
-               goto parsebackq_oldreturn;
-       goto parsebackq_newreturn;
-}
-
-#if ENABLE_ASH_MATH_SUPPORT
-/*
- * Parse an arithmetic expansion (indicate start of one and set state)
- */
-parsearith: {
-       if (++arinest == 1) {
-               prevsyntax = syntax;
-               syntax = ARISYNTAX;
-               USTPUTC(CTLARI, out);
-               if (dblquote)
-                       USTPUTC('"', out);
-               else
-                       USTPUTC(' ', out);
-       } else {
-               /*
-                * we collapse embedded arithmetic expansion to
-                * parenthesis, which should be equivalent
-                */
-               USTPUTC('(', out);
-       }
-       goto parsearith_return;
-}
-#endif
-
-} /* end of readtoken */
-
-
-/*
- * Returns true if the text contains nothing to expand (no dollar signs
- * or backquotes).
- */
-static int
-noexpand(char *text)
-{
-       char *p;
-       char c;
-
-       p = text;
-       while ((c = *p++) != '\0') {
-               if (c == CTLQUOTEMARK)
-                       continue;
-               if (c == CTLESC)
-                       p++;
-               else if (SIT(c, BASESYNTAX) == CCTL)
-                       return 0;
-       }
-       return 1;
-}
-
-
-/*
- * Return of a legal variable name (a letter or underscore followed by zero or
- * more letters, underscores, and digits).
- */
-static char *
-endofname(const char *name)
-{
-       char *p;
-
-       p = (char *) name;
-       if (!is_name(*p))
-               return p;
-       while (*++p) {
-               if (!is_in_name(*p))
-                       break;
-       }
-       return p;
-}
-
-
-/*
- * called by editline -- any expansions to the prompt
- *    should be added here.
- */
-#if ENABLE_ASH_EXPAND_PRMT
-static const char *
-expandstr(const char *ps)
-{
-       union node n;
-
-       /* XXX Fix (char *) cast. */
-       setinputstring((char *)ps);
-       readtoken1(pgetc(), DQSYNTAX, nullstr, 0);
-       popfile();
-
-       n.narg.type = NARG;
-       n.narg.next = NULL;
-       n.narg.text = wordtext;
-       n.narg.backquote = backquotelist;
-
-       expandarg(&n, NULL, 0);
-       return stackblock();
-}
-#endif
-
-static void setprompt(int whichprompt)
-{
-       const char *prompt;
-#if ENABLE_ASH_EXPAND_PRMT
-       struct stackmark smark;
-#endif
-
-       needprompt = 0;
-
-       switch (whichprompt) {
-       case 1:
-               prompt = ps1val();
-               break;
-       case 2:
-               prompt = ps2val();
-               break;
-       default:                        /* 0 */
-               prompt = nullstr;
-       }
-#if ENABLE_ASH_EXPAND_PRMT
-       setstackmark(&smark);
-       stalloc(stackblocksize());
-#endif
-       putprompt(expandstr(prompt));
-#if ENABLE_ASH_EXPAND_PRMT
-       popstackmark(&smark);
-#endif
-}
-
-
-/*
- * Execute a command or commands contained in a string.
- */
-static int
-evalstring(char *s, int mask)
-{
-       union node *n;
-       struct stackmark smark;
-       int skip;
-
-       setinputstring(s);
-       setstackmark(&smark);
-
-       skip = 0;
-       while ((n = parsecmd(0)) != NEOF) {
-               evaltree(n, 0);
-               popstackmark(&smark);
-               skip = evalskip;
-               if (skip)
-                       break;
-       }
-       popfile();
-
-       skip &= mask;
-       evalskip = skip;
-       return skip;
+               goto parsebackq_oldreturn;
+       goto parsebackq_newreturn;
 }
 
+#if ENABLE_ASH_MATH_SUPPORT
 /*
- * The eval command.
+ * Parse an arithmetic expansion (indicate start of one and set state)
  */
-static int
-evalcmd(int argc, char **argv)
-{
-       char *p;
-       char *concat;
-       char **ap;
-
-       if (argc > 1) {
-               p = argv[1];
-               if (argc > 2) {
-                       STARTSTACKSTR(concat);
-                       ap = argv + 2;
-                       for (;;) {
-                               concat = stack_putstr(p, concat);
-                               p = *ap++;
-                               if (p == NULL)
-                                       break;
-                               STPUTC(' ', concat);
-                       }
-                       STPUTC('\0', concat);
-                       p = grabstackstr(concat);
-               }
-               evalstring(p, ~SKIPEVAL);
-
+parsearith: {
+       if (++arinest == 1) {
+               prevsyntax = syntax;
+               syntax = ARISYNTAX;
+               USTPUTC(CTLARI, out);
+               if (dblquote)
+                       USTPUTC('"', out);
+               else
+                       USTPUTC(' ', out);
+       } else {
+               /*
+                * we collapse embedded arithmetic expansion to
+                * parenthesis, which should be equivalent
+                */
+               USTPUTC('(', out);
        }
-       return exitstatus;
+       goto parsearith_return;
 }
+#endif
+
+} /* end of readtoken */
 
 /*
- * Read and execute commands.  "Top" is nonzero for the top level command
- * loop; it turns on prompting if the shell is interactive.
+ * Read the next input token.
+ * If the token is a word, we set backquotelist to the list of cmds in
+ *      backquotes.  We set quoteflag to true if any part of the word was
+ *      quoted.
+ * If the token is TREDIR, then we set redirnode to a structure containing
+ *      the redirection.
+ * In all cases, the variable startlinno is set to the number of the line
+ *      on which the token starts.
+ *
+ * [Change comment:  here documents and internal procedures]
+ * [Readtoken shouldn't have any arguments.  Perhaps we should make the
+ *  word parsing code into a separate routine.  In this case, readtoken
+ *  doesn't need to have any internal procedures, but parseword does.
+ *  We could also make parseoperator in essence the main routine, and
+ *  have parseword (readtoken1?) handle both words and redirection.]
  */
-static int
-cmdloop(int top)
-{
-       union node *n;
-       struct stackmark smark;
-       int inter;
-       int numeof = 0;
-
-       TRACE(("cmdloop(%d) called\n", top));
-       for (;;) {
-               int skip;
+#define NEW_xxreadtoken
+#ifdef NEW_xxreadtoken
+/* singles must be first! */
+static const char xxreadtoken_chars[7] = { '\n', '(', ')', '&', '|', ';', 0 };
 
-               setstackmark(&smark);
-#if JOBS
-               if (jobctl)
-                       showjobs(stderr, SHOW_CHANGED);
-#endif
-               inter = 0;
-               if (iflag && top) {
-                       inter++;
-#if ENABLE_ASH_MAIL
-                       chkmail();
-#endif
-               }
-               n = parsecmd(inter);
-               /* showtree(n); DEBUG */
-               if (n == NEOF) {
-                       if (!top || numeof >= 50)
-                               break;
-                       if (!stoppedjobs()) {
-                               if (!Iflag)
-                                       break;
-                               out2str("\nUse \"exit\" to leave shell.\n");
-                       }
-                       numeof++;
-               } else if (nflag == 0) {
-                       job_warning = (job_warning == 2) ? 1 : 0;
-                       numeof = 0;
-                       evaltree(n, 0);
-               }
-               popstackmark(&smark);
-               skip = evalskip;
+static const char xxreadtoken_tokens[] = {
+       TNL, TLP, TRP,          /* only single occurrence allowed */
+       TBACKGND, TPIPE, TSEMI, /* if single occurrence */
+       TEOF,                   /* corresponds to trailing nul */
+       TAND, TOR, TENDCASE,    /* if double occurrence */
+};
 
-               if (skip) {
-                       evalskip = 0;
-                       return skip & SKIPEVAL;
-               }
-       }
-       return 0;
-}
+#define xxreadtoken_doubles \
+       (sizeof(xxreadtoken_tokens) - sizeof(xxreadtoken_chars))
+#define xxreadtoken_singles \
+       (sizeof(xxreadtoken_chars) - xxreadtoken_doubles - 1)
 
 static int
-dotcmd(int argc, char **argv)
+xxreadtoken(void)
 {
-       struct strlist *sp;
-       volatile struct shparam saveparam;
-       int status = 0;
+       int c;
 
-       for (sp = cmdenviron; sp; sp = sp->next)
-               setvareq(xstrdup(sp->text), VSTRFIXED | VTEXTFIXED);
+       if (tokpushback) {
+               tokpushback = 0;
+               return lasttoken;
+       }
+       if (needprompt) {
+               setprompt(2);
+       }
+       startlinno = plinno;
+       for (;;) {                      /* until token or start of word found */
+               c = pgetc_macro();
 
-       if (argc >= 2) {        /* That's what SVR2 does */
-               char *fullname;
+               if ((c != ' ') && (c != '\t')
+#if ENABLE_ASH_ALIAS
+                && (c != PEOA)
+#endif
+               ) {
+                       if (c == '#') {
+                               while ((c = pgetc()) != '\n' && c != PEOF);
+                               pungetc();
+                       } else if (c == '\\') {
+                               if (pgetc() != '\n') {
+                                       pungetc();
+                                       goto READTOKEN1;
+                               }
+                               startlinno = ++plinno;
+                               if (doprompt)
+                                       setprompt(2);
+                       } else {
+                               const char *p
+                                       = xxreadtoken_chars + sizeof(xxreadtoken_chars) - 1;
 
-               fullname = find_dot_file(argv[1]);
+                               if (c != PEOF) {
+                                       if (c == '\n') {
+                                               plinno++;
+                                               needprompt = doprompt;
+                                       }
 
-               if (argc > 2) {
-                       saveparam = shellparam;
-                       shellparam.malloc = 0;
-                       shellparam.nparam = argc - 2;
-                       shellparam.p = argv + 2;
-               };
+                                       p = strchr(xxreadtoken_chars, c);
+                                       if (p == NULL) {
+ READTOKEN1:
+                                               return readtoken1(c, BASESYNTAX, (char *) NULL, 0);
+                                       }
 
-               setinputfile(fullname, INPUT_PUSH_FILE);
-               commandname = fullname;
-               cmdloop(0);
-               popfile();
+                                       if (p - xxreadtoken_chars >= xxreadtoken_singles) {
+                                               if (pgetc() == *p) {    /* double occurrence? */
+                                                       p += xxreadtoken_doubles + 1;
+                                               } else {
+                                                       pungetc();
+                                               }
+                                       }
+                               }
+                               return lasttoken = xxreadtoken_tokens[p - xxreadtoken_chars];
+                       }
+               }
+       } /* for */
+}
+#else
+#define RETURN(token)   return lasttoken = token
+static int
+xxreadtoken(void)
+{
+       int c;
 
-               if (argc > 2) {
-                       freeparam(&shellparam);
-                       shellparam = saveparam;
-               };
-               status = exitstatus;
+       if (tokpushback) {
+               tokpushback = 0;
+               return lasttoken;
+       }
+       if (needprompt) {
+               setprompt(2);
+       }
+       startlinno = plinno;
+       for (;;) {      /* until token or start of word found */
+               c = pgetc_macro();
+               switch (c) {
+               case ' ': case '\t':
+#if ENABLE_ASH_ALIAS
+               case PEOA:
+#endif
+                       continue;
+               case '#':
+                       while ((c = pgetc()) != '\n' && c != PEOF);
+                       pungetc();
+                       continue;
+               case '\\':
+                       if (pgetc() == '\n') {
+                               startlinno = ++plinno;
+                               if (doprompt)
+                                       setprompt(2);
+                               continue;
+                       }
+                       pungetc();
+                       goto breakloop;
+               case '\n':
+                       plinno++;
+                       needprompt = doprompt;
+                       RETURN(TNL);
+               case PEOF:
+                       RETURN(TEOF);
+               case '&':
+                       if (pgetc() == '&')
+                               RETURN(TAND);
+                       pungetc();
+                       RETURN(TBACKGND);
+               case '|':
+                       if (pgetc() == '|')
+                               RETURN(TOR);
+                       pungetc();
+                       RETURN(TPIPE);
+               case ';':
+                       if (pgetc() == ';')
+                               RETURN(TENDCASE);
+                       pungetc();
+                       RETURN(TSEMI);
+               case '(':
+                       RETURN(TLP);
+               case ')':
+                       RETURN(TRP);
+               default:
+                       goto breakloop;
+               }
        }
-       return status;
-}
-
-static int
-exitcmd(int argc, char **argv)
-{
-       if (stoppedjobs())
-               return 0;
-       if (argc > 1)
-               exitstatus = number(argv[1]);
-       raise_exception(EXEXIT);
-       /* NOTREACHED */
-}
-
-#if ENABLE_ASH_BUILTIN_ECHO
-static int
-echocmd(int argc, char **argv)
-{
-       return bb_echo(argv);
+ breakloop:
+       return readtoken1(c, BASESYNTAX, (char *)NULL, 0);
+#undef RETURN
 }
-#endif
+#endif /* NEW_xxreadtoken */
 
-#if ENABLE_ASH_BUILTIN_TEST
 static int
-testcmd(int argc, char **argv)
+readtoken(void)
 {
-       return bb_test(argc, argv);
-}
+       int t;
+#if DEBUG
+       int alreadyseen = tokpushback;
 #endif
 
-/*
- * Read a file containing shell functions.
- */
-static void
-readcmdfile(char *name)
-{
-       setinputfile(name, INPUT_PUSH_FILE);
-       cmdloop(0);
-       popfile();
-}
-
-
-/*      redir.c      */
-
-/*
- * Code for dealing with input/output redirection.
- */
-
-#define EMPTY -2                /* marks an unused slot in redirtab */
-#ifndef PIPE_BUF
-# define PIPESIZE 4096          /* amount of buffering in a pipe */
-#else
-# define PIPESIZE PIPE_BUF
+#if ENABLE_ASH_ALIAS
+ top:
 #endif
 
-/*
- * Open a file in noclobber mode.
- * The code was copied from bash.
- */
-static int
-noclobberopen(const char *fname)
-{
-       int r, fd;
-       struct stat finfo, finfo2;
+       t = xxreadtoken();
 
        /*
-        * If the file exists and is a regular file, return an error
-        * immediately.
+        * eat newlines
         */
-       r = stat(fname, &finfo);
-       if (r == 0 && S_ISREG(finfo.st_mode)) {
-               errno = EEXIST;
-               return -1;
+       if (checkkwd & CHKNL) {
+               while (t == TNL) {
+                       parseheredoc();
+                       t = xxreadtoken();
+               }
        }
 
-       /*
-        * If the file was not present (r != 0), make sure we open it
-        * exclusively so that if it is created before we open it, our open
-        * will fail.  Make sure that we do not truncate an existing file.
-        * Note that we don't turn on O_EXCL unless the stat failed -- if the
-        * file was not a regular file, we leave O_EXCL off.
-        */
-       if (r != 0)
-               return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666);
-       fd = open(fname, O_WRONLY|O_CREAT, 0666);
-
-       /* If the open failed, return the file descriptor right away. */
-       if (fd < 0)
-               return fd;
-
-       /*
-        * OK, the open succeeded, but the file may have been changed from a
-        * non-regular file to a regular file between the stat and the open.
-        * We are assuming that the O_EXCL open handles the case where FILENAME
-        * did not exist and is symlinked to an existing file between the stat
-        * and open.
-        */
+       if (t != TWORD || quoteflag) {
+               goto out;
+       }
 
        /*
-        * If we can open it and fstat the file descriptor, and neither check
-        * revealed that it was a regular file, and the file has not been
-        * replaced, return the file descriptor.
+        * check for keywords
         */
-       if (fstat(fd, &finfo2) == 0 && !S_ISREG(finfo2.st_mode)
-        && finfo.st_dev == finfo2.st_dev && finfo.st_ino == finfo2.st_ino)
-               return fd;
-
-       /* The file has been replaced.  badness. */
-       close(fd);
-       errno = EEXIST;
-       return -1;
-}
-
-
-/*
- * Handle here documents.  Normally we fork off a process to write the
- * data to a pipe.  If the document is short, we can stuff the data in
- * the pipe without forking.
- */
-static int
-openhere(union node *redir)
-{
-       int pip[2];
-       size_t len = 0;
+       if (checkkwd & CHKKWD) {
+               const char *const *pp;
 
-       if (pipe(pip) < 0)
-               ash_msg_and_raise_error("Pipe call failed");
-       if (redir->type == NHERE) {
-               len = strlen(redir->nhere.doc->narg.text);
-               if (len <= PIPESIZE) {
-                       full_write(pip[1], redir->nhere.doc->narg.text, len);
+               pp = findkwd(wordtext);
+               if (pp) {
+                       lasttoken = t = pp - tokname_array;
+                       TRACE(("keyword %s recognized\n", tokname(t)));
                        goto out;
                }
        }
-       if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) {
-               close(pip[0]);
-               signal(SIGINT, SIG_IGN);
-               signal(SIGQUIT, SIG_IGN);
-               signal(SIGHUP, SIG_IGN);
-#ifdef SIGTSTP
-               signal(SIGTSTP, SIG_IGN);
+
+       if (checkkwd & CHKALIAS) {
+#if ENABLE_ASH_ALIAS
+               struct alias *ap;
+               ap = lookupalias(wordtext, 1);
+               if (ap != NULL) {
+                       if (*ap->val) {
+                               pushstring(ap->val, ap);
+                       }
+                       goto top;
+               }
 #endif
-               signal(SIGPIPE, SIG_DFL);
-               if (redir->type == NHERE)
-                       full_write(pip[1], redir->nhere.doc->narg.text, len);
-               else
-                       expandhere(redir->nhere.doc, pip[1]);
-               _exit(0);
        }
  out:
-       close(pip[1]);
-       return pip[0];
+       checkkwd = 0;
+#if DEBUG
+       if (!alreadyseen)
+               TRACE(("token %s %s\n", tokname(t), t == TWORD ? wordtext : ""));
+       else
+               TRACE(("reread token %s %s\n", tokname(t), t == TWORD ? wordtext : ""));
+#endif
+       return t;
 }
 
-static int
-openredirect(union node *redir)
+static char
+peektoken(void)
 {
-       char *fname;
-       int f;
+       int t;
 
-       switch (redir->nfile.type) {
-       case NFROM:
-               fname = redir->nfile.expfname;
-               f = open(fname, O_RDONLY);
-               if (f < 0)
-                       goto eopen;
-               break;
-       case NFROMTO:
-               fname = redir->nfile.expfname;
-               f = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0666);
-               if (f < 0)
-                       goto ecreate;
-               break;
-       case NTO:
-               /* Take care of noclobber mode. */
-               if (Cflag) {
-                       fname = redir->nfile.expfname;
-                       f = noclobberopen(fname);
-                       if (f < 0)
-                               goto ecreate;
-                       break;
-               }
-               /* FALLTHROUGH */
-       case NCLOBBER:
-               fname = redir->nfile.expfname;
-               f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666);
-               if (f < 0)
-                       goto ecreate;
-               break;
-       case NAPPEND:
-               fname = redir->nfile.expfname;
-               f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666);
-               if (f < 0)
-                       goto ecreate;
-               break;
-       default:
-#if DEBUG
-               abort();
-#endif
-               /* Fall through to eliminate warning. */
-       case NTOFD:
-       case NFROMFD:
-               f = -1;
-               break;
-       case NHERE:
-       case NXHERE:
-               f = openhere(redir);
-               break;
-       }
+       t = readtoken();
+       tokpushback++;
+       return tokname_array[t][0];
+}
+
+/*
+ * Read and parse a command.  Returns NEOF on end of file.  (NULL is a
+ * valid parse tree indicating a blank line.)
+ */
+static union node *
+parsecmd(int interact)
+{
+       int t;
 
-       return f;
- ecreate:
-       ash_msg_and_raise_error("cannot create %s: %s", fname, errmsg(errno, "Directory nonexistent"));
- eopen:
-       ash_msg_and_raise_error("cannot open %s: %s", fname, errmsg(errno, "No such file"));
+       tokpushback = 0;
+       doprompt = interact;
+       if (doprompt)
+               setprompt(doprompt);
+       needprompt = 0;
+       t = readtoken();
+       if (t == TEOF)
+               return NEOF;
+       if (t == TNL)
+               return NULL;
+       tokpushback++;
+       return list(1);
 }
 
+/*
+ * Input any here documents.
+ */
 static void
-dupredirect(union node *redir, int f)
+parseheredoc(void)
 {
-       int fd = redir->nfile.fd;
+       struct heredoc *here;
+       union node *n;
 
-       if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) {
-               if (redir->ndup.dupfd >= 0) {   /* if not ">&-" */
-                       copyfd(redir->ndup.dupfd, fd);
-               }
-               return;
-       }
+       here = heredoclist;
+       heredoclist = 0;
 
-       if (f != fd) {
-               copyfd(f, fd);
-               close(f);
+       while (here) {
+               if (needprompt) {
+                       setprompt(2);
+               }
+               readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX,
+                               here->eofmark, here->striptabs);
+               n = stalloc(sizeof(struct narg));
+               n->narg.type = NARG;
+               n->narg.next = NULL;
+               n->narg.text = wordtext;
+               n->narg.backquote = backquotelist;
+               here->here->nhere.doc = n;
+               here = here->next;
        }
 }
 
 
 /*
- * Process a list of redirection commands.  If the REDIR_PUSH flag is set,
- * old file descriptors are stashed away so that the redirection can be
- * undone by calling popredir.  If the REDIR_BACKQ flag is set, then the
- * standard output, and the standard error if it becomes a duplicate of
- * stdout, is saved in memory.
+ * called by editline -- any expansions to the prompt
+ *    should be added here.
  */
-static void
-redirect(union node *redir, int flags)
+#if ENABLE_ASH_EXPAND_PRMT
+static const char *
+expandstr(const char *ps)
 {
-       union node *n;
-       struct redirtab *sv;
-       int i;
-       int fd;
-       int newfd;
-       int *p;
-       nullredirs++;
-       if (!redir) {
-               return;
-       }
-       sv = NULL;
-       INT_OFF;
-       if (flags & REDIR_PUSH) {
-               struct redirtab *q;
-               q = ckmalloc(sizeof(struct redirtab));
-               q->next = redirlist;
-               redirlist = q;
-               q->nullredirs = nullredirs - 1;
-               for (i = 0; i < 10; i++)
-                       q->renamed[i] = EMPTY;
-               nullredirs = 0;
-               sv = q;
-       }
-       n = redir;
-       do {
-               fd = n->nfile.fd;
-               if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD)
-                && n->ndup.dupfd == fd)
-                       continue; /* redirect from/to same file descriptor */
+       union node n;
 
-               newfd = openredirect(n);
-               if (fd == newfd)
-                       continue;
-               if (sv && *(p = &sv->renamed[fd]) == EMPTY) {
-                       i = fcntl(fd, F_DUPFD, 10);
+       /* XXX Fix (char *) cast. */
+       setinputstring((char *)ps);
+       readtoken1(pgetc(), DQSYNTAX, nullstr, 0);
+       popfile();
 
-                       if (i == -1) {
-                               i = errno;
-                               if (i != EBADF) {
-                                       close(newfd);
-                                       errno = i;
-                                       ash_msg_and_raise_error("%d: %m", fd);
-                                       /* NOTREACHED */
-                               }
-                       } else {
-                               *p = i;
-                               close(fd);
-                       }
-               } else {
-                       close(fd);
-               }
-               dupredirect(n, newfd);
-       } while ((n = n->nfile.next));
-       INT_ON;
-       if (flags & REDIR_SAVEFD2 && sv && sv->renamed[2] >= 0)
-               preverrout_fd = sv->renamed[2];
+       n.narg.type = NARG;
+       n.narg.next = NULL;
+       n.narg.text = wordtext;
+       n.narg.backquote = backquotelist;
+
+       expandarg(&n, NULL, 0);
+       return stackblock();
 }
+#endif
 
 
 /*
- * Undo the effects of the last redirection.
+ * Execute a command or commands contained in a string.
  */
-static void
-popredir(int drop)
+static int
+evalstring(char *s, int mask)
 {
-       struct redirtab *rp;
-       int i;
+       union node *n;
+       struct stackmark smark;
+       int skip;
 
-       if (--nullredirs >= 0)
-               return;
-       INT_OFF;
-       rp = redirlist;
-       for (i = 0; i < 10; i++) {
-               if (rp->renamed[i] != EMPTY) {
-                       if (!drop) {
-                               close(i);
-                               copyfd(rp->renamed[i], i);
-                       }
-                       close(rp->renamed[i]);
-               }
+       setinputstring(s);
+       setstackmark(&smark);
+
+       skip = 0;
+       while ((n = parsecmd(0)) != NEOF) {
+               evaltree(n, 0);
+               popstackmark(&smark);
+               skip = evalskip;
+               if (skip)
+                       break;
        }
-       redirlist = rp->next;
-       nullredirs = rp->nullredirs;
-       free(rp);
-       INT_ON;
-}
+       popfile();
 
-/*
- * Undo all redirections.  Called on error or interrupt.
- */
+       skip &= mask;
+       evalskip = skip;
+       return skip;
+}
 
 /*
- * Discard all saved file descriptors.
+ * The eval command.
  */
-static void
-clearredir(int drop)
+static int
+evalcmd(int argc, char **argv)
 {
-       for (;;) {
-               nullredirs = 0;
-               if (!redirlist)
-                       break;
-               popredir(drop);
+       char *p;
+       char *concat;
+       char **ap;
+
+       if (argc > 1) {
+               p = argv[1];
+               if (argc > 2) {
+                       STARTSTACKSTR(concat);
+                       ap = argv + 2;
+                       for (;;) {
+                               concat = stack_putstr(p, concat);
+                               p = *ap++;
+                               if (p == NULL)
+                                       break;
+                               STPUTC(' ', concat);
+                       }
+                       STPUTC('\0', concat);
+                       p = grabstackstr(concat);
+               }
+               evalstring(p, ~SKIPEVAL);
+
        }
+       return exitstatus;
 }
 
-
 /*
- * Copy a file descriptor to be >= to.  Returns -1
- * if the source file descriptor is closed, EMPTY if there are no unused
- * file descriptors left.
+ * Read and execute commands.  "Top" is nonzero for the top level command
+ * loop; it turns on prompting if the shell is interactive.
  */
 static int
-copyfd(int from, int to)
+cmdloop(int top)
 {
-       int newfd;
+       union node *n;
+       struct stackmark smark;
+       int inter;
+       int numeof = 0;
+
+       TRACE(("cmdloop(%d) called\n", top));
+       for (;;) {
+               int skip;
+
+               setstackmark(&smark);
+#if JOBS
+               if (jobctl)
+                       showjobs(stderr, SHOW_CHANGED);
+#endif
+               inter = 0;
+               if (iflag && top) {
+                       inter++;
+#if ENABLE_ASH_MAIL
+                       chkmail();
+#endif
+               }
+               n = parsecmd(inter);
+               /* showtree(n); DEBUG */
+               if (n == NEOF) {
+                       if (!top || numeof >= 50)
+                               break;
+                       if (!stoppedjobs()) {
+                               if (!Iflag)
+                                       break;
+                               out2str("\nUse \"exit\" to leave shell.\n");
+                       }
+                       numeof++;
+               } else if (nflag == 0) {
+                       job_warning = (job_warning == 2) ? 1 : 0;
+                       numeof = 0;
+                       evaltree(n, 0);
+               }
+               popstackmark(&smark);
+               skip = evalskip;
 
-       newfd = fcntl(from, F_DUPFD, to);
-       if (newfd < 0) {
-               if (errno == EMFILE)
-                       return EMPTY;
-               ash_msg_and_raise_error("%d: %m", from);
+               if (skip) {
+                       evalskip = 0;
+                       return skip & SKIPEVAL;
+               }
        }
-       return newfd;
+       return 0;
 }
 
-
 static int
-redirectsafe(union node *redir, int flags)
+dotcmd(int argc, char **argv)
 {
-       int err;
-       volatile int saveint;
-       struct jmploc *volatile savehandler = exception_handler;
-       struct jmploc jmploc;
-
-       SAVE_INT(saveint);
-       err = setjmp(jmploc.loc) * 2;
-       if (!err) {
-               exception_handler = &jmploc;
-               redirect(redir, flags);
-       }
-       exception_handler = savehandler;
-       if (err && exception != EXERROR)
-               longjmp(exception_handler->loc, 1);
-       RESTORE_INT(saveint);
-       return err;
-}
-
-/*      show.c    */
-
-#if DEBUG
-static void shtree(union node *, int, char *, FILE*);
-static void shcmd(union node *, FILE *);
-static void sharg(union node *, FILE *);
-static void indent(int, char *, FILE *);
-static void trstring(char *);
+       struct strlist *sp;
+       volatile struct shparam saveparam;
+       int status = 0;
 
+       for (sp = cmdenviron; sp; sp = sp->next)
+               setvareq(xstrdup(sp->text), VSTRFIXED | VTEXTFIXED);
 
-static void
-showtree(union node *n)
-{
-       trputs("showtree called\n");
-       shtree(n, 1, NULL, stdout);
-}
+       if (argc >= 2) {        /* That's what SVR2 does */
+               char *fullname;
 
+               fullname = find_dot_file(argv[1]);
 
-static void
-shtree(union node *n, int ind, char *pfx, FILE *fp)
-{
-       struct nodelist *lp;
-       const char *s;
+               if (argc > 2) {
+                       saveparam = shellparam;
+                       shellparam.malloc = 0;
+                       shellparam.nparam = argc - 2;
+                       shellparam.p = argv + 2;
+               };
 
-       if (n == NULL)
-               return;
+               setinputfile(fullname, INPUT_PUSH_FILE);
+               commandname = fullname;
+               cmdloop(0);
+               popfile();
 
-       indent(ind, pfx, fp);
-       switch (n->type) {
-       case NSEMI:
-               s = "; ";
-               goto binop;
-       case NAND:
-               s = " && ";
-               goto binop;
-       case NOR:
-               s = " || ";
- binop:
-               shtree(n->nbinary.ch1, ind, NULL, fp);
-          /*    if (ind < 0) */
-                       fputs(s, fp);
-               shtree(n->nbinary.ch2, ind, NULL, fp);
-               break;
-       case NCMD:
-               shcmd(n, fp);
-               if (ind >= 0)
-                       putc('\n', fp);
-               break;
-       case NPIPE:
-               for (lp = n->npipe.cmdlist; lp; lp = lp->next) {
-                       shcmd(lp->n, fp);
-                       if (lp->next)
-                               fputs(" | ", fp);
-               }
-               if (n->npipe.backgnd)
-                       fputs(" &", fp);
-               if (ind >= 0)
-                       putc('\n', fp);
-               break;
-       default:
-               fprintf(fp, "<node type %d>", n->type);
-               if (ind >= 0)
-                       putc('\n', fp);
-               break;
+               if (argc > 2) {
+                       freeparam(&shellparam);
+                       shellparam = saveparam;
+               };
+               status = exitstatus;
        }
+       return status;
 }
 
-
-static void
-shcmd(union node *cmd, FILE *fp)
+static int
+exitcmd(int argc, char **argv)
 {
-       union node *np;
-       int first;
-       const char *s;
-       int dftfd;
-
-       first = 1;
-       for (np = cmd->ncmd.args; np; np = np->narg.next) {
-               if (! first)
-                       putchar(' ');
-               sharg(np, fp);
-               first = 0;
-       }
-       for (np = cmd->ncmd.redirect; np; np = np->nfile.next) {
-               if (! first)
-                       putchar(' ');
-               switch (np->nfile.type) {
-               case NTO:       s = ">";  dftfd = 1; break;
-               case NCLOBBER:  s = ">|"; dftfd = 1; break;
-               case NAPPEND:   s = ">>"; dftfd = 1; break;
-               case NTOFD:     s = ">&"; dftfd = 1; break;
-               case NFROM:     s = "<";  dftfd = 0; break;
-               case NFROMFD:   s = "<&"; dftfd = 0; break;
-               case NFROMTO:   s = "<>"; dftfd = 0; break;
-               default:        s = "*error*"; dftfd = 0; break;
-               }
-               if (np->nfile.fd != dftfd)
-                       fprintf(fp, "%d", np->nfile.fd);
-               fputs(s, fp);
-               if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) {
-                       fprintf(fp, "%d", np->ndup.dupfd);
-               } else {
-                       sharg(np->nfile.fname, fp);
-               }
-               first = 0;
-       }
+       if (stoppedjobs())
+               return 0;
+       if (argc > 1)
+               exitstatus = number(argv[1]);
+       raise_exception(EXEXIT);
+       /* NOTREACHED */
 }
 
-
-static void
-sharg(union node *arg, FILE *fp)
+#if ENABLE_ASH_BUILTIN_ECHO
+static int
+echocmd(int argc, char **argv)
 {
-       char *p;
-       struct nodelist *bqlist;
-       int subtype;
-
-       if (arg->type != NARG) {
-               out1fmt("<node type %d>\n", arg->type);
-               abort();
-       }
-       bqlist = arg->narg.backquote;
-       for (p = arg->narg.text; *p; p++) {
-               switch (*p) {
-               case CTLESC:
-                       putc(*++p, fp);
-                       break;
-               case CTLVAR:
-                       putc('$', fp);
-                       putc('{', fp);
-                       subtype = *++p;
-                       if (subtype == VSLENGTH)
-                               putc('#', fp);
-
-                       while (*p != '=')
-                               putc(*p++, fp);
-
-                       if (subtype & VSNUL)
-                               putc(':', fp);
-
-                       switch (subtype & VSTYPE) {
-                       case VSNORMAL:
-                               putc('}', fp);
-                               break;
-                       case VSMINUS:
-                               putc('-', fp);
-                               break;
-                       case VSPLUS:
-                               putc('+', fp);
-                               break;
-                       case VSQUESTION:
-                               putc('?', fp);
-                               break;
-                       case VSASSIGN:
-                               putc('=', fp);
-                               break;
-                       case VSTRIMLEFT:
-                               putc('#', fp);
-                               break;
-                       case VSTRIMLEFTMAX:
-                               putc('#', fp);
-                               putc('#', fp);
-                               break;
-                       case VSTRIMRIGHT:
-                               putc('%', fp);
-                               break;
-                       case VSTRIMRIGHTMAX:
-                               putc('%', fp);
-                               putc('%', fp);
-                               break;
-                       case VSLENGTH:
-                               break;
-                       default:
-                               out1fmt("<subtype %d>", subtype);
-                       }
-                       break;
-               case CTLENDVAR:
-                       putc('}', fp);
-                       break;
-               case CTLBACKQ:
-               case CTLBACKQ|CTLQUOTE:
-                       putc('$', fp);
-                       putc('(', fp);
-                       shtree(bqlist->n, -1, NULL, fp);
-                       putc(')', fp);
-                       break;
-               default:
-                       putc(*p, fp);
-                       break;
-               }
-       }
+       return bb_echo(argv);
 }
+#endif
 
+#if ENABLE_ASH_BUILTIN_TEST
+static int
+testcmd(int argc, char **argv)
+{
+       return bb_test(argc, argv);
+}
+#endif
 
+/*
+ * Read a file containing shell functions.
+ */
 static void
-indent(int amount, char *pfx, FILE *fp)
+readcmdfile(char *name)
 {
-       int i;
-
-       for (i = 0; i < amount; i++) {
-               if (pfx && i == amount - 1)
-                       fputs(pfx, fp);
-               putc('\t', fp);
-       }
+       setinputfile(name, INPUT_PUSH_FILE);
+       cmdloop(0);
+       popfile();
 }
 
 
+/*      redir.c      */
+
 /*
- * Debugging stuff.
+ * Code for dealing with input/output redirection.
  */
 
+#define EMPTY -2                /* marks an unused slot in redirtab */
+#ifndef PIPE_BUF
+# define PIPESIZE 4096          /* amount of buffering in a pipe */
+#else
+# define PIPESIZE PIPE_BUF
+#endif
 
-static FILE *tracefile;
-
-
-static void
-trputc(int c)
+/*
+ * Open a file in noclobber mode.
+ * The code was copied from bash.
+ */
+static int
+noclobberopen(const char *fname)
 {
-       if (debug != 1)
-               return;
-       putc(c, tracefile);
-}
+       int r, fd;
+       struct stat finfo, finfo2;
 
-static void
-trace(const char *fmt, ...)
-{
-       va_list va;
+       /*
+        * If the file exists and is a regular file, return an error
+        * immediately.
+        */
+       r = stat(fname, &finfo);
+       if (r == 0 && S_ISREG(finfo.st_mode)) {
+               errno = EEXIST;
+               return -1;
+       }
 
-       if (debug != 1)
-               return;
-       va_start(va, fmt);
-       (void) vfprintf(tracefile, fmt, va);
-       va_end(va);
-}
+       /*
+        * If the file was not present (r != 0), make sure we open it
+        * exclusively so that if it is created before we open it, our open
+        * will fail.  Make sure that we do not truncate an existing file.
+        * Note that we don't turn on O_EXCL unless the stat failed -- if the
+        * file was not a regular file, we leave O_EXCL off.
+        */
+       if (r != 0)
+               return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666);
+       fd = open(fname, O_WRONLY|O_CREAT, 0666);
 
-static void
-tracev(const char *fmt, va_list va)
-{
-       if (debug != 1)
-               return;
-       (void) vfprintf(tracefile, fmt, va);
-}
+       /* If the open failed, return the file descriptor right away. */
+       if (fd < 0)
+               return fd;
 
+       /*
+        * OK, the open succeeded, but the file may have been changed from a
+        * non-regular file to a regular file between the stat and the open.
+        * We are assuming that the O_EXCL open handles the case where FILENAME
+        * did not exist and is symlinked to an existing file between the stat
+        * and open.
+        */
 
-static void
-trputs(const char *s)
-{
-       if (debug != 1)
-               return;
-       fputs(s, tracefile);
+       /*
+        * If we can open it and fstat the file descriptor, and neither check
+        * revealed that it was a regular file, and the file has not been
+        * replaced, return the file descriptor.
+        */
+       if (fstat(fd, &finfo2) == 0 && !S_ISREG(finfo2.st_mode)
+        && finfo.st_dev == finfo2.st_dev && finfo.st_ino == finfo2.st_ino)
+               return fd;
+
+       /* The file has been replaced.  badness. */
+       close(fd);
+       errno = EEXIST;
+       return -1;
 }
 
 
-static void
-trstring(char *s)
+/*
+ * Handle here documents.  Normally we fork off a process to write the
+ * data to a pipe.  If the document is short, we can stuff the data in
+ * the pipe without forking.
+ */
+static int
+openhere(union node *redir)
 {
-       char *p;
-       char c;
+       int pip[2];
+       size_t len = 0;
 
-       if (debug != 1)
-               return;
-       putc('"', tracefile);
-       for (p = s; *p; p++) {
-               switch (*p) {
-               case '\n':  c = 'n';  goto backslash;
-               case '\t':  c = 't';  goto backslash;
-               case '\r':  c = 'r';  goto backslash;
-               case '"':  c = '"';  goto backslash;
-               case '\\':  c = '\\';  goto backslash;
-               case CTLESC:  c = 'e';  goto backslash;
-               case CTLVAR:  c = 'v';  goto backslash;
-               case CTLVAR+CTLQUOTE:  c = 'V'; goto backslash;
-               case CTLBACKQ:  c = 'q';  goto backslash;
-               case CTLBACKQ+CTLQUOTE:  c = 'Q'; goto backslash;
- backslash:
-                       putc('\\', tracefile);
-                       putc(c, tracefile);
-                       break;
-               default:
-                       if (*p >= ' ' && *p <= '~')
-                               putc(*p, tracefile);
-                       else {
-                               putc('\\', tracefile);
-                               putc(*p >> 6 & 03, tracefile);
-                               putc(*p >> 3 & 07, tracefile);
-                               putc(*p & 07, tracefile);
-                       }
-                       break;
+       if (pipe(pip) < 0)
+               ash_msg_and_raise_error("Pipe call failed");
+       if (redir->type == NHERE) {
+               len = strlen(redir->nhere.doc->narg.text);
+               if (len <= PIPESIZE) {
+                       full_write(pip[1], redir->nhere.doc->narg.text, len);
+                       goto out;
                }
        }
-       putc('"', tracefile);
-}
-
-
-static void
-trargs(char **ap)
-{
-       if (debug != 1)
-               return;
-       while (*ap) {
-               trstring(*ap++);
-               if (*ap)
-                       putc(' ', tracefile);
+       if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) {
+               close(pip[0]);
+               signal(SIGINT, SIG_IGN);
+               signal(SIGQUIT, SIG_IGN);
+               signal(SIGHUP, SIG_IGN);
+#ifdef SIGTSTP
+               signal(SIGTSTP, SIG_IGN);
+#endif
+               signal(SIGPIPE, SIG_DFL);
+               if (redir->type == NHERE)
+                       full_write(pip[1], redir->nhere.doc->narg.text, len);
                else
-                       putc('\n', tracefile);
+                       expandhere(redir->nhere.doc, pip[1]);
+               _exit(0);
        }
+ out:
+       close(pip[1]);
+       return pip[0];
 }
 
-
-static void
-opentrace(void)
+static int
+openredirect(union node *redir)
 {
-       char s[100];
-#ifdef O_APPEND
-       int flags;
-#endif
+       char *fname;
+       int f;
 
-       if (debug != 1) {
-               if (tracefile)
-                       fflush(tracefile);
-               /* leave open because libedit might be using it */
-               return;
-       }
-       scopy("./trace", s);
-       if (tracefile) {
-               if (!freopen(s, "a", tracefile)) {
-                       fprintf(stderr, "Can't re-open %s\n", s);
-                       debug = 0;
-                       return;
-               }
-       } else {
-               tracefile = fopen(s, "a");
-               if (tracefile == NULL) {
-                       fprintf(stderr, "Can't open %s\n", s);
-                       debug = 0;
-                       return;
+       switch (redir->nfile.type) {
+       case NFROM:
+               fname = redir->nfile.expfname;
+               f = open(fname, O_RDONLY);
+               if (f < 0)
+                       goto eopen;
+               break;
+       case NFROMTO:
+               fname = redir->nfile.expfname;
+               f = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0666);
+               if (f < 0)
+                       goto ecreate;
+               break;
+       case NTO:
+               /* Take care of noclobber mode. */
+               if (Cflag) {
+                       fname = redir->nfile.expfname;
+                       f = noclobberopen(fname);
+                       if (f < 0)
+                               goto ecreate;
+                       break;
                }
+               /* FALLTHROUGH */
+       case NCLOBBER:
+               fname = redir->nfile.expfname;
+               f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666);
+               if (f < 0)
+                       goto ecreate;
+               break;
+       case NAPPEND:
+               fname = redir->nfile.expfname;
+               f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666);
+               if (f < 0)
+                       goto ecreate;
+               break;
+       default:
+#if DEBUG
+               abort();
+#endif
+               /* Fall through to eliminate warning. */
+       case NTOFD:
+       case NFROMFD:
+               f = -1;
+               break;
+       case NHERE:
+       case NXHERE:
+               f = openhere(redir);
+               break;
        }
-#ifdef O_APPEND
-       flags = fcntl(fileno(tracefile), F_GETFL, 0);
-       if (flags >= 0)
-               fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND);
-#endif
-       setlinebuf(tracefile);
-       fputs("\nTracing started.\n", tracefile);
-}
-#endif /* DEBUG */
-
-
-/*      trap.c       */
-
-/*
- * Sigmode records the current value of the signal handlers for the various
- * modes.  A value of zero means that the current handler is not known.
- * S_HARD_IGN indicates that the signal was ignored on entry to the shell,
- */
-
-#define S_DFL 1                 /* default signal handling (SIG_DFL) */
-#define S_CATCH 2               /* signal is caught */
-#define S_IGN 3                 /* signal is ignored (SIG_IGN) */
-#define S_HARD_IGN 4            /* signal is ignored permenantly */
-#define S_RESET 5               /* temporary - to reset a hard ignored sig */
 
+       return f;
+ ecreate:
+       ash_msg_and_raise_error("cannot create %s: %s", fname, errmsg(errno, "Directory nonexistent"));
+ eopen:
+       ash_msg_and_raise_error("cannot open %s: %s", fname, errmsg(errno, "No such file"));
+}
 
-/*
- * The trap builtin.
- */
-static int
-trapcmd(int argc, char **argv)
+static void
+dupredirect(union node *redir, int f)
 {
-       char *action;
-       char **ap;
-       int signo;
-
-       nextopt(nullstr);
-       ap = argptr;
-       if (!*ap) {
-               for (signo = 0; signo < NSIG; signo++) {
-                       if (trap[signo] != NULL) {
-                               const char *sn;
+       int fd = redir->nfile.fd;
 
-                               sn = get_signame(signo);
-                               out1fmt("trap -- %s %s\n",
-                                       single_quote(trap[signo]), sn);
-                       }
+       if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) {
+               if (redir->ndup.dupfd >= 0) {   /* if not ">&-" */
+                       copyfd(redir->ndup.dupfd, fd);
                }
-               return 0;
+               return;
        }
-       if (!ap[1])
-               action = NULL;
-       else
-               action = *ap++;
-       while (*ap) {
-               signo = get_signum(*ap);
-               if (signo < 0)
-                       ash_msg_and_raise_error("%s: bad trap", *ap);
-               INT_OFF;
-               if (action) {
-                       if (LONE_DASH(action))
-                               action = NULL;
-                       else
-                               action = ckstrdup(action);
-               }
-               if (trap[signo])
-                       free(trap[signo]);
-               trap[signo] = action;
-               if (signo != 0)
-                       setsignal(signo);
-               INT_ON;
-               ap++;
+
+       if (f != fd) {
+               copyfd(f, fd);
+               close(f);
        }
-       return 0;
 }
 
 
 /*
- * Clear traps on a fork.
+ * Process a list of redirection commands.  If the REDIR_PUSH flag is set,
+ * old file descriptors are stashed away so that the redirection can be
+ * undone by calling popredir.  If the REDIR_BACKQ flag is set, then the
+ * standard output, and the standard error if it becomes a duplicate of
+ * stdout, is saved in memory.
  */
 static void
-clear_traps(void)
+redirect(union node *redir, int flags)
 {
-       char **tp;
+       union node *n;
+       struct redirtab *sv;
+       int i;
+       int fd;
+       int newfd;
+       int *p;
+       nullredirs++;
+       if (!redir) {
+               return;
+       }
+       sv = NULL;
+       INT_OFF;
+       if (flags & REDIR_PUSH) {
+               struct redirtab *q;
+               q = ckmalloc(sizeof(struct redirtab));
+               q->next = redirlist;
+               redirlist = q;
+               q->nullredirs = nullredirs - 1;
+               for (i = 0; i < 10; i++)
+                       q->renamed[i] = EMPTY;
+               nullredirs = 0;
+               sv = q;
+       }
+       n = redir;
+       do {
+               fd = n->nfile.fd;
+               if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD)
+                && n->ndup.dupfd == fd)
+                       continue; /* redirect from/to same file descriptor */
 
-       for (tp = trap; tp < &trap[NSIG]; tp++) {
-               if (*tp && **tp) {      /* trap not NULL or SIG_IGN */
-                       INT_OFF;
-                       free(*tp);
-                       *tp = NULL;
-                       if (tp != &trap[0])
-                               setsignal(tp - trap);
-                       INT_ON;
+               newfd = openredirect(n);
+               if (fd == newfd)
+                       continue;
+               if (sv && *(p = &sv->renamed[fd]) == EMPTY) {
+                       i = fcntl(fd, F_DUPFD, 10);
+
+                       if (i == -1) {
+                               i = errno;
+                               if (i != EBADF) {
+                                       close(newfd);
+                                       errno = i;
+                                       ash_msg_and_raise_error("%d: %m", fd);
+                                       /* NOTREACHED */
+                               }
+                       } else {
+                               *p = i;
+                               close(fd);
+                       }
+               } else {
+                       close(fd);
                }
-       }
+               dupredirect(n, newfd);
+       } while ((n = n->nfile.next));
+       INT_ON;
+       if (flags & REDIR_SAVEFD2 && sv && sv->renamed[2] >= 0)
+               preverrout_fd = sv->renamed[2];
 }
 
 
 /*
- * Set the signal handler for the specified signal.  The routine figures
- * out what it should be set to.
+ * Undo the effects of the last redirection.
  */
 static void
-setsignal(int signo)
+popredir(int drop)
 {
-       int action;
-       char *t, tsig;
-       struct sigaction act;
-
-       t = trap[signo];
-       if (t == NULL)
-               action = S_DFL;
-       else if (*t != '\0')
-               action = S_CATCH;
-       else
-               action = S_IGN;
-       if (rootshell && action == S_DFL) {
-               switch (signo) {
-               case SIGINT:
-                       if (iflag || minusc || sflag == 0)
-                               action = S_CATCH;
-                       break;
-               case SIGQUIT:
-#if DEBUG
-                       if (debug)
-                               break;
-#endif
-                       /* FALLTHROUGH */
-               case SIGTERM:
-                       if (iflag)
-                               action = S_IGN;
-                       break;
-#if JOBS
-               case SIGTSTP:
-               case SIGTTOU:
-                       if (mflag)
-                               action = S_IGN;
-                       break;
-#endif
-               }
-       }
+       struct redirtab *rp;
+       int i;
 
-       t = &sigmode[signo - 1];
-       tsig = *t;
-       if (tsig == 0) {
-               /*
-                * current setting unknown
-                */
-               if (sigaction(signo, 0, &act) == -1) {
-                       /*
-                        * Pretend it worked; maybe we should give a warning
-                        * here, but other shells don't. We don't alter
-                        * sigmode, so that we retry every time.
-                        */
-                       return;
-               }
-               if (act.sa_handler == SIG_IGN) {
-                       if (mflag
-                        && (signo == SIGTSTP || signo == SIGTTIN || signo == SIGTTOU)
-                       ) {
-                               tsig = S_IGN;   /* don't hard ignore these */
-                       } else
-                               tsig = S_HARD_IGN;
-               } else {
-                       tsig = S_RESET; /* force to be set */
-               }
-       }
-       if (tsig == S_HARD_IGN || tsig == action)
+       if (--nullredirs >= 0)
                return;
-       switch (action) {
-       case S_CATCH:
-               act.sa_handler = onsig;
-               break;
-       case S_IGN:
-               act.sa_handler = SIG_IGN;
-               break;
-       default:
-               act.sa_handler = SIG_DFL;
+       INT_OFF;
+       rp = redirlist;
+       for (i = 0; i < 10; i++) {
+               if (rp->renamed[i] != EMPTY) {
+                       if (!drop) {
+                               close(i);
+                               copyfd(rp->renamed[i], i);
+                       }
+                       close(rp->renamed[i]);
+               }
        }
-       *t = action;
-       act.sa_flags = 0;
-       sigfillset(&act.sa_mask);
-       sigaction(signo, &act, 0);
+       redirlist = rp->next;
+       nullredirs = rp->nullredirs;
+       free(rp);
+       INT_ON;
 }
 
+/*
+ * Undo all redirections.  Called on error or interrupt.
+ */
 
 /*
- * Ignore a signal.
+ * Discard all saved file descriptors.
  */
 static void
-ignoresig(int signo)
+clearredir(int drop)
 {
-       if (sigmode[signo - 1] != S_IGN && sigmode[signo - 1] != S_HARD_IGN) {
-               signal(signo, SIG_IGN);
+       for (;;) {
+               nullredirs = 0;
+               if (!redirlist)
+                       break;
+               popredir(drop);
        }
-       sigmode[signo - 1] = S_HARD_IGN;
 }
 
 
 /*
- * Signal handler.
+ * Copy a file descriptor to be >= to.  Returns -1
+ * if the source file descriptor is closed, EMPTY if there are no unused
+ * file descriptors left.
  */
+static int
+copyfd(int from, int to)
+{
+       int newfd;
+
+       newfd = fcntl(from, F_DUPFD, to);
+       if (newfd < 0) {
+               if (errno == EMFILE)
+                       return EMPTY;
+               ash_msg_and_raise_error("%d: %m", from);
+       }
+       return newfd;
+}
+
+static int
+redirectsafe(union node *redir, int flags)
+{
+       int err;
+       volatile int saveint;
+       struct jmploc *volatile savehandler = exception_handler;
+       struct jmploc jmploc;
+
+       SAVE_INT(saveint);
+       err = setjmp(jmploc.loc) * 2;
+       if (!err) {
+               exception_handler = &jmploc;
+               redirect(redir, flags);
+       }
+       exception_handler = savehandler;
+       if (err && exception != EXERROR)
+               longjmp(exception_handler->loc, 1);
+       RESTORE_INT(saveint);
+       return err;
+}
+
+/*      show.c    */
+
+#if DEBUG
+static void shtree(union node *, int, char *, FILE*);
+static void shcmd(union node *, FILE *);
+static void sharg(union node *, FILE *);
+static void indent(int, char *, FILE *);
+static void trstring(char *);
+
 static void
-onsig(int signo)
+showtree(union node *n)
 {
-       gotsig[signo - 1] = 1;
-       pendingsigs = signo;
+       trputs("showtree called\n");
+       shtree(n, 1, NULL, stdout);
+}
 
-       if (exsig || (signo == SIGINT && !trap[SIGINT])) {
-               if (!suppressint)
-                       raise_interrupt();
-               intpending = 1;
+static void
+shtree(union node *n, int ind, char *pfx, FILE *fp)
+{
+       struct nodelist *lp;
+       const char *s;
+
+       if (n == NULL)
+               return;
+
+       indent(ind, pfx, fp);
+       switch (n->type) {
+       case NSEMI:
+               s = "; ";
+               goto binop;
+       case NAND:
+               s = " && ";
+               goto binop;
+       case NOR:
+               s = " || ";
+ binop:
+               shtree(n->nbinary.ch1, ind, NULL, fp);
+          /*    if (ind < 0) */
+                       fputs(s, fp);
+               shtree(n->nbinary.ch2, ind, NULL, fp);
+               break;
+       case NCMD:
+               shcmd(n, fp);
+               if (ind >= 0)
+                       putc('\n', fp);
+               break;
+       case NPIPE:
+               for (lp = n->npipe.cmdlist; lp; lp = lp->next) {
+                       shcmd(lp->n, fp);
+                       if (lp->next)
+                               fputs(" | ", fp);
+               }
+               if (n->npipe.backgnd)
+                       fputs(" &", fp);
+               if (ind >= 0)
+                       putc('\n', fp);
+               break;
+       default:
+               fprintf(fp, "<node type %d>", n->type);
+               if (ind >= 0)
+                       putc('\n', fp);
+               break;
        }
 }
 
+static void
+shcmd(union node *cmd, FILE *fp)
+{
+       union node *np;
+       int first;
+       const char *s;
+       int dftfd;
+
+       first = 1;
+       for (np = cmd->ncmd.args; np; np = np->narg.next) {
+               if (! first)
+                       putchar(' ');
+               sharg(np, fp);
+               first = 0;
+       }
+       for (np = cmd->ncmd.redirect; np; np = np->nfile.next) {
+               if (! first)
+                       putchar(' ');
+               switch (np->nfile.type) {
+               case NTO:       s = ">";  dftfd = 1; break;
+               case NCLOBBER:  s = ">|"; dftfd = 1; break;
+               case NAPPEND:   s = ">>"; dftfd = 1; break;
+               case NTOFD:     s = ">&"; dftfd = 1; break;
+               case NFROM:     s = "<";  dftfd = 0; break;
+               case NFROMFD:   s = "<&"; dftfd = 0; break;
+               case NFROMTO:   s = "<>"; dftfd = 0; break;
+               default:        s = "*error*"; dftfd = 0; break;
+               }
+               if (np->nfile.fd != dftfd)
+                       fprintf(fp, "%d", np->nfile.fd);
+               fputs(s, fp);
+               if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) {
+                       fprintf(fp, "%d", np->ndup.dupfd);
+               } else {
+                       sharg(np->nfile.fname, fp);
+               }
+               first = 0;
+       }
+}
 
-/*
- * Called to execute a trap.  Perhaps we should avoid entering new trap
- * handlers while we are executing a trap handler.
- */
-static int
-dotrap(void)
+static void
+sharg(union node *arg, FILE *fp)
 {
        char *p;
-       char *q;
-       int i;
-       int savestatus;
-       int skip = 0;
+       struct nodelist *bqlist;
+       int subtype;
 
-       savestatus = exitstatus;
-       pendingsigs = 0;
-       xbarrier();
+       if (arg->type != NARG) {
+               out1fmt("<node type %d>\n", arg->type);
+               abort();
+       }
+       bqlist = arg->narg.backquote;
+       for (p = arg->narg.text; *p; p++) {
+               switch (*p) {
+               case CTLESC:
+                       putc(*++p, fp);
+                       break;
+               case CTLVAR:
+                       putc('$', fp);
+                       putc('{', fp);
+                       subtype = *++p;
+                       if (subtype == VSLENGTH)
+                               putc('#', fp);
 
-       for (i = 0, q = gotsig; i < NSIG - 1; i++, q++) {
-               if (!*q)
-                       continue;
-               *q = '\0';
+                       while (*p != '=')
+                               putc(*p++, fp);
 
-               p = trap[i + 1];
-               if (!p)
-                       continue;
-               skip = evalstring(p, SKIPEVAL);
-               exitstatus = savestatus;
-               if (skip)
+                       if (subtype & VSNUL)
+                               putc(':', fp);
+
+                       switch (subtype & VSTYPE) {
+                       case VSNORMAL:
+                               putc('}', fp);
+                               break;
+                       case VSMINUS:
+                               putc('-', fp);
+                               break;
+                       case VSPLUS:
+                               putc('+', fp);
+                               break;
+                       case VSQUESTION:
+                               putc('?', fp);
+                               break;
+                       case VSASSIGN:
+                               putc('=', fp);
+                               break;
+                       case VSTRIMLEFT:
+                               putc('#', fp);
+                               break;
+                       case VSTRIMLEFTMAX:
+                               putc('#', fp);
+                               putc('#', fp);
+                               break;
+                       case VSTRIMRIGHT:
+                               putc('%', fp);
+                               break;
+                       case VSTRIMRIGHTMAX:
+                               putc('%', fp);
+                               putc('%', fp);
+                               break;
+                       case VSLENGTH:
+                               break;
+                       default:
+                               out1fmt("<subtype %d>", subtype);
+                       }
+                       break;
+               case CTLENDVAR:
+                       putc('}', fp);
+                       break;
+               case CTLBACKQ:
+               case CTLBACKQ|CTLQUOTE:
+                       putc('$', fp);
+                       putc('(', fp);
+                       shtree(bqlist->n, -1, NULL, fp);
+                       putc(')', fp);
+                       break;
+               default:
+                       putc(*p, fp);
                        break;
+               }
        }
+}
 
-       return skip;
+
+static void
+indent(int amount, char *pfx, FILE *fp)
+{
+       int i;
+
+       for (i = 0; i < amount; i++) {
+               if (pfx && i == amount - 1)
+                       fputs(pfx, fp);
+               putc('\t', fp);
+       }
 }
 
 
 /*
- * Controls whether the shell is interactive or not.
+ * Debugging stuff.
  */
+
+
+static FILE *tracefile;
+
+
 static void
-setinteractive(int on)
+trputc(int c)
 {
-       static int is_interactive;
+       if (debug != 1)
+               return;
+       putc(c, tracefile);
+}
 
-       if (++on == is_interactive)
+static void
+trace(const char *fmt, ...)
+{
+       va_list va;
+
+       if (debug != 1)
                return;
-       is_interactive = on;
-       setsignal(SIGINT);
-       setsignal(SIGQUIT);
-       setsignal(SIGTERM);
-#if !ENABLE_FEATURE_SH_EXTRA_QUIET
-       if (is_interactive > 1) {
-               /* Looks like they want an interactive shell */
-               static int do_banner;
+       va_start(va, fmt);
+       (void) vfprintf(tracefile, fmt, va);
+       va_end(va);
+}
 
-               if (!do_banner) {
-                       out1fmt(
-                               "\n\n"
-                               "%s Built-in shell (ash)\n"
-                               "Enter 'help' for a list of built-in commands."
-                               "\n\n",
-                               BB_BANNER);
-                       do_banner++;
-               }
-       }
-#endif
+static void
+tracev(const char *fmt, va_list va)
+{
+       if (debug != 1)
+               return;
+       (void) vfprintf(tracefile, fmt, va);
 }
 
 
-#if !ENABLE_FEATURE_SH_EXTRA_QUIET
-/*** List the available builtins ***/
+static void
+trputs(const char *s)
+{
+       if (debug != 1)
+               return;
+       fputs(s, tracefile);
+}
+
 
-static int helpcmd(int argc, char **argv)
+static void
+trstring(char *s)
 {
-       int col, i;
+       char *p;
+       char c;
 
-       out1fmt("\nBuilt-in commands:\n-------------------\n");
-       for (col = 0, i = 0; i < NUMBUILTINS; i++) {
-               col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '),
-                                         builtincmd[i].name + 1);
-               if (col > 60) {
-                       out1fmt("\n");
-                       col = 0;
+       if (debug != 1)
+               return;
+       putc('"', tracefile);
+       for (p = s; *p; p++) {
+               switch (*p) {
+               case '\n':  c = 'n';  goto backslash;
+               case '\t':  c = 't';  goto backslash;
+               case '\r':  c = 'r';  goto backslash;
+               case '"':  c = '"';  goto backslash;
+               case '\\':  c = '\\';  goto backslash;
+               case CTLESC:  c = 'e';  goto backslash;
+               case CTLVAR:  c = 'v';  goto backslash;
+               case CTLVAR+CTLQUOTE:  c = 'V'; goto backslash;
+               case CTLBACKQ:  c = 'q';  goto backslash;
+               case CTLBACKQ+CTLQUOTE:  c = 'Q'; goto backslash;
+ backslash:
+                       putc('\\', tracefile);
+                       putc(c, tracefile);
+                       break;
+               default:
+                       if (*p >= ' ' && *p <= '~')
+                               putc(*p, tracefile);
+                       else {
+                               putc('\\', tracefile);
+                               putc(*p >> 6 & 03, tracefile);
+                               putc(*p >> 3 & 07, tracefile);
+                               putc(*p & 07, tracefile);
+                       }
+                       break;
                }
        }
-#if ENABLE_FEATURE_SH_STANDALONE_SHELL
-       for (i = 0; i < NUM_APPLETS; i++) {
-               col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '), applets[i].name);
-               if (col > 60) {
-                       out1fmt("\n");
-                       col = 0;
-               }
+       putc('"', tracefile);
+}
+
+
+static void
+trargs(char **ap)
+{
+       if (debug != 1)
+               return;
+       while (*ap) {
+               trstring(*ap++);
+               if (*ap)
+                       putc(' ', tracefile);
+               else
+                       putc('\n', tracefile);
        }
-#endif
-       out1fmt("\n\n");
-       return EXIT_SUCCESS;
 }
-#endif /* FEATURE_SH_EXTRA_QUIET */
 
 
-/*
- * Called to exit the shell.
- */
 static void
-exitshell(void)
+opentrace(void)
 {
-       struct jmploc loc;
-       char *p;
-       int status;
+       char s[100];
+#ifdef O_APPEND
+       int flags;
+#endif
 
-       status = exitstatus;
-       TRACE(("pid %d, exitshell(%d)\n", getpid(), status));
-       if (setjmp(loc.loc)) {
-               if (exception == EXEXIT)
-/* dash bug: it just does _exit(exitstatus) here
- * but we have to do setjobctl(0) first!
- * (bug is still not fixed in dash-0.5.3 - if you run dash
- * under Midnight Commander, on exit from dash MC is backgrounded) */
-                       status = exitstatus;
-               goto out;
+       if (debug != 1) {
+               if (tracefile)
+                       fflush(tracefile);
+               /* leave open because libedit might be using it */
+               return;
        }
-       exception_handler = &loc;
-       p = trap[0];
-       if (p) {
-               trap[0] = NULL;
-               evalstring(p, 0);
+       scopy("./trace", s);
+       if (tracefile) {
+               if (!freopen(s, "a", tracefile)) {
+                       fprintf(stderr, "Can't re-open %s\n", s);
+                       debug = 0;
+                       return;
+               }
+       } else {
+               tracefile = fopen(s, "a");
+               if (tracefile == NULL) {
+                       fprintf(stderr, "Can't open %s\n", s);
+                       debug = 0;
+                       return;
+               }
        }
-       flush_stdout_stderr();
- out:
-       setjobctl(0);
-       _exit(status);
-       /* NOTREACHED */
+#ifdef O_APPEND
+       flags = fcntl(fileno(tracefile), F_GETFL, 0);
+       if (flags >= 0)
+               fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND);
+#endif
+       setlinebuf(tracefile);
+       fputs("\nTracing started.\n", tracefile);
 }
+#endif /* DEBUG */
 
-/*      var.c     */
-
-static struct var *vartab[VTABSIZE];
 
-static int vpcmp(const void *, const void *);
-static struct var **findvar(struct var **, const char *);
+/*      trap.c       */
 
 /*
- * Initialize the variable symbol tables and import the environment
+ * Sigmode records the current value of the signal handlers for the various
+ * modes.  A value of zero means that the current handler is not known.
+ * S_HARD_IGN indicates that the signal was ignored on entry to the shell,
  */
 
+#define S_DFL 1                 /* default signal handling (SIG_DFL) */
+#define S_CATCH 2               /* signal is caught */
+#define S_IGN 3                 /* signal is ignored (SIG_IGN) */
+#define S_HARD_IGN 4            /* signal is ignored permenantly */
+#define S_RESET 5               /* temporary - to reset a hard ignored sig */
+
 
-#if ENABLE_ASH_GETOPTS
 /*
- * Safe version of setvar, returns 1 on success 0 on failure.
+ * The trap builtin.
  */
 static int
-setvarsafe(const char *name, const char *val, int flags)
+trapcmd(int argc, char **argv)
 {
-       int err;
-       volatile int saveint;
-       struct jmploc *volatile savehandler = exception_handler;
-       struct jmploc jmploc;
+       char *action;
+       char **ap;
+       int signo;
 
-       SAVE_INT(saveint);
-       if (setjmp(jmploc.loc))
-               err = 1;
-       else {
-               exception_handler = &jmploc;
-               setvar(name, val, flags);
-               err = 0;
+       nextopt(nullstr);
+       ap = argptr;
+       if (!*ap) {
+               for (signo = 0; signo < NSIG; signo++) {
+                       if (trap[signo] != NULL) {
+                               const char *sn;
+
+                               sn = get_signame(signo);
+                               out1fmt("trap -- %s %s\n",
+                                       single_quote(trap[signo]), sn);
+                       }
+               }
+               return 0;
+       }
+       if (!ap[1])
+               action = NULL;
+       else
+               action = *ap++;
+       while (*ap) {
+               signo = get_signum(*ap);
+               if (signo < 0)
+                       ash_msg_and_raise_error("%s: bad trap", *ap);
+               INT_OFF;
+               if (action) {
+                       if (LONE_DASH(action))
+                               action = NULL;
+                       else
+                               action = ckstrdup(action);
+               }
+               if (trap[signo])
+                       free(trap[signo]);
+               trap[signo] = action;
+               if (signo != 0)
+                       setsignal(signo);
+               INT_ON;
+               ap++;
        }
-       exception_handler = savehandler;
-       RESTORE_INT(saveint);
-       return err;
+       return 0;
 }
-#endif
 
 
 /*
- * Set the value of a variable.  The flags argument is ored with the
- * flags of the variable.  If val is NULL, the variable is unset.
+ * Clear traps on a fork.
  */
 static void
-setvar(const char *name, const char *val, int flags)
+clear_traps(void)
 {
-       char *p, *q;
-       size_t namelen;
-       char *nameeq;
-       size_t vallen;
+       char **tp;
 
-       q = endofname(name);
-       p = strchrnul(q, '=');
-       namelen = p - name;
-       if (!namelen || p != q)
-               ash_msg_and_raise_error("%.*s: bad variable name", namelen, name);
-       vallen = 0;
-       if (val == NULL) {
-               flags |= VUNSET;
-       } else {
-               vallen = strlen(val);
-       }
-       INT_OFF;
-       nameeq = ckmalloc(namelen + vallen + 2);
-       p = memcpy(nameeq, name, namelen) + namelen;
-       if (val) {
-               *p++ = '=';
-               p = memcpy(p, val, vallen) + vallen;
+       for (tp = trap; tp < &trap[NSIG]; tp++) {
+               if (*tp && **tp) {      /* trap not NULL or SIG_IGN */
+                       INT_OFF;
+                       free(*tp);
+                       *tp = NULL;
+                       if (tp != &trap[0])
+                               setsignal(tp - trap);
+                       INT_ON;
+               }
        }
-       *p = '\0';
-       setvareq(nameeq, flags | VNOSAVE);
-       INT_ON;
 }
 
 
 /*
- * Same as setvar except that the variable and value are passed in
- * the first argument as name=value.  Since the first argument will
- * be actually stored in the table, it should not be a string that
- * will go away.
- * Called with interrupts off.
+ * Set the signal handler for the specified signal.  The routine figures
+ * out what it should be set to.
  */
 static void
-setvareq(char *s, int flags)
+setsignal(int signo)
 {
-       struct var *vp, **vpp;
-
-       vpp = hashvar(s);
-       flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1));
-       vp = *findvar(vpp, s);
-       if (vp) {
-               if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) {
-                       const char *n;
+       int action;
+       char *t, tsig;
+       struct sigaction act;
 
-                       if (flags & VNOSAVE)
-                               free(s);
-                       n = vp->text;
-                       ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n);
+       t = trap[signo];
+       if (t == NULL)
+               action = S_DFL;
+       else if (*t != '\0')
+               action = S_CATCH;
+       else
+               action = S_IGN;
+       if (rootshell && action == S_DFL) {
+               switch (signo) {
+               case SIGINT:
+                       if (iflag || minusc || sflag == 0)
+                               action = S_CATCH;
+                       break;
+               case SIGQUIT:
+#if DEBUG
+                       if (debug)
+                               break;
+#endif
+                       /* FALLTHROUGH */
+               case SIGTERM:
+                       if (iflag)
+                               action = S_IGN;
+                       break;
+#if JOBS
+               case SIGTSTP:
+               case SIGTTOU:
+                       if (mflag)
+                               action = S_IGN;
+                       break;
+#endif
                }
+       }
 
-               if (flags & VNOSET)
+       t = &sigmode[signo - 1];
+       tsig = *t;
+       if (tsig == 0) {
+               /*
+                * current setting unknown
+                */
+               if (sigaction(signo, 0, &act) == -1) {
+                       /*
+                        * Pretend it worked; maybe we should give a warning
+                        * here, but other shells don't. We don't alter
+                        * sigmode, so that we retry every time.
+                        */
                        return;
+               }
+               if (act.sa_handler == SIG_IGN) {
+                       if (mflag
+                        && (signo == SIGTSTP || signo == SIGTTIN || signo == SIGTTOU)
+                       ) {
+                               tsig = S_IGN;   /* don't hard ignore these */
+                       } else
+                               tsig = S_HARD_IGN;
+               } else {
+                       tsig = S_RESET; /* force to be set */
+               }
+       }
+       if (tsig == S_HARD_IGN || tsig == action)
+               return;
+       switch (action) {
+       case S_CATCH:
+               act.sa_handler = onsig;
+               break;
+       case S_IGN:
+               act.sa_handler = SIG_IGN;
+               break;
+       default:
+               act.sa_handler = SIG_DFL;
+       }
+       *t = action;
+       act.sa_flags = 0;
+       sigfillset(&act.sa_mask);
+       sigaction(signo, &act, 0);
+}
 
-               if (vp->func && (flags & VNOFUNC) == 0)
-                       (*vp->func)(strchrnul(s, '=') + 1);
-
-               if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
-                       free((char*)vp->text);
 
-               flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET);
-       } else {
-               if (flags & VNOSET)
-                       return;
-               /* not found */
-               vp = ckmalloc(sizeof(*vp));
-               vp->next = *vpp;
-               vp->func = NULL;
-               *vpp = vp;
+/*
+ * Ignore a signal.
+ */
+static void
+ignoresig(int signo)
+{
+       if (sigmode[signo - 1] != S_IGN && sigmode[signo - 1] != S_HARD_IGN) {
+               signal(signo, SIG_IGN);
        }
-       if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE)))
-               s = ckstrdup(s);
-       vp->text = s;
-       vp->flags = flags;
+       sigmode[signo - 1] = S_HARD_IGN;
 }
 
 
 /*
- * Process a linked list of variable assignments.
+ * Signal handler.
  */
 static void
-listsetvar(struct strlist *list_set_var, int flags)
+onsig(int signo)
 {
-       struct strlist *lp = list_set_var;
+       gotsig[signo - 1] = 1;
+       pendingsigs = signo;
 
-       if (!lp)
-               return;
-       INT_OFF;
-       do {
-               setvareq(lp->text, flags);
-       } while ((lp = lp->next));
-       INT_ON;
+       if (exsig || (signo == SIGINT && !trap[SIGINT])) {
+               if (!suppressint)
+                       raise_interrupt();
+               intpending = 1;
+       }
 }
 
 
 /*
- * Find the value of a variable.  Returns NULL if not set.
+ * Called to execute a trap.  Perhaps we should avoid entering new trap
+ * handlers while we are executing a trap handler.
  */
-static char *
-lookupvar(const char *name)
+static int
+dotrap(void)
 {
-       struct var *v;
+       char *p;
+       char *q;
+       int i;
+       int savestatus;
+       int skip = 0;
 
-       v = *findvar(hashvar(name), name);
-       if (v) {
-#ifdef DYNAMIC_VAR
-       /*
-        * Dynamic variables are implemented roughly the same way they are
-        * in bash. Namely, they're "special" so long as they aren't unset.
-        * As soon as they're unset, they're no longer dynamic, and dynamic
-        * lookup will no longer happen at that point. -- PFM.
-        */
-               if ((v->flags & VDYNAMIC))
-                       (*v->func)(NULL);
-#endif
-               if (!(v->flags & VUNSET))
-                       return strchrnul(v->text, '=') + 1;
+       savestatus = exitstatus;
+       pendingsigs = 0;
+       xbarrier();
+
+       for (i = 0, q = gotsig; i < NSIG - 1; i++, q++) {
+               if (!*q)
+                       continue;
+               *q = '\0';
+
+               p = trap[i + 1];
+               if (!p)
+                       continue;
+               skip = evalstring(p, SKIPEVAL);
+               exitstatus = savestatus;
+               if (skip)
+                       break;
        }
 
-       return NULL;
+       return skip;
 }
 
-
 /*
- * Search the environment of a builtin command.
+ * Controls whether the shell is interactive or not.
  */
-static char *
-bltinlookup(const char *name)
+static void
+setinteractive(int on)
 {
-       struct strlist *sp;
+       static int is_interactive;
+
+       if (++on == is_interactive)
+               return;
+       is_interactive = on;
+       setsignal(SIGINT);
+       setsignal(SIGQUIT);
+       setsignal(SIGTERM);
+#if !ENABLE_FEATURE_SH_EXTRA_QUIET
+       if (is_interactive > 1) {
+               /* Looks like they want an interactive shell */
+               static int do_banner;
 
-       for (sp = cmdenviron; sp; sp = sp->next) {
-               if (varequal(sp->text, name))
-                       return strchrnul(sp->text, '=') + 1;
+               if (!do_banner) {
+                       out1fmt(
+                               "\n\n"
+                               "%s Built-in shell (ash)\n"
+                               "Enter 'help' for a list of built-in commands."
+                               "\n\n",
+                               BB_BANNER);
+                       do_banner++;
+               }
        }
-       return lookupvar(name);
+#endif
 }
 
+#if !ENABLE_FEATURE_SH_EXTRA_QUIET
+/*** List the available builtins ***/
 
-/*
- * Generate a list of variables satisfying the given conditions.
- */
-static char **
-listvars(int on, int off, char ***end)
+static int
+helpcmd(int argc, char **argv)
 {
-       struct var **vpp;
-       struct var *vp;
-       char **ep;
-       int mask;
+       int col, i;
 
-       STARTSTACKSTR(ep);
-       vpp = vartab;
-       mask = on | off;
-       do {
-               for (vp = *vpp; vp; vp = vp->next)
-                       if ((vp->flags & mask) == on) {
-                               if (ep == stackstrend())
-                                       ep = growstackstr();
-                               *ep++ = (char *) vp->text;
-                       }
-       } while (++vpp < vartab + VTABSIZE);
-       if (ep == stackstrend())
-               ep = growstackstr();
-       if (end)
-               *end = ep;
-       *ep++ = NULL;
-       return grabstackstr(ep);
+       out1fmt("\nBuilt-in commands:\n-------------------\n");
+       for (col = 0, i = 0; i < NUMBUILTINS; i++) {
+               col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '),
+                                         builtincmd[i].name + 1);
+               if (col > 60) {
+                       out1fmt("\n");
+                       col = 0;
+               }
+       }
+#if ENABLE_FEATURE_SH_STANDALONE_SHELL
+       for (i = 0; i < NUM_APPLETS; i++) {
+               col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '), applets[i].name);
+               if (col > 60) {
+                       out1fmt("\n");
+                       col = 0;
+               }
+       }
+#endif
+       out1fmt("\n\n");
+       return EXIT_SUCCESS;
 }
-
+#endif /* FEATURE_SH_EXTRA_QUIET */
 
 /*
- * POSIX requires that 'set' (but not export or readonly) output the
- * variables in lexicographic order - by the locale's collating order (sigh).
- * Maybe we could keep them in an ordered balanced binary tree
- * instead of hashed lists.
- * For now just roll 'em through qsort for printing...
+ * Called to exit the shell.
  */
-static int
-showvars(const char *sep_prefix, int on, int off)
+static void
+exitshell(void)
 {
-       const char *sep;
-       char **ep, **epend;
-
-       ep = listvars(on, off, &epend);
-       qsort(ep, epend - ep, sizeof(char *), vpcmp);
-
-       sep = *sep_prefix ? spcstr : sep_prefix;
-
-       for (; ep < epend; ep++) {
-               const char *p;
-               const char *q;
-
-               p = strchrnul(*ep, '=');
-               q = nullstr;
-               if (*p)
-                       q = single_quote(++p);
+       struct jmploc loc;
+       char *p;
+       int status;
 
-               out1fmt("%s%s%.*s%s\n", sep_prefix, sep, (int)(p - *ep), *ep, q);
+       status = exitstatus;
+       TRACE(("pid %d, exitshell(%d)\n", getpid(), status));
+       if (setjmp(loc.loc)) {
+               if (exception == EXEXIT)
+/* dash bug: it just does _exit(exitstatus) here
+ * but we have to do setjobctl(0) first!
+ * (bug is still not fixed in dash-0.5.3 - if you run dash
+ * under Midnight Commander, on exit from dash MC is backgrounded) */
+                       status = exitstatus;
+               goto out;
        }
-
-       return 0;
+       exception_handler = &loc;
+       p = trap[0];
+       if (p) {
+               trap[0] = NULL;
+               evalstring(p, 0);
+       }
+       flush_stdout_stderr();
+ out:
+       setjobctl(0);
+       _exit(status);
+       /* NOTREACHED */
 }
 
 
@@ -11780,39 +11813,6 @@ localcmd(int argc, char **argv)
 }
 
 
-/*
- * Called after a function returns.
- * Interrupts must be off.
- */
-static void
-poplocalvars(void)
-{
-       struct localvar *lvp;
-       struct var *vp;
-
-       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);
-       }
-}
-
-
 /*
  * The unset builtin command.  We unset the function before we unset the
  * variable to allow a function to be unset when there is a readonly variable
@@ -11844,104 +11844,6 @@ unsetcmd(int argc, char **argv)
 }
 
 
-/*
- * Unset the specified variable.
- */
-static int
-unsetvar(const char *s)
-{
-       struct var **vpp;
-       struct var *vp;
-       int retval;
-
-       vpp = findvar(hashvar(s), s);
-       vp = *vpp;
-       retval = 2;
-       if (vp) {
-               int flags = vp->flags;
-
-               retval = 1;
-               if (flags & VREADONLY)
-                       goto out;
-#ifdef DYNAMIC_VAR
-               vp->flags &= ~VDYNAMIC;
-#endif
-               if (flags & VUNSET)
-                       goto ok;
-               if ((flags & VSTRFIXED) == 0) {
-                       INT_OFF;
-                       if ((flags & (VTEXTFIXED|VSTACK)) == 0)
-                               free((char*)vp->text);
-                       *vpp = vp->next;
-                       free(vp);
-                       INT_ON;
-               } else {
-                       setvar(s, 0, 0);
-                       vp->flags &= ~VEXPORT;
-               }
- ok:
-               retval = 0;
-       }
- out:
-       return retval;
-}
-
-
-/*
- * Find the appropriate entry in the hash table from the name.
- */
-static struct var **
-hashvar(const char *p)
-{
-       unsigned int hashval;
-
-       hashval = ((unsigned char) *p) << 4;
-       while (*p && *p != '=')
-               hashval += (unsigned char) *p++;
-       return &vartab[hashval % VTABSIZE];
-}
-
-
-/*
- * Compares two strings up to the first = or '\0'.  The first
- * string must be terminated by '='; the second may be terminated by
- * either '=' or '\0'.
- */
-static int
-varcmp(const char *p, const char *q)
-{
-       int c, d;
-
-       while ((c = *p) == (d = *q)) {
-               if (!c || c == '=')
-                       goto out;
-               p++;
-               q++;
-       }
-       if (c == '=')
-               c = 0;
-       if (d == '=')
-               d = 0;
- out:
-       return c - d;
-}
-
-static int
-vpcmp(const void *a, const void *b)
-{
-       return varcmp(*(const char **)a, *(const char **)b);
-}
-
-static struct var **
-findvar(struct var **vpp, const char *name)
-{
-       for (; *vpp; vpp = &(*vpp)->next) {
-               if (varequal((*vpp)->text, name)) {
-                       break;
-               }
-       }
-       return vpp;
-}
 /*      setmode.c      */
 
 #include <sys/times.h>
@@ -12274,7 +12176,7 @@ static int umaskcmd(int argc, char **argv)
                        out1fmt("%.4o\n", mask);
                }
        } else {
-               if (is_digit((unsigned char) *ap)) {
+               if (isdigit((unsigned char) *ap)) {
                        mask = 0;
                        do {
                                if (*ap >= '8' || *ap < '0')
@@ -13012,7 +12914,7 @@ static arith_t arith(const char *expr, int *perrcode)
                        lasttok = TOK_NUM;
                        continue;
                }
-               if (is_digit(arithval)) {
+               if (isdigit(arithval)) {
                        numstackptr->var = NULL;
 #if ENABLE_ASH_MATH_SUPPORT_64
                        numstackptr->val = strtoll(expr, (char **) &expr, 0);