From aa74445514a71aebfa5d9b22c5ba1c47635b4dba Mon Sep 17 00:00:00 2001
From: Denis Vlasenko <vda.linux@googlemail.com>
Date: Fri, 23 Feb 2007 01:04:22 +0000
Subject: ash: cleanup part 2

---
 shell/ash.c | 7492 +++++++++++++++++++++++++++++------------------------------
 1 file changed, 3697 insertions(+), 3795 deletions(-)

(limited to 'shell')

diff --git a/shell/ash.c b/shell/ash.c
index 379e8ab7f..731b07996 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -65,6 +65,13 @@
 #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);
-- 
cgit v1.2.3-55-g6feb