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

index 3107181a61101493bf4251e6ce157aad2a660242..7ffecf43d055f74f46e34f4b5ad1924e5f88033e 100644 (file)
 #error "Do not even bother, ash will not run on uClinux"
 #endif
 
-#if DEBUG
-#define TRACE(param)    trace param
-#define TRACEV(param)   tracev param
-#else
-#define TRACE(param)
-#define TRACEV(param)
-#endif
-
-#ifdef __GLIBC__
-/* glibc sucks */
-static int *dash_errno;
-#undef errno
-#define errno (*dash_errno)
-#endif
-
-
-#if ENABLE_ASH_ALIAS
-#define ALIASINUSE 1
-#define ALIASDEAD  2
-struct alias {
-       struct alias *next;
-       char *name;
-       char *val;
-       int flag;
-};
-static int aliascmd(int, char **);
-static int unaliascmd(int, char **);
-static void printalias(const struct alias *);
-#endif
-
 
 /* ============ Shell options */
 
@@ -148,6 +118,13 @@ static char optlist[NOPTS];
 
 /* ============ Misc data */
 
+#ifdef __GLIBC__
+/* glibc sucks */
+static int *dash_errno;
+#undef errno
+#define errno (*dash_errno)
+#endif
+
 static char nullstr[1];                /* zero length string */
 static const char homestr[] = "HOME";
 static const char snlfmt[] = "%s\n";
@@ -256,14 +233,16 @@ raise_interrupt(void)
 }
 
 #if ENABLE_ASH_OPTIMIZE_FOR_SIZE
-static void int_on(void)
+static void
+int_on(void)
 {
        if (--suppressint == 0 && intpending) {
                raise_interrupt();
        }
 }
 #define INT_ON int_on()
-static void force_int_on(void)
+static void
+force_int_on(void)
 {
        suppressint = 0;
        if (intpending)
@@ -387,1794 +366,2135 @@ out2str(const char *p)
 }
 
 
-/* ============ Parser data
- *
- * ash_vmsg() needs parsefile->fd, hence parsefile definition is moved up.
- */
+/* ============ Parsing structures */
+#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 strlist {
-       struct strlist *next;
+union node;
+
+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 strpush {
-       struct strpush *prev;   /* preceding string on stack */
-       char *prevstring;
-       int prevnleft;
-#if ENABLE_ASH_ALIAS
-       struct alias *ap;       /* if push was associated with an alias */
-#endif
-       char *string;           /* remember the string since it may change */
+struct nfile {
+       int type;
+       union node *next;
+       int fd;
+       union node *fname;
+       char *expfname;
 };
 
-struct parsefile {
-       struct parsefile *prev; /* preceding file on stack */
-       int linno;              /* current line */
-       int fd;                 /* file descriptor (or -1 if string) */
-       int nleft;              /* number of chars left in this line */
-       int lleft;              /* number of chars left in this buffer */
-       char *nextc;            /* next char in buffer */
-       char *buf;              /* input buffer */
-       struct strpush *strpush; /* for pushing strings at this level */
-       struct strpush basestrpush; /* so pushing one is fast */
+struct ndup {
+       int type;
+       union node *next;
+       int fd;
+       int dupfd;
+       union node *vname;
 };
 
-static struct parsefile basepf;         /* top level input file */
-static struct parsefile *parsefile = &basepf;  /* current input file */
-static int startlinno;                 /* line # where last token started */
-static char *commandname;              /* currently executing command */
-static struct strlist *cmdenviron;     /* environment for builtin command */
-static int exitstatus;                 /* exit status of last command */
-static int back_exitstatus;            /* exit status of backquoted command */
+struct nhere {
+       int type;
+       union node *next;
+       int fd;
+       union node *doc;
+};
 
+struct nnot {
+       int type;
+       union node *com;
+};
 
-/* ============ Message printing */
+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;
+};
 
-static void
-ash_vmsg(const char *msg, va_list ap)
-{
-       fprintf(stderr, "%s: ", arg0);
-       if (commandname) {
-               const char *fmt = (!iflag || parsefile->fd) ?
-                                       "%s: %d: " : "%s: ";
-               fprintf(stderr, fmt, commandname, startlinno);
-       }
-       vfprintf(stderr, msg, ap);
-       outcslow('\n', stderr);
-}
+struct nodelist {
+       struct nodelist *next;
+       union node *n;
+};
+
+struct funcnode {
+       int count;
+       union node n;
+};
+
+
+/* ============ Debugging output */
 
-/*
- * Exverror is called to raise the error exception.  If the second argument
- * is not NULL then error prints an error message using printf style
- * formatting.  It then raises the error exception.
- */
-static void ash_vmsg_and_raise(int, const char *, va_list) ATTRIBUTE_NORETURN;
-static void
-ash_vmsg_and_raise(int cond, const char *msg, va_list ap)
-{
 #if DEBUG
-       if (msg) {
-               TRACE(("ash_vmsg_and_raise(%d, \"", cond));
-               TRACEV((msg, ap));
-               TRACE(("\") pid=%d\n", getpid()));
-       } else
-               TRACE(("ash_vmsg_and_raise(%d, NULL) pid=%d\n", cond, getpid()));
-       if (msg)
-#endif
-               ash_vmsg(msg, ap);
 
-       flush_stdout_stderr();
-       raise_exception(cond);
-       /* NOTREACHED */
-}
+static FILE *tracefile;
 
-static void ash_msg_and_raise_error(const char *, ...) ATTRIBUTE_NORETURN;
 static void
-ash_msg_and_raise_error(const char *msg, ...)
+trace_printf(const char *fmt, ...)
 {
-       va_list ap;
+       va_list va;
 
-       va_start(ap, msg);
-       ash_vmsg_and_raise(EXERROR, msg, ap);
-       /* NOTREACHED */
-       va_end(ap);
+       if (debug != 1)
+               return;
+       va_start(va, fmt);
+       vfprintf(tracefile, fmt, va);
+       va_end(va);
 }
 
-static void ash_msg_and_raise(int, const char *, ...) ATTRIBUTE_NORETURN;
 static void
-ash_msg_and_raise(int cond, const char *msg, ...)
+trace_vprintf(const char *fmt, va_list va)
 {
-       va_list ap;
+       if (debug != 1)
+               return;
+       vfprintf(tracefile, fmt, va);
+}
 
-       va_start(ap, msg);
-       ash_vmsg_and_raise(cond, msg, ap);
-       /* NOTREACHED */
-       va_end(ap);
-}
-
-/*
- * error/warning routines for external builtins
- */
 static void
-ash_msg(const char *fmt, ...)
-{
-       va_list ap;
-
-       va_start(ap, fmt);
-       ash_vmsg(fmt, ap);
-       va_end(ap);
-}
-
-/*
- * Return a string describing an error.  The returned string may be a
- * pointer to a static buffer that will be overwritten on the next call.
- * Action describes the operation that got the error.
- */
-static const char *
-errmsg(int e, const char *em)
-{
-       if (e == ENOENT || e == ENOTDIR) {
-               return em;
-       }
-       return strerror(e);
-}
-
-
-/* ============ Memory allocation */
-
-/*
- * It appears that grabstackstr() will barf with such alignments
- * because stalloc() will return a string allocated in a new stackblock.
- */
-#define SHELL_ALIGN(nbytes) (((nbytes) + SHELL_SIZE) & ~SHELL_SIZE)
-enum {
-       /* Most machines require the value returned from malloc to be aligned
-        * in some way.  The following macro will get this right
-        * on many machines.  */
-       SHELL_SIZE = sizeof(union {int i; char *cp; double d; }) - 1,
-       /* Minimum size of a block */
-       MINSIZE  = SHELL_ALIGN(504),
-};
-
-struct stack_block {
-       struct stack_block *prev;
-       char space[MINSIZE];
-};
-
-struct stackmark {
-       struct stack_block *stackp;
-       char *stacknxt;
-       size_t stacknleft;
-       struct stackmark *marknext;
-};
-
-static struct stack_block stackbase;
-static struct stack_block *stackp = &stackbase;
-static struct stackmark *markp;
-static char *stacknxt = stackbase.space;
-static size_t stacknleft = MINSIZE;
-static char *sstrend = stackbase.space + MINSIZE;
-static int herefd = -1;
-
-#define stackblock() ((void *)stacknxt)
-#define stackblocksize() stacknleft
-
-static void *
-ckrealloc(void * p, size_t nbytes)
-{
-       p = realloc(p, nbytes);
-       if (!p)
-               ash_msg_and_raise_error(bb_msg_memory_exhausted);
-       return p;
-}
-
-static void *
-ckmalloc(size_t nbytes)
-{
-       return ckrealloc(NULL, nbytes);
-}
-
-/*
- * Make a copy of a string in safe storage.
- */
-static char *
-ckstrdup(const char *s)
+trace_puts(const char *s)
 {
-       char *p = strdup(s);
-       if (!p)
-               ash_msg_and_raise_error(bb_msg_memory_exhausted);
-       return p;
+       if (debug != 1)
+               return;
+       fputs(s, tracefile);
 }
 
-/*
- * Parse trees for commands are allocated in lifo order, so we use a stack
- * to make this more efficient, and also to avoid all sorts of exception
- * handling code to handle interrupts in the middle of a parse.
- *
- * The size 504 was chosen because the Ultrix malloc handles that size
- * well.
- */
-static void *
-stalloc(size_t nbytes)
+static void
+trace_puts_quoted(char *s)
 {
        char *p;
-       size_t aligned;
-
-       aligned = SHELL_ALIGN(nbytes);
-       if (aligned > stacknleft) {
-               size_t len;
-               size_t blocksize;
-               struct stack_block *sp;
+       char c;
 
-               blocksize = aligned;
-               if (blocksize < MINSIZE)
-                       blocksize = MINSIZE;
-               len = sizeof(struct stack_block) - MINSIZE + blocksize;
-               if (len < blocksize)
-                       ash_msg_and_raise_error(bb_msg_memory_exhausted);
-               INT_OFF;
-               sp = ckmalloc(len);
-               sp->prev = stackp;
-               stacknxt = sp->space;
-               stacknleft = blocksize;
-               sstrend = stacknxt + blocksize;
-               stackp = sp;
-               INT_ON;
+       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;
+               }
        }
-       p = stacknxt;
-       stacknxt += aligned;
-       stacknleft -= aligned;
-       return p;
+       putc('"', tracefile);
 }
 
 static void
-stunalloc(void *p)
+trace_puts_args(char **ap)
 {
-#if DEBUG
-       if (!p || (stacknxt < (char *)p) || ((char *)p < stackp->space)) {
-               write(2, "stunalloc\n", 10);
-               abort();
+       if (debug != 1)
+               return;
+       if (!*ap)
+               return;
+       while (1) {
+               trace_puts_quoted(*ap);
+               if (!*++ap) {
+                       putc('\n', tracefile);
+                       break;
+               }
+               putc(' ', tracefile);
        }
-#endif
-       stacknleft += stacknxt - (char *)p;
-       stacknxt = p;
 }
 
-/*
- * Like strdup but works with the ash stack.
- */
-static char *
-ststrdup(const char *p)
+static void
+opentrace(void)
 {
-       size_t len = strlen(p) + 1;
-       return memcpy(stalloc(len), p, len);
+       char s[100];
+#ifdef O_APPEND
+       int flags;
+#endif
+
+       if (debug != 1) {
+               if (tracefile)
+                       fflush(tracefile);
+               /* leave open because libedit might be using it */
+               return;
+       }
+       strcpy(s, "./trace");
+       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;
+               }
+       }
+#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);
 }
 
 static void
-setstackmark(struct stackmark *mark)
+indent(int amount, char *pfx, FILE *fp)
 {
-       mark->stackp = stackp;
-       mark->stacknxt = stacknxt;
-       mark->stacknleft = stacknleft;
-       mark->marknext = markp;
-       markp = mark;
+       int i;
+
+       for (i = 0; i < amount; i++) {
+               if (pfx && i == amount - 1)
+                       fputs(pfx, fp);
+               putc('\t', fp);
+       }
 }
 
+/* little circular references here... */
+static void shtree(union node *n, int ind, char *pfx, FILE *fp);
+
 static void
-popstackmark(struct stackmark *mark)
+sharg(union node *arg, FILE *fp)
 {
-       struct stack_block *sp;
+       char *p;
+       struct nodelist *bqlist;
+       int subtype;
 
-       INT_OFF;
-       markp = mark->marknext;
-       while (stackp != mark->stackp) {
-               sp = stackp;
-               stackp = sp->prev;
-               free(sp);
+       if (arg->type != NARG) {
+               out1fmt("<node type %d>\n", arg->type);
+               abort();
        }
-       stacknxt = mark->stacknxt;
-       stacknleft = mark->stacknleft;
-       sstrend = mark->stacknxt + mark->stacknleft;
-       INT_ON;
-}
-
-/*
- * When the parser reads in a string, it wants to stick the string on the
- * stack and only adjust the stack pointer when it knows how big the
- * string is.  Stackblock (defined in stack.h) returns a pointer to a block
- * of space on top of the stack and stackblocklen returns the length of
- * this block.  Growstackblock will grow this space by at least one byte,
- * possibly moving it (like realloc).  Grabstackblock actually allocates the
- * part of the block that has been used.
- */
-static void
-growstackblock(void)
-{
-       size_t newlen;
-
-       newlen = stacknleft * 2;
-       if (newlen < stacknleft)
-               ash_msg_and_raise_error(bb_msg_memory_exhausted);
-       if (newlen < 128)
-               newlen += 128;
+       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);
 
-       if (stacknxt == stackp->space && stackp != &stackbase) {
-               struct stack_block *oldstackp;
-               struct stackmark *xmark;
-               struct stack_block *sp;
-               struct stack_block *prevstackp;
-               size_t grosslen;
+                       while (*p != '=')
+                               putc(*p++, fp);
 
-               INT_OFF;
-               oldstackp = stackp;
-               sp = stackp;
-               prevstackp = sp->prev;
-               grosslen = newlen + sizeof(struct stack_block) - MINSIZE;
-               sp = ckrealloc(sp, grosslen);
-               sp->prev = prevstackp;
-               stackp = sp;
-               stacknxt = sp->space;
-               stacknleft = newlen;
-               sstrend = sp->space + newlen;
+                       if (subtype & VSNUL)
+                               putc(':', fp);
 
-               /*
-                * Stack marks pointing to the start of the old block
-                * must be relocated to point to the new block
-                */
-               xmark = markp;
-               while (xmark != NULL && xmark->stackp == oldstackp) {
-                       xmark->stackp = stackp;
-                       xmark->stacknxt = stacknxt;
-                       xmark->stacknleft = stacknleft;
-                       xmark = xmark->marknext;
+                       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;
                }
-               INT_ON;
-       } else {
-               char *oldspace = stacknxt;
-               int oldlen = stacknleft;
-               char *p = stalloc(newlen);
-
-               /* free the space we just allocated */
-               stacknxt = memcpy(p, oldspace, oldlen);
-               stacknleft += newlen;
        }
 }
 
 static void
-grabstackblock(size_t len)
+shcmd(union node *cmd, FILE *fp)
 {
-       len = SHELL_ALIGN(len);
-       stacknxt += len;
-       stacknleft -= len;
-}
+       union node *np;
+       int first;
+       const char *s;
+       int dftfd;
 
-/*
- * The following routines are somewhat easier to use than the above.
- * The user declares a variable of type STACKSTR, which may be declared
- * to be a register.  The macro STARTSTACKSTR initializes things.  Then
- * the user uses the macro STPUTC to add characters to the string.  In
- * effect, STPUTC(c, p) is the same as *p++ = c except that the stack is
- * grown as necessary.  When the user is done, she can just leave the
- * string there and refer to it using stackblock().  Or she can allocate
- * the space for it using grabstackstr().  If it is necessary to allow
- * someone else to use the stack temporarily and then continue to grow
- * the string, the user should use grabstack to allocate the space, and
- * then call ungrabstr(p) to return to the previous mode of operation.
- *
- * USTPUTC is like STPUTC except that it doesn't check for overflow.
- * CHECKSTACKSPACE can be called before USTPUTC to ensure that there
- * is space for at least one character.
- */
-static void *
-growstackstr(void)
-{
-       size_t len = stackblocksize();
-       if (herefd >= 0 && len >= 1024) {
-               full_write(herefd, stackblock(), len);
-               return stackblock();
+       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;
        }
-       growstackblock();
-       return stackblock() + len;
 }
 
-/*
- * Called from CHECKSTRSPACE.
- */
-static char *
-makestrspace(size_t newlen, char *p)
+static void
+shtree(union node *n, int ind, char *pfx, FILE *fp)
 {
-       size_t len = p - stacknxt;
-       size_t size = stackblocksize();
+       struct nodelist *lp;
+       const char *s;
 
-       for (;;) {
-               size_t nleft;
+       if (n == NULL)
+               return;
 
-               size = stackblocksize();
-               nleft = size - len;
-               if (nleft >= newlen)
-                       break;
-               growstackblock();
+       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;
        }
-       return stackblock() + len;
 }
 
-static char *
-stack_nputstr(const char *s, size_t n, char *p)
+static void
+showtree(union node *n)
 {
-       p = makestrspace(n, p);
-       p = memcpy(p, s, n) + n;
-       return p;
+       trace_puts("showtree called\n");
+       shtree(n, 1, NULL, stdout);
 }
 
-static char *
-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;
-}
+#define TRACE(param)    trace_printf param
+#define TRACEV(param)   trace_vprintf param
 
-#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))
+#else
 
-#define grabstackstr(p)         stalloc((char *)(p) - (char *)stackblock())
-#define ungrabstackstr(s, p)    stunalloc((s))
-#define stackstrend()           ((void *)sstrend)
+#define TRACE(param)
+#define TRACEV(param)
 
+#endif /* DEBUG */
 
-/* ============ String helpers */
 
-/*
- * prefix -- see if pfx is a prefix of string.
+/* ============ Parser data
+ *
+ * ash_vmsg() needs parsefile->fd, hence parsefile definition is moved up.
  */
-static char *
-prefix(const char *string, const char *pfx)
+
+struct strlist {
+       struct strlist *next;
+       char *text;
+};
+
+#if ENABLE_ASH_ALIAS
+#define ALIASINUSE 1
+#define ALIASDEAD  2
+struct alias;
+static int aliascmd(int, char **);
+static int unaliascmd(int, char **);
+static void printalias(const struct alias *);
+#endif
+
+struct strpush {
+       struct strpush *prev;   /* preceding string on stack */
+       char *prevstring;
+       int prevnleft;
+#if ENABLE_ASH_ALIAS
+       struct alias *ap;       /* if push was associated with an alias */
+#endif
+       char *string;           /* remember the string since it may change */
+};
+
+struct parsefile {
+       struct parsefile *prev; /* preceding file on stack */
+       int linno;              /* current line */
+       int fd;                 /* file descriptor (or -1 if string) */
+       int nleft;              /* number of chars left in this line */
+       int lleft;              /* number of chars left in this buffer */
+       char *nextc;            /* next char in buffer */
+       char *buf;              /* input buffer */
+       struct strpush *strpush; /* for pushing strings at this level */
+       struct strpush basestrpush; /* so pushing one is fast */
+};
+
+static struct parsefile basepf;         /* top level input file */
+static struct parsefile *parsefile = &basepf;  /* current input file */
+static int startlinno;                 /* line # where last token started */
+static char *commandname;              /* currently executing command */
+static struct strlist *cmdenviron;     /* environment for builtin command */
+static int exitstatus;                 /* exit status of last command */
+static int back_exitstatus;            /* exit status of backquoted command */
+
+
+/* ============ Message printing */
+
+static void
+ash_vmsg(const char *msg, va_list ap)
 {
-       while (*pfx) {
-               if (*pfx++ != *string++)
-                       return 0;
+       fprintf(stderr, "%s: ", arg0);
+       if (commandname) {
+               const char *fmt = (!iflag || parsefile->fd) ?
+                                       "%s: %d: " : "%s: ";
+               fprintf(stderr, fmt, commandname, startlinno);
        }
-       return (char *) string;
+       vfprintf(stderr, msg, ap);
+       outcslow('\n', stderr);
 }
 
 /*
- * Check for a valid number.  This should be elsewhere.
+ * Exverror is called to raise the error exception.  If the second argument
+ * is not NULL then error prints an error message using printf style
+ * formatting.  It then raises the error exception.
  */
-static int
-is_number(const char *p)
+static void ash_vmsg_and_raise(int, const char *, va_list) ATTRIBUTE_NORETURN;
+static void
+ash_vmsg_and_raise(int cond, const char *msg, va_list ap)
 {
-       do {
-               if (!isdigit(*p))
-                       return 0;
-       } while (*++p != '\0');
-       return 1;
+#if DEBUG
+       if (msg) {
+               TRACE(("ash_vmsg_and_raise(%d, \"", cond));
+               TRACEV((msg, ap));
+               TRACE(("\") pid=%d\n", getpid()));
+       } else
+               TRACE(("ash_vmsg_and_raise(%d, NULL) pid=%d\n", cond, getpid()));
+       if (msg)
+#endif
+               ash_vmsg(msg, ap);
+
+       flush_stdout_stderr();
+       raise_exception(cond);
+       /* NOTREACHED */
 }
 
-/*
- * Convert a string of digits to an integer, printing an error message on
- * failure.
- */
-static int
-number(const char *s)
+static void ash_msg_and_raise_error(const char *, ...) ATTRIBUTE_NORETURN;
+static void
+ash_msg_and_raise_error(const char *msg, ...)
 {
-       if (!is_number(s))
-               ash_msg_and_raise_error(illnum, s);
-       return atoi(s);
+       va_list ap;
+
+       va_start(ap, msg);
+       ash_vmsg_and_raise(EXERROR, msg, ap);
+       /* NOTREACHED */
+       va_end(ap);
 }
 
-/*
- * 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)
+static void ash_msg_and_raise(int, const char *, ...) ATTRIBUTE_NORETURN;
+static void
+ash_msg_and_raise(int cond, const char *msg, ...)
 {
-       char *p;
+       va_list ap;
 
-       STARTSTACKSTR(p);
+       va_start(ap, msg);
+       ash_vmsg_and_raise(cond, msg, ap);
+       /* NOTREACHED */
+       va_end(ap);
+}
 
-       do {
-               char *q;
-               size_t len;
+/*
+ * error/warning routines for external builtins
+ */
+static void
+ash_msg(const char *fmt, ...)
+{
+       va_list ap;
 
-               len = strchrnul(s, '\'') - s;
+       va_start(ap, fmt);
+       ash_vmsg(fmt, ap);
+       va_end(ap);
+}
 
-               q = p = makestrspace(len + 3, p);
+/*
+ * Return a string describing an error.  The returned string may be a
+ * pointer to a static buffer that will be overwritten on the next call.
+ * Action describes the operation that got the error.
+ */
+static const char *
+errmsg(int e, const char *em)
+{
+       if (e == ENOENT || e == ENOTDIR) {
+               return em;
+       }
+       return strerror(e);
+}
 
-               *q++ = '\'';
-               q = memcpy(q, s, len) + len;
-               *q++ = '\'';
-               s += len;
 
-               STADJUST(q - p, p);
+/* ============ Memory allocation */
 
-               len = strspn(s, "'");
-               if (!len)
-                       break;
+/*
+ * It appears that grabstackstr() will barf with such alignments
+ * because stalloc() will return a string allocated in a new stackblock.
+ */
+#define SHELL_ALIGN(nbytes) (((nbytes) + SHELL_SIZE) & ~SHELL_SIZE)
+enum {
+       /* Most machines require the value returned from malloc to be aligned
+        * in some way.  The following macro will get this right
+        * on many machines.  */
+       SHELL_SIZE = sizeof(union {int i; char *cp; double d; }) - 1,
+       /* Minimum size of a block */
+       MINSIZE  = SHELL_ALIGN(504),
+};
 
-               q = p = makestrspace(len + 3, p);
+struct stack_block {
+       struct stack_block *prev;
+       char space[MINSIZE];
+};
 
-               *q++ = '"';
-               q = memcpy(q, s, len) + len;
-               *q++ = '"';
-               s += len;
+struct stackmark {
+       struct stack_block *stackp;
+       char *stacknxt;
+       size_t stacknleft;
+       struct stackmark *marknext;
+};
 
-               STADJUST(q - p, p);
-       } while (*s);
+static struct stack_block stackbase;
+static struct stack_block *stackp = &stackbase;
+static struct stackmark *markp;
+static char *stacknxt = stackbase.space;
+static size_t stacknleft = MINSIZE;
+static char *sstrend = stackbase.space + MINSIZE;
+static int herefd = -1;
 
-       USTPUTC(0, p);
+#define stackblock() ((void *)stacknxt)
+#define stackblocksize() stacknleft
 
-       return stackblock();
+static void *
+ckrealloc(void * p, size_t nbytes)
+{
+       p = realloc(p, nbytes);
+       if (!p)
+               ash_msg_and_raise_error(bb_msg_memory_exhausted);
+       return p;
 }
 
+static void *
+ckmalloc(size_t nbytes)
+{
+       return ckrealloc(NULL, nbytes);
+}
 
-/* ============ ... */
-
-static char **argptr;                  /* argument list for builtin commands */
-static char *optionarg;                /* set by nextopt (like getopt) */
-static char *optptr;                   /* used by nextopt */
+/*
+ * Make a copy of a string in safe storage.
+ */
+static char *
+ckstrdup(const char *s)
+{
+       char *p = strdup(s);
+       if (!p)
+               ash_msg_and_raise_error(bb_msg_memory_exhausted);
+       return p;
+}
 
 /*
- * 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.
+ * Parse trees for commands are allocated in lifo order, so we use a stack
+ * to make this more efficient, and also to avoid all sorts of exception
+ * handling code to handle interrupts in the middle of a parse.
  *
- * 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.
+ * The size 504 was chosen because the Ultrix malloc handles that size
+ * well.
  */
-static int
-nextopt(const char *optstring)
+static void *
+stalloc(size_t nbytes)
 {
        char *p;
-       const char *q;
-       char c;
+       size_t aligned;
 
-       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++;
+       aligned = SHELL_ALIGN(nbytes);
+       if (aligned > stacknleft) {
+               size_t len;
+               size_t blocksize;
+               struct stack_block *sp;
+
+               blocksize = aligned;
+               if (blocksize < MINSIZE)
+                       blocksize = MINSIZE;
+               len = sizeof(struct stack_block) - MINSIZE + blocksize;
+               if (len < blocksize)
+                       ash_msg_and_raise_error(bb_msg_memory_exhausted);
+               INT_OFF;
+               sp = ckmalloc(len);
+               sp->prev = stackp;
+               stacknxt = sp->space;
+               stacknleft = blocksize;
+               sstrend = stacknxt + blocksize;
+               stackp = sp;
+               INT_ON;
        }
-       if (*++q == ':') {
-               if (*p == '\0' && (p = *argptr++) == NULL)
-                       ash_msg_and_raise_error("No arg for -%c option", c);
-               optionarg = p;
-               p = NULL;
+       p = stacknxt;
+       stacknxt += aligned;
+       stacknleft -= aligned;
+       return p;
+}
+
+static void
+stunalloc(void *p)
+{
+#if DEBUG
+       if (!p || (stacknxt < (char *)p) || ((char *)p < stackp->space)) {
+               write(2, "stunalloc\n", 10);
+               abort();
        }
-       optptr = p;
-       return c;
+#endif
+       stacknleft += stacknxt - (char *)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);
+}
 
-/* ============ Variables */
+static void
+setstackmark(struct stackmark *mark)
+{
+       mark->stackp = stackp;
+       mark->stacknxt = stacknxt;
+       mark->stacknleft = stacknleft;
+       mark->marknext = markp;
+       markp = mark;
+}
 
-/* 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
+static void
+popstackmark(struct stackmark *mark)
+{
+       struct stack_block *sp;
 
-#if ENABLE_LOCALE_SUPPORT
-static void change_lc_all(const char *value);
-static void change_lc_ctype(const char *value);
-#endif
+       INT_OFF;
+       markp = mark->marknext;
+       while (stackp != mark->stackp) {
+               sp = stackp;
+               stackp = sp->prev;
+               free(sp);
+       }
+       stacknxt = mark->stacknxt;
+       stacknleft = mark->stacknleft;
+       sstrend = mark->stacknxt + mark->stacknleft;
+       INT_ON;
+}
 
-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
+/*
+ * When the parser reads in a string, it wants to stick the string on the
+ * stack and only adjust the stack pointer when it knows how big the
+ * string is.  Stackblock (defined in stack.h) returns a pointer to a block
+ * of space on top of the stack and stackblocklen returns the length of
+ * this block.  Growstackblock will grow this space by at least one byte,
+ * possibly moving it (like realloc).  Grabstackblock actually allocates the
+ * part of the block that has been used.
+ */
+static void
+growstackblock(void)
+{
+       size_t newlen;
 
-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 */
-};
+       newlen = stacknleft * 2;
+       if (newlen < stacknleft)
+               ash_msg_and_raise_error(bb_msg_memory_exhausted);
+       if (newlen < 128)
+               newlen += 128;
 
-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 */
-};
+       if (stacknxt == stackp->space && stackp != &stackbase) {
+               struct stack_block *oldstackp;
+               struct stackmark *xmark;
+               struct stack_block *sp;
+               struct stack_block *prevstackp;
+               size_t grosslen;
 
-/* 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
+               INT_OFF;
+               oldstackp = stackp;
+               sp = stackp;
+               prevstackp = sp->prev;
+               grosslen = newlen + sizeof(struct stack_block) - MINSIZE;
+               sp = ckrealloc(sp, grosslen);
+               sp->prev = prevstackp;
+               stackp = sp;
+               stacknxt = sp->space;
+               stacknleft = newlen;
+               sstrend = sp->space + newlen;
 
-static struct var varinit[] = {
-#ifdef IFS_BROKEN
-       { 0,    VSTRFIXED|VTEXTFIXED,           defifsvar,      0 },
-#else
-       { 0,    VSTRFIXED|VTEXTFIXED|VUNSET,    "IFS\0",        0 },
-#endif
+               /*
+                * Stack marks pointing to the start of the old block
+                * must be relocated to point to the new block
+                */
+               xmark = markp;
+               while (xmark != NULL && xmark->stackp == oldstackp) {
+                       xmark->stackp = stackp;
+                       xmark->stacknxt = stacknxt;
+                       xmark->stacknleft = stacknleft;
+                       xmark = xmark->marknext;
+               }
+               INT_ON;
+       } else {
+               char *oldspace = stacknxt;
+               int oldlen = stacknleft;
+               char *p = stalloc(newlen);
 
-#if ENABLE_ASH_MAIL
-       { 0,    VSTRFIXED|VTEXTFIXED|VUNSET,    "MAIL\0",       changemail },
-       { 0,    VSTRFIXED|VTEXTFIXED|VUNSET,    "MAILPATH\0",   changemail },
-#endif
+               /* free the space we just allocated */
+               stacknxt = memcpy(p, oldspace, oldlen);
+               stacknleft += newlen;
+       }
+}
 
-       { 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)
+static void
+grabstackblock(size_t len)
+{
+       len = SHELL_ALIGN(len);
+       stacknxt += len;
+       stacknleft -= len;
+}
 
 /*
- * 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.
+ * The following routines are somewhat easier to use than the above.
+ * The user declares a variable of type STACKSTR, which may be declared
+ * to be a register.  The macro STARTSTACKSTR initializes things.  Then
+ * the user uses the macro STPUTC to add characters to the string.  In
+ * effect, STPUTC(c, p) is the same as *p++ = c except that the stack is
+ * grown as necessary.  When the user is done, she can just leave the
+ * string there and refer to it using stackblock().  Or she can allocate
+ * the space for it using grabstackstr().  If it is necessary to allow
+ * someone else to use the stack temporarily and then continue to grow
+ * the string, the user should use grabstack to allocate the space, and
+ * then call ungrabstr(p) to return to the previous mode of operation.
+ *
+ * USTPUTC is like STPUTC except that it doesn't check for overflow.
+ * CHECKSTACKSPACE can be called before USTPUTC to ensure that there
+ * is space for at least one character.
  */
-#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 struct var **hashvar(const char *);
-
-static int loopnest;            /* current loop nesting level */
+static void *
+growstackstr(void)
+{
+       size_t len = stackblocksize();
+       if (herefd >= 0 && len >= 1024) {
+               full_write(herefd, stackblock(), len);
+               return stackblock();
+       }
+       growstackblock();
+       return stackblock() + len;
+}
 
 /*
- * The parsefile structure pointed to by the global variable parsefile
- * contains information about the current file being read.
+ * Called from CHECKSTRSPACE.
  */
-struct redirtab {
-       struct redirtab *next;
-       int renamed[10];
-       int nullredirs;
-};
+static char *
+makestrspace(size_t newlen, char *p)
+{
+       size_t len = p - stacknxt;
+       size_t size = stackblocksize();
 
-static struct redirtab *redirlist;
-static int nullredirs;
+       for (;;) {
+               size_t nleft;
 
-extern char **environ;
+               size = stackblocksize();
+               nleft = size - len;
+               if (nleft >= newlen)
+                       break;
+               growstackblock();
+       }
+       return stackblock() + len;
+}
 
-static int preverrout_fd;   /* save fd2 before print debug if xflag is set. */
+static char *
+stack_nputstr(const char *s, size_t n, char *p)
+{
+       p = makestrspace(n, p);
+       p = memcpy(p, s, n) + n;
+       return p;
+}
 
-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 char *
+stack_putstr(const char *s, char *p)
+{
+       return stack_nputstr(s, strlen(s), p);
+}
 
-static struct shparam shellparam;      /* $@ current positional parameters */
+static char *
+_STPUTC(int c, char *p)
+{
+       if (p == sstrend)
+               p = growstackstr();
+       *p++ = c;
+       return p;
+}
 
-#define VTABSIZE 39
+#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))
 
-static struct var *vartab[VTABSIZE];
+#define grabstackstr(p)         stalloc((char *)(p) - (char *)stackblock())
+#define ungrabstackstr(s, p)    stunalloc((s))
+#define stackstrend()           ((void *)sstrend)
 
-#if ENABLE_ASH_GETOPTS
-static void
-getoptsreset(const char *value)
-{
-       shellparam.optind = number(value);
-       shellparam.optoff = -1;
-}
-#endif
 
-#define is_name(c)      ((c) == '_' || isalpha((unsigned char)(c)))
-#define is_in_name(c)   ((c) == '_' || isalnum((unsigned char)(c)))
+/* ============ String helpers */
 
 /*
- * Return of a legal variable name (a letter or underscore followed by zero or
- * more letters, underscores, and digits).
+ * prefix -- see if pfx is a prefix of string.
  */
 static char *
-endofname(const char *name)
+prefix(const char *string, const char *pfx)
 {
-       char *p;
-
-       p = (char *) name;
-       if (!is_name(*p))
-               return p;
-       while (*++p) {
-               if (!is_in_name(*p))
-                       break;
+       while (*pfx) {
+               if (*pfx++ != *string++)
+                       return 0;
        }
-       return p;
+       return (char *) string;
 }
 
 /*
- * 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'.
+ * Check for a valid number.  This should be elsewhere.
  */
 static int
-varcmp(const char *p, const char *q)
+is_number(const char *p)
 {
-       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;
+       do {
+               if (!isdigit(*p))
+                       return 0;
+       } while (*++p != '\0');
+       return 1;
 }
 
+/*
+ * Convert a string of digits to an integer, printing an error message on
+ * failure.
+ */
 static int
-varequal(const char *a, const char *b)
+number(const char *s)
 {
-       return !varcmp(a, b);
+       if (!is_number(s))
+               ash_msg_and_raise_error(illnum, s);
+       return atoi(s);
 }
 
 /*
- * Find the appropriate entry in the hash table from the name.
+ * Produce a possibly single quoted string suitable as input to the shell.
+ * The return string is allocated on the stack.
  */
-static struct var **
-hashvar(const char *p)
+static char *
+single_quote(const char *s)
 {
-       unsigned hashval;
+       char *p;
 
-       hashval = ((unsigned char) *p) << 4;
-       while (*p && *p != '=')
-               hashval += (unsigned char) *p++;
-       return &vartab[hashval % VTABSIZE];
-}
+       STARTSTACKSTR(p);
 
-static int
-vpcmp(const void *a, const void *b)
-{
-       return varcmp(*(const char **)a, *(const char **)b);
-}
+       do {
+               char *q;
+               size_t len;
 
-/*
- * This routine initializes the builtin variables.
- */
-static void
-initvar(void)
-{
-       struct var *vp;
-       struct var *end;
-       struct var **vpp;
+               len = strchrnul(s, '\'') - s;
 
-       /*
-        * 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;
-}
+               q = p = makestrspace(len + 3, p);
 
-/*
- * Find the value of a variable.  Returns NULL if not set.
- */
-static char *
-lookupvar(const char *name)
-{
-       struct var *v;
+               *q++ = '\'';
+               q = memcpy(q, s, len) + len;
+               *q++ = '\'';
+               s += len;
 
-       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;
-}
+               STADJUST(q - p, p);
 
-/*
- * Search the environment of a builtin command.
- */
-static char *
-bltinlookup(const char *name)
-{
-       struct strlist *sp;
+               len = strspn(s, "'");
+               if (!len)
+                       break;
 
-       for (sp = cmdenviron; sp; sp = sp->next) {
-               if (varequal(sp->text, name))
-                       return strchrnul(sp->text, '=') + 1;
-       }
-       return lookupvar(name);
-}
+               q = p = makestrspace(len + 3, p);
 
-/*
- * 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;
+               *q++ = '"';
+               q = memcpy(q, s, len) + len;
+               *q++ = '"';
+               s += len;
 
-       vpp = hashvar(s);
-       flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1));
-       vp = *findvar(vpp, s);
-       if (vp) {
-               if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) {
-                       const char *n;
+               STADJUST(q - p, p);
+       } while (*s);
 
-                       if (flags & VNOSAVE)
-                               free(s);
-                       n = vp->text;
-                       ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n);
-               }
+       USTPUTC(0, p);
 
-               if (flags & VNOSET)
-                       return;
+       return stackblock();
+}
 
-               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;
-}
+static char **argptr;                  /* argument list for builtin commands */
+static char *optionarg;                /* set by nextopt (like getopt) */
+static char *optptr;                   /* used by nextopt */
 
 /*
- * 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.
+ * 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 void
-setvar(const char *name, const char *val, int flags)
+static int
+nextopt(const char *optstring)
 {
-       char *p, *q;
-       size_t namelen;
-       char *nameeq;
-       size_t vallen;
+       char *p;
+       const char *q;
+       char c;
 
-       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);
+       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';
        }
-       INT_OFF;
-       nameeq = ckmalloc(namelen + vallen + 2);
-       p = memcpy(nameeq, name, namelen) + namelen;
-       if (val) {
-               *p++ = '=';
-               p = memcpy(p, val, vallen) + vallen;
+       c = *p++;
+       for (q = optstring; *q != c; ) {
+               if (*q == '\0')
+                       ash_msg_and_raise_error("Illegal option -%c", c);
+               if (*++q == ':')
+                       q++;
        }
-       *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
-setvarsafe(const char *name, const char *val, int flags)
-{
-       int err;
-       volatile int saveint;
-       struct jmploc *volatile savehandler = exception_handler;
-       struct jmploc jmploc;
-
-       SAVE_INT(saveint);
-       if (setjmp(jmploc.loc))
-               err = 1;
-       else {
-               exception_handler = &jmploc;
-               setvar(name, val, flags);
-               err = 0;
+       if (*++q == ':') {
+               if (*p == '\0' && (p = *argptr++) == NULL)
+                       ash_msg_and_raise_error("No arg for -%c option", c);
+               optionarg = p;
+               p = NULL;
        }
-       exception_handler = savehandler;
-       RESTORE_INT(saveint);
-       return err;
+       optptr = p;
+       return c;
 }
-#endif
 
-/*
- * 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;
+/* ============ Variables */
 
-               retval = 1;
-               if (flags & VREADONLY)
-                       goto out;
+/* 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
-               vp->flags &= ~VDYNAMIC;
+# define VDYNAMIC        0x200   /* dynamic variable */
+# else
+# define VDYNAMIC        0
 #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 ENABLE_LOCALE_SUPPORT
+static void change_lc_all(const char *value);
+static void change_lc_ctype(const char *value);
+#endif
 
-       if (!lp)
-               return;
-       INT_OFF;
-       do {
-               setvareq(lp->text, flags);
-               lp = lp->next;
-       } while (lp);
-       INT_ON;
-}
+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
+
+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 */
+};
+
+/* 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)
 
 /*
- * Generate a list of variables satisfying the given conditions.
+ * 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 char **
-listvars(int on, int off, char ***end)
-{
-       struct var **vpp;
-       struct var *vp;
-       char **ep;
-       int mask;
+#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)
 
-       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);
-}
+#define mpathset()      ((vmpath.flags & VUNSET) == 0)
 
+static struct var **hashvar(const char *);
 
-/* ============ 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 int loopnest;            /* current loop nesting level */
+
+/*
+ * The parsefile structure pointed to by the global variable parsefile
+ * contains information about the current file being read.
  */
-static const char *pathopt;     /* set by padvance */
+struct redirtab {
+       struct redirtab *next;
+       int renamed[10];
+       int nullredirs;
+};
 
-static char *
-padvance(const char **path, const char *name)
-{
-       const char *p;
-       char *q;
-       const char *start;
-       size_t len;
+static struct redirtab *redirlist;
+static int nullredirs;
 
-       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);
-}
+extern char **environ;
 
+static int preverrout_fd;   /* save fd2 before print debug if xflag is set. */
 
-/* ============ Prompt */
+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 int doprompt;                   /* if set, prompt the user */
-static int needprompt;                 /* true if interactive and at start of line */
+static struct shparam shellparam;      /* $@ current positional parameters */
 
-#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
+#define VTABSIZE 39
+
+static struct var *vartab[VTABSIZE];
+
+#if ENABLE_ASH_GETOPTS
 static void
-putprompt(const char *s)
+getoptsreset(const char *value)
 {
-       out2str(s);
+       shellparam.optind = number(value);
+       shellparam.optoff = -1;
 }
 #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
+#define is_name(c)      ((c) == '_' || isalpha((unsigned char)(c)))
+#define is_in_name(c)   ((c) == '_' || isalnum((unsigned char)(c)))
 
-static void
-setprompt(int whichprompt)
+/*
+ * 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)
 {
-       const char *prompt;
-#if ENABLE_ASH_EXPAND_PRMT
-       struct stackmark smark;
-#endif
-
-       needprompt = 0;
+       char *p;
 
-       switch (whichprompt) {
-       case 1:
-               prompt = ps1val();
-               break;
-       case 2:
-               prompt = ps2val();
-               break;
-       default:                        /* 0 */
-               prompt = nullstr;
+       p = (char *) name;
+       if (!is_name(*p))
+               return p;
+       while (*++p) {
+               if (!is_in_name(*p))
+                       break;
        }
-#if ENABLE_ASH_EXPAND_PRMT
-       setstackmark(&smark);
-       stalloc(stackblocksize());
-#endif
-       putprompt(expandstr(prompt));
-#if ENABLE_ASH_EXPAND_PRMT
-       popstackmark(&smark);
-#endif
+       return p;
 }
 
+/*
+ * 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;
 
-/* ============ The cd and pwd commands */
+       while ((c = *p) == (d = *q)) {
+               if (!c || c == '=')
+                       goto out;
+               p++;
+               q++;
+       }
+       if (c == '=')
+               c = '\0';
+       if (d == '=')
+               d = '\0';
+ out:
+       return c - d;
+}
 
-#define CD_PHYSICAL 1
-#define CD_PRINT 2
+static int
+varequal(const char *a, const char *b)
+{
+       return !varcmp(a, b);
+}
 
-static int docd(const char *, int);
+/*
+ * Find the appropriate entry in the hash table from the name.
+ */
+static struct var **
+hashvar(const char *p)
+{
+       unsigned hashval;
 
-static char *curdir = nullstr;          /* current working directory */
-static char *physdir = nullstr;         /* physical working directory */
+       hashval = ((unsigned char) *p) << 4;
+       while (*p && *p != '=')
+               hashval += (unsigned char) *p++;
+       return &vartab[hashval % VTABSIZE];
+}
 
 static int
-cdopt(void)
+vpcmp(const void *a, const void *b)
 {
-       int flags = 0;
-       int i, j;
-
-       j = 'L';
-       while ((i = nextopt("LP"))) {
-               if (i != j) {
-                       flags ^= CD_PHYSICAL;
-                       j = i;
-               }
-       }
-
-       return flags;
+       return varcmp(*(const char **)a, *(const char **)b);
 }
 
 /*
- * Update curdir (the name of the current directory) in response to a
- * cd command.
+ * This routine initializes the builtin variables.
  */
-static const char *
-updatepwd(const char *dir)
+static void
+initvar(void)
 {
-       char *new;
-       char *p;
-       char *cdcomppath;
-       const char *lim;
+       struct var *vp;
+       struct var *end;
+       struct var **vpp;
 
-       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);
+       /*
+        * 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;
                }
-               p = strtok(0, "/");
        }
-       if (new > lim)
-               STUNPUTC(new);
-       *new = 0;
-       return stackblock();
+       return vpp;
 }
 
 /*
- * Find out what the current directory is. If we already know the current
- * directory, this routine returns immediately.
+ * Find the value of a variable.  Returns NULL if not set.
  */
 static char *
-getpwd(void)
+lookupvar(const char *name)
 {
-       char *dir = getcwd(0, 0);
-       return dir ? dir : nullstr;
+       struct var *v;
+
+       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;
 }
 
-static void
-setpwd(const char *val, int setold)
+/*
+ * Search the environment of a builtin command.
+ */
+static char *
+bltinlookup(const char *name)
 {
-       char *oldcur, *dir;
-
-       oldcur = dir = curdir;
+       struct strlist *sp;
 
-       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);
+       for (sp = cmdenviron; sp; sp = sp->next) {
+               if (varequal(sp->text, name))
+                       return strchrnul(sp->text, '=') + 1;
        }
-       curdir = dir;
-       INT_ON;
-       setvar("PWD", dir, VEXPORT);
+       return lookupvar(name);
 }
 
-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.
+ * 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 int
-docd(const char *dest, int flags)
+static void
+setvareq(char *s, int flags)
 {
-       const char *dir = 0;
-       int err;
+       struct var *vp, **vpp;
 
-       TRACE(("docd(\"%s\", %d) called\n", dest, flags));
+       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_OFF;
-       if (!(flags & CD_PHYSICAL)) {
-               dir = updatepwd(dest);
-               if (dir)
-                       dest = dir;
+                       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;
        }
-       err = chdir(dest);
-       if (err)
-               goto out;
-       setpwd(dir, 1);
-       hashcd();
- out:
-       INT_ON;
-       return err;
+       if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE)))
+               s = ckstrdup(s);
+       vp->text = s;
+       vp->flags = flags;
 }
 
-static int
-cdcmd(int argc, char **argv)
+/*
+ * 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)
 {
-       const char *dest;
-       const char *path;
-       const char *p;
-       char c;
-       struct stat statb;
-       int flags;
+       char *p, *q;
+       size_t namelen;
+       char *nameeq;
+       size_t vallen;
 
-       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;
-               }
+       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);
        }
-       if (!*dest)
-               dest = ".";
-       path = bltinlookup("CDPATH");
-       if (!path) {
- step6:
- step7:
-               p = dest;
-               goto docd;
+       INT_OFF;
+       nameeq = ckmalloc(namelen + vallen + 2);
+       p = memcpy(nameeq, name, namelen) + namelen;
+       if (val) {
+               *p++ = '=';
+               p = memcpy(p, val, vallen) + vallen;
        }
-       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;
+       *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
-pwdcmd(int argc, char **argv)
+setvarsafe(const char *name, const char *val, int flags)
 {
-       int flags;
-       const char *dir = curdir;
+       int err;
+       volatile int saveint;
+       struct jmploc *volatile savehandler = exception_handler;
+       struct jmploc jmploc;
 
-       flags = cdopt();
-       if (flags) {
-               if (physdir == nullstr)
-                       setpwd(dir, 0);
-               dir = physdir;
+       SAVE_INT(saveint);
+       if (setjmp(jmploc.loc))
+               err = 1;
+       else {
+               exception_handler = &jmploc;
+               setvar(name, val, flags);
+               err = 0;
        }
-       out1fmt(snlfmt, dir);
-       return 0;
+       exception_handler = savehandler;
+       RESTORE_INT(saveint);
+       return err;
 }
-
-
-/* ============ Unsorted yet */
-
-
-/*      expand.h     */
-
-struct arglist {
-       struct strlist *list;
-       struct strlist **lastp;
-};
+#endif
 
 /*
- * expandarg() flags
+ * Unset the specified variable.
  */
-#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 */
-
+static int
+unsetvar(const char *s)
+{
+       struct var **vpp;
+       struct var *vp;
+       int retval;
 
-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 *);
+       vpp = findvar(hashvar(s), s);
+       vp = *vpp;
+       retval = 2;
+       if (vp) {
+               int flags = vp->flags;
 
-#if ENABLE_ASH_MATH_SUPPORT
-static void expari(int);
+               retval = 1;
+               if (flags & VREADONLY)
+                       goto out;
+#ifdef DYNAMIC_VAR
+               vp->flags &= ~VDYNAMIC;
 #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 */
-};
+               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;
+}
 
 /*
- * This file was generated by the mknodes program.
+ * Process a linked list of variable assignments.
  */
+static void
+listsetvar(struct strlist *list_set_var, int flags)
+{
+       struct strlist *lp = list_set_var;
 
-#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
-
+       if (!lp)
+               return;
+       INT_OFF;
+       do {
+               setvareq(lp->text, flags);
+               lp = lp->next;
+       } while (lp);
+       INT_ON;
+}
 
-struct ncmd {
-       int type;
-       union node *assign;
-       union node *args;
-       union node *redirect;
-};
+/*
+ * 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;
 
-struct npipe {
-       int type;
-       int backgnd;
-       struct nodelist *cmdlist;
-};
+       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);
+}
 
-struct nredir {
-       int type;
-       union node *n;
-       union node *redirect;
-};
 
-struct nbinary {
-       int type;
-       union node *ch1;
-       union node *ch2;
-};
+/* ============ 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 */
 
-struct nif {
-       int type;
-       union node *test;
-       union node *ifpart;
-       union node *elsepart;
-};
+static char *
+padvance(const char **path, const char *name)
+{
+       const char *p;
+       char *q;
+       const char *start;
+       size_t len;
 
-struct nfor {
-       int type;
-       union node *args;
-       union node *body;
-       char *var;
-};
+       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);
+}
 
-struct ncase {
-       int type;
-       union node *expr;
-       union node *cases;
-};
 
-struct nclist {
-       int type;
-       union node *next;
-       union node *pattern;
-       union node *body;
-};
+/* ============ Prompt */
 
-struct narg {
-       int type;
-       union node *next;
-       char *text;
-       struct nodelist *backquote;
-};
+static int doprompt;                   /* if set, prompt the user */
+static int needprompt;                 /* true if interactive and at start of line */
 
-struct nfile {
-       int type;
-       union node *next;
-       int fd;
-       union node *fname;
-       char *expfname;
-};
+#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
 
-struct ndup {
-       int type;
-       union node *next;
-       int fd;
-       int dupfd;
-       union node *vname;
-};
+#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
 
-struct nhere {
-       int type;
-       union node *next;
-       int fd;
-       union node *doc;
-};
+static void
+setprompt(int whichprompt)
+{
+       const char *prompt;
+#if ENABLE_ASH_EXPAND_PRMT
+       struct stackmark smark;
+#endif
 
-struct nnot {
-       int type;
-       union node *com;
-};
+       needprompt = 0;
 
-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;
-};
+       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
+}
 
-struct nodelist {
-       struct nodelist *next;
-       union node *n;
-};
 
-struct funcnode {
-       int count;
-       union node n;
-};
+/* ============ The cd and pwd commands */
 
+#define CD_PHYSICAL 1
+#define CD_PRINT 2
 
-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 */
+static int docd(const char *, int);
 
-/* 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 char *curdir = nullstr;          /* current working directory */
+static char *physdir = nullstr;         /* physical working directory */
 
-/* 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} */
+static int
+cdopt(void)
+{
+       int flags = 0;
+       int i, j;
 
-/* values of checkkwd variable */
-#define CHKALIAS        0x1
-#define CHKKWD          0x2
-#define CHKNL           0x4
+       j = 'L';
+       while ((i = nextopt("LP"))) {
+               if (i != j) {
+                       flags ^= CD_PHYSICAL;
+                       j = i;
+               }
+       }
 
-#define IBUFSIZ (BUFSIZ + 1)
+       return flags;
+}
 
 /*
- * 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.
+ * Update curdir (the name of the current directory) in response to a
+ * cd command.
  */
-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 */
+static const char *
+updatepwd(const char *dir)
+{
+       char *new;
+       char *p;
+       char *cdcomppath;
+       const char *lim;
 
-#define basebuf bb_common_bufsiz1       /* buffer for top level input file */
+       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 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
+setpwd(const char *val, int setold)
+{
+       char *oldcur, *dir;
 
-static void fixredir(union node *, const char *, int);
-static char *endofname(const char *);
+       oldcur = dir = curdir;
 
-/*      shell.h   */
+       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 const char spcstr[] = " ";
-static const char dolatstr[] = { CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' };
+static void hashcd(void);
 
-#if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96)
-#define __builtin_expect(x, expected_value) (x)
-#endif
+/*
+ * 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;
 
-#define xlikely(x)       __builtin_expect((x),1)
+       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;
+}
 
-#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
+static int
+cdcmd(int argc, char **argv)
+{
+       const char *dest;
+       const char *path;
+       const char *p;
+       char c;
+       struct stat statb;
+       int flags;
 
-/* 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(",
+       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 */
+
+
+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 */
+};
+
+
+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`",
@@ -2736,7 +3056,7 @@ static int funcnest;                   /* depth of function calls */
  */
 
 #if JOBS
-static int bgcmd(int, char **);
+static int fg_bgcmd(int, char **);
 #endif
 static int breakcmd(int, char **);
 static int cdcmd(int, char **);
@@ -2755,9 +3075,6 @@ static int execcmd(int, char **);
 static int exitcmd(int, char **);
 static int exportcmd(int, char **);
 static int falsecmd(int, char **);
-#if JOBS
-static int fgcmd(int, char **);
-#endif
 #if ENABLE_ASH_GETOPTS
 static int getoptscmd(int, char **);
 #endif
@@ -2840,7 +3157,7 @@ static const struct builtincmd builtincmd[] = {
        { BUILTIN_REG_ASSG      "alias", aliascmd },
 #endif
 #if JOBS
-       { BUILTIN_REGULAR       "bg", bgcmd },
+       { BUILTIN_REGULAR       "bg", fg_bgcmd },
 #endif
        { BUILTIN_SPEC_REG      "break", breakcmd },
        { BUILTIN_REGULAR       "cd", cdcmd },
@@ -2858,7 +3175,7 @@ static const struct builtincmd builtincmd[] = {
        { BUILTIN_SPEC_REG_ASSG "export", exportcmd },
        { BUILTIN_REGULAR       "false", falsecmd },
 #if JOBS
-       { BUILTIN_REGULAR       "fg", fgcmd },
+       { BUILTIN_REGULAR       "fg", fg_bgcmd },
 #endif
 #if ENABLE_ASH_GETOPTS
        { BUILTIN_REGULAR       "getopts", getoptscmd },
@@ -3033,25 +3350,12 @@ static void setjobctl(int);
 static void showjobs(FILE *, int);
 #endif
 
-/*      main.h        */
 
+/*      main.h        */
 
 static void readcmdfile(char *);
  
 
-/*      mystring.h   */
-
-
-#define DOLATSTRLEN 4
-
-static char *prefix(const char *, const char *);
-static int number(const char *);
-static int is_number(const char *);
-static char *single_quote(const char *);
-
-#define equal(s1, s2)   (strcmp(s1, s2) == 0)
-#define scopy(s1, s2)   ((void)strcpy(s2, s1))
-
 /*      options.h */
 
 static char *minusc;                   /* argument to -c option */
@@ -3076,22 +3380,8 @@ static void clearredir(int);
 static int copyfd(int, int);
 static int redirectsafe(union node *, int);
 
-/*      show.h     */
-
-
-#if DEBUG
-static void showtree(union node *);
-static void trace(const char *, ...);
-static void tracev(const char *, va_list);
-static void trargs(char **);
-static void trputc(int);
-static void trputs(const char *);
-static void opentrace(void);
-#endif
-
 /*      trap.h       */
 
-
 static void clear_traps(void);
 static void setsignal(int);
 static void ignoresig(int);
@@ -3137,11 +3427,67 @@ static int is_safe_applet(char *name)
 
 
 #if ENABLE_ASH_ALIAS
+struct alias {
+       struct alias *next;
+       char *name;
+       char *val;
+       int flag;
+};
+
 static struct alias *atab[ATABSIZE];
 
-static void setalias(const char *, const char *);
-static struct alias *freealias(struct alias *);
-static struct alias **__lookupalias(const char *);
+static struct alias **
+__lookupalias(const char *name) {
+       unsigned int hashval;
+       struct alias **app;
+       const char *p;
+       unsigned int ch;
+
+       p = name;
+
+       ch = (unsigned char)*p;
+       hashval = ch << 4;
+       while (ch) {
+               hashval += ch;
+               ch = (unsigned char)*++p;
+       }
+       app = &atab[hashval % ATABSIZE];
+
+       for (; *app; app = &(*app)->next) {
+               if (strcmp(name, (*app)->name) == 0) {
+                       break;
+               }
+       }
+
+       return app;
+}
+
+static struct alias *
+lookupalias(const char *name, int check)
+{
+       struct alias *ap = *__lookupalias(name);
+
+       if (check && ap && (ap->flag & ALIASINUSE))
+               return NULL;
+       return ap;
+}
+
+static struct alias *
+freealias(struct alias *ap)
+{
+       struct alias *next;
+
+       if (ap->flag & ALIASINUSE) {
+               ap->flag |= ALIASDEAD;
+               return ap;
+       }
+
+       next = ap->next;
+       free(ap->name);
+       free(ap->val);
+       free(ap);
+       return next;
+}
 
 static void
 setalias(const char *name, const char *val)
@@ -3205,16 +3551,6 @@ rmaliases(void)
        INT_ON;
 }
 
-static struct alias *
-lookupalias(const char *name, int check)
-{
-       struct alias *ap = *__lookupalias(name);
-
-       if (check && ap && (ap->flag & ALIASINUSE))
-               return NULL;
-       return ap;
-}
-
 /*
  * TODO - sort output
  */
@@ -3273,54 +3609,11 @@ unaliascmd(int argc, char **argv)
        return i;
 }
 
-static struct alias *
-freealias(struct alias *ap)
-{
-       struct alias *next;
-
-       if (ap->flag & ALIASINUSE) {
-               ap->flag |= ALIASDEAD;
-               return ap;
-       }
-
-       next = ap->next;
-       free(ap->name);
-       free(ap->val);
-       free(ap);
-       return next;
-}
-
 static void
 printalias(const struct alias *ap)
 {
        out1fmt("%s=%s\n", ap->name, single_quote(ap->val));
 }
-
-static struct alias **
-__lookupalias(const char *name) {
-       unsigned int hashval;
-       struct alias **app;
-       const char *p;
-       unsigned int ch;
-
-       p = name;
-
-       ch = (unsigned char)*p;
-       hashval = ch << 4;
-       while (ch) {
-               hashval += ch;
-               ch = (unsigned char)*++p;
-       }
-       app = &atab[hashval % ATABSIZE];
-
-       for (; *app; app = &(*app)->next) {
-               if (equal(name, (*app)->name)) {
-                       break;
-               }
-       }
-
-       return app;
-}
 #endif /* ASH_ALIAS */
 
 /*      eval.c  */
@@ -3602,7 +3895,7 @@ evalsubshell(union node *n, int flags)
                flags |= EV_EXIT;
                if (backgnd)
                        flags &=~ EV_TESTED;
-nofork:
+ nofork:
                redirect(n->nredir.redirect, 0);
                evaltreenr(n->nredir.n, flags);
                /* never returns */
@@ -4789,7 +5082,7 @@ cmdlookup(const char *name, int add)
        hashval &= 0x7FFF;
        pp = &cmdtable[hashval % CMDTABLESIZE];
        for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
-               if (equal(cmdp->cmdname, name))
+               if (strcmp(cmdp->cmdname, name) == 0)
                        break;
                pp = &cmdp->next;
        }
@@ -5292,7 +5585,7 @@ argstr(char *p, int flag)
                        /* "$@" syntax adherence hack */
                        if (
                                !inquotes &&
-                               !memcmp(p, dolatstr, DOLATSTRLEN) &&
+                               !memcmp(p, dolatstr, 4) &&
                                (p[4] == CTLQUOTEMARK || (
                                        p[4] == CTLENDVAR &&
                                        p[5] == CTLQUOTEMARK
@@ -6274,7 +6567,7 @@ expmeta(char *enddir, char *name)
                        continue;
                if (pmatch(start, dp->d_name)) {
                        if (atend) {
-                               scopy(dp->d_name, enddir);
+                               strcpy(enddir, dp->d_name);
                                addfname(expdir);
                        } else {
                                for (p = enddir, cp = dp->d_name; (*p++ = *cp++) != '\0';)
@@ -7186,7 +7479,7 @@ jobno(const struct job *jp)
 
 #if JOBS
 static int
-fgcmd(int argc, char **argv)
+fg_bgcmd(int argc, char **argv)
 {
        struct job *jp;
        FILE *out;
@@ -7210,9 +7503,6 @@ fgcmd(int argc, char **argv)
        return retval;
 }
 
-static int bgcmd(int, char **) __attribute__((__alias__("fgcmd")));
-
-
 static int
 restartjob(struct job *jp, int mode)
 {
@@ -7313,8 +7603,8 @@ showjob(FILE *out, struct job *jp, int mode)
        psend = ps + jp->nprocs;
 
        if (jp->state == JOBRUNNING) {
-               scopy("Running", s + col);
-               col += strlen("Running");
+               strcpy(s + col, "Running");
+               col += sizeof("Running") - 1;
        } else {
                int status = psend[-1].status;
 #if JOBS
@@ -7359,19 +7649,20 @@ jobscmd(int argc, char **argv)
        FILE *out;
 
        mode = 0;
-       while ((m = nextopt("lp")))
+       while ((m = nextopt("lp"))) {
                if (m == 'l')
                        mode = SHOW_PID;
                else
                        mode = SHOW_PGID;
+       }
 
        out = stdout;
        argv = argptr;
-       if (*argv)
+       if (*argv) {
                do
                        showjob(out, getjob(*argv,0), mode);
                while (*++argv);
-       else
+       else
                showjobs(out, mode);
 
        return 0;
@@ -7515,7 +7806,8 @@ getjob(const char *name, int getctl)
  currentjob:
                        err_msg = "No current job";
                        goto check;
-               } else if (c == '-') {
+               }
+               if (c == '-') {
                        if (jp)
                                jp = jp->prev_job;
                        err_msg = "No previous job";
@@ -7571,7 +7863,6 @@ getjob(const char *name, int getctl)
  * Return a new job structure.
  * Called with interrupts off.
  */
-
 static struct job *
 makejob(union node *node, int nprocs)
 {
@@ -7720,7 +8011,7 @@ static void forkchild(struct job *jp, union node *n, int mode)
 
 static void forkparent(struct job *jp, union node *n, int mode, pid_t pid)
 {
-       TRACE(("In parent shell:  child = %d\n", pid));
+       TRACE(("In parent shell: child = %d\n", pid));
        if (!jp) {
                while (jobless && dowait(DOWAIT_NORMAL, 0) > 0);
                jobless++;
@@ -7735,7 +8026,7 @@ static void forkparent(struct job *jp, union node *n, int mode, pid_t pid)
                else
                        pgrp = jp->ps[0].pid;
                /* This can fail because we are doing it in the child also */
-               (void)setpgid(pid, pgrp);
+               setpgid(pid, pgrp);
        }
 #endif
        if (mode == FORK_BG) {
@@ -7854,7 +8145,8 @@ waitforjob(struct job *jp)
  * (as opposed to running a builtin command or just typing return),
  * and the jobs command may give out of date information.
  */
-static int waitproc(int block, int *status)
+static int
+waitproc(int block, int *status)
 {
        int flags = 0;
 
@@ -8147,10 +8439,9 @@ cmdtxt(union node *n)
                        s[0] = n->ndup.dupfd + '0';
                        p = s;
                        goto dotail2;
-               } else {
-                       n = n->nfile.fname;
-                       goto donode;
                }
+               n = n->nfile.fname;
+               goto donode;
        }
 }
 
@@ -8178,6 +8469,7 @@ cmdputs(const char *s)
                "", "}", "-", "+", "?", "=",
                "%", "%%", "#", "##"
        };
+
        nextc = makestrspace((strlen(s) + 1) * 8, cmdnextc);
        p = s;
        while ((c = *p++) != 0) {
@@ -8192,11 +8484,10 @@ cmdputs(const char *s)
                                str = "${#";
                        else
                                str = "${";
-                       if (!(subtype & VSQUOTE) != !(quoted & 1)) {
-                               quoted ^= 1;
-                               c = '"';
-                       } else
+                       if (!(subtype & VSQUOTE) == !(quoted & 1))
                                goto dostr;
+                       quoted ^= 1;
+                       c = '"';
                        break;
                case CTLENDVAR:
                        str = "\"}" + !(quoted & 1);
@@ -8650,7 +8941,7 @@ minus_o(char *name, int val)
 
        if (name) {
                for (i = 0; i < NOPTS; i++) {
-                       if (equal(name, optnames(i))) {
+                       if (strcmp(name, optnames(i)) == 0) {
                                optlist[i] = val;
                                return;
                        }
@@ -8859,13 +9150,15 @@ setcmd(int argc, char **argv)
 
 
 #if ENABLE_LOCALE_SUPPORT
-static void change_lc_all(const char *value)
+static void
+change_lc_all(const char *value)
 {
        if (value && *value != '\0')
                setlocale(LC_ALL, value);
 }
 
-static void change_lc_ctype(const char *value)
+static void
+change_lc_ctype(const char *value)
 {
        if (value && *value != '\0')
                setlocale(LC_CTYPE, value);
@@ -8874,7 +9167,8 @@ static void change_lc_ctype(const char *value)
 
 #if ENABLE_ASH_RANDOM_SUPPORT
 /* Roughly copied from bash.. */
-static void change_random(const char *value)
+static void
+change_random(const char *value)
 {
        if (value == NULL) {
                /* "get", generate */
@@ -10841,543 +11135,200 @@ openredirect(union node *redir)
                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;
-       }
-
-       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"));
-}
-
-static void
-dupredirect(union node *redir, int f)
-{
-       int fd = redir->nfile.fd;
-
-       if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) {
-               if (redir->ndup.dupfd >= 0) {   /* if not ">&-" */
-                       copyfd(redir->ndup.dupfd, fd);
-               }
-               return;
-       }
-
-       if (f != fd) {
-               copyfd(f, fd);
-               close(f);
-       }
-}
-
-
-/*
- * 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
-redirect(union node *redir, int flags)
-{
-       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 */
-
-               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];
-}
-
-
-/*
- * Undo the effects of the last redirection.
- */
-static void
-popredir(int drop)
-{
-       struct redirtab *rp;
-       int i;
-
-       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]);
-               }
-       }
-       redirlist = rp->next;
-       nullredirs = rp->nullredirs;
-       free(rp);
-       INT_ON;
-}
-
-/*
- * Undo all redirections.  Called on error or interrupt.
- */
-
-/*
- * Discard all saved file descriptors.
- */
-static void
-clearredir(int drop)
-{
-       for (;;) {
-               nullredirs = 0;
-               if (!redirlist)
-                       break;
-               popredir(drop);
-       }
-}
-
-
-/*
- * 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
-showtree(union node *n)
-{
-       trputs("showtree called\n");
-       shtree(n, 1, NULL, stdout);
-}
-
-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;
-       }
-}
-
-static void
-sharg(union node *arg, FILE *fp)
-{
-       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;
-               }
+               abort();
+#endif
+               /* Fall through to eliminate warning. */
+       case NTOFD:
+       case NFROMFD:
+               f = -1;
+               break;
+       case NHERE:
+       case NXHERE:
+               f = openhere(redir);
+               break;
        }
-}
 
+       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"));
+}
 
 static void
-indent(int amount, char *pfx, FILE *fp)
+dupredirect(union node *redir, int f)
 {
-       int i;
+       int fd = redir->nfile.fd;
 
-       for (i = 0; i < amount; i++) {
-               if (pfx && i == amount - 1)
-                       fputs(pfx, fp);
-               putc('\t', fp);
+       if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) {
+               if (redir->ndup.dupfd >= 0) {   /* if not ">&-" */
+                       copyfd(redir->ndup.dupfd, fd);
+               }
+               return;
+       }
+
+       if (f != fd) {
+               copyfd(f, fd);
+               close(f);
        }
 }
 
 
 /*
- * Debugging stuff.
+ * 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 FILE *tracefile;
-
-
-static void
-trputc(int c)
-{
-       if (debug != 1)
-               return;
-       putc(c, tracefile);
-}
-
-static void
-trace(const char *fmt, ...)
-{
-       va_list va;
-
-       if (debug != 1)
-               return;
-       va_start(va, fmt);
-       (void) vfprintf(tracefile, fmt, va);
-       va_end(va);
-}
-
 static void
-tracev(const char *fmt, va_list va)
+redirect(union node *redir, int flags)
 {
-       if (debug != 1)
+       union node *n;
+       struct redirtab *sv;
+       int i;
+       int fd;
+       int newfd;
+       int *p;
+       nullredirs++;
+       if (!redir) {
                return;
-       (void) vfprintf(tracefile, fmt, va);
-}
+       }
+       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 */
 
+               newfd = openredirect(n);
+               if (fd == newfd)
+                       continue;
+               if (sv && *(p = &sv->renamed[fd]) == EMPTY) {
+                       i = fcntl(fd, F_DUPFD, 10);
 
-static void
-trputs(const char *s)
-{
-       if (debug != 1)
-               return;
-       fputs(s, tracefile);
+                       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];
 }
 
 
+/*
+ * Undo the effects of the last redirection.
+ */
 static void
-trstring(char *s)
+popredir(int drop)
 {
-       char *p;
-       char c;
+       struct redirtab *rp;
+       int i;
 
-       if (debug != 1)
+       if (--nullredirs >= 0)
                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);
+       INT_OFF;
+       rp = redirlist;
+       for (i = 0; i < 10; i++) {
+               if (rp->renamed[i] != EMPTY) {
+                       if (!drop) {
+                               close(i);
+                               copyfd(rp->renamed[i], i);
                        }
-                       break;
+                       close(rp->renamed[i]);
                }
        }
-       putc('"', tracefile);
+       redirlist = rp->next;
+       nullredirs = rp->nullredirs;
+       free(rp);
+       INT_ON;
 }
 
+/*
+ * Undo all redirections.  Called on error or interrupt.
+ */
 
+/*
+ * Discard all saved file descriptors.
+ */
 static void
-trargs(char **ap)
+clearredir(int drop)
 {
-       if (debug != 1)
-               return;
-       while (*ap) {
-               trstring(*ap++);
-               if (*ap)
-                       putc(' ', tracefile);
-               else
-                       putc('\n', tracefile);
+       for (;;) {
+               nullredirs = 0;
+               if (!redirlist)
+                       break;
+               popredir(drop);
        }
 }
 
 
-static void
-opentrace(void)
+/*
+ * 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)
 {
-       char s[100];
-#ifdef O_APPEND
-       int flags;
-#endif
+       int newfd;
 
-       if (debug != 1) {
-               if (tracefile)
-                       fflush(tracefile);
-               /* leave open because libedit might be using it */
-               return;
+       newfd = fcntl(from, F_DUPFD, to);
+       if (newfd < 0) {
+               if (errno == EMFILE)
+                       return EMPTY;
+               ash_msg_and_raise_error("%d: %m", from);
        }
-       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;
-               }
+       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);
        }
-#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);
+       exception_handler = savehandler;
+       if (err && exception != EXERROR)
+               longjmp(exception_handler->loc, 1);
+       RESTORE_INT(saveint);
+       return err;
 }
-#endif /* DEBUG */
 
 
 /*      trap.c       */
@@ -13215,18 +13166,17 @@ int ash_main(int argc, char **argv)
                FORCE_INT_ON; /* enable interrupts */
                if (s == 1)
                        goto state1;
-               else if (s == 2)
+               if (s == 2)
                        goto state2;
-               else if (s == 3)
+               if (s == 3)
                        goto state3;
-               else
-                       goto state4;
+               goto state4;
        }
        exception_handler = &jmploc;
 #if DEBUG
        opentrace();
-       trputs("Shell args: ");
-       trargs(argv);
+       trace_puts("Shell args: ");
+       trace_puts_args(argv);
 #endif
        rootpid = getpid();