This a an ash applet I put together. It is similar to the one put
authorEric Andersen <andersen@codepoet.org>
Thu, 28 Jun 2001 07:25:16 +0000 (07:25 -0000)
committerEric Andersen <andersen@codepoet.org>
Thu, 28 Jun 2001 07:25:16 +0000 (07:25 -0000)
together by vodz, but uses newer sources, has the removed features
commented out instead of simply deleted (so they could be re-enabled)
and the builtins all work.  This adds 72k.
 -Erik

ash.c [new file with mode: 0644]
ash.h [new file with mode: 0644]
shell/ash.c [new file with mode: 0644]

diff --git a/ash.c b/ash.c
new file mode 100644 (file)
index 0000000..bab12b9
--- /dev/null
+++ b/ash.c
@@ -0,0 +1,14242 @@
+/*-
+ * Copyright (c) 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#undef _GNU_SOURCE
+#undef ASH_TYPE
+#undef ASH_GETOPTS
+#undef ASH_MATH_SUPPORT
+
+#include <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <paths.h>
+#include <pwd.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/cdefs.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/times.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+
+#if !defined(FNMATCH_BROKEN)
+#include <fnmatch.h>
+#endif
+#if !defined(GLOB_BROKEN)
+#include <glob.h>
+#endif
+
+#if JOBS
+#include <termios.h>
+#undef CEOF                    /* syntax.h redefines this */
+#endif
+
+#include "cmdedit.h"
+#include "busybox.h"
+#include "ash.h"
+
+
+#define _DIAGASSERT(x)
+
+#define ATABSIZE 39
+
+#define S_DFL 1                        /* default signal handling (SIG_DFL) */
+#define S_CATCH 2              /* signal is caught */
+#define S_IGN 3                        /* signal is ignored (SIG_IGN) */
+#define S_HARD_IGN 4           /* signal is ignored permenantly */
+#define S_RESET 5              /* temporary - to reset a hard ignored sig */
+
+
+
+struct alias *atab[ATABSIZE];
+
+static void setalias __P((char *, char *));
+static struct alias **hashalias __P((const char *));
+static struct alias *freealias __P((struct alias *));
+static struct alias **__lookupalias __P((const char *));
+static char *trap[NSIG];               /* trap handler commands */
+static char sigmode[NSIG - 1]; /* current value of signal */
+static char gotsig[NSIG - 1];          /* indicates specified signal received */
+static int pendingsigs;                        /* indicates some signal received */
+
+
+static void
+setalias(name, val)
+       char *name, *val;
+{
+       struct alias *ap, **app;
+
+       app = __lookupalias(name);
+       ap = *app;
+       INTOFF;
+       if (ap) {
+               if (!(ap->flag & ALIASINUSE)) {
+                       ckfree(ap->val);
+               }
+               ap->val = savestr(val);
+               ap->flag &= ~ALIASDEAD;
+       } else {
+               /* not found */
+               ap = ckmalloc(sizeof (struct alias));
+               ap->name = savestr(name);
+               ap->val = savestr(val);
+               ap->flag = 0;
+               ap->next = 0;
+               *app = ap;
+       }
+       INTON;
+}
+
+static int
+unalias(name)
+       char *name;
+       {
+       struct alias **app;
+
+       app = __lookupalias(name);
+
+       if (*app) {
+               INTOFF;
+               *app = freealias(*app);
+               INTON;
+               return (0);
+       }
+
+       return (1);
+}
+
+#ifdef mkinit
+static void rmaliases __P((void));
+
+SHELLPROC {
+       rmaliases();
+}
+#endif
+
+static void
+rmaliases() {
+       struct alias *ap, **app;
+       int i;
+
+       INTOFF;
+       for (i = 0; i < ATABSIZE; i++) {
+               app = &atab[i];
+               for (ap = *app; ap; ap = *app) {
+                       *app = freealias(*app);
+                       if (ap == *app) {
+                               app = &ap->next;
+                       }
+               }
+       }
+       INTON;
+}
+
+struct alias *
+lookupalias(name, check)
+       const char *name;
+       int check;
+{
+       struct alias *ap = *__lookupalias(name);
+
+       if (check && ap && (ap->flag & ALIASINUSE))
+               return (NULL);
+       return (ap);
+}
+
+
+/*
+ * TODO - sort output
+ */
+static int
+aliascmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       char *n, *v;
+       int ret = 0;
+       struct alias *ap;
+
+       if (argc == 1) {
+               int i;
+
+               for (i = 0; i < ATABSIZE; i++)
+                       for (ap = atab[i]; ap; ap = ap->next) {
+                               printalias(ap);
+                       }
+               return (0);
+       }
+       while ((n = *++argv) != NULL) {
+               if ((v = strchr(n+1, '=')) == NULL) { /* n+1: funny ksh stuff */
+                       if ((ap = *__lookupalias(n)) == NULL) {
+                               outfmt(out2, "%s: %s not found\n", "alias", n);
+                               ret = 1;
+                       } else
+                               printalias(ap);
+               }
+               else {
+                       *v++ = '\0';
+                       setalias(n, v);
+               }
+       }
+
+       return (ret);
+}
+
+static int
+unaliascmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       int i;
+
+       while ((i = nextopt("a")) != '\0') {
+               if (i == 'a') {
+                       rmaliases();
+                       return (0);
+               }
+       }
+       for (i = 0; *argptr; argptr++) {
+               if (unalias(*argptr)) {
+                       outfmt(out2, "%s: %s not found\n", "unalias", *argptr);
+                       i = 1;
+               }
+       }
+
+       return (i);
+}
+
+static struct alias **
+hashalias(p)
+       const char *p;
+       {
+       unsigned int hashval;
+
+       hashval = *p << 4;
+       while (*p)
+               hashval+= *p++;
+       return &atab[hashval % ATABSIZE];
+}
+
+static struct alias *
+freealias(struct alias *ap) {
+       struct alias *next;
+
+       if (ap->flag & ALIASINUSE) {
+               ap->flag |= ALIASDEAD;
+               return ap;
+       }
+
+       next = ap->next;
+       ckfree(ap->name);
+       ckfree(ap->val);
+       ckfree(ap);
+       return next;
+}
+
+static void
+printalias(const struct alias *ap) {
+       char *p;
+
+       p = single_quote(ap->val);
+       out1fmt("alias %s=%s\n", ap->name, p);
+       stunalloc(p);
+}
+
+static struct alias **
+__lookupalias(const char *name) {
+       struct alias **app = hashalias(name);
+
+       for (; *app; app = &(*app)->next) {
+               if (equal(name, (*app)->name)) {
+                       break;
+               }
+       }
+
+       return app;
+}
+
+#ifdef ASH_MATH_SUPPORT
+/* The generated file arith.c has been snipped.  If you want this
+ * stuff back in, feel free to add it to your own copy.  */
+#endif
+
+/*
+ * This file was generated by the mkbuiltins program.
+ */
+
+static int bgcmd __P((int, char **));
+static int breakcmd __P((int, char **));
+static int cdcmd __P((int, char **));
+static int commandcmd __P((int, char **));
+static int dotcmd __P((int, char **));
+static int evalcmd __P((int, char **));
+static int execcmd __P((int, char **));
+static int exitcmd __P((int, char **));
+static int exportcmd __P((int, char **));
+static int histcmd __P((int, char **));
+static int fgcmd __P((int, char **));
+static int hashcmd __P((int, char **));
+static int jobscmd __P((int, char **));
+static int killcmd __P((int, char **));
+static int localcmd __P((int, char **));
+static int pwdcmd __P((int, char **));
+static int readcmd __P((int, char **));
+static int returncmd __P((int, char **));
+static int setcmd __P((int, char **));
+static int setvarcmd __P((int, char **));
+static int shiftcmd __P((int, char **));
+static int trapcmd __P((int, char **));
+static int umaskcmd __P((int, char **));
+static int unaliascmd __P((int, char **));
+static int unsetcmd __P((int, char **));
+static int waitcmd __P((int, char **));
+static int aliascmd __P((int, char **));
+static int ulimitcmd __P((int, char **));
+static int timescmd __P((int, char **));
+#ifdef ASH_MATH_SUPPORT
+static int expcmd __P((int, char **));
+#endif
+#ifdef ASH_TYPE
+static int typecmd __P((int, char **));
+#endif
+#ifdef ASH_GETOPTS
+static int getoptscmd __P((int, char **));
+#endif
+#ifndef BB_TRUE_FALSE
+static int true_main __P((int, char **));
+static int false_main __P((int, char **));
+#endif
+
+static struct builtincmd *DOTCMD;
+static struct builtincmd *BLTINCMD;
+static struct builtincmd *COMMANDCMD;
+static struct builtincmd *EXECCMD;
+static struct builtincmd *EVALCMD;
+
+/* It is CRUCIAL that this listing be kept in ascii order, otherwise
+ * the binary search in find_builtin() will stop working. If you value
+ * your kneecaps, you'll be sure to *make sure* that any changes made
+ * to this array result in the listing remaining in ascii order. You
+ * have been warned.
+ */
+static const struct builtincmd builtincmds[] = {
+       { ".", dotcmd, 1 },
+       { ":", true_main, 1 },
+       { "alias", aliascmd, 6 },
+       { "bg", bgcmd, 2 },
+       { "break", breakcmd, 1 },
+       { "builtin", bltincmd, 1 },
+       { "cd", cdcmd, 2 },
+       { "chdir", cdcmd, 0 },
+       { "command", commandcmd, 2 },
+       { "continue", breakcmd, 1 },
+       { "eval", evalcmd, 1 },
+       { "exec", execcmd, 1 },
+       { "exit", exitcmd, 1 },
+#ifdef ASH_MATH_SUPPORT
+       { "exp", expcmd, 0 },
+#endif
+       { "export", exportcmd, 5 },
+       { "false", false_main, 2 },
+       { "fc", histcmd, 2 },
+       { "fg", fgcmd, 2 },
+#ifdef ASH_GETOPTS
+       { "getopts", getoptscmd, 2 },
+#endif 
+       { "hash", hashcmd, 0 },
+       { "jobs", jobscmd, 2 },
+       { "kill", killcmd, 2 },
+#ifdef ASH_MATH_SUPPORT
+       { "let", expcmd, 0 },
+#endif
+       { "local", localcmd, 4 },
+       { "pwd", pwdcmd, 0 },
+       { "read", readcmd, 2 },
+       { "readonly", exportcmd, 5 },
+       { "return", returncmd, 1 },
+       { "set", setcmd, 1 },
+       { "setvar", setvarcmd, 0 },
+       { "shift", shiftcmd, 1 },
+       { "times", timescmd, 1 },
+       { "trap", trapcmd, 1 },
+       { "true", true_main, 2 },
+#ifdef ASH_TYPE
+       { "type", typecmd, 0 },
+#endif
+       { "ulimit", ulimitcmd, 0 },
+       { "umask", umaskcmd, 2 },
+       { "unalias", unaliascmd, 2 },
+       { "unset", unsetcmd, 1 },
+       { "wait", waitcmd, 2 },
+};
+#define NUMBUILTINS  (sizeof (builtincmds) / sizeof (struct builtincmd) )
+
+
+/*     $NetBSD: cd.c,v 1.27 1999/07/09 03:05:49 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+static int docd __P((char *, int));
+static char *getcomponent __P((void));
+static void updatepwd __P((char *));
+static void getpwd __P((void));
+
+static char *curdir = nullstr;         /* current working directory */
+static char *cdcomppath;
+
+#ifdef mkinit
+INCLUDE "cd.h"
+INIT {
+       setpwd(0, 0);
+}
+#endif
+
+static int
+cdcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       const char *dest;
+       const char *path;
+       char *p;
+       struct stat statb;
+       int print = 0;
+
+       nextopt(nullstr);
+       if ((dest = *argptr) == NULL && (dest = bltinlookup("HOME")) == NULL)
+               error("HOME not set");
+       if (*dest == '\0')
+               dest = ".";
+       if (dest[0] == '-' && dest[1] == '\0') {
+               dest = bltinlookup("OLDPWD");
+               if (!dest || !*dest) {
+                       dest = curdir;
+               }
+               print = 1;
+               if (dest)
+                       print = 1;
+               else
+                       dest = ".";
+       }
+       if (*dest == '/' || (path = bltinlookup("CDPATH")) == NULL)
+               path = nullstr;
+       while ((p = padvance(&path, dest)) != NULL) {
+               if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
+                       if (!print) {
+                               /*
+                                * XXX - rethink
+                                */
+                               if (p[0] == '.' && p[1] == '/' && p[2] != '\0')
+                                       p += 2;
+                               print = strcmp(p, dest);
+                       }
+                       if (docd(p, print) >= 0)
+                               return 0;
+
+               }
+       }
+       error("can't cd to %s", dest);
+       /* NOTREACHED */
+}
+
+
+/*
+ * Actually do the chdir.  In an interactive shell, print the
+ * directory name if "print" is nonzero.
+ */
+
+static int
+docd(dest, print)
+       char *dest;
+       int print;
+{
+       char *p;
+       char *q;
+       char *component;
+       struct stat statb;
+       int first;
+       int badstat;
+
+       TRACE(("docd(\"%s\", %d) called\n", dest, print));
+
+       /*
+        *  Check each component of the path. If we find a symlink or
+        *  something we can't stat, clear curdir to force a getcwd()
+        *  next time we get the value of the current directory.
+        */
+       badstat = 0;
+       cdcomppath = sstrdup(dest);
+       STARTSTACKSTR(p);
+       if (*dest == '/') {
+               STPUTC('/', p);
+               cdcomppath++;
+       }
+       first = 1;
+       while ((q = getcomponent()) != NULL) {
+               if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0'))
+                       continue;
+               if (! first)
+                       STPUTC('/', p);
+               first = 0;
+               component = q;
+               while (*q)
+                       STPUTC(*q++, p);
+               if (equal(component, ".."))
+                       continue;
+               STACKSTRNUL(p);
+               if ((lstat(stackblock(), &statb) < 0)
+                   || (S_ISLNK(statb.st_mode)))  {
+                       /* print = 1; */
+                       badstat = 1;
+                       break;
+               }
+       }
+
+       INTOFF;
+       if (chdir(dest) < 0) {
+               INTON;
+               return -1;
+       }
+       updatepwd(badstat ? NULL : dest);
+       INTON;
+       if (print && iflag)
+               out1fmt(snlfmt, curdir);
+       return 0;
+}
+
+
+/*
+ * Get the next component of the path name pointed to by cdcomppath.
+ * This routine overwrites the string pointed to by cdcomppath.
+ */
+
+static char *
+getcomponent() {
+       char *p;
+       char *start;
+
+       if ((p = cdcomppath) == NULL)
+               return NULL;
+       start = cdcomppath;
+       while (*p != '/' && *p != '\0')
+               p++;
+       if (*p == '\0') {
+               cdcomppath = NULL;
+       } else {
+               *p++ = '\0';
+               cdcomppath = p;
+       }
+       return start;
+}
+
+
+
+/*
+ * Update curdir (the name of the current directory) in response to a
+ * cd command.  We also call hashcd to let the routines in exec.c know
+ * that the current directory has changed.
+ */
+
+static void
+updatepwd(dir)
+       char *dir;
+       {
+       char *new;
+       char *p;
+       size_t len;
+
+       hashcd();                               /* update command hash table */
+
+       /*
+        * If our argument is NULL, we don't know the current directory
+        * any more because we traversed a symbolic link or something
+        * we couldn't stat().
+        */
+       if (dir == NULL || curdir == nullstr)  {
+               setpwd(0, 1);
+               return;
+       }
+       len = strlen(dir);
+       cdcomppath = sstrdup(dir);
+       STARTSTACKSTR(new);
+       if (*dir != '/') {
+               p = curdir;
+               while (*p)
+                       STPUTC(*p++, new);
+               if (p[-1] == '/')
+                       STUNPUTC(new);
+       }
+       while ((p = getcomponent()) != NULL) {
+               if (equal(p, "..")) {
+                       while (new > stackblock() && (STUNPUTC(new), *new) != '/');
+               } else if (*p != '\0' && ! equal(p, ".")) {
+                       STPUTC('/', new);
+                       while (*p)
+                               STPUTC(*p++, new);
+               }
+       }
+       if (new == stackblock())
+               STPUTC('/', new);
+       STACKSTRNUL(new);
+       setpwd(stackblock(), 1);
+}
+
+
+
+static int
+pwdcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       out1fmt(snlfmt, curdir);
+       return 0;
+}
+
+
+
+
+#define MAXPWD 256
+
+/*
+ * Find out what the current directory is. If we already know the current
+ * directory, this routine returns immediately.
+ */
+static void
+getpwd()
+{
+       char buf[MAXPWD];
+
+       /*
+        * Things are a bit complicated here; we could have just used
+        * getcwd, but traditionally getcwd is implemented using popen
+        * to /bin/pwd. This creates a problem for us, since we cannot
+        * keep track of the job if it is being ran behind our backs.
+        * So we re-implement getcwd(), and we suppress interrupts
+        * throughout the process. This is not completely safe, since
+        * the user can still break out of it by killing the pwd program.
+        * We still try to use getcwd for systems that we know have a
+        * c implementation of getcwd, that does not open a pipe to
+        * /bin/pwd.
+        */
+#if defined(__NetBSD__) || defined(__SVR4) || defined(__GLIBC__)
+               
+       if (getcwd(buf, sizeof(buf)) == NULL) {
+               char *pwd = getenv("PWD");
+               struct stat stdot, stpwd;
+
+               if (pwd && *pwd == '/' && stat(".", &stdot) != -1 &&
+                   stat(pwd, &stpwd) != -1 &&
+                   stdot.st_dev == stpwd.st_dev &&
+                   stdot.st_ino == stpwd.st_ino) {
+                       curdir = savestr(pwd);
+                       return;
+               }
+               error("getcwd() failed: %s", strerror(errno));
+       }
+       curdir = savestr(buf);
+#else
+       {
+               char *p;
+               int i;
+               int status;
+               struct job *jp;
+               int pip[2];
+
+               if (pipe(pip) < 0)
+                       error("Pipe call failed");
+               jp = makejob((union node *)NULL, 1);
+               if (forkshell(jp, (union node *)NULL, FORK_NOJOB) == 0) {
+                       (void) close(pip[0]);
+                       if (pip[1] != 1) {
+                               close(1);
+                               dup_as_newfd(pip[1], 1);
+                               close(pip[1]);
+                       }
+                       (void) execl("/bin/pwd", "pwd", (char *)0);
+                       error("Cannot exec /bin/pwd");
+               }
+               (void) close(pip[1]);
+               pip[1] = -1;
+               p = buf;
+               while ((i = read(pip[0], p, buf + MAXPWD - p)) > 0
+                    || (i == -1 && errno == EINTR)) {
+                       if (i > 0)
+                               p += i;
+               }
+               (void) close(pip[0]);
+               pip[0] = -1;
+               status = waitforjob(jp);
+               if (status != 0)
+                       error((char *)0);
+               if (i < 0 || p == buf || p[-1] != '\n')
+                       error("pwd command failed");
+               p[-1] = '\0';
+       }
+       curdir = savestr(buf);
+#endif
+}
+
+static void
+setpwd(const char *val, int setold)
+{
+       if (setold) {
+               setvar("OLDPWD", curdir, VEXPORT);
+       }
+       INTOFF;
+       if (curdir != nullstr) {
+               free(curdir);
+               curdir = nullstr;
+       }
+       if (!val) {
+               getpwd();
+       } else {
+               curdir = savestr(val);
+       }
+       INTON;
+       setvar("PWD", curdir, VEXPORT);
+}
+
+/*     $NetBSD: error.c,v 1.23 2000/07/03 03:26:19 matt Exp $  */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Errors and exceptions.
+ */
+
+/*
+ * Code to handle exceptions in C.
+ */
+
+struct jmploc *handler;
+static int exception;
+volatile int suppressint;
+volatile int intpending;
+
+
+static void exverror __P((int, const char *, va_list))
+    __attribute__((__noreturn__));
+
+/*
+ * Called to raise an exception.  Since C doesn't include exceptions, we
+ * just do a longjmp to the exception handler.  The type of exception is
+ * stored in the global variable "exception".
+ */
+
+static void
+exraise(e)
+       int e;
+{
+#ifdef DEBUG
+       if (handler == NULL)
+               abort();
+#endif
+       exception = e;
+       longjmp(handler->loc, 1);
+}
+
+
+/*
+ * Called from trap.c when a SIGINT is received.  (If the user specifies
+ * that SIGINT is to be trapped or ignored using the trap builtin, then
+ * this routine is not called.)  Suppressint is nonzero when interrupts
+ * are held using the INTOFF macro.  The call to _exit is necessary because
+ * there is a short period after a fork before the signal handlers are
+ * set to the appropriate value for the child.  (The test for iflag is
+ * just defensive programming.)
+ */
+
+static void
+onint() {
+       sigset_t mysigset;
+
+       if (suppressint) {
+               intpending++;
+               return;
+       }
+       intpending = 0;
+       sigemptyset(&mysigset);
+       sigprocmask(SIG_SETMASK, &mysigset, NULL);
+       if (rootshell && iflag)
+               exraise(EXINT);
+       else {
+               signal(SIGINT, SIG_DFL);
+               raise(SIGINT);
+       }
+       /* NOTREACHED */
+}
+
+
+/*
+ * Exverror is called to raise the error exception.  If the first argument
+ * is not NULL then error prints an error message using printf style
+ * formatting.  It then raises the error exception.
+ */
+static void
+exverror(cond, msg, ap)
+       int cond;
+       const char *msg;
+       va_list ap;
+{
+       CLEAR_PENDING_INT;
+       INTOFF;
+
+#ifdef DEBUG
+       if (msg)
+               TRACE(("exverror(%d, \"%s\") pid=%d\n", cond, msg, getpid()));
+       else
+               TRACE(("exverror(%d, NULL) pid=%d\n", cond, getpid()));
+#endif
+       if (msg) {
+               if (commandname)
+                       outfmt(&errout, "%s: ", commandname);
+               doformat(&errout, msg, ap);
+#if FLUSHERR
+               outc('\n', &errout);
+#else
+               outcslow('\n', &errout);
+#endif
+       }
+       flushall();
+       exraise(cond);
+       /* NOTREACHED */
+}
+
+
+#ifdef __STDC__
+static void
+error(const char *msg, ...)
+#else
+static void
+error(va_alist)
+       va_dcl
+#endif
+{
+#ifndef __STDC__
+       const char *msg;
+#endif
+       va_list ap;
+#ifdef __STDC__
+       va_start(ap, msg);
+#else
+       va_start(ap);
+       msg = va_arg(ap, const char *);
+#endif
+       exverror(EXERROR, msg, ap);
+       /* NOTREACHED */
+       va_end(ap);
+}
+
+
+#ifdef __STDC__
+static void
+exerror(int cond, const char *msg, ...)
+#else
+static void
+exerror(va_alist)
+       va_dcl
+#endif
+{
+#ifndef __STDC__
+       int cond;
+       const char *msg;
+#endif
+       va_list ap;
+#ifdef __STDC__
+       va_start(ap, msg);
+#else
+       va_start(ap);
+       cond = va_arg(ap, int);
+       msg = va_arg(ap, const char *);
+#endif
+       exverror(cond, msg, ap);
+       /* NOTREACHED */
+       va_end(ap);
+}
+
+
+
+/*
+ * Table of error messages.
+ */
+
+struct errname {
+       short errcode;          /* error number */
+       short action;           /* operation which encountered the error */
+       const char *msg;        /* text describing the error */
+};
+
+
+#define ALL (E_OPEN|E_CREAT|E_EXEC)
+
+static const struct errname errormsg[] = {
+       { EINTR,        ALL,    "interrupted" },
+       { EACCES,       ALL,    "permission denied" },
+       { EIO,          ALL,    "I/O error" },
+       { ENOENT,       E_OPEN, "no such file" },
+       { ENOENT,       E_CREAT,"directory nonexistent" },
+       { ENOENT,       E_EXEC, "not found" },
+       { ENOTDIR,      E_OPEN, "no such file" },
+       { ENOTDIR,      E_CREAT,"directory nonexistent" },
+       { ENOTDIR,      E_EXEC, "not found" },
+       { EISDIR,       ALL,    "is a directory" },
+       { EEXIST,       E_CREAT,"file exists" },
+#ifdef notdef
+       { EMFILE,       ALL,    "too many open files" },
+#endif
+       { ENFILE,       ALL,    "file table overflow" },
+       { ENOSPC,       ALL,    "file system full" },
+#ifdef EDQUOT
+       { EDQUOT,       ALL,    "disk quota exceeded" },
+#endif
+#ifdef ENOSR
+       { ENOSR,        ALL,    "no streams resources" },
+#endif
+       { ENXIO,        ALL,    "no such device or address" },
+       { EROFS,        ALL,    "read-only file system" },
+       { ETXTBSY,      ALL,    "text busy" },
+#ifdef SYSV
+       { EAGAIN,       E_EXEC, "not enough memory" },
+#endif
+       { ENOMEM,       ALL,    "not enough memory" },
+#ifdef ENOLINK
+       { ENOLINK,      ALL,    "remote access failed" },
+#endif
+#ifdef EMULTIHOP
+       { EMULTIHOP,    ALL,    "remote access failed" },
+#endif
+#ifdef ECOMM
+       { ECOMM,        ALL,    "remote access failed" },
+#endif
+#ifdef ESTALE
+       { ESTALE,       ALL,    "remote access failed" },
+#endif
+#ifdef ETIMEDOUT
+       { ETIMEDOUT,    ALL,    "remote access failed" },
+#endif
+#ifdef ELOOP
+       { ELOOP,        ALL,    "symbolic link loop" },
+#endif
+       { E2BIG,        E_EXEC, "argument list too long" },
+#ifdef ELIBACC
+       { ELIBACC,      E_EXEC, "shared library missing" },
+#endif
+       { 0,            0,      NULL },
+};
+
+
+/*
+ * 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(e, action)
+       int e;
+       int action;
+{
+       struct errname const *ep;
+       static char buf[12];
+
+       for (ep = errormsg ; ep->errcode ; ep++) {
+               if (ep->errcode == e && (ep->action & action) != 0)
+                       return ep->msg;
+       }
+       fmtstr(buf, sizeof buf, "error %d", e);
+       return buf;
+}
+
+
+#ifdef REALLY_SMALL
+static void
+__inton() {
+       if (--suppressint == 0 && intpending) {
+               onint();
+       }
+}
+#endif
+/*     $NetBSD: eval.c,v 1.57 2001/02/04 19:52:06 christos Exp $       */
+
+/*-
+ * Copyright (c) 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+
+/* flags in argument to evaltree */
+#define EV_EXIT 01             /* exit after evaluating tree */
+#define EV_TESTED 02           /* exit status is checked; ignore -e flag */
+#define EV_BACKCMD 04          /* command executing within back quotes */
+
+static int evalskip;                   /* set if we are skipping commands */
+static int skipcount;          /* number of levels to skip */
+static int loopnest;           /* current loop nesting level */
+static int funcnest;                   /* depth of function calls */
+
+
+static char *commandname;
+struct strlist *cmdenviron;
+static int exitstatus;                 /* exit status of last command */
+static int oexitstatus;                /* saved exit status */
+
+
+static void evalloop __P((union node *, int));
+static void evalfor __P((union node *, int));
+static void evalcase __P((union node *, int));
+static void evalsubshell __P((union node *, int));
+static void expredir __P((union node *));
+static void evalpipe __P((union node *));
+#ifdef notyet
+static void evalcommand __P((union node *, int, struct backcmd *));
+#else
+static void evalcommand __P((union node *, int));
+#endif
+static void prehash __P((union node *));
+static void eprintlist __P((struct strlist *));
+
+
+/*
+ * Called to reset things after an exception.
+ */
+
+#ifdef mkinit
+INCLUDE "eval.h"
+
+RESET {
+       evalskip = 0;
+       loopnest = 0;
+       funcnest = 0;
+}
+
+SHELLPROC {
+       exitstatus = 0;
+}
+#endif
+
+
+
+/*
+ * The eval commmand.
+ */
+
+static int
+evalcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+        char *p;
+        char *concat;
+        char **ap;
+
+        if (argc > 1) {
+                p = argv[1];
+                if (argc > 2) {
+                        STARTSTACKSTR(concat);
+                        ap = argv + 2;
+                        for (;;) {
+                                while (*p)
+                                        STPUTC(*p++, concat);
+                                if ((p = *ap++) == NULL)
+                                        break;
+                                STPUTC(' ', concat);
+                        }
+                        STPUTC('\0', concat);
+                        p = grabstackstr(concat);
+                }
+                evalstring(p, EV_TESTED);
+        }
+        return exitstatus;
+}
+
+
+/*
+ * Execute a command or commands contained in a string.
+ */
+
+static void
+evalstring(s, flag)
+       char *s;
+       int flag;
+       {
+       union node *n;
+       struct stackmark smark;
+
+       setstackmark(&smark);
+       setinputstring(s);
+       while ((n = parsecmd(0)) != NEOF) {
+               evaltree(n, flag);
+               popstackmark(&smark);
+       }
+       popfile();
+       popstackmark(&smark);
+}
+
+
+
+/*
+ * Evaluate a parse tree.  The value is left in the global variable
+ * exitstatus.
+ */
+
+static void
+evaltree(n, flags)
+       union node *n;
+       int flags;
+{
+       int checkexit = 0;
+       if (n == NULL) {
+               TRACE(("evaltree(NULL) called\n"));
+               goto out;
+       }
+       TRACE(("evaltree(0x%lx: %d) called\n", (long)n, n->type));
+       switch (n->type) {
+       case NSEMI:
+               evaltree(n->nbinary.ch1, flags & EV_TESTED);
+               if (evalskip)
+                       goto out;
+               evaltree(n->nbinary.ch2, flags);
+               break;
+       case NAND:
+               evaltree(n->nbinary.ch1, EV_TESTED);
+               if (evalskip || exitstatus != 0)
+                       goto out;
+               evaltree(n->nbinary.ch2, flags);
+               break;
+       case NOR:
+               evaltree(n->nbinary.ch1, EV_TESTED);
+               if (evalskip || exitstatus == 0)
+                       goto out;
+               evaltree(n->nbinary.ch2, flags);
+               break;
+       case NREDIR:
+               expredir(n->nredir.redirect);
+               redirect(n->nredir.redirect, REDIR_PUSH);
+               evaltree(n->nredir.n, flags);
+               popredir();
+               break;
+       case NSUBSHELL:
+               evalsubshell(n, flags);
+               break;
+       case NBACKGND:
+               evalsubshell(n, flags);
+               break;
+       case NIF: {
+               evaltree(n->nif.test, EV_TESTED);
+               if (evalskip)
+                       goto out;
+               if (exitstatus == 0)
+                       evaltree(n->nif.ifpart, flags);
+               else if (n->nif.elsepart)
+                       evaltree(n->nif.elsepart, flags);
+               else
+                       exitstatus = 0;
+               break;
+       }
+       case NWHILE:
+       case NUNTIL:
+               evalloop(n, flags);
+               break;
+       case NFOR:
+               evalfor(n, flags);
+               break;
+       case NCASE:
+               evalcase(n, flags);
+               break;
+       case NDEFUN: {
+               struct builtincmd *bcmd;
+               if (
+                       (bcmd = find_builtin(n->narg.text)) &&
+                       bcmd->flags & BUILTIN_SPECIAL
+               ) {
+                       outfmt(out2, "%s is a special built-in\n", n->narg.text);
+                       exitstatus = 1;
+                       break;
+               }
+               defun(n->narg.text, n->narg.next);
+               exitstatus = 0;
+               break;
+       }
+       case NNOT:
+               evaltree(n->nnot.com, EV_TESTED);
+               exitstatus = !exitstatus;
+               break;
+
+       case NPIPE:
+               evalpipe(n);
+               checkexit = 1;
+               break;
+       case NCMD:
+#ifdef notyet
+               evalcommand(n, flags, (struct backcmd *)NULL);
+#else
+               evalcommand(n, flags);
+#endif
+               checkexit = 1;
+               break;
+#ifdef DEBUG
+       default:
+               out1fmt("Node type = %d\n", n->type);
+#ifndef USE_GLIBC_STDIO
+               flushout(out1);
+#endif
+               break;
+#endif
+       }
+out:
+       if (pendingsigs)
+               dotrap();
+       if (
+               flags & EV_EXIT ||
+               (checkexit && eflag && exitstatus && !(flags & EV_TESTED))
+       )
+               exitshell(exitstatus);
+}
+
+
+static void
+evalloop(n, flags)
+       union node *n;
+       int flags;
+{
+       int status;
+
+       loopnest++;
+       status = 0;
+       for (;;) {
+               evaltree(n->nbinary.ch1, EV_TESTED);
+               if (evalskip) {
+skipping:        if (evalskip == SKIPCONT && --skipcount <= 0) {
+                               evalskip = 0;
+                               continue;
+                       }
+                       if (evalskip == SKIPBREAK && --skipcount <= 0)
+                               evalskip = 0;
+                       break;
+               }
+               if (n->type == NWHILE) {
+                       if (exitstatus != 0)
+                               break;
+               } else {
+                       if (exitstatus == 0)
+                               break;
+               }
+               evaltree(n->nbinary.ch2, flags & EV_TESTED);
+               status = exitstatus;
+               if (evalskip)
+                       goto skipping;
+       }
+       loopnest--;
+       exitstatus = status;
+}
+
+
+
+static void
+evalfor(n, flags)
+    union node *n;
+    int flags;
+{
+       struct arglist arglist;
+       union node *argp;
+       struct strlist *sp;
+       struct stackmark smark;
+
+       setstackmark(&smark);
+       arglist.lastp = &arglist.list;
+       for (argp = n->nfor.args ; argp ; argp = argp->narg.next) {
+               oexitstatus = exitstatus;
+               expandarg(argp, &arglist, EXP_FULL | EXP_TILDE | EXP_RECORD);
+               if (evalskip)
+                       goto out;
+       }
+       *arglist.lastp = NULL;
+
+       exitstatus = 0;
+       loopnest++;
+       for (sp = arglist.list ; sp ; sp = sp->next) {
+               setvar(n->nfor.var, sp->text, 0);
+               evaltree(n->nfor.body, flags & EV_TESTED);
+               if (evalskip) {
+                       if (evalskip == SKIPCONT && --skipcount <= 0) {
+                               evalskip = 0;
+                               continue;
+                       }
+                       if (evalskip == SKIPBREAK && --skipcount <= 0)
+                               evalskip = 0;
+                       break;
+               }
+       }
+       loopnest--;
+out:
+       popstackmark(&smark);
+}
+
+
+
+static void
+evalcase(n, flags)
+       union node *n;
+       int flags;
+{
+       union node *cp;
+       union node *patp;
+       struct arglist arglist;
+       struct stackmark smark;
+
+       setstackmark(&smark);
+       arglist.lastp = &arglist.list;
+       oexitstatus = exitstatus;
+       expandarg(n->ncase.expr, &arglist, EXP_TILDE);
+       for (cp = n->ncase.cases ; cp && evalskip == 0 ; cp = cp->nclist.next) {
+               for (patp = cp->nclist.pattern ; patp ; patp = patp->narg.next) {
+                       if (casematch(patp, arglist.list->text)) {
+                               if (evalskip == 0) {
+                                       evaltree(cp->nclist.body, flags);
+                               }
+                               goto out;
+                       }
+               }
+       }
+out:
+       popstackmark(&smark);
+}
+
+
+
+/*
+ * Kick off a subshell to evaluate a tree.
+ */
+
+static void
+evalsubshell(n, flags)
+       union node *n;
+       int flags;
+{
+       struct job *jp;
+       int backgnd = (n->type == NBACKGND);
+
+       expredir(n->nredir.redirect);
+       jp = makejob(n, 1);
+       if (forkshell(jp, n, backgnd) == 0) {
+               if (backgnd)
+                       flags &=~ EV_TESTED;
+               redirect(n->nredir.redirect, 0);
+               evaltree(n->nredir.n, flags | EV_EXIT); /* never returns */
+       }
+       if (! backgnd) {
+               INTOFF;
+               exitstatus = waitforjob(jp);
+               INTON;
+       }
+}
+
+
+
+/*
+ * Compute the names of the files in a redirection list.
+ */
+
+static void
+expredir(n)
+       union node *n;
+{
+       union node *redir;
+
+       for (redir = n ; redir ; redir = redir->nfile.next) {
+               struct arglist fn;
+               fn.lastp = &fn.list;
+               oexitstatus = exitstatus;
+               switch (redir->type) {
+               case NFROMTO:
+               case NFROM:
+               case NTO:
+               case NAPPEND:
+               case NTOOV:
+                       expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR);
+                       redir->nfile.expfname = fn.list->text;
+                       break;
+               case NFROMFD:
+               case NTOFD:
+                       if (redir->ndup.vname) {
+                               expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE);
+                               fixredir(redir, fn.list->text, 1);
+                       }
+                       break;
+               }
+       }
+}
+
+
+
+/*
+ * Evaluate a pipeline.  All the processes in the pipeline are children
+ * of the process creating the pipeline.  (This differs from some versions
+ * of the shell, which make the last process in a pipeline the parent
+ * of all the rest.)
+ */
+
+static void
+evalpipe(n)
+       union node *n;
+{
+       struct job *jp;
+       struct nodelist *lp;
+       int pipelen;
+       int prevfd;
+       int pip[2];
+
+       TRACE(("evalpipe(0x%lx) called\n", (long)n));
+       pipelen = 0;
+       for (lp = n->npipe.cmdlist ; lp ; lp = lp->next)
+               pipelen++;
+       INTOFF;
+       jp = makejob(n, pipelen);
+       prevfd = -1;
+       for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
+               prehash(lp->n);
+               pip[1] = -1;
+               if (lp->next) {
+                       if (pipe(pip) < 0) {
+                               close(prevfd);
+                               error("Pipe call failed");
+                       }
+               }
+               if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) {
+                       INTON;
+                       if (prevfd > 0) {
+                               close(0);
+                               dup_as_newfd(prevfd, 0);
+                               close(prevfd);
+                               if (pip[0] == 0) {
+                                       pip[0] = -1;
+                               }
+                       }
+                       if (pip[1] >= 0) {
+                               if (pip[0] >= 0) {
+                                       close(pip[0]);
+                               }
+                               if (pip[1] != 1) {
+                                       close(1);
+                                       dup_as_newfd(pip[1], 1);
+                                       close(pip[1]);
+                               }
+                       }
+                       evaltree(lp->n, EV_EXIT);
+               }
+               if (prevfd >= 0)
+                       close(prevfd);
+               prevfd = pip[0];
+               close(pip[1]);
+       }
+       INTON;
+       if (n->npipe.backgnd == 0) {
+               INTOFF;
+               exitstatus = waitforjob(jp);
+               TRACE(("evalpipe:  job done exit status %d\n", exitstatus));
+               INTON;
+       }
+}
+
+
+
+/*
+ * Execute a command inside back quotes.  If it's a builtin command, we
+ * want to save its output in a block obtained from malloc.  Otherwise
+ * we fork off a subprocess and get the output of the command via a pipe.
+ * Should be called with interrupts off.
+ */
+
+static void
+evalbackcmd(n, result)
+       union node *n;
+       struct backcmd *result;
+{
+       int pip[2];
+       struct job *jp;
+       struct stackmark smark;         /* unnecessary */
+
+       setstackmark(&smark);
+       result->fd = -1;
+       result->buf = NULL;
+       result->nleft = 0;
+       result->jp = NULL;
+       if (n == NULL) {
+               exitstatus = 0;
+               goto out;
+       }
+#ifdef notyet
+       /*
+        * For now we disable executing builtins in the same
+        * context as the shell, because we are not keeping
+        * enough state to recover from changes that are
+        * supposed only to affect subshells. eg. echo "`cd /`"
+        */
+       if (n->type == NCMD) {
+               exitstatus = oexitstatus;
+               evalcommand(n, EV_BACKCMD, result);
+       } else
+#endif
+       {
+               exitstatus = 0;
+               if (pipe(pip) < 0)
+                       error("Pipe call failed");
+               jp = makejob(n, 1);
+               if (forkshell(jp, n, FORK_NOJOB) == 0) {
+                       FORCEINTON;
+                       close(pip[0]);
+                       if (pip[1] != 1) {
+                               close(1);
+                               dup_as_newfd(pip[1], 1);
+                               close(pip[1]);
+                       }
+                       eflag = 0;
+                       evaltree(n, EV_EXIT);
+               }
+               close(pip[1]);
+               result->fd = pip[0];
+               result->jp = jp;
+       }
+out:
+       popstackmark(&smark);
+       TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n",
+               result->fd, result->buf, result->nleft, result->jp));
+}
+
+
+
+/*
+ * Execute a simple command.
+ */
+
+static void
+#ifdef notyet
+evalcommand(cmd, flags, backcmd)
+       union node *cmd;
+       int flags;
+       struct backcmd *backcmd;
+#else
+evalcommand(cmd, flags)
+       union node *cmd;
+       int flags;
+#endif
+{
+       struct stackmark smark;
+       union node *argp;
+       struct arglist arglist;
+       struct arglist varlist;
+       char **argv;
+       int argc;
+       char **envp;
+       struct strlist *sp;
+       int mode;
+#ifdef notyet
+       int pip[2];
+#endif
+       struct cmdentry cmdentry;
+       struct job *jp;
+       char *volatile savecmdname;
+       volatile struct shparam saveparam;
+       struct localvar *volatile savelocalvars;
+       volatile int e;
+       char *lastarg;
+       const char *path;
+       const struct builtincmd *firstbltin;
+       struct jmploc *volatile savehandler;
+       struct jmploc jmploc;
+#if __GNUC__
+       /* Avoid longjmp clobbering */
+       (void) &argv;
+       (void) &argc;
+       (void) &lastarg;
+       (void) &flags;
+#endif
+
+       /* First expand the arguments. */
+       TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
+       setstackmark(&smark);
+       arglist.lastp = &arglist.list;
+       varlist.lastp = &varlist.list;
+       arglist.list = 0;
+       oexitstatus = exitstatus;
+       exitstatus = 0;
+       path = pathval();
+       for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) {
+               expandarg(argp, &varlist, EXP_VARTILDE);
+       }
+       for (
+               argp = cmd->ncmd.args; argp && !arglist.list;
+               argp = argp->narg.next
+       ) {
+               expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
+       }
+       if (argp) {
+               struct builtincmd *bcmd;
+               bool pseudovarflag;
+               bcmd = find_builtin(arglist.list->text);
+               pseudovarflag = bcmd && bcmd->flags & BUILTIN_ASSIGN;
+               for (; argp; argp = argp->narg.next) {
+                       if (pseudovarflag && isassignment(argp->narg.text)) {
+                               expandarg(argp, &arglist, EXP_VARTILDE);
+                               continue;
+                       }
+                       expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
+               }
+       }
+       *arglist.lastp = NULL;
+       *varlist.lastp = NULL;
+       expredir(cmd->ncmd.redirect);
+       argc = 0;
+       for (sp = arglist.list ; sp ; sp = sp->next)
+               argc++;
+       argv = stalloc(sizeof (char *) * (argc + 1));
+
+       for (sp = arglist.list ; sp ; sp = sp->next) {
+               TRACE(("evalcommand arg: %s\n", sp->text));
+               *argv++ = sp->text;
+       }
+       *argv = NULL;
+       lastarg = NULL;
+       if (iflag && funcnest == 0 && argc > 0)
+               lastarg = argv[-1];
+       argv -= argc;
+
+       /* Print the command if xflag is set. */
+       if (xflag) {
+#ifdef FLUSHERR
+               outc('+', &errout);
+#else
+               outcslow('+', &errout);
+#endif
+               eprintlist(varlist.list);
+               eprintlist(arglist.list);
+#ifdef FLUSHERR
+               outc('\n', &errout);
+               flushout(&errout);
+#else
+               outcslow('\n', &errout);
+#endif
+       }
+
+       /* Now locate the command. */
+       if (argc == 0) {
+               cmdentry.cmdtype = CMDBUILTIN;
+               firstbltin = cmdentry.u.cmd = BLTINCMD;
+       } else {
+               const char *oldpath;
+               int findflag = DO_ERR;
+               int oldfindflag;
+
+               /*
+                * Modify the command lookup path, if a PATH= assignment
+                * is present
+                */
+               for (sp = varlist.list ; sp ; sp = sp->next)
+                       if (varequal(sp->text, defpathvar)) {
+                               path = sp->text + 5;
+                               findflag |= DO_BRUTE;
+                       }
+               oldpath = path;
+               oldfindflag = findflag;
+               firstbltin = 0;
+               for(;;) {
+                       find_command(argv[0], &cmdentry, findflag, path);
+                       if (cmdentry.cmdtype == CMDUNKNOWN) {   /* command not found */
+                               exitstatus = 127;
+#ifdef FLUSHERR
+                               flushout(&errout);
+#endif
+                               goto out;
+                       }
+                       /* implement bltin and command here */
+                       if (cmdentry.cmdtype != CMDBUILTIN) {
+                               break;
+                       }
+                       if (!firstbltin) {
+                               firstbltin = cmdentry.u.cmd;
+                       }
+                       if (cmdentry.u.cmd == BLTINCMD) {
+                               for(;;) {
+                                       struct builtincmd *bcmd;
+
+                                       argv++;
+                                       if (--argc == 0)
+                                               goto found;
+                                       if (!(bcmd = find_builtin(*argv))) {
+                                               outfmt(&errout, "%s: not found\n", *argv);
+                                               exitstatus = 127;
+#ifdef FLUSHERR
+                                               flushout(&errout);
+#endif
+                                               goto out;
+                                       }
+                                       cmdentry.u.cmd = bcmd;
+                                       if (bcmd != BLTINCMD)
+                                               break;
+                               }
+                       }
+                       if (cmdentry.u.cmd == COMMANDCMD) {
+                               argv++;
+                               if (--argc == 0) {
+                                       goto found;
+                               }
+                               if (*argv[0] == '-') {
+                                       if (!equal(argv[0], "-p")) {
+                                               argv--;
+                                               argc++;
+                                               break;
+                                       }
+                                       argv++;
+                                       if (--argc == 0) {
+                                               goto found;
+                                       }
+                                       path = defpath;
+                                       findflag |= DO_BRUTE;
+                               } else {
+                                       path = oldpath;
+                                       findflag = oldfindflag;
+                               }
+                               findflag |= DO_NOFUN;
+                               continue;
+                       }
+found:
+                       break;
+               }
+       }
+
+       /* Fork off a child process if necessary. */
+       if (cmd->ncmd.backgnd
+        || (cmdentry.cmdtype == CMDNORMAL && (flags & EV_EXIT) == 0)
+#ifdef notyet
+        || ((flags & EV_BACKCMD) != 0
+           && (cmdentry.cmdtype != CMDBUILTIN
+                || cmdentry.u.bcmd == DOTCMD
+                || cmdentry.u.bcmd == EVALCMD))
+#endif
+       ) {
+               jp = makejob(cmd, 1);
+               mode = cmd->ncmd.backgnd;
+#ifdef notyet
+               if (flags & EV_BACKCMD) {
+                       mode = FORK_NOJOB;
+                       if (pipe(pip) < 0)
+                               error("Pipe call failed");
+               }
+#endif
+               if (forkshell(jp, cmd, mode) != 0)
+                       goto parent;    /* at end of routine */
+#ifdef notyet
+               if (flags & EV_BACKCMD) {
+                       FORCEINTON;
+                       close(pip[0]);
+                       if (pip[1] != 1) {
+                               close(1);
+                               dup_as_newfd(pip[1], 1);
+                               close(pip[1]);
+                       }
+               }
+#endif
+               flags |= EV_EXIT;
+       }
+
+       /* This is the child process if a fork occurred. */
+       /* Execute the command. */
+       if (cmdentry.cmdtype == CMDFUNCTION) {
+#ifdef DEBUG
+               trputs("Shell function:  ");  trargs(argv);
+#endif
+               exitstatus = oexitstatus;
+               redirect(cmd->ncmd.redirect, REDIR_PUSH);
+               saveparam = shellparam;
+               shellparam.malloc = 0;
+               shellparam.nparam = argc - 1;
+               shellparam.p = argv + 1;
+               INTOFF;
+               savelocalvars = localvars;
+               localvars = NULL;
+               INTON;
+               if (setjmp(jmploc.loc)) {
+                       if (exception == EXSHELLPROC) {
+                               freeparam((volatile struct shparam *)
+                                   &saveparam);
+                       } else {
+                               saveparam.optind = shellparam.optind;
+                               saveparam.optoff = shellparam.optoff;
+                               freeparam(&shellparam);
+                               shellparam = saveparam;
+                       }
+                       poplocalvars();
+                       localvars = savelocalvars;
+                       handler = savehandler;
+                       longjmp(handler->loc, 1);
+               }
+               savehandler = handler;
+               handler = &jmploc;
+               for (sp = varlist.list ; sp ; sp = sp->next)
+                       mklocal(sp->text);
+               funcnest++;
+               evaltree(cmdentry.u.func, flags & EV_TESTED);
+               funcnest--;
+               INTOFF;
+               poplocalvars();
+               localvars = savelocalvars;
+               saveparam.optind = shellparam.optind;
+               saveparam.optoff = shellparam.optoff;
+               freeparam(&shellparam);
+               shellparam = saveparam;
+               handler = savehandler;
+               popredir();
+               INTON;
+               if (evalskip == SKIPFUNC) {
+                       evalskip = 0;
+                       skipcount = 0;
+               }
+               if (flags & EV_EXIT)
+                       exitshell(exitstatus);
+       } else if (cmdentry.cmdtype == CMDBUILTIN) {
+#ifdef DEBUG
+               trputs("builtin command:  ");  trargs(argv);
+#endif
+               mode = (cmdentry.u.cmd == EXECCMD)? 0 : REDIR_PUSH;
+#ifdef notyet
+               if (flags == EV_BACKCMD) {
+#ifdef USE_GLIBC_STDIO
+                       openmemout();
+#else
+                       memout.nleft = 0;
+                       memout.nextc = memout.buf;
+                       memout.bufsize = 64;
+#endif
+                       mode |= REDIR_BACKQ;
+               }
+#endif
+               redirect(cmd->ncmd.redirect, mode);
+               savecmdname = commandname;
+               if (firstbltin->flags & BUILTIN_SPECIAL) {
+                       listsetvar(varlist.list);
+               } else {
+                       cmdenviron = varlist.list;
+               }
+               e = -1;
+               if (setjmp(jmploc.loc)) {
+                       e = exception;
+                       exitstatus = (e == EXINT)? SIGINT+128 : 2;
+                       goto cmddone;
+               }
+               savehandler = handler;
+               handler = &jmploc;
+               commandname = argv[0];
+               argptr = argv + 1;
+               optptr = NULL;                  /* initialize nextopt */
+               exitstatus = (*cmdentry.u.cmd->builtinfunc)(argc, argv);
+               flushall();
+cmddone:
+               exitstatus |= outerr(out1);
+               out1 = &output;
+               out2 = &errout;
+               freestdout();
+               cmdenviron = NULL;
+               if (e != EXSHELLPROC) {
+                       commandname = savecmdname;
+                       if (flags & EV_EXIT)
+                               exitshell(exitstatus);
+               }
+               handler = savehandler;
+               if (e != -1) {
+                       if ((e != EXERROR && e != EXEXEC)
+                          || cmdentry.u.cmd == BLTINCMD
+                          || cmdentry.u.cmd == DOTCMD
+                          || cmdentry.u.cmd == EVALCMD
+                          || cmdentry.u.cmd == EXECCMD)
+                               exraise(e);
+                       FORCEINTON;
+               }
+               if (cmdentry.u.cmd != EXECCMD)
+                       popredir();
+#ifdef notyet
+               if (flags == EV_BACKCMD) {
+                       INTOFF;
+#ifdef USE_GLIBC_STDIO
+                       if (__closememout()) {
+                               error(
+                                       "__closememout() failed: %s",
+                                       strerror(errno)
+                               );
+                       }
+#endif
+                       backcmd->buf = memout.buf;
+#ifdef USE_GLIBC_STDIO
+                       backcmd->nleft = memout.bufsize;
+#else
+                       backcmd->nleft = memout.nextc - memout.buf;
+#endif
+                       memout.buf = NULL;
+                       INTON;
+               }
+#endif
+       } else {
+#ifdef DEBUG
+               trputs("normal command:  ");  trargs(argv);
+#endif
+               redirect(cmd->ncmd.redirect, 0);
+               clearredir();
+               for (sp = varlist.list ; sp ; sp = sp->next)
+                       setvareq(sp->text, VEXPORT|VSTACK);
+               envp = environment();
+               shellexec(argv, envp, path, cmdentry.u.index);
+       }
+       goto out;
+
+parent:        /* parent process gets here (if we forked) */
+       if (mode == 0) {        /* argument to fork */
+               INTOFF;
+               exitstatus = waitforjob(jp);
+               INTON;
+#ifdef notyet
+       } else if (mode == 2) {
+               backcmd->fd = pip[0];
+               close(pip[1]);
+               backcmd->jp = jp;
+#endif
+       }
+
+out:
+       if (lastarg)
+               setvar("_", lastarg, 0);
+       popstackmark(&smark);
+}
+
+
+
+/*
+ * Search for a command.  This is called before we fork so that the
+ * location of the command will be available in the parent as well as
+ * the child.  The check for "goodname" is an overly conservative
+ * check that the name will not be subject to expansion.
+ */
+
+static void
+prehash(n)
+       union node *n;
+{
+       struct cmdentry entry;
+
+       if (n->type == NCMD && n->ncmd.args)
+               if (goodname(n->ncmd.args->narg.text))
+                       find_command(n->ncmd.args->narg.text, &entry, 0,
+                                    pathval());
+}
+
+
+
+/*
+ * Builtin commands.  Builtin commands whose functions are closely
+ * tied to evaluation are implemented here.
+ */
+
+/*
+ * No command given, or a bltin command with no arguments.  Set the
+ * specified variables.
+ */
+
+int
+bltincmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       /*
+        * Preserve exitstatus of a previous possible redirection
+        * as POSIX mandates
+        */
+       return exitstatus;
+}
+
+
+/*
+ * Handle break and continue commands.  Break, continue, and return are
+ * all handled by setting the evalskip flag.  The evaluation routines
+ * above all check this flag, and if it is set they start skipping
+ * commands rather than executing them.  The variable skipcount is
+ * the number of loops to break/continue, or the number of function
+ * levels to return.  (The latter is always 1.)  It should probably
+ * be an error to break out of more loops than exist, but it isn't
+ * in the standard shell so we don't make it one here.
+ */
+
+static int
+breakcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       int n = argc > 1 ? number(argv[1]) : 1;
+
+       if (n > loopnest)
+               n = loopnest;
+       if (n > 0) {
+               evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK;
+               skipcount = n;
+       }
+       return 0;
+}
+
+
+/*
+ * The return command.
+ */
+
+static int
+returncmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       int ret = argc > 1 ? number(argv[1]) : oexitstatus;
+
+       if (funcnest) {
+               evalskip = SKIPFUNC;
+               skipcount = 1;
+               return ret;
+       }
+       else {
+               /* Do what ksh does; skip the rest of the file */
+               evalskip = SKIPFILE;
+               skipcount = 1;
+               return ret;
+       }
+}
+
+
+#ifndef BB_TRUE_FALSE
+static int
+false_main(argc, argv)
+       int argc;
+       char **argv;
+{
+       return 1;
+}
+
+
+static int
+true_main(argc, argv)
+       int argc;
+       char **argv;
+{
+       return 0;
+}
+#endif
+
+static int
+execcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       if (argc > 1) {
+               struct strlist *sp;
+
+               iflag = 0;              /* exit on error */
+               mflag = 0;
+               optschanged();
+               for (sp = cmdenviron; sp ; sp = sp->next)
+                       setvareq(sp->text, VEXPORT|VSTACK);
+               shellexec(argv + 1, environment(), pathval(), 0);
+       }
+       return 0;
+}
+
+static void
+eprintlist(struct strlist *sp)
+{
+       for (; sp; sp = sp->next) {
+               outfmt(&errout, " %s",sp->text);
+       }
+}
+/*     $NetBSD: exec.c,v 1.32 2001/02/04 19:52:06 christos Exp $       */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * When commands are first encountered, they are entered in a hash table.
+ * This ensures that a full path search will not have to be done for them
+ * on each invocation.
+ *
+ * We should investigate converting to a linear search, even though that
+ * would make the command name "hash" a misnomer.
+ */
+#define CMDTABLESIZE 31                /* should be prime */
+#define ARB 1                  /* actual size determined at run time */
+
+
+
+struct tblentry {
+       struct tblentry *next;  /* next entry in hash chain */
+       union param param;      /* definition of builtin function */
+       short cmdtype;          /* index identifying command */
+       char rehash;            /* if set, cd done since entry created */
+       char cmdname[ARB];      /* name of command */
+};
+
+
+static struct tblentry *cmdtable[CMDTABLESIZE];
+static int builtinloc = -1;            /* index in path of %builtin, or -1 */
+static int exerrno = 0;                        /* Last exec error */
+
+
+static void tryexec __P((char *, char **, char **));
+#if !defined(BSD) && !defined(linux)
+static void execinterp __P((char **, char **));
+#endif
+static void printentry __P((struct tblentry *, int));
+static void clearcmdentry __P((int));
+static struct tblentry *cmdlookup __P((char *, int));
+static void delete_cmd_entry __P((void));
+#ifdef ASH_TYPE
+static int describe_command __P((char *, int));
+#endif
+static int path_change __P((const char *, int *));
+
+
+/*
+ * Exec a program.  Never returns.  If you change this routine, you may
+ * have to change the find_command routine as well.
+ */
+
+static void
+shellexec(argv, envp, path, idx)
+       char **argv, **envp;
+       const char *path;
+       int idx;
+{
+       char *cmdname;
+       int e;
+
+       if (strchr(argv[0], '/') != NULL) {
+               tryexec(argv[0], argv, envp);
+               e = errno;
+       } else {
+               e = ENOENT;
+               while ((cmdname = padvance(&path, argv[0])) != NULL) {
+                       if (--idx < 0 && pathopt == NULL) {
+                               tryexec(cmdname, argv, envp);
+                               if (errno != ENOENT && errno != ENOTDIR)
+                                       e = errno;
+                       }
+                       stunalloc(cmdname);
+               }
+       }
+
+       /* Map to POSIX errors */
+       switch (e) {
+       case EACCES:
+               exerrno = 126;
+               break;
+       case ENOENT:
+               exerrno = 127;
+               break;
+       default:
+               exerrno = 2;
+               break;
+       }
+       exerror(EXEXEC, "%s: %s", argv[0], errmsg(e, E_EXEC));
+       /* NOTREACHED */
+}
+
+
+static void
+tryexec(cmd, argv, envp)
+       char *cmd;
+       char **argv;
+       char **envp;
+       {
+       int e;
+#if !defined(BSD) && !defined(linux)
+       char *p;
+#endif
+
+#ifdef SYSV
+       do {
+               execve(cmd, argv, envp);
+       } while (errno == EINTR);
+#else
+       execve(cmd, argv, envp);
+#endif
+       e = errno;
+       if (e == ENOEXEC) {
+               INTOFF;
+               initshellproc();
+               setinputfile(cmd, 0);
+               commandname = arg0 = savestr(argv[0]);
+#if !defined(BSD) && !defined(linux)
+               INTON;
+               pgetc(); pungetc();             /* fill up input buffer */
+               p = parsenextc;
+               if (parsenleft > 2 && p[0] == '#' && p[1] == '!') {
+                       argv[0] = cmd;
+                       execinterp(argv, envp);
+               }
+               INTOFF;
+#endif
+               setparam(argv + 1);
+               exraise(EXSHELLPROC);
+       }
+       errno = e;
+}
+
+
+#if !defined(BSD) && !defined(linux)
+/*
+ * Execute an interpreter introduced by "#!", for systems where this
+ * feature has not been built into the kernel.  If the interpreter is
+ * the shell, return (effectively ignoring the "#!").  If the execution
+ * of the interpreter fails, exit.
+ *
+ * This code peeks inside the input buffer in order to avoid actually
+ * reading any input.  It would benefit from a rewrite.
+ */
+
+#define NEWARGS 5
+
+static void
+execinterp(argv, envp)
+       char **argv, **envp;
+       {
+       int n;
+       char *inp;
+       char *outp;
+       char c;
+       char *p;
+       char **ap;
+       char *newargs[NEWARGS];
+       int i;
+       char **ap2;
+       char **new;
+
+       n = parsenleft - 2;
+       inp = parsenextc + 2;
+       ap = newargs;
+       for (;;) {
+               while (--n >= 0 && (*inp == ' ' || *inp == '\t'))
+                       inp++;
+               if (n < 0)
+                       goto bad;
+               if ((c = *inp++) == '\n')
+                       break;
+               if (ap == &newargs[NEWARGS])
+bad:             error("Bad #! line");
+               STARTSTACKSTR(outp);
+               do {
+                       STPUTC(c, outp);
+               } while (--n >= 0 && (c = *inp++) != ' ' && c != '\t' && c != '\n');
+               STPUTC('\0', outp);
+               n++, inp--;
+               *ap++ = grabstackstr(outp);
+       }
+       if (ap == newargs + 1) {        /* if no args, maybe no exec is needed */
+               p = newargs[0];
+               for (;;) {
+                       if (equal(p, "sh") || equal(p, "ash")) {
+                               return;
+                       }
+                       while (*p != '/') {
+                               if (*p == '\0')
+                                       goto break2;
+                               p++;
+                       }
+                       p++;
+               }
+break2:;
+       }
+       i = (char *)ap - (char *)newargs;               /* size in bytes */
+       if (i == 0)
+               error("Bad #! line");
+       for (ap2 = argv ; *ap2++ != NULL ; );
+       new = ckmalloc(i + ((char *)ap2 - (char *)argv));
+       ap = newargs, ap2 = new;
+       while ((i -= sizeof (char **)) >= 0)
+               *ap2++ = *ap++;
+       ap = argv;
+       while (*ap2++ = *ap++);
+       shellexec(new, envp, pathval(), 0);
+       /* NOTREACHED */
+}
+#endif
+
+
+
+/*
+ * Do a path search.  The variable path (passed by reference) should be
+ * set to the start of the path before the first call; padvance will update
+ * this value as it proceeds.  Successive calls to padvance will return
+ * the possible path expansions in sequence.  If an option (indicated by
+ * a percent sign) appears in the path entry then the global variable
+ * pathopt will be set to point to it; otherwise pathopt will be set to
+ * NULL.
+ */
+
+static const char *pathopt;
+
+static char *
+padvance(path, name)
+       const char **path;
+       const char *name;
+       {
+       const char *p;
+       char *q;
+       const char *start;
+       int len;
+
+       if (*path == NULL)
+               return NULL;
+       start = *path;
+       for (p = start ; *p && *p != ':' && *p != '%' ; p++);
+       len = p - start + strlen(name) + 2;     /* "2" is for '/' and '\0' */
+       while (stackblocksize() < len)
+               growstackblock();
+       q = stackblock();
+       if (p != start) {
+               memcpy(q, start, p - start);
+               q += p - start;
+               *q++ = '/';
+       }
+       strcpy(q, name);
+       pathopt = NULL;
+       if (*p == '%') {
+               pathopt = ++p;
+               while (*p && *p != ':')  p++;
+       }
+       if (*p == ':')
+               *path = p + 1;
+       else
+               *path = NULL;
+       return stalloc(len);
+}
+
+
+
+/*** Command hashing code ***/
+
+
+static int
+hashcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       struct tblentry **pp;
+       struct tblentry *cmdp;
+       int c;
+       int verbose;
+       struct cmdentry entry;
+       char *name;
+
+       verbose = 0;
+       while ((c = nextopt("rv")) != '\0') {
+               if (c == 'r') {
+                       clearcmdentry(0);
+                       return 0;
+               } else if (c == 'v') {
+                       verbose++;
+               }
+       }
+       if (*argptr == NULL) {
+               for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) {
+                       for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
+                               if (cmdp->cmdtype != CMDBUILTIN) {
+                                       printentry(cmdp, verbose);
+                               }
+                       }
+               }
+               return 0;
+       }
+       c = 0;
+       while ((name = *argptr) != NULL) {
+               if ((cmdp = cmdlookup(name, 0)) != NULL
+                && (cmdp->cmdtype == CMDNORMAL
+                    || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0)))
+                       delete_cmd_entry();
+               find_command(name, &entry, DO_ERR, pathval());
+               if (entry.cmdtype == CMDUNKNOWN) c = 1;
+               else if (verbose) {
+                       cmdp = cmdlookup(name, 0);
+                       if (cmdp) printentry(cmdp, verbose);
+                       flushall();
+               }
+               argptr++;
+       }
+       return c;
+}
+
+
+static void
+printentry(cmdp, verbose)
+       struct tblentry *cmdp;
+       int verbose;
+       {
+       int idx;
+       const char *path;
+       char *name;
+
+       if (cmdp->cmdtype == CMDNORMAL) {
+               idx = cmdp->param.index;
+               path = pathval();
+               do {
+                       name = padvance(&path, cmdp->cmdname);
+                       stunalloc(name);
+               } while (--idx >= 0);
+               out1str(name);
+       } else if (cmdp->cmdtype == CMDBUILTIN) {
+               out1fmt("builtin %s", cmdp->cmdname);
+       } else if (cmdp->cmdtype == CMDFUNCTION) {
+               out1fmt("function %s", cmdp->cmdname);
+               if (verbose) {
+                       INTOFF;
+                       name = commandtext(cmdp->param.func);
+                       out1fmt(" %s", name);
+                       ckfree(name);
+                       INTON;
+               }
+#ifdef DEBUG
+       } else {
+               error("internal error: cmdtype %d", cmdp->cmdtype);
+#endif
+       }
+       out1fmt(snlfmt, cmdp->rehash ? "*" : nullstr);
+}
+
+
+
+/*
+ * Resolve a command name.  If you change this routine, you may have to
+ * change the shellexec routine as well.
+ */
+
+static void
+find_command(name, entry, act, path)
+       char *name;
+       struct cmdentry *entry;
+       int act;
+       const char *path;
+{
+       struct tblentry *cmdp;
+       int idx;
+       int prev;
+       char *fullname;
+       struct stat statb;
+       int e;
+       int bltin;
+       int firstchange;
+       int updatetbl;
+       bool regular;
+       struct builtincmd *bcmd;
+
+       /* If name contains a slash, don't use the hash table */
+       if (strchr(name, '/') != NULL) {
+               if (act & DO_ABS) {
+                       while (stat(name, &statb) < 0) {
+       #ifdef SYSV
+                               if (errno == EINTR)
+                                       continue;
+       #endif
+                               if (errno != ENOENT && errno != ENOTDIR)
+                                       e = errno;
+                               entry->cmdtype = CMDUNKNOWN;
+                               entry->u.index = -1;
+                               return;
+                       }
+                       entry->cmdtype = CMDNORMAL;
+                       entry->u.index = -1;
+                       return;
+               }
+               entry->cmdtype = CMDNORMAL;
+               entry->u.index = 0;
+               return;
+       }
+
+       updatetbl = 1;
+       if (act & DO_BRUTE) {
+               firstchange = path_change(path, &bltin);
+       } else {
+               bltin = builtinloc;
+               firstchange = 9999;
+       }
+
+       /* If name is in the table, and not invalidated by cd, we're done */
+       if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->rehash == 0) {
+               if (cmdp->cmdtype == CMDFUNCTION) {
+                       if (act & DO_NOFUN) {
+                               updatetbl = 0;
+                       } else {
+                               goto success;
+                       }
+               } else if (act & DO_BRUTE) {
+                       if ((cmdp->cmdtype == CMDNORMAL &&
+                            cmdp->param.index >= firstchange) ||
+                           (cmdp->cmdtype == CMDBUILTIN &&
+                            ((builtinloc < 0 && bltin >= 0) ?
+                             bltin : builtinloc) >= firstchange)) {
+                               /* need to recompute the entry */
+                       } else {
+                               goto success;
+                       }
+               } else {
+                       goto success;
+               }
+       }
+
+       bcmd = find_builtin(name);
+       regular = bcmd && bcmd->flags & BUILTIN_REGULAR;
+
+       if (regular) {
+               if (cmdp && (cmdp->cmdtype == CMDBUILTIN)) {
+                       goto success;
+               }
+       } else if (act & DO_BRUTE) {
+               if (firstchange == 0) {
+                       updatetbl = 0;
+               }
+       }
+
+       /* If %builtin not in path, check for builtin next */
+       if (regular || (bltin < 0 && bcmd)) {
+builtin:
+               if (!updatetbl) {
+                       entry->cmdtype = CMDBUILTIN;
+                       entry->u.cmd = bcmd;
+                       return;
+               }
+               INTOFF;
+               cmdp = cmdlookup(name, 1);
+               cmdp->cmdtype = CMDBUILTIN;
+               cmdp->param.cmd = bcmd;
+               INTON;
+               goto success;
+       }
+
+       /* We have to search path. */
+       prev = -1;              /* where to start */
+       if (cmdp && cmdp->rehash) {     /* doing a rehash */
+               if (cmdp->cmdtype == CMDBUILTIN)
+                       prev = builtinloc;
+               else
+                       prev = cmdp->param.index;
+       }
+
+       e = ENOENT;
+       idx = -1;
+loop:
+       while ((fullname = padvance(&path, name)) != NULL) {
+               stunalloc(fullname);
+               idx++;
+               if (idx >= firstchange) {
+                       updatetbl = 0;
+               }
+               if (pathopt) {
+                       if (prefix("builtin", pathopt)) {
+                               if ((bcmd = find_builtin(name))) {
+                                       goto builtin;
+                               }
+                               continue;
+                       } else if (!(act & DO_NOFUN) &&
+                                  prefix("func", pathopt)) {
+                               /* handled below */
+                       } else {
+                               continue;       /* ignore unimplemented options */
+                       }
+               }
+               /* if rehash, don't redo absolute path names */
+               if (fullname[0] == '/' && idx <= prev &&
+                   idx < firstchange) {
+                       if (idx < prev)
+                               continue;
+                       TRACE(("searchexec \"%s\": no change\n", name));
+                       goto success;
+               }
+               while (stat(fullname, &statb) < 0) {
+#ifdef SYSV
+                       if (errno == EINTR)
+                               continue;
+#endif
+                       if (errno != ENOENT && errno != ENOTDIR)
+                               e = errno;
+                       goto loop;
+               }
+               e = EACCES;     /* if we fail, this will be the error */
+               if (!S_ISREG(statb.st_mode))
+                       continue;
+               if (pathopt) {          /* this is a %func directory */
+                       stalloc(strlen(fullname) + 1);
+                       readcmdfile(fullname);
+                       if ((cmdp = cmdlookup(name, 0)) == NULL || cmdp->cmdtype != CMDFUNCTION)
+                               error("%s not defined in %s", name, fullname);
+                       stunalloc(fullname);
+                       goto success;
+               }
+#ifdef notdef
+               if (statb.st_uid == geteuid()) {
+                       if ((statb.st_mode & 0100) == 0)
+                               goto loop;
+               } else if (statb.st_gid == getegid()) {
+                       if ((statb.st_mode & 010) == 0)
+                               goto loop;
+               } else {
+                       if ((statb.st_mode & 01) == 0)
+                               goto loop;
+               }
+#endif
+               TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname));
+               /* If we aren't called with DO_BRUTE and cmdp is set, it must
+                  be a function and we're being called with DO_NOFUN */
+               if (!updatetbl) {
+                       entry->cmdtype = CMDNORMAL;
+                       entry->u.index = idx;
+                       return;
+               }
+               INTOFF;
+               cmdp = cmdlookup(name, 1);
+               cmdp->cmdtype = CMDNORMAL;
+               cmdp->param.index = idx;
+               INTON;
+               goto success;
+       }
+
+       /* We failed.  If there was an entry for this command, delete it */
+       if (cmdp && updatetbl)
+               delete_cmd_entry();
+       if (act & DO_ERR)
+               outfmt(out2, "%s: %s\n", name, errmsg(e, E_EXEC));
+       entry->cmdtype = CMDUNKNOWN;
+       return;
+
+success:
+       cmdp->rehash = 0;
+       entry->cmdtype = cmdp->cmdtype;
+       entry->u = cmdp->param;
+}
+
+
+
+/*
+ * Search the table of builtin commands.
+ */
+
+struct builtincmd *
+find_builtin(name)
+       char *name;
+{
+       struct builtincmd *bp;
+
+       bp = bsearch( &name, builtincmds, NUMBUILTINS, sizeof(struct builtincmd),
+               pstrcmp
+       );
+       return bp;
+}
+
+
+/*
+ * Called when a cd is done.  Marks all commands so the next time they
+ * are executed they will be rehashed.
+ */
+
+static void
+hashcd() {
+       struct tblentry **pp;
+       struct tblentry *cmdp;
+
+       for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) {
+               for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
+                       if (cmdp->cmdtype == CMDNORMAL
+                        || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0))
+                               cmdp->rehash = 1;
+               }
+       }
+}
+
+
+
+/*
+ * Called before PATH is changed.  The argument is the new value of PATH;
+ * pathval() still returns the old value at this point.  Called with
+ * interrupts off.
+ */
+
+static void
+changepath(newval)
+       const char *newval;
+{
+       int firstchange;
+       int bltin;
+
+       firstchange = path_change(newval, &bltin);
+       if (builtinloc < 0 && bltin >= 0)
+               builtinloc = bltin;             /* zap builtins */
+       clearcmdentry(firstchange);
+       builtinloc = bltin;
+}
+
+
+/*
+ * Clear out command entries.  The argument specifies the first entry in
+ * PATH which has changed.
+ */
+
+static void
+clearcmdentry(firstchange)
+       int firstchange;
+{
+       struct tblentry **tblp;
+       struct tblentry **pp;
+       struct tblentry *cmdp;
+
+       INTOFF;
+       for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) {
+               pp = tblp;
+               while ((cmdp = *pp) != NULL) {
+                       if ((cmdp->cmdtype == CMDNORMAL &&
+                            cmdp->param.index >= firstchange)
+                        || (cmdp->cmdtype == CMDBUILTIN &&
+                            builtinloc >= firstchange)) {
+                               *pp = cmdp->next;
+                               ckfree(cmdp);
+                       } else {
+                               pp = &cmdp->next;
+                       }
+               }
+       }
+       INTON;
+}
+
+
+/*
+ * Delete all functions.
+ */
+
+#ifdef mkinit
+static void deletefuncs __P((void));
+
+SHELLPROC {
+       deletefuncs();
+}
+#endif
+
+static void
+deletefuncs() {
+       struct tblentry **tblp;
+       struct tblentry **pp;
+       struct tblentry *cmdp;
+
+       INTOFF;
+       for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) {
+               pp = tblp;
+               while ((cmdp = *pp) != NULL) {
+                       if (cmdp->cmdtype == CMDFUNCTION) {
+                               *pp = cmdp->next;
+                               freefunc(cmdp->param.func);
+                               ckfree(cmdp);
+                       } else {
+                               pp = &cmdp->next;
+                       }
+               }
+       }
+       INTON;
+}
+
+
+
+/*
+ * Locate a command in the command hash table.  If "add" is nonzero,
+ * add the command to the table if it is not already present.  The
+ * variable "lastcmdentry" is set to point to the address of the link
+ * pointing to the entry, so that delete_cmd_entry can delete the
+ * entry.
+ */
+
+struct tblentry **lastcmdentry;
+
+
+static struct tblentry *
+cmdlookup(name, add)
+       char *name;
+       int add;
+{
+       int hashval;
+       char *p;
+       struct tblentry *cmdp;
+       struct tblentry **pp;
+
+       p = name;
+       hashval = *p << 4;
+       while (*p)
+               hashval += *p++;
+       hashval &= 0x7FFF;
+       pp = &cmdtable[hashval % CMDTABLESIZE];
+       for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
+               if (equal(cmdp->cmdname, name))
+                       break;
+               pp = &cmdp->next;
+       }
+       if (add && cmdp == NULL) {
+               INTOFF;
+               cmdp = *pp = ckmalloc(sizeof (struct tblentry) - ARB
+                                       + strlen(name) + 1);
+               cmdp->next = NULL;
+               cmdp->cmdtype = CMDUNKNOWN;
+               cmdp->rehash = 0;
+               strcpy(cmdp->cmdname, name);
+               INTON;
+       }
+       lastcmdentry = pp;
+       return cmdp;
+}
+
+/*
+ * Delete the command entry returned on the last lookup.
+ */
+
+static void
+delete_cmd_entry() {
+       struct tblentry *cmdp;
+
+       INTOFF;
+       cmdp = *lastcmdentry;
+       *lastcmdentry = cmdp->next;
+       ckfree(cmdp);
+       INTON;
+}
+
+
+
+#ifdef notdef
+static void
+getcmdentry(name, entry)
+       char *name;
+       struct cmdentry *entry;
+       {
+       struct tblentry *cmdp = cmdlookup(name, 0);
+
+       if (cmdp) {
+               entry->u = cmdp->param;
+               entry->cmdtype = cmdp->cmdtype;
+       } else {
+               entry->cmdtype = CMDUNKNOWN;
+               entry->u.index = 0;
+       }
+}
+#endif
+
+
+/*
+ * Add a new command entry, replacing any existing command entry for
+ * the same name.
+ */
+
+static void
+addcmdentry(name, entry)
+       char *name;
+       struct cmdentry *entry;
+       {
+       struct tblentry *cmdp;
+
+       INTOFF;
+       cmdp = cmdlookup(name, 1);
+       if (cmdp->cmdtype == CMDFUNCTION) {
+               freefunc(cmdp->param.func);
+       }
+       cmdp->cmdtype = entry->cmdtype;
+       cmdp->param = entry->u;
+       INTON;
+}
+
+
+/*
+ * Define a shell function.
+ */
+
+static void
+defun(name, func)
+       char *name;
+       union node *func;
+       {
+       struct cmdentry entry;
+
+       entry.cmdtype = CMDFUNCTION;
+       entry.u.func = copyfunc(func);
+       addcmdentry(name, &entry);
+}
+
+
+/*
+ * Delete a function if it exists.
+ */
+
+static void
+unsetfunc(name)
+       char *name;
+       {
+       struct tblentry *cmdp;
+
+       if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->cmdtype == CMDFUNCTION) {
+               freefunc(cmdp->param.func);
+               delete_cmd_entry();
+       }
+}
+
+#ifdef ASH_TYPE
+/*
+ * Locate and print what a word is...
+ */
+
+static int
+typecmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       int i;
+       int err = 0;
+
+       for (i = 1; i < argc; i++) {
+               err |= describe_command(argv[i], 1);
+       }
+       return err;
+}
+
+static int
+describe_command(command, verbose)
+       char *command;
+       int verbose;
+{
+       struct cmdentry entry;
+       struct tblentry *cmdp;
+       const struct alias *ap;
+       const char *path = pathval();
+
+       if (verbose) {
+               out1str(command);
+       }
+
+       /* First look at the keywords */
+       if (findkwd(command)) {
+               out1str(verbose ? " is a shell keyword" : command);
+               goto out;
+       }
+
+       /* Then look at the aliases */
+       if ((ap = lookupalias(command, 0)) != NULL) {
+               if (verbose) {
+                       out1fmt(" is an alias for %s", ap->val);
+               } else {
+                       printalias(ap);
+               }
+               goto out;
+       }
+
+       /* Then check if it is a tracked alias */
+       if ((cmdp = cmdlookup(command, 0)) != NULL) {
+               entry.cmdtype = cmdp->cmdtype;
+               entry.u = cmdp->param;
+       } else {
+               /* Finally use brute force */
+               find_command(command, &entry, DO_ABS, path);
+       }
+
+       switch (entry.cmdtype) {
+       case CMDNORMAL: {
+               int j = entry.u.index;
+               char *p;
+               if (j == -1) {
+                       p = command;
+               } else {
+                       do {
+                               p = padvance(&path, command);
+                               stunalloc(p);
+                       } while (--j >= 0);
+               }
+               if (verbose) {
+                       out1fmt(
+                               " is%s %s",
+                               cmdp ? " a tracked alias for" : nullstr, p
+                       );
+               } else {
+                       out1str(p);
+               }
+               break;
+       }
+
+       case CMDFUNCTION:
+               if (verbose) {
+                       out1str(" is a shell function");
+               } else {
+                       out1str(command);
+               }
+               break;
+
+       case CMDBUILTIN:
+               if (verbose) {
+                       out1fmt(
+                               " is a %sshell builtin",
+                               entry.u.cmd->flags & BUILTIN_SPECIAL ?
+                                       "special " : nullstr
+                       );
+               } else {
+                       out1str(command);
+               }
+               break;
+
+       default:
+               if (verbose) {
+                       out1str(": not found\n");
+               }
+               return 127;
+       }
+
+out:
+       out1c('\n');
+       return 0;
+}
+#endif 
+
+static int
+commandcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       int c;
+       int default_path = 0;
+       int verify_only = 0;
+       int verbose_verify_only = 0;
+
+       while ((c = nextopt("pvV")) != '\0')
+               switch (c) {
+               case 'p':
+                       default_path = 1;
+                       break;
+               case 'v':
+                       verify_only = 1;
+                       break;
+               case 'V':
+                       verbose_verify_only = 1;
+                       break;
+               default:
+                       outfmt(out2,
+"command: nextopt returned character code 0%o\n", c);
+                       return EX_SOFTWARE;
+               }
+
+       if (default_path + verify_only + verbose_verify_only > 1 ||
+           !*argptr) {
+                       outfmt(out2,
+"command [-p] command [arg ...]\n");
+                       outfmt(out2,
+"command {-v|-V} command\n");
+                       return EX_USAGE;
+       }
+
+#ifdef ASH_TYPE
+       if (verify_only || verbose_verify_only) {
+               return describe_command(*argptr, verbose_verify_only);
+       }
+#endif 
+
+       return 0;
+}
+
+static int
+path_change(newval, bltin)
+       const char *newval;
+       int *bltin;
+{
+       const char *old, *new;
+       int idx;
+       int firstchange;
+
+       old = pathval();
+       new = newval;
+       firstchange = 9999;     /* assume no change */
+       idx = 0;
+       *bltin = -1;
+       for (;;) {
+               if (*old != *new) {
+                       firstchange = idx;
+                       if ((*old == '\0' && *new == ':')
+                        || (*old == ':' && *new == '\0'))
+                               firstchange++;
+                       old = new;      /* ignore subsequent differences */
+               }
+               if (*new == '\0')
+                       break;
+               if (*new == '%' && *bltin < 0 && prefix("builtin", new + 1))
+                       *bltin = idx;
+               if (*new == ':') {
+                       idx++;
+               }
+               new++, old++;
+       }
+       if (builtinloc >= 0 && *bltin < 0)
+               firstchange = 0;
+       return firstchange;
+}
+/*     $NetBSD: expand.c,v 1.50 2001/02/04 19:52:06 christos Exp $     */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Routines to expand arguments to commands.  We have to deal with
+ * backquotes, shell variables, and file metacharacters.
+ */
+/*
+ * _rmescape() flags
+ */
+#define RMESCAPE_ALLOC 0x1     /* Allocate a new string */
+#define RMESCAPE_GLOB  0x2     /* Add backslashes for glob */
+
+/*
+ * Structure specifying which parts of the string should be searched
+ * for IFS characters.
+ */
+
+struct ifsregion {
+       struct ifsregion *next; /* next region in list */
+       int begoff;             /* offset of start of region */
+       int endoff;             /* offset of end of region */
+       int nulonly;            /* search for nul bytes only */
+};
+
+
+static char *expdest;                  /* output of current string */
+struct nodelist *argbackq;     /* list of back quote expressions */
+struct ifsregion ifsfirst;     /* first struct in list of ifs regions */
+struct ifsregion *ifslastp;    /* last struct in list */
+struct arglist exparg;         /* holds expanded arg list */
+
+static void argstr __P((char *, int));
+static char *exptilde __P((char *, int));
+static void expbackq __P((union node *, int, int));
+static int subevalvar __P((char *, char *, int, int, int, int, int));
+static char *evalvar __P((char *, int));
+static int varisset __P((char *, int));
+static void strtodest __P((const char *, const char *, int));
+static void varvalue __P((char *, int, int));
+static void recordregion __P((int, int, int));
+static void removerecordregions __P((int)); 
+static void ifsbreakup __P((char *, struct arglist *));
+static void ifsfree __P((void));
+static void expandmeta __P((struct strlist *, int));
+#if defined(__GLIBC__) && !defined(FNMATCH_BROKEN)
+#define preglob(p) _rmescapes((p), RMESCAPE_ALLOC | RMESCAPE_GLOB)
+#if !defined(GLOB_BROKEN)
+static void addglob __P((const glob_t *));
+#endif
+#endif
+#if !(defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN))
+static void expmeta __P((char *, char *));
+#endif
+static void addfname __P((char *));
+#if !(defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN))
+static struct strlist *expsort __P((struct strlist *));
+static struct strlist *msort __P((struct strlist *, int));
+#endif
+#if defined(__GLIBC__) && !defined(FNMATCH_BROKEN)
+static int patmatch __P((char *, char *, int));
+static int patmatch2 __P((char *, char *, int));
+#else
+static int pmatch __P((char *, char *, int));
+#define patmatch2 patmatch
+#endif
+static char *cvtnum __P((int, char *));
+
+extern int oexitstatus;
+
+/*
+ * Expand shell variables and backquotes inside a here document.
+ */
+
+static void
+expandhere(arg, fd)
+       union node *arg;        /* the document */
+       int fd;                 /* where to write the expanded version */
+       {
+       herefd = fd;
+       expandarg(arg, (struct arglist *)NULL, 0);
+       xwrite(fd, stackblock(), expdest - stackblock());
+}
+
+
+/*
+ * Perform variable substitution and command substitution on an argument,
+ * placing the resulting list of arguments in arglist.  If EXP_FULL is true,
+ * perform splitting and file name expansion.  When arglist is NULL, perform
+ * here document expansion.
+ */
+
+static void
+expandarg(arg, arglist, flag)
+       union node *arg;
+       struct arglist *arglist;
+       int flag;
+{
+       struct strlist *sp;
+       char *p;
+
+       argbackq = arg->narg.backquote;
+       STARTSTACKSTR(expdest);
+       ifsfirst.next = NULL;
+       ifslastp = NULL;
+       argstr(arg->narg.text, flag);
+       if (arglist == NULL) {
+               return;                 /* here document expanded */
+       }
+       STPUTC('\0', expdest);
+       p = grabstackstr(expdest);
+       exparg.lastp = &exparg.list;
+       /*
+        * TODO - EXP_REDIR
+        */
+       if (flag & EXP_FULL) {
+               ifsbreakup(p, &exparg);
+               *exparg.lastp = NULL;
+               exparg.lastp = &exparg.list;
+               expandmeta(exparg.list, flag);
+       } else {
+               if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */
+                       rmescapes(p);
+               sp = (struct strlist *)stalloc(sizeof (struct strlist));
+               sp->text = p;
+               *exparg.lastp = sp;
+               exparg.lastp = &sp->next;
+       }
+       ifsfree();
+       *exparg.lastp = NULL;
+       if (exparg.list) {
+               *arglist->lastp = exparg.list;
+               arglist->lastp = exparg.lastp;
+       }
+}
+
+
+
+/*
+ * Perform variable and command substitution.  If EXP_FULL is set, output CTLESC
+ * characters to allow for further processing.  Otherwise treat
+ * $@ like $* since no splitting will be performed.
+ */
+
+static void
+argstr(p, flag)
+       char *p;
+       int flag;
+{
+       char c;
+       int quotes = flag & (EXP_FULL | EXP_CASE);      /* do CTLESC */
+       int firsteq = 1;
+
+       if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE)))
+               p = exptilde(p, flag);
+       for (;;) {
+               switch (c = *p++) {
+               case '\0':
+               case CTLENDVAR: /* ??? */
+                       goto breakloop;
+               case CTLQUOTEMARK:
+                       /* "$@" syntax adherence hack */
+                       if (p[0] == CTLVAR && p[2] == '@' && p[3] == '=')
+                               break;
+                       if ((flag & EXP_FULL) != 0)
+                               STPUTC(c, expdest);
+                       break;
+               case CTLESC:
+                       if (quotes)
+                               STPUTC(c, expdest);
+                       c = *p++;
+                       STPUTC(c, expdest);
+                       break;
+               case CTLVAR:
+                       p = evalvar(p, flag);
+                       break;
+               case CTLBACKQ:
+               case CTLBACKQ|CTLQUOTE:
+                       expbackq(argbackq->n, c & CTLQUOTE, flag);
+                       argbackq = argbackq->next;
+                       break;
+#ifdef ASH_MATH_SUPPORT
+               case CTLENDARI:
+                       expari(flag);
+                       break;
+#endif 
+               case ':':
+               case '=':
+                       /*
+                        * sort of a hack - expand tildes in variable
+                        * assignments (after the first '=' and after ':'s).
+                        */
+                       STPUTC(c, expdest);
+                       if (flag & EXP_VARTILDE && *p == '~') {
+                               if (c == '=') {
+                                       if (firsteq)
+                                               firsteq = 0;
+                                       else
+                                               break;
+                               }
+                               p = exptilde(p, flag);
+                       }
+                       break;
+               default:
+                       STPUTC(c, expdest);
+               }
+       }
+breakloop:;
+       return;
+}
+
+static char *
+exptilde(p, flag)
+       char *p;
+       int flag;
+{
+       char c, *startp = p;
+       struct passwd *pw;
+       const char *home;
+       int quotes = flag & (EXP_FULL | EXP_CASE);
+
+       while ((c = *p) != '\0') {
+               switch(c) {
+               case CTLESC:
+                       return (startp);
+               case CTLQUOTEMARK:
+                       return (startp);
+               case ':':
+                       if (flag & EXP_VARTILDE)
+                               goto done;
+                       break;
+               case '/':
+                       goto done;
+               }
+               p++;
+       }
+done:
+       *p = '\0';
+       if (*(startp+1) == '\0') {
+               if ((home = lookupvar("HOME")) == NULL)
+                       goto lose;
+       } else {
+               if ((pw = getpwnam(startp+1)) == NULL)
+                       goto lose;
+               home = pw->pw_dir;
+       }
+       if (*home == '\0')
+               goto lose;
+       *p = c;
+       strtodest(home, SQSYNTAX, quotes);
+       return (p);
+lose:
+       *p = c;
+       return (startp);
+}
+
+
+static void 
+removerecordregions(endoff)
+       int endoff;
+{
+       if (ifslastp == NULL)
+               return;
+
+       if (ifsfirst.endoff > endoff) {
+               while (ifsfirst.next != NULL) {
+                       struct ifsregion *ifsp;
+                       INTOFF;
+                       ifsp = ifsfirst.next->next;
+                       ckfree(ifsfirst.next);
+                       ifsfirst.next = ifsp;
+                       INTON;
+               }
+               if (ifsfirst.begoff > endoff)
+                       ifslastp = NULL;
+               else {
+                       ifslastp = &ifsfirst;
+                       ifsfirst.endoff = endoff;
+               }
+               return;
+       }
+       
+       ifslastp = &ifsfirst;
+       while (ifslastp->next && ifslastp->next->begoff < endoff)
+               ifslastp=ifslastp->next;
+       while (ifslastp->next != NULL) {
+               struct ifsregion *ifsp;
+               INTOFF;
+               ifsp = ifslastp->next->next;
+               ckfree(ifslastp->next);
+               ifslastp->next = ifsp;
+               INTON;
+       }
+       if (ifslastp->endoff > endoff)
+               ifslastp->endoff = endoff;
+}
+
+
+#ifdef ASH_MATH_SUPPORT
+/*
+ * Expand arithmetic expression.  Backup to start of expression,
+ * evaluate, place result in (backed up) result, adjust string position.
+ */
+static void
+expari(flag)
+       int flag;
+{
+       char *p, *start;
+       int result;
+       int begoff;
+       int quotes = flag & (EXP_FULL | EXP_CASE);
+       int quoted;
+
+       /*      ifsfree(); */
+
+       /*
+        * This routine is slightly over-complicated for
+        * efficiency.  First we make sure there is
+        * enough space for the result, which may be bigger
+        * than the expression if we add exponentation.  Next we
+        * scan backwards looking for the start of arithmetic.  If the
+        * next previous character is a CTLESC character, then we
+        * have to rescan starting from the beginning since CTLESC
+        * characters have to be processed left to right.
+        */
+       CHECKSTRSPACE(10, expdest);
+       USTPUTC('\0', expdest);
+       start = stackblock();
+       p = expdest - 1;
+       while (*p != CTLARI && p >= start)
+               --p;
+       if (*p != CTLARI)
+               error("missing CTLARI (shouldn't happen)");
+       if (p > start && *(p-1) == CTLESC)
+               for (p = start; *p != CTLARI; p++)
+                       if (*p == CTLESC)
+                               p++;
+
+       if (p[1] == '"')
+               quoted=1;
+       else
+               quoted=0;
+       begoff = p - start;
+       removerecordregions(begoff);
+       if (quotes)
+               rmescapes(p+2);
+       result = arith(p+2);
+       fmtstr(p, 12, "%d", result);
+
+       while (*p++)
+               ;
+
+       if (quoted == 0)
+               recordregion(begoff, p - 1 - start, 0);
+       result = expdest - p + 1;
+       STADJUST(-result, expdest);
+}
+#endif 
+
+
+/*
+ * Expand stuff in backwards quotes.
+ */
+
+static void
+expbackq(cmd, quoted, flag)
+       union node *cmd;
+       int quoted;
+       int flag;
+{
+       volatile struct backcmd in;
+       int i;
+       char buf[128];
+       char *p;
+       char *dest = expdest;
+       volatile struct ifsregion saveifs;
+       struct ifsregion *volatile savelastp;
+       struct nodelist *volatile saveargbackq;
+       char lastc;
+       int startloc = dest - stackblock();
+       char const *syntax = quoted? DQSYNTAX : BASESYNTAX;
+       volatile int saveherefd;
+       int quotes = flag & (EXP_FULL | EXP_CASE);
+       struct jmploc jmploc;
+       struct jmploc *volatile savehandler;
+       int ex;
+
+#if __GNUC__
+       /* Avoid longjmp clobbering */
+       (void) &dest;
+       (void) &syntax;
+#endif
+
+       in.fd = -1;
+       in.buf = 0;
+       in.jp = 0;
+
+       INTOFF;
+       saveifs = ifsfirst;
+       savelastp = ifslastp;
+       saveargbackq = argbackq;
+       saveherefd = herefd;
+       herefd = -1;
+       if ((ex = setjmp(jmploc.loc))) {
+               goto err1;
+       }
+       savehandler = handler;
+       handler = &jmploc;
+       INTON;
+       p = grabstackstr(dest);
+       evalbackcmd(cmd, (struct backcmd *) &in);
+       ungrabstackstr(p, dest);
+err1:
+       INTOFF;
+       ifsfirst = saveifs;
+       ifslastp = savelastp;
+       argbackq = saveargbackq;
+       herefd = saveherefd;
+       if (ex) {
+               goto err2;
+       }
+
+       p = in.buf;
+       lastc = '\0';
+       for (;;) {
+               if (--in.nleft < 0) {
+                       if (in.fd < 0)
+                               break;
+                       while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR);
+                       TRACE(("expbackq: read returns %d\n", i));
+                       if (i <= 0)
+                               break;
+                       p = buf;
+                       in.nleft = i - 1;
+               }
+               lastc = *p++;
+               if (lastc != '\0') {
+                       if (quotes && syntax[(int)lastc] == CCTL)
+                               STPUTC(CTLESC, dest);
+                       STPUTC(lastc, dest);
+               }
+       }
+
+       /* Eat all trailing newlines */
+       for (; dest > stackblock() && dest[-1] == '\n';)
+               STUNPUTC(dest);
+
+err2:
+       if (in.fd >= 0)
+               close(in.fd);
+       if (in.buf)
+               ckfree(in.buf);
+       if (in.jp)
+               exitstatus = waitforjob(in.jp);
+       handler = savehandler;
+       if (ex) {
+               longjmp(handler->loc, 1);
+       }
+       if (quoted == 0)
+               recordregion(startloc, dest - stackblock(), 0);
+       TRACE(("evalbackq: size=%d: \"%.*s\"\n",
+               (dest - stackblock()) - startloc,
+               (dest - stackblock()) - startloc,
+               stackblock() + startloc));
+       expdest = dest;
+       INTON;
+}
+
+
+
+static int
+subevalvar(p, str, strloc, subtype, startloc, varflags, quotes)
+       char *p;
+       char *str;
+       int strloc;
+       int subtype;
+       int startloc;
+       int varflags;
+       int quotes;
+{
+       char *startp;
+       char *loc = NULL;
+       char *q;
+       int c = 0;
+       int saveherefd = herefd;
+       struct nodelist *saveargbackq = argbackq;
+       int amount;
+
+       herefd = -1;
+       argstr(p, subtype != VSASSIGN && subtype != VSQUESTION ? EXP_CASE : 0);
+       STACKSTRNUL(expdest);
+       herefd = saveherefd;
+       argbackq = saveargbackq;
+       startp = stackblock() + startloc;
+       if (str == NULL)
+           str = stackblock() + strloc;
+
+       switch (subtype) {
+       case VSASSIGN:
+               setvar(str, startp, 0);
+               amount = startp - expdest;
+               STADJUST(amount, expdest);
+               varflags &= ~VSNUL;
+               if (c != 0)
+                       *loc = c;
+               return 1;
+
+       case VSQUESTION:
+               if (*p != CTLENDVAR) {
+                       outfmt(&errout, snlfmt, startp);
+                       error((char *)NULL);
+               }
+               error("%.*s: parameter %snot set", p - str - 1,
+                     str, (varflags & VSNUL) ? "null or "
+                                             : nullstr);
+               /* NOTREACHED */
+
+       case VSTRIMLEFT:
+               for (loc = startp; loc < str; loc++) {
+                       c = *loc;
+                       *loc = '\0';
+                       if (patmatch2(str, startp, quotes))
+                               goto recordleft;
+                       *loc = c;
+                       if (quotes && *loc == CTLESC)
+                               loc++;
+               }
+               return 0;
+
+       case VSTRIMLEFTMAX:
+               for (loc = str - 1; loc >= startp;) {
+                       c = *loc;
+                       *loc = '\0';
+                       if (patmatch2(str, startp, quotes))
+                               goto recordleft;
+                       *loc = c;
+                       loc--;
+                       if (quotes && loc > startp && *(loc - 1) == CTLESC) {
+                               for (q = startp; q < loc; q++)
+                                       if (*q == CTLESC)
+                                               q++;
+                               if (q > loc)
+                                       loc--;
+                       }
+               }
+               return 0;
+
+       case VSTRIMRIGHT:
+               for (loc = str - 1; loc >= startp;) {
+                       if (patmatch2(str, loc, quotes))
+                               goto recordright;
+                       loc--;
+                       if (quotes && loc > startp && *(loc - 1) == CTLESC) { 
+                               for (q = startp; q < loc; q++)
+                                       if (*q == CTLESC)
+                                               q++;
+                               if (q > loc)
+                                       loc--;
+                       }
+               }
+               return 0;
+
+       case VSTRIMRIGHTMAX:
+               for (loc = startp; loc < str - 1; loc++) {
+                       if (patmatch2(str, loc, quotes))
+                               goto recordright;
+                       if (quotes && *loc == CTLESC)
+                               loc++;
+               }
+               return 0;
+
+#ifdef DEBUG
+       default:
+               abort();
+#endif
+       }
+
+recordleft:
+       *loc = c;
+       amount = ((str - 1) - (loc - startp)) - expdest;
+       STADJUST(amount, expdest);
+       while (loc != str - 1)
+               *startp++ = *loc++;
+       return 1;
+
+recordright:
+       amount = loc - expdest;
+       STADJUST(amount, expdest);
+       STPUTC('\0', expdest);
+       STADJUST(-1, expdest);
+       return 1;
+}
+
+
+/*
+ * Expand a variable, and return a pointer to the next character in the
+ * input string.
+ */
+
+static char *
+evalvar(p, flag)
+       char *p;
+       int flag;
+{
+       int subtype;
+       int varflags;
+       char *var;
+       char *val;
+       int patloc;
+       int c;
+       int set;
+       int special;
+       int startloc;
+       int varlen;
+       int easy;
+       int quotes = flag & (EXP_FULL | EXP_CASE);
+
+       varflags = *p++;
+       subtype = varflags & VSTYPE;
+       var = p;
+       special = 0;
+       if (! is_name(*p))
+               special = 1;
+       p = strchr(p, '=') + 1;
+again: /* jump here after setting a variable with ${var=text} */
+       if (special) {
+               set = varisset(var, varflags & VSNUL);
+               val = NULL;
+       } else {
+               val = lookupvar(var);
+               if (val == NULL || ((varflags & VSNUL) && val[0] == '\0')) {
+                       val = NULL;
+                       set = 0;
+               } else
+                       set = 1;
+       }
+       varlen = 0;
+       startloc = expdest - stackblock();
+       if (set && subtype != VSPLUS) {
+               /* insert the value of the variable */
+               if (special) {
+                       varvalue(var, varflags & VSQUOTE, flag);
+                       if (subtype == VSLENGTH) {
+                               varlen = expdest - stackblock() - startloc;
+                               STADJUST(-varlen, expdest);
+                       }
+               } else {
+                       if (subtype == VSLENGTH) {
+                               varlen = strlen(val);
+                       } else {
+                               strtodest(
+                                       val,
+                                       varflags & VSQUOTE ?
+                                               DQSYNTAX : BASESYNTAX,
+                                       quotes
+                               );
+                       }
+               }
+       }
+
+       if (subtype == VSPLUS)
+               set = ! set;
+
+       easy = ((varflags & VSQUOTE) == 0 ||
+               (*var == '@' && shellparam.nparam != 1));
+
+
+       switch (subtype) {
+       case VSLENGTH:
+               expdest = cvtnum(varlen, expdest);
+               goto record;
+
+       case VSNORMAL:
+               if (!easy)
+                       break;
+record:
+               recordregion(startloc, expdest - stackblock(),
+                            varflags & VSQUOTE);
+               break;
+
+       case VSPLUS:
+       case VSMINUS:
+               if (!set) {
+                       argstr(p, flag);
+                       break;
+               }
+               if (easy)
+                       goto record;
+               break;
+
+       case VSTRIMLEFT:
+       case VSTRIMLEFTMAX:
+       case VSTRIMRIGHT:
+       case VSTRIMRIGHTMAX:
+               if (!set)
+                       break;
+               /*
+                * Terminate the string and start recording the pattern
+                * right after it
+                */
+               STPUTC('\0', expdest);
+               patloc = expdest - stackblock();
+               if (subevalvar(p, NULL, patloc, subtype,
+                              startloc, varflags, quotes) == 0) {
+                       int amount = (expdest - stackblock() - patloc) + 1;
+                       STADJUST(-amount, expdest);
+               }
+               /* Remove any recorded regions beyond start of variable */
+               removerecordregions(startloc);
+               goto record;
+
+       case VSASSIGN:
+       case VSQUESTION:
+               if (!set) {
+                       if (subevalvar(p, var, 0, subtype, startloc,
+                                      varflags, quotes)) {
+                               varflags &= ~VSNUL;
+                               /* 
+                                * Remove any recorded regions beyond 
+                                * start of variable 
+                                */
+                               removerecordregions(startloc);
+                               goto again;
+                       }
+                       break;
+               }
+               if (easy)
+                       goto record;
+               break;
+
+#ifdef DEBUG
+       default:
+               abort();
+#endif
+       }
+
+       if (subtype != VSNORMAL) {      /* skip to end of alternative */
+               int nesting = 1;
+               for (;;) {
+                       if ((c = *p++) == CTLESC)
+                               p++;
+                       else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) {
+                               if (set)
+                                       argbackq = argbackq->next;
+                       } else if (c == CTLVAR) {
+                               if ((*p++ & VSTYPE) != VSNORMAL)
+                                       nesting++;
+                       } else if (c == CTLENDVAR) {
+                               if (--nesting == 0)
+                                       break;
+                       }
+               }
+       }
+       return p;
+}
+
+
+
+/*
+ * Test whether a specialized variable is set.
+ */
+
+static int
+varisset(name, nulok)
+       char *name;
+       int nulok;
+{
+       if (*name == '!')
+               return backgndpid != -1;
+       else if (*name == '@' || *name == '*') {
+               if (*shellparam.p == NULL)
+                       return 0;
+
+               if (nulok) {
+                       char **av;
+
+                       for (av = shellparam.p; *av; av++)
+                               if (**av != '\0')
+                                       return 1;
+                       return 0;
+               }
+       } else if (is_digit(*name)) {
+               char *ap;
+               int num = atoi(name);
+
+               if (num > shellparam.nparam)
+                       return 0;
+
+               if (num == 0)
+                       ap = arg0;
+               else
+                       ap = shellparam.p[num - 1];
+
+               if (nulok && (ap == NULL || *ap == '\0'))
+                       return 0;
+       }
+       return 1;
+}
+
+
+
+/*
+ * Put a string on the stack.
+ */
+
+static void
+strtodest(p, syntax, quotes)
+       const char *p;
+       const char *syntax;
+       int quotes;
+{
+       while (*p) {
+               if (quotes && syntax[(int) *p] == CCTL)
+                       STPUTC(CTLESC, expdest);
+               STPUTC(*p++, expdest);
+       }
+}
+
+
+
+/*
+ * Add the value of a specialized variable to the stack string.
+ */
+
+static void
+varvalue(name, quoted, flags)
+       char *name;
+       int quoted;
+       int flags;
+{
+       int num;
+       char *p;
+       int i;
+       int sep;
+       int sepq = 0;
+       char **ap;
+       char const *syntax;
+       int allow_split = flags & EXP_FULL;
+       int quotes = flags & (EXP_FULL | EXP_CASE);
+
+       syntax = quoted ? DQSYNTAX : BASESYNTAX;
+       switch (*name) {
+       case '$':
+               num = rootpid;
+               goto numvar;
+       case '?':
+               num = oexitstatus;
+               goto numvar;
+       case '#':
+               num = shellparam.nparam;
+               goto numvar;
+       case '!':
+               num = backgndpid;
+numvar:
+               expdest = cvtnum(num, expdest);
+               break;
+       case '-':
+               for (i = 0 ; i < NOPTS ; i++) {
+                       if (optlist[i].val)
+                               STPUTC(optlist[i].letter, expdest);
+               }
+               break;
+       case '@':
+               if (allow_split && quoted) {
+                       sep = 1 << CHAR_BIT;
+                       goto param;
+               }
+               /* fall through */
+       case '*':
+               sep = ifsset() ? ifsval()[0] : ' ';
+               if (quotes) {
+                       sepq = syntax[(int) sep] == CCTL;
+               }
+param:
+               for (ap = shellparam.p ; (p = *ap++) != NULL ; ) {
+                       strtodest(p, syntax, quotes);
+                       if (*ap && sep) {
+                               if (sepq)
+                                       STPUTC(CTLESC, expdest);
+                               STPUTC(sep, expdest);
+                       }
+               }
+               break;
+       case '0':
+               strtodest(arg0, syntax, quotes);
+               break;
+       default:
+               num = atoi(name);
+               if (num > 0 && num <= shellparam.nparam) {
+                       strtodest(shellparam.p[num - 1], syntax, quotes);
+               }
+               break;
+       }
+}
+
+
+
+/*
+ * Record the fact that we have to scan this region of the
+ * string for IFS characters.
+ */
+
+static void
+recordregion(start, end, nulonly)
+       int start;
+       int end;
+       int nulonly;
+{
+       struct ifsregion *ifsp;
+
+       if (ifslastp == NULL) {
+               ifsp = &ifsfirst;
+       } else {
+               INTOFF;
+               ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion));
+               ifsp->next = NULL;
+               ifslastp->next = ifsp;
+               INTON;
+       }
+       ifslastp = ifsp;
+       ifslastp->begoff = start;
+       ifslastp->endoff = end;
+       ifslastp->nulonly = nulonly;
+}
+
+
+
+/*
+ * Break the argument string into pieces based upon IFS and add the
+ * strings to the argument list.  The regions of the string to be
+ * searched for IFS characters have been stored by recordregion.
+ */
+static void
+ifsbreakup(string, arglist)
+       char *string;
+       struct arglist *arglist;
+       {
+       struct ifsregion *ifsp;
+       struct strlist *sp;
+       char *start;
+       char *p;
+       char *q;
+       const char *ifs, *realifs;
+       int ifsspc;
+       int nulonly;
+
+
+       start = string;
+       ifsspc = 0;
+       nulonly = 0;
+       realifs = ifsset() ? ifsval() : defifs;
+       if (ifslastp != NULL) {
+               ifsp = &ifsfirst;
+               do {
+                       p = string + ifsp->begoff;
+                       nulonly = ifsp->nulonly;
+                       ifs = nulonly ? nullstr : realifs;
+                       ifsspc = 0;
+                       while (p < string + ifsp->endoff) {
+                               q = p;
+                               if (*p == CTLESC)
+                                       p++;
+                               if (strchr(ifs, *p)) {
+                                       if (!nulonly)
+                                               ifsspc = (strchr(defifs, *p) != NULL);
+                                       /* Ignore IFS whitespace at start */
+                                       if (q == start && ifsspc) {
+                                               p++;
+                                               start = p;
+                                               continue;
+                                       }
+                                       *q = '\0';
+                                       sp = (struct strlist *)stalloc(sizeof *sp);
+                                       sp->text = start;
+                                       *arglist->lastp = sp;
+                                       arglist->lastp = &sp->next;
+                                       p++;
+                                       if (!nulonly) {
+                                               for (;;) {
+                                                       if (p >= string + ifsp->endoff) {
+                                                               break;
+                                                       }
+                                                       q = p;
+                                                       if (*p == CTLESC)
+                                                               p++;
+                                                       if (strchr(ifs, *p) == NULL ) {
+                                                               p = q;
+                                                               break;
+                                                       } else if (strchr(defifs, *p) == NULL) {
+                                                               if (ifsspc) {
+                                                                       p++;
+                                                                       ifsspc = 0;
+                                                               } else {
+                                                                       p = q;
+                                                                       break;
+                                                               }
+                                                       } else
+                                                               p++;
+                                               }
+                                       }
+                                       start = p;
+                               } else
+                                       p++;
+                       }
+               } while ((ifsp = ifsp->next) != NULL);
+               if (!(*start || (!ifsspc && start > string && nulonly))) {
+                       return;
+               }
+       }
+
+       sp = (struct strlist *)stalloc(sizeof *sp);
+       sp->text = start;
+       *arglist->lastp = sp;
+       arglist->lastp = &sp->next;
+}
+
+static void
+ifsfree()
+{
+       while (ifsfirst.next != NULL) {
+               struct ifsregion *ifsp;
+               INTOFF;
+               ifsp = ifsfirst.next->next;
+               ckfree(ifsfirst.next);
+               ifsfirst.next = ifsp;
+               INTON;
+       }
+       ifslastp = NULL;
+       ifsfirst.next = NULL;
+}
+
+
+
+/*
+ * Expand shell metacharacters.  At this point, the only control characters
+ * should be escapes.  The results are stored in the list exparg.
+ */
+
+#if defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN)
+static void
+expandmeta(str, flag)
+       struct strlist *str;
+       int flag;
+{
+       const char *p;
+       glob_t pglob;
+       /* TODO - EXP_REDIR */
+
+       while (str) {
+               if (fflag)
+                       goto nometa;
+               p = preglob(str->text);
+               INTOFF;
+               switch (glob(p, GLOB_NOMAGIC, 0, &pglob)) {
+               case 0:
+                       if (!(pglob.gl_flags & GLOB_MAGCHAR))
+                               goto nometa2;
+                       addglob(&pglob);
+                       globfree(&pglob);
+                       INTON;
+                       break;
+               case GLOB_NOMATCH:
+nometa2:
+                       globfree(&pglob);
+                       INTON;
+nometa:
+                       *exparg.lastp = str;
+                       rmescapes(str->text);
+                       exparg.lastp = &str->next;
+                       break;
+               default:        /* GLOB_NOSPACE */
+                       error("Out of space");
+               }
+               str = str->next;
+       }
+}
+
+
+/*
+ * Add the result of glob(3) to the list.
+ */
+
+static void
+addglob(pglob)
+       const glob_t *pglob;
+{
+       char **p = pglob->gl_pathv;
+
+       do {
+               addfname(*p);
+       } while (*++p);
+}
+
+
+#else  /* defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN) */
+static char *expdir;
+
+
+static void
+expandmeta(str, flag)
+       struct strlist *str;
+       int flag;
+{
+       char *p;
+       struct strlist **savelastp;
+       struct strlist *sp;
+       char c;
+       /* TODO - EXP_REDIR */
+
+       while (str) {
+               if (fflag)
+                       goto nometa;
+               p = str->text;
+               for (;;) {                      /* fast check for meta chars */
+                       if ((c = *p++) == '\0')
+                               goto nometa;
+                       if (c == '*' || c == '?' || c == '[' || c == '!')
+                               break;
+               }
+               savelastp = exparg.lastp;
+               INTOFF;
+               if (expdir == NULL) {
+                       int i = strlen(str->text);
+                       expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */
+               }
+
+               expmeta(expdir, str->text);
+               ckfree(expdir);
+               expdir = NULL;
+               INTON;
+               if (exparg.lastp == savelastp) {
+                       /*
+                        * no matches
+                        */
+nometa:
+                       *exparg.lastp = str;
+                       rmescapes(str->text);
+                       exparg.lastp = &str->next;
+               } else {
+                       *exparg.lastp = NULL;
+                       *savelastp = sp = expsort(*savelastp);
+                       while (sp->next != NULL)
+                               sp = sp->next;
+                       exparg.lastp = &sp->next;
+               }
+               str = str->next;
+       }
+}
+
+
+/*
+ * Do metacharacter (i.e. *, ?, [...]) expansion.
+ */
+
+static void
+expmeta(enddir, name)
+       char *enddir;
+       char *name;
+       {
+       char *p;
+       const char *cp;
+       char *q;
+       char *start;
+       char *endname;
+       int metaflag;
+       struct stat statb;
+       DIR *dirp;
+       struct dirent *dp;
+       int atend;
+       int matchdot;
+
+       metaflag = 0;
+       start = name;
+       for (p = name ; ; p++) {
+               if (*p == '*' || *p == '?')
+                       metaflag = 1;
+               else if (*p == '[') {
+                       q = p + 1;
+                       if (*q == '!')
+                               q++;
+                       for (;;) {
+                               while (*q == CTLQUOTEMARK)
+                                       q++;
+                               if (*q == CTLESC)
+                                       q++;
+                               if (*q == '/' || *q == '\0')
+                                       break;
+                               if (*++q == ']') {
+                                       metaflag = 1;
+                                       break;
+                               }
+                       }
+               } else if (*p == '!' && p[1] == '!'     && (p == name || p[-1] == '/')) {
+                       metaflag = 1;
+               } else if (*p == '\0')
+                       break;
+               else if (*p == CTLQUOTEMARK)
+                       continue;
+               else if (*p == CTLESC)
+                       p++;
+               if (*p == '/') {
+                       if (metaflag)
+                               break;
+                       start = p + 1;
+               }
+       }
+       if (metaflag == 0) {    /* we've reached the end of the file name */
+               if (enddir != expdir)
+                       metaflag++;
+               for (p = name ; ; p++) {
+                       if (*p == CTLQUOTEMARK)
+                               continue;
+                       if (*p == CTLESC)
+                               p++;
+                       *enddir++ = *p;
+                       if (*p == '\0')
+                               break;
+               }
+               if (metaflag == 0 || lstat(expdir, &statb) >= 0)
+                       addfname(expdir);
+               return;
+       }
+       endname = p;
+       if (start != name) {
+               p = name;
+               while (p < start) {
+                       while (*p == CTLQUOTEMARK)
+                               p++;
+                       if (*p == CTLESC)
+                               p++;
+                       *enddir++ = *p++;
+               }
+       }
+       if (enddir == expdir) {
+               cp = ".";
+       } else if (enddir == expdir + 1 && *expdir == '/') {
+               cp = "/";
+       } else {
+               cp = expdir;
+               enddir[-1] = '\0';
+       }
+       if ((dirp = opendir(cp)) == NULL)
+               return;
+       if (enddir != expdir)
+               enddir[-1] = '/';
+       if (*endname == 0) {
+               atend = 1;
+       } else {
+               atend = 0;
+               *endname++ = '\0';
+       }
+       matchdot = 0;
+       p = start;
+       while (*p == CTLQUOTEMARK)
+               p++;
+       if (*p == CTLESC)
+               p++;
+       if (*p == '.')
+               matchdot++;
+       while (! int_pending() && (dp = readdir(dirp)) != NULL) {
+               if (dp->d_name[0] == '.' && ! matchdot)
+                       continue;
+               if (patmatch(start, dp->d_name, 0)) {
+                       if (atend) {
+                               scopy(dp->d_name, enddir);
+                               addfname(expdir);
+                       } else {
+                               for (p = enddir, cp = dp->d_name;
+                                    (*p++ = *cp++) != '\0';)
+                                       continue;
+                               p[-1] = '/';
+                               expmeta(p, endname);
+                       }
+               }
+       }
+       closedir(dirp);
+       if (! atend)
+               endname[-1] = '/';
+}
+#endif /* defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN) */
+
+
+/*
+ * Add a file name to the list.
+ */
+
+static void
+addfname(name)
+       char *name;
+       {
+       char *p;
+       struct strlist *sp;
+
+       p = sstrdup(name);
+       sp = (struct strlist *)stalloc(sizeof *sp);
+       sp->text = p;
+       *exparg.lastp = sp;
+       exparg.lastp = &sp->next;
+}
+
+
+#if !(defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN))
+/*
+ * Sort the results of file name expansion.  It calculates the number of
+ * strings to sort and then calls msort (short for merge sort) to do the
+ * work.
+ */
+
+static struct strlist *
+expsort(str)
+       struct strlist *str;
+       {
+       int len;
+       struct strlist *sp;
+
+       len = 0;
+       for (sp = str ; sp ; sp = sp->next)
+               len++;
+       return msort(str, len);
+}
+
+
+static struct strlist *
+msort(list, len)
+       struct strlist *list;
+       int len;
+{
+       struct strlist *p, *q = NULL;
+       struct strlist **lpp;
+       int half;
+       int n;
+
+       if (len <= 1)
+               return list;
+       half = len >> 1;
+       p = list;
+       for (n = half ; --n >= 0 ; ) {
+               q = p;
+               p = p->next;
+       }
+       q->next = NULL;                 /* terminate first half of list */
+       q = msort(list, half);          /* sort first half of list */
+       p = msort(p, len - half);               /* sort second half */
+       lpp = &list;
+       for (;;) {
+               if (strcmp(p->text, q->text) < 0) {
+                       *lpp = p;
+                       lpp = &p->next;
+                       if ((p = *lpp) == NULL) {
+                               *lpp = q;
+                               break;
+                       }
+               } else {
+                       *lpp = q;
+                       lpp = &q->next;
+                       if ((q = *lpp) == NULL) {
+                               *lpp = p;
+                               break;
+                       }
+               }
+       }
+       return list;
+}
+#endif
+
+
+
+/*
+ * Returns true if the pattern matches the string.
+ */
+
+#if defined(__GLIBC__) && !defined(FNMATCH_BROKEN)
+static int
+patmatch(pattern, string, squoted)
+       char *pattern;
+       char *string;
+       int squoted;    /* string might have quote chars */
+       {
+       const char *p;
+       char *q;
+
+       p = preglob(pattern);
+       q = squoted ? _rmescapes(string, RMESCAPE_ALLOC) : string;
+
+       return !fnmatch(p, q, 0);
+}
+
+
+static int
+patmatch2(pattern, string, squoted)
+       char *pattern;
+       char *string;
+       int squoted;    /* string might have quote chars */
+       {
+       char *p;
+       int res;
+
+       sstrnleft--;
+       p = grabstackstr(expdest);
+       res = patmatch(pattern, string, squoted);
+       ungrabstackstr(p, expdest);
+       return res;
+}
+#else
+static int
+patmatch(pattern, string, squoted)
+       char *pattern;
+       char *string;
+       int squoted;    /* string might have quote chars */
+       {
+#ifdef notdef
+       if (pattern[0] == '!' && pattern[1] == '!')
+               return 1 - pmatch(pattern + 2, string);
+       else
+#endif
+               return pmatch(pattern, string, squoted);
+}
+
+
+static int
+pmatch(pattern, string, squoted)
+       char *pattern;
+       char *string;
+       int squoted;
+       {
+       char *p, *q;
+       char c;
+
+       p = pattern;
+       q = string;
+       for (;;) {
+               switch (c = *p++) {
+               case '\0':
+                       goto breakloop;
+               case CTLESC:
+                       if (squoted && *q == CTLESC)
+                               q++;
+                       if (*q++ != *p++)
+                               return 0;
+                       break;
+               case CTLQUOTEMARK:
+                       continue;
+               case '?':
+                       if (squoted && *q == CTLESC)
+                               q++;
+                       if (*q++ == '\0')
+                               return 0;
+                       break;
+               case '*':
+                       c = *p;
+                       while (c == CTLQUOTEMARK || c == '*')
+                               c = *++p;
+                       if (c != CTLESC &&  c != CTLQUOTEMARK &&
+                           c != '?' && c != '*' && c != '[') {
+                               while (*q != c) {
+                                       if (squoted && *q == CTLESC &&
+                                           q[1] == c)
+                                               break;
+                                       if (*q == '\0')
+                                               return 0;
+                                       if (squoted && *q == CTLESC)
+                                               q++;
+                                       q++;
+                               }
+                       }
+                       do {
+                               if (pmatch(p, q, squoted))
+                                       return 1;
+                               if (squoted && *q == CTLESC)
+                                       q++;
+                       } while (*q++ != '\0');
+                       return 0;
+               case '[': {
+                       char *endp;
+                       int invert, found;
+                       char chr;
+
+                       endp = p;
+                       if (*endp == '!')
+                               endp++;
+                       for (;;) {
+                               while (*endp == CTLQUOTEMARK)
+                                       endp++;
+                               if (*endp == '\0')
+                                       goto dft;               /* no matching ] */
+                               if (*endp == CTLESC)
+                                       endp++;
+                               if (*++endp == ']')
+                                       break;
+                       }
+                       invert = 0;
+                       if (*p == '!') {
+                               invert++;
+                               p++;
+                       }
+                       found = 0;
+                       chr = *q++;
+                       if (squoted && chr == CTLESC)
+                               chr = *q++;
+                       if (chr == '\0')
+                               return 0;
+                       c = *p++;
+                       do {
+                               if (c == CTLQUOTEMARK)
+                                       continue;
+                               if (c == CTLESC)
+                                       c = *p++;
+                               if (*p == '-' && p[1] != ']') {
+                                       p++;
+                                       while (*p == CTLQUOTEMARK)
+                                               p++;
+                                       if (*p == CTLESC)
+                                               p++;
+                                       if (chr >= c && chr <= *p)
+                                               found = 1;
+                                       p++;
+                               } else {
+                                       if (chr == c)
+                                               found = 1;
+                               }
+                       } while ((c = *p++) != ']');
+                       if (found == invert)
+                               return 0;
+                       break;
+               }
+dft:           default:
+                       if (squoted && *q == CTLESC)
+                               q++;
+                       if (*q++ != c)
+                               return 0;
+                       break;
+               }
+       }
+breakloop:
+       if (*q != '\0')
+               return 0;
+       return 1;
+}
+#endif
+
+
+
+/*
+ * Remove any CTLESC characters from a string.
+ */
+
+#if defined(__GLIBC__) && !defined(FNMATCH_BROKEN)
+static char *
+_rmescapes(str, flag)
+       char *str;
+       int flag;
+{
+       char *p, *q, *r;
+       static const char qchars[] = { CTLESC, CTLQUOTEMARK, 0 };
+
+       p = strpbrk(str, qchars);
+       if (!p) {
+               return str;
+       }
+       q = p;
+       r = str;
+       if (flag & RMESCAPE_ALLOC) {
+               size_t len = p - str;
+               q = r = stalloc(strlen(p) + len + 1);
+               if (len > 0) {
+#ifdef _GNU_SOURCE
+                       q = mempcpy(q, str, len);
+#else
+                       memcpy(q, str, len);
+                       q += len;
+#endif
+               }
+       }
+       while (*p) {
+               if (*p == CTLQUOTEMARK) {
+                       p++;
+                       continue;
+               }
+               if (*p == CTLESC) {
+                       p++;
+                       if (flag & RMESCAPE_GLOB && *p != '/') {
+                               *q++ = '\\';
+                       }
+               }
+               *q++ = *p++;
+       }
+       *q = '\0';
+       return r;
+}
+#else
+static void
+rmescapes(str)
+       char *str;
+{
+       char *p, *q;
+
+       p = str;
+       while (*p != CTLESC && *p != CTLQUOTEMARK) {
+               if (*p++ == '\0')
+                       return;
+       }
+       q = p;
+       while (*p) {
+               if (*p == CTLQUOTEMARK) {
+                       p++;
+                       continue;
+               }
+               if (*p == CTLESC)
+                       p++;
+               *q++ = *p++;
+       }
+       *q = '\0';
+}
+#endif
+
+
+
+/*
+ * See if a pattern matches in a case statement.
+ */
+
+static int
+casematch(pattern, val)
+       union node *pattern;
+       char *val;
+       {
+       struct stackmark smark;
+       int result;
+       char *p;
+
+       setstackmark(&smark);
+       argbackq = pattern->narg.backquote;
+       STARTSTACKSTR(expdest);
+       ifslastp = NULL;
+       argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
+       STPUTC('\0', expdest);
+       p = grabstackstr(expdest);
+       result = patmatch(p, val, 0);
+       popstackmark(&smark);
+       return result;
+}
+
+/*
+ * Our own itoa().
+ */
+
+static char *
+cvtnum(num, buf)
+       int num;
+       char *buf;
+       {
+       int len;
+
+       CHECKSTRSPACE(32, buf);
+       len = sprintf(buf, "%d", num);
+       STADJUST(len, buf);
+       return buf;
+}
+/*     $NetBSD: histedit.c,v 1.25 2001/02/04 19:52:06 christos Exp $   */
+
+/*-
+ * Copyright (c) 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Editline and history functions (and glue).
+ */
+static int histcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       error("not compiled with history support");
+       /* NOTREACHED */
+}
+
+
+/*
+ * This file was generated by the mkinit program.
+ */
+
+extern void rmaliases __P((void));
+
+extern int loopnest;           /* current loop nesting level */
+
+extern void deletefuncs __P((void));
+
+struct strpush {
+       struct strpush *prev;   /* preceding string on stack */
+       char *prevstring;
+       int prevnleft;
+       struct alias *ap;       /* if push was associated with an alias */
+       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 */
+};
+
+extern int parselleft;         /* copy of parsefile->lleft */
+extern struct parsefile basepf;        /* top level input file */
+extern char basebuf[BUFSIZ];   /* buffer for top level input file */
+
+extern short backgndpid;       /* pid of last background process */
+extern int jobctl;
+
+extern int tokpushback;                /* last token pushed back */
+extern int checkkwd;            /* 1 == check for kwds, 2 == also eat newlines */
+
+struct redirtab {
+       struct redirtab *next;
+       short renamed[10];
+};
+
+extern struct redirtab *redirlist;
+
+extern char sigmode[NSIG - 1]; /* current value of signal */
+
+extern char **environ;
+
+
+
+/*
+ * Initialization code.
+ */
+
+static void
+init() {
+
+      /* from cd.c: */
+      {
+             setpwd(0, 0);
+      }
+
+      /* from input.c: */
+      {
+             basepf.nextc = basepf.buf = basebuf;
+      }
+
+      /* from output.c: */
+      {
+#ifdef USE_GLIBC_STDIO
+             initstreams();
+#endif
+      }
+
+      /* from var.c: */
+      {
+             char **envp;
+             char ppid[32];
+
+             initvar();
+             for (envp = environ ; *envp ; envp++) {
+                     if (strchr(*envp, '=')) {
+                             setvareq(*envp, VEXPORT|VTEXTFIXED);
+                     }
+             }
+
+             fmtstr(ppid, sizeof(ppid), "%d", (int) getppid());
+             setvar("PPID", ppid, 0);
+      }
+}
+
+
+
+/*
+ * This routine is called when an error or an interrupt occurs in an
+ * interactive shell and control is returned to the main command loop.
+ */
+
+static void
+reset() {
+
+      /* from eval.c: */
+      {
+             evalskip = 0;
+             loopnest = 0;
+             funcnest = 0;
+      }
+
+      /* from input.c: */
+      {
+             if (exception != EXSHELLPROC)
+                     parselleft = parsenleft = 0;      /* clear input buffer */
+             popallfiles();
+      }
+
+      /* from parser.c: */
+      {
+             tokpushback = 0;
+             checkkwd = 0;
+             checkalias = 0;
+      }
+
+      /* from redir.c: */
+      {
+             while (redirlist)
+                     popredir();
+      }
+
+      /* from output.c: */
+      {
+             out1 = &output;
+             out2 = &errout;
+#ifdef USE_GLIBC_STDIO
+             if (memout.stream != NULL)
+                     __closememout();
+#endif
+             if (memout.buf != NULL) {
+                     ckfree(memout.buf);
+                     memout.buf = NULL;
+             }
+      }
+}
+
+
+
+/*
+ * This routine is called to initialize the shell to run a shell procedure.
+ */
+
+static void
+initshellproc() {
+
+      /* from alias.c: */
+      {
+             rmaliases();
+      }
+
+      /* from eval.c: */
+      {
+             exitstatus = 0;
+      }
+
+      /* from exec.c: */
+      {
+             deletefuncs();
+      }
+
+      /* from jobs.c: */
+      {
+             backgndpid = -1;
+#if JOBS
+             jobctl = 0;
+#endif
+      }
+
+      /* from options.c: */
+      {
+             int i;
+
+             for (i = 0; i < NOPTS; i++)
+                     optlist[i].val = 0;
+             optschanged();
+
+      }
+
+      /* from redir.c: */
+      {
+             clearredir();
+      }
+
+      /* from trap.c: */
+      {
+             char *sm;
+
+             clear_traps();
+             for (sm = sigmode ; sm < sigmode + NSIG - 1; sm++) {
+                     if (*sm == S_IGN)
+                             *sm = S_HARD_IGN;
+             }
+      }
+
+      /* from var.c: */
+      {
+             shprocvar();
+      }
+}
+/*     $NetBSD: input.c,v 1.35 2001/02/04 19:52:06 christos Exp $      */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file implements the input routines used by the parser.
+ */
+
+#ifdef BB_FEATURE_COMMAND_EDITING
+unsigned int shell_context;
+static const char * cmdedit_prompt;
+static inline void putprompt(const char *s) {
+    cmdedit_prompt = s;
+}
+#else
+static inline void putprompt(const char *s) {
+    out2str(s);
+}
+#endif
+
+#define EOF_NLEFT -99          /* value of parsenleft when EOF pushed back */
+
+static int plinno = 1;                 /* input line number */
+static int parsenleft;                 /* copy of parsefile->nleft */
+static int parselleft;         /* copy of parsefile->lleft */
+static char *parsenextc;               /* copy of parsefile->nextc */
+struct parsefile basepf;       /* top level input file */
+static char basebuf[BUFSIZ];   /* buffer for top level input file */
+struct parsefile *parsefile = &basepf; /* current input file */
+static int whichprompt;                /* 1 == PS1, 2 == PS2 */
+
+static void pushfile __P((void));
+static int preadfd __P((void));
+
+#ifdef mkinit
+INCLUDE <stdio.h>
+INCLUDE "input.h"
+INCLUDE "error.h"
+
+INIT {
+       basepf.nextc = basepf.buf = basebuf;
+}
+
+RESET {
+       if (exception != EXSHELLPROC)
+               parselleft = parsenleft = 0;    /* clear input buffer */
+       popallfiles();
+}
+#endif
+
+
+/*
+ * Read a line from the script.
+ */
+
+static char *
+pfgets(line, len)
+       char *line;
+       int len;
+{
+       char *p = line;
+       int nleft = len;
+       int c;
+
+       while (--nleft > 0) {
+               c = pgetc2();
+               if (c == PEOF) {
+                       if (p == line)
+                               return NULL;
+                       break;
+               }
+               *p++ = c;
+               if (c == '\n')
+                       break;
+       }
+       *p = '\0';
+       return line;
+}
+
+
+/*
+ * Read a character from the script, returning PEOF on end of file.
+ * Nul characters in the input are silently discarded.
+ */
+
+static int
+pgetc()
+{
+       return pgetc_macro();
+}
+
+
+/*
+ * Same as pgetc(), but ignores PEOA.
+ */
+
+static int
+pgetc2()
+{
+       int c;
+       do {
+               c = pgetc_macro();
+       } while (c == PEOA);
+       return c;
+}
+
+
+static int
+preadfd()
+{
+    int nr;
+    char *buf =  parsefile->buf;
+    parsenextc = buf;
+
+retry:
+#ifdef BB_FEATURE_COMMAND_EDITING
+       {
+           if (parsefile->fd)
+               nr = read(parsefile->fd, buf, BUFSIZ - 1);
+           else { 
+               do {
+                   cmdedit_read_input((char*)cmdedit_prompt, buf);
+                   nr = strlen(buf);
+               } while (nr <=0 || shell_context);
+               cmdedit_terminate();
+           }
+       }
+#else
+       nr = read(parsefile->fd, buf, BUFSIZ - 1);
+#endif
+
+       if (nr < 0) {
+               if (errno == EINTR)
+                       goto retry;
+               if (parsefile->fd == 0 && errno == EWOULDBLOCK) {
+                       int flags = fcntl(0, F_GETFL, 0);
+                       if (flags >= 0 && flags & O_NONBLOCK) {
+                               flags &=~ O_NONBLOCK;
+                               if (fcntl(0, F_SETFL, flags) >= 0) {
+                                       out2str("sh: turning off NDELAY mode\n");
+                                       goto retry;
+                               }
+                       }
+               }
+       }
+       return nr;
+}
+
+/*
+ * Refill the input buffer and return the next input character:
+ *
+ * 1) If a string was pushed back on the input, pop it;
+ * 2) If an EOF was pushed back (parsenleft == EOF_NLEFT) or we are reading
+ *    from a string so we can't refill the buffer, return EOF.
+ * 3) If the is more stuff in this buffer, use it else call read to fill it.
+ * 4) Process input up to the next newline, deleting nul characters.
+ */
+
+static int
+preadbuffer()
+{
+       char *p, *q;
+       int more;
+       char savec;
+
+       while (parsefile->strpush) {
+               if (
+                       parsenleft == -1 && parsefile->strpush->ap &&
+                       parsenextc[-1] != ' ' && parsenextc[-1] != '\t'
+               ) {
+                       return PEOA;
+               }
+               popstring();
+               if (--parsenleft >= 0)
+                       return (*parsenextc++);
+       }
+       if (parsenleft == EOF_NLEFT || parsefile->buf == NULL)
+               return PEOF;
+       flushout(&output);
+#ifdef FLUSHERR
+       flushout(&errout);
+#endif
+
+again:
+       if (parselleft <= 0) {
+               if ((parselleft = preadfd()) <= 0) {
+                       parselleft = parsenleft = EOF_NLEFT;
+                       return PEOF;
+               }
+       }
+
+       q = p = parsenextc;
+
+       /* delete nul characters */
+       for (more = 1; more;) {
+               switch (*p) {
+               case '\0':
+                       p++;    /* Skip nul */
+                       goto check;
+
+
+               case '\n':
+                       parsenleft = q - parsenextc;
+                       more = 0; /* Stop processing here */
+                       break;
+               }
+
+               *q++ = *p++;
+check:
+               if (--parselleft <= 0 && more) {
+                       parsenleft = q - parsenextc - 1;
+                       if (parsenleft < 0)
+                               goto again;
+                       more = 0;
+               }
+       }
+
+       savec = *q;
+       *q = '\0';
+
+       if (vflag) {
+               out2str(parsenextc);
+#ifdef FLUSHERR
+               flushout(out2);
+#endif
+       }
+
+       *q = savec;
+
+       return *parsenextc++;
+}
+
+/*
+ * Undo the last call to pgetc.  Only one character may be pushed back.
+ * PEOF may be pushed back.
+ */
+
+static void
+pungetc() {
+       parsenleft++;
+       parsenextc--;
+}
+
+/*
+ * Push a string back onto the input at this current parsefile level.
+ * We handle aliases this way.
+ */
+static void
+pushstring(s, len, ap)
+       char *s;
+       int len;
+       void *ap;
+       {
+       struct strpush *sp;
+
+       INTOFF;
+/*dprintf("*** calling pushstring: %s, %d\n", s, len);*/
+       if (parsefile->strpush) {
+               sp = ckmalloc(sizeof (struct strpush));
+               sp->prev = parsefile->strpush;
+               parsefile->strpush = sp;
+       } else
+               sp = parsefile->strpush = &(parsefile->basestrpush);
+       sp->prevstring = parsenextc;
+       sp->prevnleft = parsenleft;
+       sp->ap = (struct alias *)ap;
+       if (ap) {
+               ((struct alias *)ap)->flag |= ALIASINUSE;
+               sp->string = s;
+       }
+       parsenextc = s;
+       parsenleft = len;
+       INTON;
+}
+
+static void
+popstring()
+{
+       struct strpush *sp = parsefile->strpush;
+
+       INTOFF;
+       if (sp->ap) {
+               if (parsenextc[-1] == ' ' || parsenextc[-1] == '\t') {
+                       if (!checkalias) {
+                               checkalias = 1;
+                       }
+               }
+               if (sp->string != sp->ap->val) {
+                       ckfree(sp->string);
+               }
+               sp->ap->flag &= ~ALIASINUSE;
+               if (sp->ap->flag & ALIASDEAD) {
+                       unalias(sp->ap->name);
+               }
+       }
+       parsenextc = sp->prevstring;
+       parsenleft = sp->prevnleft;
+/*dprintf("*** calling popstring: restoring to '%s'\n", parsenextc);*/
+       parsefile->strpush = sp->prev;
+       if (sp != &(parsefile->basestrpush))
+               ckfree(sp);
+       INTON;
+}
+
+/*
+ * Set the input to take input from a file.  If push is set, push the
+ * old input onto the stack first.
+ */
+
+static void
+setinputfile(fname, push)
+       const char *fname;
+       int push;
+{
+       int fd;
+       int myfileno2;
+
+       INTOFF;
+       if ((fd = open(fname, O_RDONLY)) < 0)
+               error("Can't open %s", fname);
+       if (fd < 10) {
+               myfileno2 = dup_as_newfd(fd, 10);
+               close(fd);
+               if (myfileno2 < 0)
+                       error("Out of file descriptors");
+               fd = myfileno2;
+       }
+       setinputfd(fd, push);
+       INTON;
+}
+
+
+/*
+ * Like setinputfile, but takes an open file descriptor.  Call this with
+ * interrupts off.
+ */
+
+static void
+setinputfd(fd, push)
+       int fd, push;
+{
+       (void) fcntl(fd, F_SETFD, FD_CLOEXEC);
+       if (push) {
+               pushfile();
+               parsefile->buf = 0;
+       } else {
+               closescript();
+               while (parsefile->strpush)
+                       popstring();
+       }
+       parsefile->fd = fd;
+       if (parsefile->buf == NULL)
+               parsefile->buf = ckmalloc(BUFSIZ);
+       parselleft = parsenleft = 0;
+       plinno = 1;
+}
+
+
+/*
+ * Like setinputfile, but takes input from a string.
+ */
+
+static void
+setinputstring(string)
+       char *string;
+       {
+       INTOFF;
+       pushfile();
+       parsenextc = string;
+       parsenleft = strlen(string);
+       parsefile->buf = NULL;
+       plinno = 1;
+       INTON;
+}
+
+
+
+/*
+ * To handle the "." command, a stack of input files is used.  Pushfile
+ * adds a new entry to the stack and popfile restores the previous level.
+ */
+
+static void
+pushfile() {
+       struct parsefile *pf;
+
+       parsefile->nleft = parsenleft;
+       parsefile->lleft = parselleft;
+       parsefile->nextc = parsenextc;
+       parsefile->linno = plinno;
+       pf = (struct parsefile *)ckmalloc(sizeof (struct parsefile));
+       pf->prev = parsefile;
+       pf->fd = -1;
+       pf->strpush = NULL;
+       pf->basestrpush.prev = NULL;
+       parsefile = pf;
+}
+
+
+static void
+popfile() {
+       struct parsefile *pf = parsefile;
+
+       INTOFF;
+       if (pf->fd >= 0)
+               close(pf->fd);
+       if (pf->buf)
+               ckfree(pf->buf);
+       while (pf->strpush)
+               popstring();
+       parsefile = pf->prev;
+       ckfree(pf);
+       parsenleft = parsefile->nleft;
+       parselleft = parsefile->lleft;
+       parsenextc = parsefile->nextc;
+       plinno = parsefile->linno;
+       INTON;
+}
+
+
+/*
+ * Return to top level.
+ */
+
+static void
+popallfiles() {
+       while (parsefile != &basepf)
+               popfile();
+}
+
+
+
+/*
+ * Close the file(s) that the shell is reading commands from.  Called
+ * after a fork is done.
+ */
+
+static void
+closescript() {
+       popallfiles();
+       if (parsefile->fd > 0) {
+               close(parsefile->fd);
+               parsefile->fd = 0;
+       }
+}
+/*     $NetBSD: jobs.c,v 1.36 2000/05/22 10:18:47 elric Exp $  */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+
+struct job *jobtab;            /* array of jobs */
+static int njobs;                      /* size of array */
+short backgndpid = -1; /* pid of last background process */
+#if JOBS
+static int initialpgrp;                /* pgrp of shell on invocation */
+short curjob;                  /* current job */
+#endif
+static int intreceived;
+
+static void restartjob __P((struct job *));
+static void freejob __P((struct job *));
+static struct job *getjob __P((char *));
+static int dowait __P((int, struct job *));
+#ifdef SYSV
+static int onsigchild __P((void));
+#endif
+static int waitproc __P((int, int *));
+static void cmdtxt __P((union node *));
+static void cmdputs __P((const char *));
+static void waitonint(int);
+
+
+#if JOBS
+/*
+ * Turn job control on and off.
+ *
+ * Note:  This code assumes that the third arg to ioctl is a character
+ * pointer, which is true on Berkeley systems but not System V.  Since
+ * System V doesn't have job control yet, this isn't a problem now.
+ */
+
+static int jobctl;
+
+static void setjobctl(int enable)
+{
+#ifdef OLD_TTY_DRIVER
+       int ldisc;
+#endif
+
+       if (enable == jobctl || rootshell == 0)
+               return;
+       if (enable) {
+               do { /* while we are in the background */
+#ifdef OLD_TTY_DRIVER
+                       if (ioctl(fileno2, TIOCGPGRP, (char *)&initialpgrp) < 0) {
+#else
+                       initialpgrp = tcgetpgrp(fileno2);
+                       if (initialpgrp < 0) {
+#endif
+                               out2str("sh: can't access tty; job cenabletrol turned off\n");
+                               mflag = 0;
+                               return;
+                       }
+                       if (initialpgrp == -1)
+                               initialpgrp = getpgrp();
+                       else if (initialpgrp != getpgrp()) {
+                               killpg(initialpgrp, SIGTTIN);
+                               continue;
+                       }
+               } while (0);
+#ifdef OLD_TTY_DRIVER
+               if (ioctl(fileno2, TIOCGETD, (char *)&ldisc) < 0 || ldisc != NTTYDISC) {
+                       out2str("sh: need new tty driver to run job cenabletrol; job cenabletrol turned off\n");
+                       mflag = 0;
+                       return;
+               }
+#endif
+               setsignal(SIGTSTP);
+               setsignal(SIGTTOU);
+               setsignal(SIGTTIN);
+               setpgid(0, rootpid);
+#ifdef OLD_TTY_DRIVER
+               ioctl(fileno2, TIOCSPGRP, (char *)&rootpid);
+#else
+               tcsetpgrp(fileno2, rootpid);
+#endif
+       } else { /* turning job cenabletrol off */
+               setpgid(0, initialpgrp);
+#ifdef OLD_TTY_DRIVER
+               ioctl(fileno2, TIOCSPGRP, (char *)&initialpgrp);
+#else
+               tcsetpgrp(fileno2, initialpgrp);
+#endif
+               setsignal(SIGTSTP);
+               setsignal(SIGTTOU);
+               setsignal(SIGTTIN);
+       }
+       jobctl = enable;
+}
+#endif
+
+
+#ifdef mkinit
+INCLUDE <stdlib.h>
+
+SHELLPROC {
+       backgndpid = -1;
+#if JOBS
+       jobctl = 0;
+#endif
+}
+
+#endif
+
+
+/* This file was automatically created by ./mksignames.
+   Do not edit.  Edit support/mksignames.c instead. */
+
+/* A translation list so we can be polite to our users. */
+static char *signal_names[NSIG + 2] = {
+    "EXIT",
+    "SIGHUP",
+    "SIGINT",
+    "SIGQUIT",
+    "SIGILL",
+    "SIGTRAP",
+    "SIGABRT",
+    "SIGBUS",
+    "SIGFPE",
+    "SIGKILL",
+    "SIGUSR1",
+    "SIGSEGV",
+    "SIGUSR2",
+    "SIGPIPE",
+    "SIGALRM",
+    "SIGTERM",
+    "SIGJUNK(16)",
+    "SIGCHLD",
+    "SIGCONT",
+    "SIGSTOP",
+    "SIGTSTP",
+    "SIGTTIN",
+    "SIGTTOU",
+    "SIGURG",
+    "SIGXCPU",
+    "SIGXFSZ",
+    "SIGVTALRM",
+    "SIGPROF",
+    "SIGWINCH",
+    "SIGIO",
+    "SIGPWR",
+    "SIGSYS",
+    "SIGRTMIN",
+    "SIGRTMIN+1",
+    "SIGRTMIN+2",
+    "SIGRTMIN+3",
+    "SIGRTMIN+4",
+    "SIGRTMIN+5",
+    "SIGRTMIN+6",
+    "SIGRTMIN+7",
+    "SIGRTMIN+8",
+    "SIGRTMIN+9",
+    "SIGRTMIN+10",
+    "SIGRTMIN+11",
+    "SIGRTMIN+12",
+    "SIGRTMIN+13",
+    "SIGRTMIN+14",
+    "SIGRTMIN+15",
+    "SIGRTMAX-15",
+    "SIGRTMAX-14",
+    "SIGRTMAX-13",
+    "SIGRTMAX-12",
+    "SIGRTMAX-11",
+    "SIGRTMAX-10",
+    "SIGRTMAX-9",
+    "SIGRTMAX-8",
+    "SIGRTMAX-7",
+    "SIGRTMAX-6",
+    "SIGRTMAX-5",
+    "SIGRTMAX-4",
+    "SIGRTMAX-3",
+    "SIGRTMAX-2",
+    "SIGRTMAX-1",
+    "SIGRTMAX",
+    "DEBUG",
+    (char *)0x0,
+};
+
+
+
+#if JOBS
+static int
+killcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       int signo = -1;
+       int list = 0;
+       int i;
+       pid_t pid;
+       struct job *jp;
+
+       if (argc <= 1) {
+usage:
+               error(
+"Usage: kill [-s sigspec | -signum | -sigspec] [pid | job]... or\n"
+"kill -l [exitstatus]"
+               );
+       }
+
+       if (*argv[1] == '-') {
+               signo = decode_signal(argv[1] + 1, 1);
+               if (signo < 0) {
+                       int c;
+
+                       while ((c = nextopt("ls:")) != '\0')
+                               switch (c) {
+                               case 'l':
+                                       list = 1;
+                                       break;
+                               case 's':
+                                       signo = decode_signal(optionarg, 1);
+                                       if (signo < 0) {
+                                               error(
+                                                       "invalid signal number or name: %s",
+                                                       optionarg
+                                               );
+                                       }
+                                       break;
+#ifdef DEBUG
+                               default:
+                                       error(
+       "nextopt returned character code 0%o", c);
+#endif
+                       }
+               } else
+                       argptr++;
+       }
+
+       if (!list && signo < 0)
+               signo = SIGTERM;
+
+       if ((signo < 0 || !*argptr) ^ list) {
+               goto usage;
+       }
+
+       if (list) {
+               if (!*argptr) {
+                       out1str("0\n");
+                       for (i = 1; i < NSIG; i++) {
+                               out1fmt(snlfmt, signal_names[i] + 3);
+                       }
+                       return 0;
+               }
+               signo = atoi(*argptr);
+               if (signo > 128)
+                       signo -= 128;
+               if (0 < signo && signo < NSIG)
+                               out1fmt(snlfmt, signal_names[signo] + 3);
+               else
+                       error("invalid signal number or exit status: %s",
+                             *argptr);
+               return 0;
+       }
+
+       do {
+               if (**argptr == '%') {
+                       jp = getjob(*argptr);
+                       if (jp->jobctl == 0)
+                               error("job %s not created under job control",
+                                     *argptr);
+                       pid = -jp->ps[0].pid;
+               } else
+                       pid = atoi(*argptr);
+               if (kill(pid, signo) != 0)
+                       error("%s: %s", *argptr, strerror(errno));
+       } while (*++argptr);
+
+       return 0;
+}
+
+static int
+fgcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       struct job *jp;
+       int pgrp;
+       int status;
+
+       jp = getjob(argv[1]);
+       if (jp->jobctl == 0)
+               error("job not created under job control");
+       pgrp = jp->ps[0].pid;
+#ifdef OLD_TTY_DRIVER
+       ioctl(fileno2, TIOCSPGRP, (char *)&pgrp);
+#else
+       tcsetpgrp(fileno2, pgrp);
+#endif
+       restartjob(jp);
+       INTOFF;
+       status = waitforjob(jp);
+       INTON;
+       return status;
+}
+
+
+static int
+bgcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       struct job *jp;
+
+       do {
+               jp = getjob(*++argv);
+               if (jp->jobctl == 0)
+                       error("job not created under job control");
+               restartjob(jp);
+       } while (--argc > 1);
+       return 0;
+}
+
+
+static void
+restartjob(jp)
+       struct job *jp;
+{
+       struct procstat *ps;
+       int i;
+
+       if (jp->state == JOBDONE)
+               return;
+       INTOFF;
+       killpg(jp->ps[0].pid, SIGCONT);
+       for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) {
+               if (WIFSTOPPED(ps->status)) {
+                       ps->status = -1;
+                       jp->state = 0;
+               }
+       }
+       INTON;
+}
+#endif
+
+
+static int
+jobscmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       showjobs(0);
+       return 0;
+}
+
+
+/*
+ * Print a list of jobs.  If "change" is nonzero, only print jobs whose
+ * statuses have changed since the last call to showjobs.
+ *
+ * If the shell is interrupted in the process of creating a job, the
+ * result may be a job structure containing zero processes.  Such structures
+ * will be freed here.
+ */
+
+static void
+showjobs(change)
+       int change;
+{
+       int jobno;
+       int procno;
+       int i;
+       struct job *jp;
+       struct procstat *ps;
+       int col;
+       char s[64];
+
+       TRACE(("showjobs(%d) called\n", change));
+       while (dowait(0, (struct job *)NULL) > 0);
+       for (jobno = 1, jp = jobtab ; jobno <= njobs ; jobno++, jp++) {
+               if (! jp->used)
+                       continue;
+               if (jp->nprocs == 0) {
+                       freejob(jp);
+                       continue;
+               }
+               if (change && ! jp->changed)
+                       continue;
+               procno = jp->nprocs;
+               for (ps = jp->ps ; ; ps++) {    /* for each process */
+                       if (ps == jp->ps)
+                               fmtstr(s, 64, "[%d] %ld ", jobno, 
+                                   (long)ps->pid);
+                       else
+                               fmtstr(s, 64, "    %ld ", 
+                                   (long)ps->pid);
+                       out1str(s);
+                       col = strlen(s);
+                       s[0] = '\0';
+                       if (ps->status == -1) {
+                               /* don't print anything */
+                       } else if (WIFEXITED(ps->status)) {
+                               fmtstr(s, 64, "Exit %d", 
+                                      WEXITSTATUS(ps->status));
+                       } else {
+#if JOBS
+                               if (WIFSTOPPED(ps->status)) 
+                                       i = WSTOPSIG(ps->status);
+                               else /* WIFSIGNALED(ps->status) */
+#endif
+                                       i = WTERMSIG(ps->status);
+                               if ((i & 0x7F) < NSIG && sys_siglist[i & 0x7F])
+                                       scopy(sys_siglist[i & 0x7F], s);
+                               else
+                                       fmtstr(s, 64, "Signal %d", i & 0x7F);
+                               if (WCOREDUMP(ps->status))
+                                       strcat(s, " (core dumped)");
+                       }
+                       out1str(s);
+                       col += strlen(s);
+                       out1fmt(
+                               "%*c%s\n", 30 - col >= 0 ? 30 - col : 0, ' ',
+                               ps->cmd
+                       );
+                       if (--procno <= 0)
+                               break;
+               }
+               jp->changed = 0;
+               if (jp->state == JOBDONE) {
+                       freejob(jp);
+               }
+       }
+}
+
+
+/*
+ * Mark a job structure as unused.
+ */
+
+static void
+freejob(jp)
+       struct job *jp;
+       {
+       struct procstat *ps;
+       int i;
+
+       INTOFF;
+       for (i = jp->nprocs, ps = jp->ps ; --i >= 0 ; ps++) {
+               if (ps->cmd != nullstr)
+                       ckfree(ps->cmd);
+       }
+       if (jp->ps != &jp->ps0)
+               ckfree(jp->ps);
+       jp->used = 0;
+#if JOBS
+       if (curjob == jp - jobtab + 1)
+               curjob = 0;
+#endif
+       INTON;
+}
+
+
+
+static int
+waitcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       struct job *job;
+       int status, retval;
+       struct job *jp;
+
+       if (--argc > 0) {
+start:
+               job = getjob(*++argv);
+       } else {
+               job = NULL;
+       }
+       for (;;) {      /* loop until process terminated or stopped */
+               if (job != NULL) {
+                       if (job->state) {
+                               status = job->ps[job->nprocs - 1].status;
+                               if (! iflag)
+                                       freejob(job);
+                               if (--argc) {
+                                       goto start;
+                               }
+                               if (WIFEXITED(status))
+                                       retval = WEXITSTATUS(status);
+#if JOBS
+                               else if (WIFSTOPPED(status))
+                                       retval = WSTOPSIG(status) + 128;
+#endif
+                               else {
+                                       /* XXX: limits number of signals */
+                                       retval = WTERMSIG(status) + 128;
+                               }
+                               return retval;
+                       }
+               } else {
+                       for (jp = jobtab ; ; jp++) {
+                               if (jp >= jobtab + njobs) {     /* no running procs */
+                                       return 0;
+                               }
+                               if (jp->used && jp->state == 0)
+                                       break;
+                       }
+               }
+               if (dowait(2, 0) < 0 && errno == EINTR) {
+                       return 129;
+               }
+       }
+}
+
+
+
+/*
+ * Convert a job name to a job structure.
+ */
+
+static struct job *
+getjob(name)
+       char *name;
+       {
+       int jobno;
+       struct job *jp;
+       int pid;
+       int i;
+
+       if (name == NULL) {
+#if JOBS
+currentjob:
+               if ((jobno = curjob) == 0 || jobtab[jobno - 1].used == 0)
+                       error("No current job");
+               return &jobtab[jobno - 1];
+#else
+               error("No current job");
+#endif
+       } else if (name[0] == '%') {
+               if (is_digit(name[1])) {
+                       jobno = number(name + 1);
+                       if (jobno > 0 && jobno <= njobs
+                        && jobtab[jobno - 1].used != 0)
+                               return &jobtab[jobno - 1];
+#if JOBS
+               } else if (name[1] == '%' && name[2] == '\0') {
+                       goto currentjob;
+#endif
+               } else {
+                       struct job *found = NULL;
+                       for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) {
+                               if (jp->used && jp->nprocs > 0
+                                && prefix(name + 1, jp->ps[0].cmd)) {
+                                       if (found)
+                                               error("%s: ambiguous", name);
+                                       found = jp;
+                               }
+                       }
+                       if (found)
+                               return found;
+               }
+       } else if (is_number(name)) {
+               pid = number(name);
+               for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) {
+                       if (jp->used && jp->nprocs > 0
+                        && jp->ps[jp->nprocs - 1].pid == pid)
+                               return jp;
+               }
+       }
+       error("No such job: %s", name);
+       /* NOTREACHED */
+}
+
+
+
+/*
+ * Return a new job structure,
+ */
+
+struct job *
+makejob(node, nprocs)
+       union node *node;
+       int nprocs;
+{
+       int i;
+       struct job *jp;
+
+       for (i = njobs, jp = jobtab ; ; jp++) {
+               if (--i < 0) {
+                       INTOFF;
+                       if (njobs == 0) {
+                               jobtab = ckmalloc(4 * sizeof jobtab[0]);
+                       } else {
+                               jp = ckmalloc((njobs + 4) * sizeof jobtab[0]);
+                               memcpy(jp, jobtab, njobs * sizeof jp[0]);
+                               /* Relocate `ps' pointers */
+                               for (i = 0; i < njobs; i++)
+                                       if (jp[i].ps == &jobtab[i].ps0)
+                                               jp[i].ps = &jp[i].ps0;
+                               ckfree(jobtab);
+                               jobtab = jp;
+                       }
+                       jp = jobtab + njobs;
+                       for (i = 4 ; --i >= 0 ; jobtab[njobs++].used = 0);
+                       INTON;
+                       break;
+               }
+               if (jp->used == 0)
+                       break;
+       }
+       INTOFF;
+       jp->state = 0;
+       jp->used = 1;
+       jp->changed = 0;
+       jp->nprocs = 0;
+#if JOBS
+       jp->jobctl = jobctl;
+#endif
+       if (nprocs > 1) {
+               jp->ps = ckmalloc(nprocs * sizeof (struct procstat));
+       } else {
+               jp->ps = &jp->ps0;
+       }
+       INTON;
+       TRACE(("makejob(0x%lx, %d) returns %%%d\n", (long)node, nprocs,
+           jp - jobtab + 1));
+       return jp;
+}
+
+
+/*
+ * Fork of a subshell.  If we are doing job control, give the subshell its
+ * own process group.  Jp is a job structure that the job is to be added to.
+ * N is the command that will be evaluated by the child.  Both jp and n may
+ * be NULL.  The mode parameter can be one of the following:
+ *     FORK_FG - Fork off a foreground process.
+ *     FORK_BG - Fork off a background process.
+ *     FORK_NOJOB - Like FORK_FG, but don't give the process its own
+ *                  process group even if job control is on.
+ *
+ * When job control is turned off, background processes have their standard
+ * input redirected to /dev/null (except for the second and later processes
+ * in a pipeline).
+ */
+
+static int
+forkshell(jp, n, mode)
+       union node *n;
+       struct job *jp;
+       int mode;
+{
+       int pid;
+       int pgrp;
+       const char *devnull = _PATH_DEVNULL;
+       const char *nullerr = "Can't open %s";
+
+       TRACE(("forkshell(%%%d, 0x%lx, %d) called\n", jp - jobtab, (long)n,
+           mode));
+       INTOFF;
+       pid = fork();
+       if (pid == -1) {
+               TRACE(("Fork failed, errno=%d\n", errno));
+               INTON;
+               error("Cannot fork");
+       }
+       if (pid == 0) {
+               struct job *p;
+               int wasroot;
+               int i;
+
+               TRACE(("Child shell %d\n", getpid()));
+               wasroot = rootshell;
+               rootshell = 0;
+               closescript();
+               INTON;
+               clear_traps();
+#if JOBS
+               jobctl = 0;             /* do job control only in root shell */
+               if (wasroot && mode != FORK_NOJOB && mflag) {
+                       if (jp == NULL || jp->nprocs == 0)
+                               pgrp = getpid();
+                       else
+                               pgrp = jp->ps[0].pid;
+                       setpgid(0, pgrp);
+                       if (mode == FORK_FG) {
+                               /*** this causes superfluous TIOCSPGRPS ***/
+#ifdef OLD_TTY_DRIVER
+                               if (ioctl(fileno2, TIOCSPGRP, (char *)&pgrp) < 0)
+                                       error("TIOCSPGRP failed, errno=%d", errno);
+#else
+                               if (tcsetpgrp(fileno2, pgrp) < 0)
+                                       error("tcsetpgrp failed, errno=%d", errno);
+#endif
+                       }
+                       setsignal(SIGTSTP);
+                       setsignal(SIGTTOU);
+               } else if (mode == FORK_BG) {
+                       ignoresig(SIGINT);
+                       ignoresig(SIGQUIT);
+                       if ((jp == NULL || jp->nprocs == 0) &&
+                           ! fd0_redirected_p ()) {
+                               close(0);
+                               if (open(devnull, O_RDONLY) != 0)
+                                       error(nullerr, devnull);
+                       }
+               }
+#else
+               if (mode == FORK_BG) {
+                       ignoresig(SIGINT);
+                       ignoresig(SIGQUIT);
+                       if ((jp == NULL || jp->nprocs == 0) &&
+                           ! fd0_redirected_p ()) {
+                               close(0);
+                               if (open(devnull, O_RDONLY) != 0)
+                                       error(nullerr, devnull);
+                       }
+               }
+#endif
+               for (i = njobs, p = jobtab ; --i >= 0 ; p++)
+                       if (p->used)
+                               freejob(p);
+               if (wasroot && iflag) {
+                       setsignal(SIGINT);
+                       setsignal(SIGQUIT);
+                       setsignal(SIGTERM);
+               }
+               return pid;
+       }
+       if (rootshell && mode != FORK_NOJOB && mflag) {
+               if (jp == NULL || jp->nprocs == 0)
+                       pgrp = pid;
+               else
+                       pgrp = jp->ps[0].pid;
+               setpgid(pid, pgrp);
+       }
+       if (mode == FORK_BG)
+               backgndpid = pid;               /* set $! */
+       if (jp) {
+               struct procstat *ps = &jp->ps[jp->nprocs++];
+               ps->pid = pid;
+               ps->status = -1;
+               ps->cmd = nullstr;
+               if (iflag && rootshell && n)
+                       ps->cmd = commandtext(n);
+       }
+       INTON;
+       TRACE(("In parent shell:  child = %d\n", pid));
+       return pid;
+}
+
+
+
+/*
+ * Wait for job to finish.
+ *
+ * Under job control we have the problem that while a child process is
+ * running interrupts generated by the user are sent to the child but not
+ * to the shell.  This means that an infinite loop started by an inter-
+ * active user may be hard to kill.  With job control turned off, an
+ * interactive user may place an interactive program inside a loop.  If
+ * the interactive program catches interrupts, the user doesn't want
+ * these interrupts to also abort the loop.  The approach we take here
+ * is to have the shell ignore interrupt signals while waiting for a
+ * forground process to terminate, and then send itself an interrupt
+ * signal if the child process was terminated by an interrupt signal.
+ * Unfortunately, some programs want to do a bit of cleanup and then
+ * exit on interrupt; unless these processes terminate themselves by
+ * sending a signal to themselves (instead of calling exit) they will
+ * confuse this approach.
+ */
+
+static int
+waitforjob(jp)
+       struct job *jp;
+       {
+#if JOBS
+       int mypgrp = getpgrp();
+#endif
+       int status;
+       int st;
+       struct sigaction act, oact;
+
+       INTOFF;
+       intreceived = 0;
+#if JOBS
+       if (!jobctl) {
+#else
+       if (!iflag) {
+#endif
+               sigaction(SIGINT, 0, &act);
+               act.sa_handler = waitonint;
+               sigaction(SIGINT, &act, &oact);
+       }
+       TRACE(("waitforjob(%%%d) called\n", jp - jobtab + 1));
+       while (jp->state == 0) {
+               dowait(1, jp);
+       }
+#if JOBS
+       if (!jobctl) {
+#else
+       if (!iflag) {
+#endif
+               sigaction(SIGINT, &oact, 0);
+               if (intreceived && trap[SIGINT]) kill(getpid(), SIGINT);
+       }
+#if JOBS
+       if (jp->jobctl) {
+#ifdef OLD_TTY_DRIVER
+               if (ioctl(fileno2, TIOCSPGRP, (char *)&mypgrp) < 0)
+                       error("TIOCSPGRP failed, errno=%d\n", errno);
+#else
+               if (tcsetpgrp(fileno2, mypgrp) < 0)
+                       error("tcsetpgrp failed, errno=%d\n", errno);
+#endif
+       }
+       if (jp->state == JOBSTOPPED)
+               curjob = jp - jobtab + 1;
+#endif
+       status = jp->ps[jp->nprocs - 1].status;
+       /* convert to 8 bits */
+       if (WIFEXITED(status))
+               st = WEXITSTATUS(status);
+#if JOBS
+       else if (WIFSTOPPED(status))
+               st = WSTOPSIG(status) + 128;
+#endif
+       else
+               st = WTERMSIG(status) + 128;
+#if JOBS
+       if (jp->jobctl) {
+               /*
+                * This is truly gross.
+                * If we're doing job control, then we did a TIOCSPGRP which
+                * caused us (the shell) to no longer be in the controlling
+                * session -- so we wouldn't have seen any ^C/SIGINT.  So, we
+                * intuit from the subprocess exit status whether a SIGINT
+                * occured, and if so interrupt ourselves.  Yuck.  - mycroft
+                */
+               if (WIFSIGNALED(status) && WTERMSIG(status) == SIGINT)
+                       raise(SIGINT);
+       }
+#endif
+       if (! JOBS || jp->state == JOBDONE)
+               freejob(jp);
+       INTON;
+       return st;
+}
+
+
+
+/*
+ * Wait for a process to terminate.
+ */
+
+static int
+dowait(block, job)
+       int block;
+       struct job *job;
+{
+       int pid;
+       int status;
+       struct procstat *sp;
+       struct job *jp;
+       struct job *thisjob;
+       int done;
+       int stopped;
+       int core;
+       int sig;
+
+       TRACE(("dowait(%d) called\n", block));
+       do {
+               pid = waitproc(block, &status);
+               TRACE(("wait returns %d, status=%d\n", pid, status));
+       } while (!(block & 2) && pid == -1 && errno == EINTR);
+       if (pid <= 0)
+               return pid;
+       INTOFF;
+       thisjob = NULL;
+       for (jp = jobtab ; jp < jobtab + njobs ; jp++) {
+               if (jp->used) {
+                       done = 1;
+                       stopped = 1;
+                       for (sp = jp->ps ; sp < jp->ps + jp->nprocs ; sp++) {
+                               if (sp->pid == -1)
+                                       continue;
+                               if (sp->pid == pid) {
+                                       TRACE(("Changing status of proc %d from 0x%x to 0x%x\n", pid, sp->status, status));
+                                       sp->status = status;
+                                       thisjob = jp;
+                               }
+                               if (sp->status == -1)
+                                       stopped = 0;
+                               else if (WIFSTOPPED(sp->status))
+                                       done = 0;
+                       }
+                       if (stopped) {          /* stopped or done */
+                               int state = done? JOBDONE : JOBSTOPPED;
+                               if (jp->state != state) {
+                                       TRACE(("Job %d: changing state from %d to %d\n", jp - jobtab + 1, jp->state, state));
+                                       jp->state = state;
+#if JOBS
+                                       if (done && curjob == jp - jobtab + 1)
+                                               curjob = 0;             /* no current job */
+#endif
+                               }
+                       }
+               }
+       }
+       INTON;
+       if (! rootshell || ! iflag || (job && thisjob == job)) {
+               core = WCOREDUMP(status);
+#if JOBS
+               if (WIFSTOPPED(status)) sig = WSTOPSIG(status);
+               else
+#endif
+               if (WIFEXITED(status)) sig = 0;
+               else sig = WTERMSIG(status);
+
+               if (sig != 0 && sig != SIGINT && sig != SIGPIPE) {
+                       if (thisjob != job)
+                               outfmt(out2, "%d: ", pid);
+#if JOBS
+                       if (sig == SIGTSTP && rootshell && iflag)
+                               outfmt(out2, "%%%ld ",
+                                   (long)(job - jobtab + 1));
+#endif
+                       if (sig < NSIG && sys_siglist[sig])
+                               out2str(sys_siglist[sig]);
+                       else
+                               outfmt(out2, "Signal %d", sig);
+                       if (core)
+                               out2str(" - core dumped");
+                       out2c('\n');
+#ifdef FLUSHERR
+                       flushout(&errout);
+#endif
+               } else {
+                       TRACE(("Not printing status: status=%d, sig=%d\n", 
+                              status, sig));
+               }
+       } else {
+               TRACE(("Not printing status, rootshell=%d, job=0x%x\n", rootshell, job));
+               if (thisjob)
+                       thisjob->changed = 1;
+       }
+       return pid;
+}
+
+
+
+/*
+ * Do a wait system call.  If job control is compiled in, we accept
+ * stopped processes.  If block is zero, we return a value of zero
+ * rather than blocking.
+ *
+ * System V doesn't have a non-blocking wait system call.  It does
+ * have a SIGCLD signal that is sent to a process when one of it's
+ * children dies.  The obvious way to use SIGCLD would be to install
+ * a handler for SIGCLD which simply bumped a counter when a SIGCLD
+ * was received, and have waitproc bump another counter when it got
+ * the status of a process.  Waitproc would then know that a wait
+ * system call would not block if the two counters were different.
+ * This approach doesn't work because if a process has children that
+ * have not been waited for, System V will send it a SIGCLD when it
+ * installs a signal handler for SIGCLD.  What this means is that when
+ * a child exits, the shell will be sent SIGCLD signals continuously
+ * until is runs out of stack space, unless it does a wait call before
+ * restoring the signal handler.  The code below takes advantage of
+ * this (mis)feature by installing a signal handler for SIGCLD and
+ * then checking to see whether it was called.  If there are any
+ * children to be waited for, it will be.
+ *
+ * If neither SYSV nor BSD is defined, we don't implement nonblocking
+ * waits at all.  In this case, the user will not be informed when
+ * a background process until the next time she runs a real program
+ * (as opposed to running a builtin command or just typing return),
+ * and the jobs command may give out of date information.
+ */
+
+#ifdef SYSV
+static int gotsigchild;
+
+static int onsigchild() {
+       gotsigchild = 1;
+}
+#endif
+
+
+static int
+waitproc(block, status)
+       int block;
+       int *status;
+{
+#ifdef BSD
+       int flags;
+
+       flags = 0;
+#if JOBS
+       if (jobctl)
+               flags |= WUNTRACED;
+#endif
+       if (block == 0)
+               flags |= WNOHANG;
+       return wait3(status, flags, (struct rusage *)NULL);
+#else
+#ifdef SYSV
+       int (*save)();
+
+       if (block == 0) {
+               gotsigchild = 0;
+               save = signal(SIGCLD, onsigchild);
+               signal(SIGCLD, save);
+               if (gotsigchild == 0)
+                       return 0;
+       }
+       return wait(status);
+#else
+       if (block == 0)
+               return 0;
+       return wait(status);
+#endif
+#endif
+}
+
+/*
+ * return 1 if there are stopped jobs, otherwise 0
+ */
+static int job_warning = 0;
+static int
+stoppedjobs()
+{
+       int jobno;
+       struct job *jp;
+
+       if (job_warning)
+               return (0);
+       for (jobno = 1, jp = jobtab; jobno <= njobs; jobno++, jp++) {
+               if (jp->used == 0)
+                       continue;
+               if (jp->state == JOBSTOPPED) {
+                       out2str("You have stopped jobs.\n");
+                       job_warning = 2;
+                       return (1);
+               }
+       }
+
+       return (0);
+}
+
+/*
+ * Return a string identifying a command (to be printed by the
+ * jobs command.
+ */
+
+static char *cmdnextc;
+static int cmdnleft;
+#define MAXCMDTEXT     200
+
+static char *
+commandtext(n)
+       union node *n;
+       {
+       char *name;
+
+       cmdnextc = name = ckmalloc(MAXCMDTEXT);
+       cmdnleft = MAXCMDTEXT - 4;
+       cmdtxt(n);
+       *cmdnextc = '\0';
+       return name;
+}
+
+
+static void
+cmdtxt(n)
+       union node *n;
+       {
+       union node *np;
+       struct nodelist *lp;
+       const char *p;
+       int i;
+       char s[2];
+
+       if (n == NULL)
+               return;
+       switch (n->type) {
+       case NSEMI:
+               cmdtxt(n->nbinary.ch1);
+               cmdputs("; ");
+               cmdtxt(n->nbinary.ch2);
+               break;
+       case NAND:
+               cmdtxt(n->nbinary.ch1);
+               cmdputs(" && ");
+               cmdtxt(n->nbinary.ch2);
+               break;
+       case NOR:
+               cmdtxt(n->nbinary.ch1);
+               cmdputs(" || ");
+               cmdtxt(n->nbinary.ch2);
+               break;
+       case NPIPE:
+               for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
+                       cmdtxt(lp->n);
+                       if (lp->next)
+                               cmdputs(" | ");
+               }
+               break;
+       case NSUBSHELL:
+               cmdputs("(");
+               cmdtxt(n->nredir.n);
+               cmdputs(")");
+               break;
+       case NREDIR:
+       case NBACKGND:
+               cmdtxt(n->nredir.n);
+               break;
+       case NIF:
+               cmdputs("if ");
+               cmdtxt(n->nif.test);
+               cmdputs("; then ");
+               cmdtxt(n->nif.ifpart);
+               cmdputs("...");
+               break;
+       case NWHILE:
+               cmdputs("while ");
+               goto until;
+       case NUNTIL:
+               cmdputs("until ");
+until:
+               cmdtxt(n->nbinary.ch1);
+               cmdputs("; do ");
+               cmdtxt(n->nbinary.ch2);
+               cmdputs("; done");
+               break;
+       case NFOR:
+               cmdputs("for ");
+               cmdputs(n->nfor.var);
+               cmdputs(" in ...");
+               break;
+       case NCASE:
+               cmdputs("case ");
+               cmdputs(n->ncase.expr->narg.text);
+               cmdputs(" in ...");
+               break;
+       case NDEFUN:
+               cmdputs(n->narg.text);
+               cmdputs("() ...");
+               break;
+       case NCMD:
+               for (np = n->ncmd.args ; np ; np = np->narg.next) {
+                       cmdtxt(np);
+                       if (np->narg.next)
+                               cmdputs(spcstr);
+               }
+               for (np = n->ncmd.redirect ; np ; np = np->nfile.next) {
+                       cmdputs(spcstr);
+                       cmdtxt(np);
+               }
+               break;
+       case NARG:
+               cmdputs(n->narg.text);
+               break;
+       case NTO:
+               p = ">";  i = 1;  goto redir;
+       case NAPPEND:
+               p = ">>";  i = 1;  goto redir;
+       case NTOFD:
+               p = ">&";  i = 1;  goto redir;
+       case NTOOV:
+               p = ">|";  i = 1;  goto redir;
+       case NFROM:
+               p = "<";  i = 0;  goto redir;
+       case NFROMFD:
+               p = "<&";  i = 0;  goto redir;
+       case NFROMTO:
+               p = "<>";  i = 0;  goto redir;
+redir:
+               if (n->nfile.fd != i) {
+                       s[0] = n->nfile.fd + '0';
+                       s[1] = '\0';
+                       cmdputs(s);
+               }
+               cmdputs(p);
+               if (n->type == NTOFD || n->type == NFROMFD) {
+                       s[0] = n->ndup.dupfd + '0';
+                       s[1] = '\0';
+                       cmdputs(s);
+               } else {
+                       cmdtxt(n->nfile.fname);
+               }
+               break;
+       case NHERE:
+       case NXHERE:
+               cmdputs("<<...");
+               break;
+       default:
+               cmdputs("???");
+               break;
+       }
+}
+
+
+
+static void
+cmdputs(s)
+       const char *s;
+       {
+       const char *p;
+       char *q;
+       char c;
+       int subtype = 0;
+
+       if (cmdnleft <= 0)
+               return;
+       p = s;
+       q = cmdnextc;
+       while ((c = *p++) != '\0') {
+               if (c == CTLESC)
+                       *q++ = *p++;
+               else if (c == CTLVAR) {
+                       *q++ = '$';
+                       if (--cmdnleft > 0)
+                               *q++ = '{';
+                       subtype = *p++;
+               } else if (c == '=' && subtype != 0) {
+                       *q++ = "}-+?="[(subtype & VSTYPE) - VSNORMAL];
+                       subtype = 0;
+               } else if (c == CTLENDVAR) {
+                       *q++ = '}';
+               } else if (c == CTLBACKQ || c == CTLBACKQ+CTLQUOTE)
+                       cmdnleft++;             /* ignore it */
+               else
+                       *q++ = c;
+               if (--cmdnleft <= 0) {
+                       *q++ = '.';
+                       *q++ = '.';
+                       *q++ = '.';
+                       break;
+               }
+       }
+       cmdnextc = q;
+}
+
+static void waitonint(int sig) {
+       intreceived = 1;
+       return;
+}
+/*     $NetBSD: mail.c,v 1.14 2000/07/03 03:26:19 matt Exp $   */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Routines to check for mail.  (Perhaps make part of main.c?)
+ */
+
+
+#define MAXMBOXES 10
+
+
+static int nmboxes;                    /* number of mailboxes */
+static time_t mailtime[MAXMBOXES];     /* times of mailboxes */
+
+
+
+/*
+ * Print appropriate message(s) if mail has arrived.  If the argument is
+ * nozero, then the value of MAIL has changed, so we just update the
+ * values.
+ */
+
+static void
+chkmail(silent)
+       int silent;
+{
+       int i;
+       const char *mpath;
+       char *p;
+       char *q;
+       struct stackmark smark;
+       struct stat statb;
+
+       if (silent)
+               nmboxes = 10;
+       if (nmboxes == 0)
+               return;
+       setstackmark(&smark);
+       mpath = mpathset()? mpathval() : mailval();
+       for (i = 0 ; i < nmboxes ; i++) {
+               p = padvance(&mpath, nullstr);
+               if (p == NULL)
+                       break;
+               if (*p == '\0')
+                       continue;
+               for (q = p ; *q ; q++);
+#ifdef DEBUG
+               if (q[-1] != '/')
+                       abort();
+#endif
+               q[-1] = '\0';                   /* delete trailing '/' */
+#ifdef notdef /* this is what the System V shell claims to do (it lies) */
+               if (stat(p, &statb) < 0)
+                       statb.st_mtime = 0;
+               if (statb.st_mtime > mailtime[i] && ! silent) {
+                       outfmt(
+                               &errout, snlfmt,
+                               pathopt? pathopt : "you have mail"
+                       );
+               }
+               mailtime[i] = statb.st_mtime;
+#else /* this is what it should do */
+               if (stat(p, &statb) < 0)
+                       statb.st_size = 0;
+               if (statb.st_size > mailtime[i] && ! silent) {
+                       outfmt(
+                               &errout, snlfmt,
+                               pathopt? pathopt : "you have mail"
+                       );
+               }
+               mailtime[i] = statb.st_size;
+#endif
+       }
+       nmboxes = i;
+       popstackmark(&smark);
+}
+/*     $NetBSD: main.c,v 1.40 2001/02/04 19:52:06 christos Exp $       */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+
+#define PROFILE 0
+
+static int rootpid;
+static int rootshell;
+#if PROFILE
+short profile_buf[16384];
+extern int etext();
+#endif
+
+static void read_profile __P((const char *));
+static char *find_dot_file __P((char *));
+int shell_main __P((int, char **));
+
+extern int oexitstatus;
+/*
+ * Main routine.  We initialize things, parse the arguments, execute
+ * profiles if we're a login shell, and then call cmdloop to execute
+ * commands.  The setjmp call sets up the location to jump to when an
+ * exception occurs.  When an exception occurs the variable "state"
+ * is used to figure out how far we had gotten.
+ */
+
+int
+shell_main(argc, argv)
+       int argc;
+       char **argv;
+{
+       struct jmploc jmploc;
+       struct stackmark smark;
+       volatile int state;
+       char *shinit;
+
+       DOTCMD = find_builtin(".");
+       BLTINCMD = find_builtin("builtin");
+       COMMANDCMD = find_builtin("command");
+       EXECCMD = find_builtin("exec");
+       EVALCMD = find_builtin("eval");
+
+#if PROFILE
+       monitor(4, etext, profile_buf, sizeof profile_buf, 50);
+#endif
+#if defined(linux) || defined(__GNU__)
+       signal(SIGCHLD, SIG_DFL);
+#endif
+       state = 0;
+       if (setjmp(jmploc.loc)) {
+               INTOFF;
+               /*
+                * When a shell procedure is executed, we raise the
+                * exception EXSHELLPROC to clean up before executing
+                * the shell procedure.
+                */
+               switch (exception) {
+               case EXSHELLPROC:
+                       rootpid = getpid();
+                       rootshell = 1;
+                       minusc = NULL;
+                       state = 3;
+                       break;
+
+               case EXEXEC:
+                       exitstatus = exerrno;
+                       break;
+
+               case EXERROR:
+                       exitstatus = 2;
+                       break;
+
+               default:
+                       break;
+               }
+
+               if (exception != EXSHELLPROC) {
+                   if (state == 0 || iflag == 0 || ! rootshell)
+                           exitshell(exitstatus);
+               }
+               reset();
+               if (exception == EXINT
+#if ATTY
+                && (! attyset() || equal(termval(), "emacs"))
+#endif
+                ) {
+                       out2c('\n');
+#ifdef FLUSHERR
+                       flushout(out2);
+#endif
+               }
+               popstackmark(&smark);
+               FORCEINTON;                             /* enable interrupts */
+               if (state == 1)
+                       goto state1;
+               else if (state == 2)
+                       goto state2;
+               else if (state == 3)
+                       goto state3;
+               else
+                       goto state4;
+       }
+       handler = &jmploc;
+#ifdef DEBUG
+       opentrace();
+       trputs("Shell args:  ");  trargs(argv);
+#endif
+       rootpid = getpid();
+       rootshell = 1;
+       init();
+       setstackmark(&smark);
+       procargs(argc, argv);
+       if (argv[0] && argv[0][0] == '-') {
+               state = 1;
+               read_profile("/etc/profile");
+state1:
+               state = 2;
+               read_profile(".profile");
+       }
+state2:
+       state = 3;
+#ifndef linux
+       if (getuid() == geteuid() && getgid() == getegid()) {
+#endif
+               if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') {
+                       state = 3;
+                       read_profile(shinit);
+               }
+#ifndef linux
+       }
+#endif
+state3:
+       state = 4;
+       if (sflag == 0 || minusc) {
+               static int sigs[] =  {
+                   SIGINT, SIGQUIT, SIGHUP, 
+#ifdef SIGTSTP
+                   SIGTSTP,
+#endif
+                   SIGPIPE
+               };
+#define SIGSSIZE (sizeof(sigs)/sizeof(sigs[0]))
+               int i;
+
+               for (i = 0; i < SIGSSIZE; i++)
+                   setsignal(sigs[i]);
+       }
+
+       if (minusc)
+               evalstring(minusc, 0);
+
+       if (sflag || minusc == NULL) {
+state4:        /* XXX ??? - why isn't this before the "if" statement */
+               cmdloop(1);
+       }
+#if PROFILE
+       monitor(0);
+#endif
+       exitshell(exitstatus);
+       /* NOTREACHED */
+}
+
+
+/*
+ * Read and execute commands.  "Top" is nonzero for the top level command
+ * loop; it turns on prompting if the shell is interactive.
+ */
+
+static void
+cmdloop(top)
+       int top;
+{
+       union node *n;
+       struct stackmark smark;
+       int inter;
+       int numeof = 0;
+
+       TRACE(("cmdloop(%d) called\n", top));
+       setstackmark(&smark);
+       for (;;) {
+               if (pendingsigs)
+                       dotrap();
+               inter = 0;
+               if (iflag && top) {
+                       inter++;
+                       showjobs(1);
+                       chkmail(0);
+                       flushout(&output);
+               }
+               n = parsecmd(inter);
+               /* showtree(n); DEBUG */
+               if (n == NEOF) {
+                       if (!top || numeof >= 50)
+                               break;
+                       if (!stoppedjobs()) {
+                               if (!Iflag)
+                                       break;
+                               out2str("\nUse \"exit\" to leave shell.\n");
+                       }
+                       numeof++;
+               } else if (n != NULL && nflag == 0) {
+                       job_warning = (job_warning == 2) ? 1 : 0;
+                       numeof = 0;
+                       evaltree(n, 0);
+               }
+               popstackmark(&smark);
+               setstackmark(&smark);
+               if (evalskip == SKIPFILE) {
+                       evalskip = 0;
+                       break;
+               }
+       }
+       popstackmark(&smark);
+}
+
+
+
+/*
+ * Read /etc/profile or .profile.  Return on error.
+ */
+
+static void
+read_profile(name)
+       const char *name;
+{
+       int fd;
+       int xflag_set = 0;
+       int vflag_set = 0;
+
+       INTOFF;
+       if ((fd = open(name, O_RDONLY)) >= 0)
+               setinputfd(fd, 1);
+       INTON;
+       if (fd < 0)
+               return;
+       /* -q turns off -x and -v just when executing init files */
+       if (qflag)  {
+           if (xflag)
+                   xflag = 0, xflag_set = 1;
+           if (vflag)
+                   vflag = 0, vflag_set = 1;
+       }
+       cmdloop(0);
+       if (qflag)  {
+           if (xflag_set)
+                   xflag = 1;
+           if (vflag_set)
+                   vflag = 1;
+       }
+       popfile();
+}
+
+
+
+/*
+ * Read a file containing shell functions.
+ */
+
+static void
+readcmdfile(name)
+       char *name;
+{
+       int fd;
+
+       INTOFF;
+       if ((fd = open(name, O_RDONLY)) >= 0)
+               setinputfd(fd, 1);
+       else
+               error("Can't open %s", name);
+       INTON;
+       cmdloop(0);
+       popfile();
+}
+
+
+
+/*
+ * Take commands from a file.  To be compatable we should do a path
+ * search for the file, which is necessary to find sub-commands.
+ */
+
+
+static char *
+find_dot_file(mybasename)
+       char *mybasename;
+{
+       char *fullname;
+       const char *path = pathval();
+       struct stat statb;
+
+       /* don't try this for absolute or relative paths */
+       if (strchr(mybasename, '/'))
+               return mybasename;
+
+       while ((fullname = padvance(&path, mybasename)) != NULL) {
+               if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) {
+                       /*
+                        * Don't bother freeing here, since it will
+                        * be freed by the caller.
+                        */
+                       return fullname;
+               }
+               stunalloc(fullname);
+       }
+
+       /* not found in the PATH */
+       error("%s: not found", mybasename);
+       /* NOTREACHED */
+}
+
+static int
+dotcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       struct strlist *sp;
+       exitstatus = 0;
+
+       for (sp = cmdenviron; sp ; sp = sp->next)
+               setvareq(savestr(sp->text), VSTRFIXED|VTEXTFIXED);
+
+       if (argc >= 2) {                /* That's what SVR2 does */
+               char *fullname;
+               struct stackmark smark;
+
+               setstackmark(&smark);
+               fullname = find_dot_file(argv[1]);
+               setinputfile(fullname, 1);
+               commandname = fullname;
+               cmdloop(0);
+               popfile();
+               popstackmark(&smark);
+       }
+       return exitstatus;
+}
+
+
+static int
+exitcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       if (stoppedjobs())
+               return 0;
+       if (argc > 1)
+               exitstatus = number(argv[1]);
+       else
+               exitstatus = oexitstatus;
+       exitshell(exitstatus);
+       /* NOTREACHED */
+}
+/*     $NetBSD: memalloc.c,v 1.23 2000/11/01 19:56:01 christos Exp $   */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+
+/*
+ * 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.
+ */
+
+#define MINSIZE 504            /* minimum size of a block */
+
+
+struct stack_block {
+       struct stack_block *prev;
+       char space[MINSIZE];
+};
+
+struct stack_block stackbase;
+struct stack_block *stackp = &stackbase;
+struct stackmark *markp;
+static char *stacknxt = stackbase.space;
+static int stacknleft = MINSIZE;
+static int sstrnleft;
+static int herefd = -1;
+
+
+
+pointer
+stalloc(nbytes)
+       int nbytes;
+{
+       char *p;
+
+       nbytes = ALIGN(nbytes);
+       if (nbytes > stacknleft) {
+               int blocksize;
+               struct stack_block *sp;
+
+               blocksize = nbytes;
+               if (blocksize < MINSIZE)
+                       blocksize = MINSIZE;
+               INTOFF;
+               sp = ckmalloc(sizeof(struct stack_block) - MINSIZE + blocksize);
+               sp->prev = stackp;
+               stacknxt = sp->space;
+               stacknleft = blocksize;
+               stackp = sp;
+               INTON;
+       }
+       p = stacknxt;
+       stacknxt += nbytes;
+       stacknleft -= nbytes;
+       return p;
+}
+
+
+static void
+stunalloc(p)
+       pointer p;
+       {
+#ifdef DEBUG
+       if (p == NULL) {                /*DEBUG */
+               write(2, "stunalloc\n", 10);
+               abort();
+       }
+#endif
+       if (!(stacknxt >= (char *)p && (char *)p >= stackp->space)) {
+               p = stackp->space;
+       }
+       stacknleft += stacknxt - (char *)p;
+       stacknxt = p;
+}
+
+
+
+static void
+setstackmark(mark)
+       struct stackmark *mark;
+       {
+       mark->stackp = stackp;
+       mark->stacknxt = stacknxt;
+       mark->stacknleft = stacknleft;
+       mark->marknext = markp;
+       markp = mark;
+}
+
+
+static void
+popstackmark(mark)
+       struct stackmark *mark;
+       {
+       struct stack_block *sp;
+
+       INTOFF;
+       markp = mark->marknext;
+       while (stackp != mark->stackp) {
+               sp = stackp;
+               stackp = sp->prev;
+               ckfree(sp);
+       }
+       stacknxt = mark->stacknxt;
+       stacknleft = mark->stacknleft;
+       INTON;
+}
+
+
+/*
+ * 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() {
+       char *p;
+       int newlen = ALIGN(stacknleft * 2 + 100);
+       char *oldspace = stacknxt;
+       int oldlen = stacknleft;
+       struct stack_block *sp;
+       struct stack_block *oldstackp;
+
+       if (stacknxt == stackp->space && stackp != &stackbase) {
+               INTOFF;
+               oldstackp = stackp;
+               sp = stackp;
+               stackp = sp->prev;
+               sp = ckrealloc((pointer)sp, sizeof(struct stack_block) - MINSIZE + newlen);
+               sp->prev = stackp;
+               stackp = sp;
+               stacknxt = sp->space;
+               stacknleft = newlen;
+               {
+                 /* Stack marks pointing to the start of the old block
+                  * must be relocated to point to the new block 
+                  */
+                 struct stackmark *xmark;
+                 xmark = markp;
+                 while (xmark != NULL && xmark->stackp == oldstackp) {
+                   xmark->stackp = stackp;
+                   xmark->stacknxt = stacknxt;
+                   xmark->stacknleft = stacknleft;
+                   xmark = xmark->marknext;
+                 }
+               }
+               INTON;
+       } else {
+               p = stalloc(newlen);
+               memcpy(p, oldspace, oldlen);
+               stacknxt = p;                   /* free the space */
+               stacknleft += newlen;           /* we just allocated */
+       }
+}
+
+
+
+static void
+grabstackblock(len)
+       int len;
+{
+       len = ALIGN(len);
+       stacknxt += len;
+       stacknleft -= len;
+}
+
+
+
+/*
+ * The following routines are somewhat easier to use that 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 char *
+growstackstr() {
+       int len = stackblocksize();
+       if (herefd >= 0 && len >= 1024) {
+               xwrite(herefd, stackblock(), len);
+               sstrnleft = len - 1;
+               return stackblock();
+       }
+       growstackblock();
+       sstrnleft = stackblocksize() - len - 1;
+       return stackblock() + len;
+}
+
+
+/*
+ * Called from CHECKSTRSPACE.
+ */
+
+static char *
+makestrspace(size_t newlen) {
+       int len = stackblocksize() - sstrnleft;
+       do {
+               growstackblock();
+               sstrnleft = stackblocksize() - len;
+       } while (sstrnleft < newlen);
+       return stackblock() + len;
+}
+
+
+
+static void
+ungrabstackstr(s, p)
+       char *s;
+       char *p;
+       {
+       stacknleft += stacknxt - s;
+       stacknxt = s;
+       sstrnleft = stacknleft - (p - s);
+}
+/*     $NetBSD: miscbltin.c,v 1.30 2001/02/04 19:52:06 christos Exp $  */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Miscelaneous builtins.
+ */
+
+
+#undef rflag
+
+#ifdef __GLIBC__
+mode_t getmode(const void *, mode_t);
+static void *setmode(const char *);
+
+#if !defined(__GLIBC__) || __GLIBC__ == 2 && __GLIBC_MINOR__ < 1
+typedef enum __rlimit_resource rlim_t;
+#endif
+#endif
+
+
+
+/*
+ * The read builtin.  The -e option causes backslashes to escape the
+ * following character.
+ *
+ * This uses unbuffered input, which may be avoidable in some cases.
+ */
+
+static int
+readcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       char **ap;
+       int backslash;
+       char c;
+       int rflag;
+       char *prompt;
+       const char *ifs;
+       char *p;
+       int startword;
+       int status;
+       int i;
+
+       rflag = 0;
+       prompt = NULL;
+       while ((i = nextopt("p:r")) != '\0') {
+               if (i == 'p')
+                       prompt = optionarg;
+               else
+                       rflag = 1;
+       }
+       if (prompt && isatty(0)) {
+               putprompt(prompt);
+               flushall();
+       }
+       if (*(ap = argptr) == NULL)
+               error("arg count");
+       if ((ifs = bltinlookup("IFS")) == NULL)
+               ifs = defifs;
+       status = 0;
+       startword = 1;
+       backslash = 0;
+       STARTSTACKSTR(p);
+       for (;;) {
+               if (read(0, &c, 1) != 1) {
+                       status = 1;
+                       break;
+               }
+               if (c == '\0')
+                       continue;
+               if (backslash) {
+                       backslash = 0;
+                       if (c != '\n')
+                               STPUTC(c, p);
+                       continue;
+               }
+               if (!rflag && c == '\\') {
+                       backslash++;
+                       continue;
+               }
+               if (c == '\n')
+                       break;
+               if (startword && *ifs == ' ' && strchr(ifs, c)) {
+                       continue;
+               }
+               startword = 0;
+               if (backslash && c == '\\') {
+                       if (read(0, &c, 1) != 1) {
+                               status = 1;
+                               break;
+                       }
+                       STPUTC(c, p);
+               } else if (ap[1] != NULL && strchr(ifs, c) != NULL) {
+                       STACKSTRNUL(p);
+                       setvar(*ap, stackblock(), 0);
+                       ap++;
+                       startword = 1;
+                       STARTSTACKSTR(p);
+               } else {
+                       STPUTC(c, p);
+               }
+       }
+       STACKSTRNUL(p);
+       /* Remove trailing blanks */
+       while (stackblock() <= --p && strchr(ifs, *p) != NULL)
+               *p = '\0';
+       setvar(*ap, stackblock(), 0);
+       while (*++ap != NULL)
+               setvar(*ap, nullstr, 0);
+       return status;
+}
+
+
+
+static int
+umaskcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       char *ap;
+       int mask;
+       int i;
+       int symbolic_mode = 0;
+
+       while ((i = nextopt("S")) != '\0') {
+               symbolic_mode = 1;
+       }
+
+       INTOFF;
+       mask = umask(0);
+       umask(mask);
+       INTON;
+
+       if ((ap = *argptr) == NULL) {
+               if (symbolic_mode) {
+                       char u[4], g[4], o[4];
+
+                       i = 0;
+                       if ((mask & S_IRUSR) == 0)
+                               u[i++] = 'r';
+                       if ((mask & S_IWUSR) == 0)
+                               u[i++] = 'w';
+                       if ((mask & S_IXUSR) == 0)
+                               u[i++] = 'x';
+                       u[i] = '\0';
+
+                       i = 0;
+                       if ((mask & S_IRGRP) == 0)
+                               g[i++] = 'r';
+                       if ((mask & S_IWGRP) == 0)
+                               g[i++] = 'w';
+                       if ((mask & S_IXGRP) == 0)
+                               g[i++] = 'x';
+                       g[i] = '\0';
+
+                       i = 0;
+                       if ((mask & S_IROTH) == 0)
+                               o[i++] = 'r';
+                       if ((mask & S_IWOTH) == 0)
+                               o[i++] = 'w';
+                       if ((mask & S_IXOTH) == 0)
+                               o[i++] = 'x';
+                       o[i] = '\0';
+
+                       out1fmt("u=%s,g=%s,o=%s\n", u, g, o);
+               } else {
+                       out1fmt("%.4o\n", mask);
+               }
+       } else {
+               if (isdigit((unsigned char)*ap)) {
+                       mask = 0;
+                       do {
+                               if (*ap >= '8' || *ap < '0')
+                                       error("Illegal number: %s", argv[1]);
+                               mask = (mask << 3) + (*ap - '0');
+                       } while (*++ap != '\0');
+                       umask(mask);
+               } else {
+                       void *set;
+
+                       INTOFF;
+                       if ((set = setmode(ap)) != 0) {
+                               mask = getmode(set, ~mask & 0777);
+                               ckfree(set);
+                       }
+                       INTON;
+                       if (!set)
+                               error("Illegal mode: %s", ap);
+
+                       umask(~mask & 0777);
+               }
+       }
+       return 0;
+}
+
+/*
+ * ulimit builtin
+ *
+ * This code, originally by Doug Gwyn, Doug Kingston, Eric Gisin, and
+ * Michael Rendell was ripped from pdksh 5.0.8 and hacked for use with
+ * ash by J.T. Conklin.
+ *
+ * Public domain.
+ */
+
+struct limits {
+       const char *name;
+       int     cmd;
+       int     factor; /* multiply by to get rlim_{cur,max} values */
+       char    option;
+};
+
+static const struct limits limits[] = {
+#ifdef RLIMIT_CPU
+       { "time(seconds)",              RLIMIT_CPU,        1, 't' },
+#endif
+#ifdef RLIMIT_FSIZE
+       { "file(blocks)",               RLIMIT_FSIZE,    512, 'f' },
+#endif
+#ifdef RLIMIT_DATA
+       { "data(kbytes)",               RLIMIT_DATA,    1024, 'd' },
+#endif
+#ifdef RLIMIT_STACK
+       { "stack(kbytes)",              RLIMIT_STACK,   1024, 's' },
+#endif
+#ifdef  RLIMIT_CORE
+       { "coredump(blocks)",           RLIMIT_CORE,     512, 'c' },
+#endif
+#ifdef RLIMIT_RSS
+       { "memory(kbytes)",             RLIMIT_RSS,     1024, 'm' },
+#endif
+#ifdef RLIMIT_MEMLOCK
+       { "locked memory(kbytes)",      RLIMIT_MEMLOCK, 1024, 'l' },
+#endif
+#ifdef RLIMIT_NPROC
+       { "process(processes)",         RLIMIT_NPROC,      1, 'p' },
+#endif
+#ifdef RLIMIT_NOFILE
+       { "nofiles(descriptors)",       RLIMIT_NOFILE,     1, 'n' },
+#endif
+#ifdef RLIMIT_VMEM
+       { "vmemory(kbytes)",            RLIMIT_VMEM,    1024, 'v' },
+#endif
+#ifdef RLIMIT_SWAP
+       { "swap(kbytes)",               RLIMIT_SWAP,    1024, 'w' },
+#endif
+       { (char *) 0,                   0,                 0,  '\0' }
+};
+
+static int
+ulimitcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       int     c;
+       rlim_t val = 0;
+       enum { SOFT = 0x1, HARD = 0x2 }
+                       how = SOFT | HARD;
+       const struct limits     *l;
+       int             set, all = 0;
+       int             optc, what;
+       struct rlimit   limit;
+
+       what = 'f';
+       while ((optc = nextopt("HSatfdsmcnpl")) != '\0')
+               switch (optc) {
+               case 'H':
+                       how = HARD;
+                       break;
+               case 'S':
+                       how = SOFT;
+                       break;
+               case 'a':
+                       all = 1;
+                       break;
+               default:
+                       what = optc;
+               }
+
+       for (l = limits; l->name && l->option != what; l++)
+               ;
+       if (!l->name)
+               error("internal error (%c)", what);
+
+       set = *argptr ? 1 : 0;
+       if (set) {
+               char *p = *argptr;
+
+               if (all || argptr[1])
+                       error("too many arguments");
+               if (strcmp(p, "unlimited") == 0)
+                       val = RLIM_INFINITY;
+               else {
+                       val = (rlim_t) 0;
+
+                       while ((c = *p++) >= '0' && c <= '9')
+                       {
+                               val = (val * 10) + (long)(c - '0');
+                               if (val < (rlim_t) 0)
+                                       break;
+                       }
+                       if (c)
+                               error("bad number");
+                       val *= l->factor;
+               }
+       }
+       if (all) {
+               for (l = limits; l->name; l++) {
+                       getrlimit(l->cmd, &limit);
+                       if (how & SOFT)
+                               val = limit.rlim_cur;
+                       else if (how & HARD)
+                               val = limit.rlim_max;
+
+                       out1fmt("%-20s ", l->name);
+                       if (val == RLIM_INFINITY)
+                               out1fmt("unlimited\n");
+                       else
+                       {
+                               val /= l->factor;
+#ifdef BSD4_4
+                               out1fmt("%lld\n", (long long) val);
+#else
+                               out1fmt("%ld\n", (long) val);
+#endif
+                       }
+               }
+               return 0;
+       }
+
+       getrlimit(l->cmd, &limit);
+       if (set) {
+               if (how & HARD)
+                       limit.rlim_max = val;
+               if (how & SOFT)
+                       limit.rlim_cur = val;
+               if (setrlimit(l->cmd, &limit) < 0)
+                       error("error setting limit (%s)", strerror(errno));
+       } else {
+               if (how & SOFT)
+                       val = limit.rlim_cur;
+               else if (how & HARD)
+                       val = limit.rlim_max;
+
+               if (val == RLIM_INFINITY)
+                       out1fmt("unlimited\n");
+               else
+               {
+                       val /= l->factor;
+#ifdef BSD4_4
+                       out1fmt("%lld\n", (long long) val);
+#else
+                       out1fmt("%ld\n", (long) val);
+#endif
+               }
+       }
+       return 0;
+}
+/*     $NetBSD: mystring.c,v 1.14 1999/07/09 03:05:50 christos Exp $   */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * String functions.
+ *
+ *     equal(s1, s2)           Return true if strings are equal.
+ *     scopy(from, to)         Copy a string.
+ *     scopyn(from, to, n)     Like scopy, but checks for overflow.
+ *     number(s)               Convert a string of digits to an integer.
+ *     is_number(s)            Return true if s is a string of digits.
+ */
+
+static char nullstr[1];                /* zero length string */
+static const char spcstr[] = " ";
+static const char snlfmt[] = "%s\n";
+
+/*
+ * equal - #defined in mystring.h
+ */
+
+/*
+ * scopy - #defined in mystring.h
+ */
+
+
+#if 0
+/*
+ * scopyn - copy a string from "from" to "to", truncating the string
+ *             if necessary.  "To" is always nul terminated, even if
+ *             truncation is performed.  "Size" is the size of "to".
+ */
+
+static void
+scopyn(from, to, size)
+       char const *from;
+       char *to;
+       int size;
+       {
+
+       while (--size > 0) {
+               if ((*to++ = *from++) == '\0')
+                       return;
+       }
+       *to = '\0';
+}
+#endif
+
+
+/*
+ * prefix -- see if pfx is a prefix of string.
+ */
+
+static int
+prefix(pfx, string)
+       char const *pfx;
+       char const *string;
+       {
+       while (*pfx) {
+               if (*pfx++ != *string++)
+                       return 0;
+       }
+       return 1;
+}
+
+
+/*
+ * Convert a string of digits to an integer, printing an error message on
+ * failure.
+ */
+
+static int
+number(s)
+       const char *s;
+       {
+
+       if (! is_number(s))
+               error("Illegal number: %s", s);
+       return atoi(s);
+}
+
+
+
+/*
+ * Check for a valid number.  This should be elsewhere.
+ */
+
+static int
+is_number(p)
+       const char *p;
+       {
+       do {
+               if (! is_digit(*p))
+                       return 0;
+       } while (*++p != '\0');
+       return 1;
+}
+
+
+/*
+ * Produce a possibly single quoted string suitable as input to the shell.
+ * The return string is allocated on the stack.
+ */
+
+static char *
+single_quote(const char *s) {
+       char *p;
+
+       STARTSTACKSTR(p);
+
+       do {
+               char *q = p;
+               size_t len1, len1p, len2, len2p;
+
+               len1 = strcspn(s, "'");
+               len2 = strspn(s + len1, "'");
+
+               len1p = len1 ? len1 + 2 : len1;
+               switch (len2) {
+               case 0:
+                       len2p = 0;
+                       break;
+               case 1:
+                       len2p = 2;
+                       break;
+               default:
+                       len2p = len2 + 2;
+               }
+
+               CHECKSTRSPACE(len1p + len2p + 1, p);
+
+               if (len1) {
+                       *p = '\'';
+#ifdef _GNU_SOURCE
+                       q = mempcpy(p + 1, s, len1);
+#else
+                       q = p + 1 + len1;
+                       memcpy(p + 1, s, len1);
+#endif
+                       *q++ = '\'';
+                       s += len1;
+               }
+
+               switch (len2) {
+               case 0:
+                       break;
+               case 1:
+                       *q++ = '\\';
+                       *q = '\'';
+                       s++;
+                       break;
+               default:
+                       *q = '"';
+#ifdef _GNU_SOURCE
+                       *(char *) mempcpy(q + 1, s, len2) = '"';
+#else
+                       q += 1 + len2;
+                       memcpy(q + 1, s, len2);
+                       *q = '"';
+#endif
+                       s += len2;
+               }
+
+               STADJUST(len1p + len2p, p);
+       } while (*s);
+
+       USTPUTC(0, p);
+
+       return grabstackstr(p);
+}
+
+/*
+ * Like strdup but works with the ash stack.
+ */
+
+static char *
+sstrdup(const char *p)
+{
+       size_t len = strlen(p) + 1;
+       return memcpy(stalloc(len), p, len);
+}
+
+/*
+ * Wrapper around strcmp for qsort/bsearch/...
+ */
+static int
+pstrcmp(const void *a, const void *b)
+{
+       return strcmp(*(const char *const *) a, *(const char *const *) b);
+}
+
+/*
+ * Find a string is in a sorted array.
+ */
+static const char *const *
+findstring(const char *s, const char *const *array, size_t nmemb)
+{
+       return bsearch(&s, array, nmemb, sizeof(const char *), pstrcmp);
+}
+/*
+ * This file was generated by the mknodes program.
+ */
+
+/*     $NetBSD: nodes.c.pat,v 1.8 1997/04/11 23:03:09 christos Exp $   */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)nodes.c.pat 8.2 (Berkeley) 5/4/95
+ */
+
+/*
+ * Routine for dealing with parsed shell commands.
+ */
+
+
+static int     funcblocksize;          /* size of structures in function */
+static int     funcstringsize;         /* size of strings in node */
+pointer funcblock;             /* block to allocate function from */
+static char   *funcstring;             /* block to allocate strings from */
+
+static const short nodesize[26] = {
+      ALIGN(sizeof (struct nbinary)),
+      ALIGN(sizeof (struct ncmd)),
+      ALIGN(sizeof (struct npipe)),
+      ALIGN(sizeof (struct nredir)),
+      ALIGN(sizeof (struct nredir)),
+      ALIGN(sizeof (struct nredir)),
+      ALIGN(sizeof (struct nbinary)),
+      ALIGN(sizeof (struct nbinary)),
+      ALIGN(sizeof (struct nif)),
+      ALIGN(sizeof (struct nbinary)),
+      ALIGN(sizeof (struct nbinary)),
+      ALIGN(sizeof (struct nfor)),
+      ALIGN(sizeof (struct ncase)),
+      ALIGN(sizeof (struct nclist)),
+      ALIGN(sizeof (struct narg)),
+      ALIGN(sizeof (struct narg)),
+      ALIGN(sizeof (struct nfile)),
+      ALIGN(sizeof (struct nfile)),
+      ALIGN(sizeof (struct nfile)),
+      ALIGN(sizeof (struct nfile)),
+      ALIGN(sizeof (struct nfile)),
+      ALIGN(sizeof (struct ndup)),
+      ALIGN(sizeof (struct ndup)),
+      ALIGN(sizeof (struct nhere)),
+      ALIGN(sizeof (struct nhere)),
+      ALIGN(sizeof (struct nnot)),
+};
+
+
+static void calcsize __P((union node *));
+static void sizenodelist __P((struct nodelist *));
+static union node *copynode __P((union node *));
+static struct nodelist *copynodelist __P((struct nodelist *));
+static char *nodesavestr __P((char *));
+
+
+
+/*
+ * Make a copy of a parse tree.
+ */
+
+union node *
+copyfunc(n)
+       union node *n;
+{
+       if (n == NULL)
+               return NULL;
+       funcblocksize = 0;
+       funcstringsize = 0;
+       calcsize(n);
+       funcblock = ckmalloc(funcblocksize + funcstringsize);
+       funcstring = (char *) funcblock + funcblocksize;
+       return copynode(n);
+}
+
+
+
+static void
+calcsize(n)
+       union node *n;
+{
+      if (n == NULL)
+           return;
+      funcblocksize += nodesize[n->type];
+      switch (n->type) {
+      case NSEMI:
+      case NAND:
+      case NOR:
+      case NWHILE:
+      case NUNTIL:
+           calcsize(n->nbinary.ch2);
+           calcsize(n->nbinary.ch1);
+           break;
+      case NCMD:
+           calcsize(n->ncmd.redirect);
+           calcsize(n->ncmd.args);
+           calcsize(n->ncmd.assign);
+           break;
+      case NPIPE:
+           sizenodelist(n->npipe.cmdlist);
+           break;
+      case NREDIR:
+      case NBACKGND:
+      case NSUBSHELL:
+           calcsize(n->nredir.redirect);
+           calcsize(n->nredir.n);
+           break;
+      case NIF:
+           calcsize(n->nif.elsepart);
+           calcsize(n->nif.ifpart);
+           calcsize(n->nif.test);
+           break;
+      case NFOR:
+           funcstringsize += strlen(n->nfor.var) + 1;
+           calcsize(n->nfor.body);
+           calcsize(n->nfor.args);
+           break;
+      case NCASE:
+           calcsize(n->ncase.cases);
+           calcsize(n->ncase.expr);
+           break;
+      case NCLIST:
+           calcsize(n->nclist.body);
+           calcsize(n->nclist.pattern);
+           calcsize(n->nclist.next);
+           break;
+      case NDEFUN:
+      case NARG:
+           sizenodelist(n->narg.backquote);
+           funcstringsize += strlen(n->narg.text) + 1;
+           calcsize(n->narg.next);
+           break;
+      case NTO:
+      case NFROM:
+      case NFROMTO:
+      case NAPPEND:
+      case NTOOV:
+           calcsize(n->nfile.fname);
+           calcsize(n->nfile.next);
+           break;
+      case NTOFD:
+      case NFROMFD:
+           calcsize(n->ndup.vname);
+           calcsize(n->ndup.next);
+           break;
+      case NHERE:
+      case NXHERE:
+           calcsize(n->nhere.doc);
+           calcsize(n->nhere.next);
+           break;
+      case NNOT:
+           calcsize(n->nnot.com);
+           break;
+      };
+}
+
+
+
+static void
+sizenodelist(lp)
+       struct nodelist *lp;
+{
+       while (lp) {
+               funcblocksize += ALIGN(sizeof(struct nodelist));
+               calcsize(lp->n);
+               lp = lp->next;
+       }
+}
+
+
+
+static union node *
+copynode(n)
+       union node *n;
+{
+       union node *new;
+
+      if (n == NULL)
+           return NULL;
+      new = funcblock;
+      funcblock = (char *) funcblock + nodesize[n->type];
+      switch (n->type) {
+      case NSEMI:
+      case NAND:
+      case NOR:
+      case NWHILE:
+      case NUNTIL:
+           new->nbinary.ch2 = copynode(n->nbinary.ch2);
+           new->nbinary.ch1 = copynode(n->nbinary.ch1);
+           break;
+      case NCMD:
+           new->ncmd.redirect = copynode(n->ncmd.redirect);
+           new->ncmd.args = copynode(n->ncmd.args);
+           new->ncmd.assign = copynode(n->ncmd.assign);
+           new->ncmd.backgnd = n->ncmd.backgnd;
+           break;
+      case NPIPE:
+           new->npipe.cmdlist = copynodelist(n->npipe.cmdlist);
+           new->npipe.backgnd = n->npipe.backgnd;
+           break;
+      case NREDIR:
+      case NBACKGND:
+      case NSUBSHELL:
+           new->nredir.redirect = copynode(n->nredir.redirect);
+           new->nredir.n = copynode(n->nredir.n);
+           break;
+      case NIF:
+           new->nif.elsepart = copynode(n->nif.elsepart);
+           new->nif.ifpart = copynode(n->nif.ifpart);
+           new->nif.test = copynode(n->nif.test);
+           break;
+      case NFOR:
+           new->nfor.var = nodesavestr(n->nfor.var);
+           new->nfor.body = copynode(n->nfor.body);
+           new->nfor.args = copynode(n->nfor.args);
+           break;
+      case NCASE:
+           new->ncase.cases = copynode(n->ncase.cases);
+           new->ncase.expr = copynode(n->ncase.expr);
+           break;
+      case NCLIST:
+           new->nclist.body = copynode(n->nclist.body);
+           new->nclist.pattern = copynode(n->nclist.pattern);
+           new->nclist.next = copynode(n->nclist.next);
+           break;
+      case NDEFUN:
+      case NARG:
+           new->narg.backquote = copynodelist(n->narg.backquote);
+           new->narg.text = nodesavestr(n->narg.text);
+           new->narg.next = copynode(n->narg.next);
+           break;
+      case NTO:
+      case NFROM:
+      case NFROMTO:
+      case NAPPEND:
+      case NTOOV:
+           new->nfile.fname = copynode(n->nfile.fname);
+           new->nfile.fd = n->nfile.fd;
+           new->nfile.next = copynode(n->nfile.next);
+           break;
+      case NTOFD:
+      case NFROMFD:
+           new->ndup.vname = copynode(n->ndup.vname);
+           new->ndup.dupfd = n->ndup.dupfd;
+           new->ndup.fd = n->ndup.fd;
+           new->ndup.next = copynode(n->ndup.next);
+           break;
+      case NHERE:
+      case NXHERE:
+           new->nhere.doc = copynode(n->nhere.doc);
+           new->nhere.fd = n->nhere.fd;
+           new->nhere.next = copynode(n->nhere.next);
+           break;
+      case NNOT:
+           new->nnot.com = copynode(n->nnot.com);
+           break;
+      };
+      new->type = n->type;
+       return new;
+}
+
+
+static struct nodelist *
+copynodelist(lp)
+       struct nodelist *lp;
+{
+       struct nodelist *start;
+       struct nodelist **lpp;
+
+       lpp = &start;
+       while (lp) {
+               *lpp = funcblock;
+               funcblock = (char *) funcblock + ALIGN(sizeof(struct nodelist));
+               (*lpp)->n = copynode(lp->n);
+               lp = lp->next;
+               lpp = &(*lpp)->next;
+       }
+       *lpp = NULL;
+       return start;
+}
+
+
+
+static char *
+nodesavestr(s)
+       char   *s;
+{
+#ifdef _GNU_SOURCE
+       char   *rtn = funcstring;
+
+       funcstring = stpcpy(funcstring, s) + 1;
+       return rtn;
+#else
+       register char *p = s;
+       register char *q = funcstring;
+       char   *rtn = funcstring;
+
+       while ((*q++ = *p++) != '\0')
+               continue;
+       funcstring = q;
+       return rtn;
+#endif
+}
+
+
+
+/*
+ * Free a parse tree.
+ */
+
+static void
+freefunc(n)
+       union node *n;
+{
+       if (n)
+               ckfree(n);
+}
+/*     $NetBSD: options.c,v 1.31 2001/02/26 13:06:43 wiz Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+
+struct optent optlist[NOPTS] = {
+       { "errexit",    'e',    0 },
+       { "noglob",     'f',    0 },
+       { "ignoreeof",  'I',    0 },
+       { "interactive",'i',    0 },
+       { "monitor",    'm',    0 },
+       { "noexec",     'n',    0 },
+       { "stdin",      's',    0 },
+       { "xtrace",     'x',    0 },
+       { "verbose",    'v',    0 },
+       { "vi",         'V',    0 },
+       { "emacs",      'E',    0 },
+       { "noclobber",  'C',    0 },
+       { "allexport",  'a',    0 },
+       { "notify",     'b',    0 },
+       { "nounset",    'u',    0 },
+       { "quietprofile", 'q',  0 },
+};
+static char *arg0;                     /* value of $0 */
+struct shparam shellparam;     /* current positional parameters */
+static char **argptr;                  /* argument list for builtin commands */
+static char *optionarg;                /* set by nextopt (like getopt) */
+static char *optptr;                   /* used by nextopt */
+
+static char *minusc;                   /* argument to -c option */
+
+
+static void options __P((int));
+static void minus_o __P((char *, int));
+static void setoption __P((int, int));
+#ifdef ASH_GETOPTS
+static int getopts __P((char *, char *, char **, int *, int *));
+#endif
+
+
+/*
+ * Process the shell command line arguments.
+ */
+
+static void
+procargs(argc, argv)
+       int argc;
+       char **argv;
+{
+       int i;
+
+       argptr = argv;
+       if (argc > 0)
+               argptr++;
+       for (i = 0; i < NOPTS; i++)
+               optlist[i].val = 2;
+       options(1);
+       if (*argptr == NULL && minusc == NULL)
+               sflag = 1;
+       if (iflag == 2 && sflag == 1 && isatty(0) && isatty(1))
+               iflag = 1;
+       if (mflag == 2)
+               mflag = iflag;
+       for (i = 0; i < NOPTS; i++)
+               if (optlist[i].val == 2)
+                       optlist[i].val = 0;
+       arg0 = argv[0];
+       if (sflag == 0 && minusc == NULL) {
+               commandname = argv[0];
+               arg0 = *argptr++;
+               setinputfile(arg0, 0);
+               commandname = arg0;
+       }
+       /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */
+       if (argptr && minusc && *argptr)
+               arg0 = *argptr++;
+
+       shellparam.p = argptr;
+       shellparam.optind = 1;
+       shellparam.optoff = -1;
+       /* assert(shellparam.malloc == 0 && shellparam.nparam == 0); */
+       while (*argptr) {
+               shellparam.nparam++;
+               argptr++;
+       }
+       optschanged();
+}
+
+
+static void
+optschanged()
+{
+       setinteractive(iflag);
+       setjobctl(mflag);
+}
+
+/*
+ * Process shell options.  The global variable argptr contains a pointer
+ * to the argument list; we advance it past the options.
+ */
+
+static void
+options(cmdline)
+       int cmdline;
+{
+       char *p;
+       int val;
+       int c;
+
+       if (cmdline)
+               minusc = NULL;
+       while ((p = *argptr) != NULL) {
+               argptr++;
+               if ((c = *p++) == '-') {
+                       val = 1;
+                        if (p[0] == '\0' || (p[0] == '-' && p[1] == '\0')) {
+                                if (!cmdline) {
+                                        /* "-" means turn off -x and -v */
+                                        if (p[0] == '\0')
+                                                xflag = vflag = 0;
+                                        /* "--" means reset params */
+                                        else if (*argptr == NULL)
+                                               setparam(argptr);
+                                }
+                               break;    /* "-" or  "--" terminates options */
+                       }
+               } else if (c == '+') {
+                       val = 0;
+               } else {
+                       argptr--;
+                       break;
+               }
+               while ((c = *p++) != '\0') {
+                       if (c == 'c' && cmdline) {
+                               char *q;
+#ifdef NOHACK  /* removing this code allows sh -ce 'foo' for compat */
+                               if (*p == '\0')
+#endif
+                                       q = *argptr++;
+                               if (q == NULL || minusc != NULL)
+                                       error("Bad -c option");
+                               minusc = q;
+#ifdef NOHACK
+                               break;
+#endif
+                       } else if (c == 'o') {
+                               minus_o(*argptr, val);
+                               if (*argptr)
+                                       argptr++;
+                       } else {
+                               setoption(c, val);
+                       }
+               }
+       }
+}
+
+static void
+minus_o(name, val)
+       char *name;
+       int val;
+{
+       int i;
+
+       if (name == NULL) {
+               out1str("Current option settings\n");
+               for (i = 0; i < NOPTS; i++)
+                       out1fmt("%-16s%s\n", optlist[i].name,
+                               optlist[i].val ? "on" : "off");
+       } else {
+               for (i = 0; i < NOPTS; i++)
+                       if (equal(name, optlist[i].name)) {
+                               setoption(optlist[i].letter, val);
+                               return;
+                       }
+               error("Illegal option -o %s", name);
+       }
+}
+
+
+static void
+setoption(flag, val)
+       char flag;
+       int val;
+       {
+       int i;
+
+       for (i = 0; i < NOPTS; i++)
+               if (optlist[i].letter == flag) {
+                       optlist[i].val = val;
+                       if (val) {
+                               /* #%$ hack for ksh semantics */
+                               if (flag == 'V')
+                                       Eflag = 0;
+                               else if (flag == 'E')
+                                       Vflag = 0;
+                       }
+                       return;
+               }
+       error("Illegal option -%c", flag);
+       /* NOTREACHED */
+}
+
+
+
+#ifdef mkinit
+SHELLPROC {
+       int i;
+
+       for (i = 0; i < NOPTS; i++)
+               optlist[i].val = 0;
+       optschanged();
+
+}
+#endif
+
+
+/*
+ * Set the shell parameters.
+ */
+
+static void
+setparam(argv)
+       char **argv;
+       {
+       char **newparam;
+       char **ap;
+       int nparam;
+
+       for (nparam = 0 ; argv[nparam] ; nparam++);
+       ap = newparam = ckmalloc((nparam + 1) * sizeof *ap);
+       while (*argv) {
+               *ap++ = savestr(*argv++);
+       }
+       *ap = NULL;
+       freeparam(&shellparam);
+       shellparam.malloc = 1;
+       shellparam.nparam = nparam;
+       shellparam.p = newparam;
+       shellparam.optind = 1;
+       shellparam.optoff = -1;
+}
+
+
+/*
+ * Free the list of positional parameters.
+ */
+
+static void
+freeparam(param)
+       volatile struct shparam *param;
+       {
+       char **ap;
+
+       if (param->malloc) {
+               for (ap = param->p ; *ap ; ap++)
+                       ckfree(*ap);
+               ckfree(param->p);
+       }
+}
+
+
+
+/*
+ * The shift builtin command.
+ */
+
+static int
+shiftcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       int n;
+       char **ap1, **ap2;
+
+       n = 1;
+       if (argc > 1)
+               n = number(argv[1]);
+       if (n > shellparam.nparam)
+               error("can't shift that many");
+       INTOFF;
+       shellparam.nparam -= n;
+       for (ap1 = shellparam.p ; --n >= 0 ; ap1++) {
+               if (shellparam.malloc)
+                       ckfree(*ap1);
+       }
+       ap2 = shellparam.p;
+       while ((*ap2++ = *ap1++) != NULL);
+       shellparam.optind = 1;
+       shellparam.optoff = -1;
+       INTON;
+       return 0;
+}
+
+
+
+/*
+ * The set command builtin.
+ */
+
+static int
+setcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       if (argc == 1)
+               return showvarscmd(argc, argv);
+       INTOFF;
+       options(0);
+       optschanged();
+       if (*argptr != NULL) {
+               setparam(argptr);
+       }
+       INTON;
+       return 0;
+}
+
+
+static void
+getoptsreset(value)
+       const char *value;
+{
+       shellparam.optind = number(value);
+       shellparam.optoff = -1;
+}
+
+#ifdef ASH_GETOPTS
+/*
+ * The getopts builtin.  Shellparam.optnext points to the next argument
+ * to be processed.  Shellparam.optptr points to the next character to
+ * be processed in the current argument.  If shellparam.optnext is NULL,
+ * then it's the first time getopts has been called.
+ */
+
+static int
+getoptscmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       char **optbase;
+
+       if (argc < 3)
+               error("Usage: getopts optstring var [arg]");
+       else if (argc == 3) {
+               optbase = shellparam.p;
+               if (shellparam.optind > shellparam.nparam + 1) {
+                       shellparam.optind = 1;
+                       shellparam.optoff = -1;
+               }
+       }
+       else {
+               optbase = &argv[3];
+               if (shellparam.optind > argc - 2) {
+                       shellparam.optind = 1;
+                       shellparam.optoff = -1;
+               }
+       }
+
+       return getopts(argv[1], argv[2], optbase, &shellparam.optind,
+                      &shellparam.optoff);
+}
+
+/*
+ * Safe version of setvar, returns 1 on success 0 on failure.
+ */
+
+static int
+setvarsafe(name, val, flags)
+       const char *name, *val;
+       int flags;
+{
+       struct jmploc jmploc;
+       struct jmploc *volatile savehandler = handler;
+       int err = 0;
+#ifdef __GNUC__
+       (void) &err;
+#endif
+
+       if (setjmp(jmploc.loc))
+               err = 1;
+       else {
+               handler = &jmploc;
+               setvar(name, val, flags);
+       }
+       handler = savehandler;
+       return err;
+}
+
+static int
+getopts(optstr, optvar, optfirst, myoptind, optoff)
+       char *optstr;
+       char *optvar;
+       char **optfirst;
+       int *myoptind;
+       int *optoff;
+{
+       char *p, *q;
+       char c = '?';
+       int done = 0;
+       int err = 0;
+       char s[10];
+       char **optnext = optfirst + *myoptind - 1;
+
+       if (*myoptind <= 1 || *optoff < 0 || !(*(optnext - 1)) ||
+           strlen(*(optnext - 1)) < *optoff)
+               p = NULL;
+       else
+               p = *(optnext - 1) + *optoff;
+       if (p == NULL || *p == '\0') {
+               /* Current word is done, advance */
+               if (optnext == NULL)
+                       return 1;
+               p = *optnext;
+               if (p == NULL || *p != '-' || *++p == '\0') {
+atend:
+                       *myoptind = optnext - optfirst + 1;
+                       p = NULL;
+                       done = 1;
+                       goto out;
+               }
+               optnext++;
+               if (p[0] == '-' && p[1] == '\0')        /* check for "--" */
+                       goto atend;
+       }
+
+       c = *p++;
+       for (q = optstr; *q != c; ) {
+               if (*q == '\0') {
+                       if (optstr[0] == ':') {
+                               s[0] = c;
+                               s[1] = '\0';
+                               err |= setvarsafe("OPTARG", s, 0);
+                       }
+                       else {
+                               outfmt(&errout, "Illegal option -%c\n", c);
+                               (void) unsetvar("OPTARG");
+                       }
+                       c = '?';
+                       goto bad;
+               }
+               if (*++q == ':')
+                       q++;
+       }
+
+       if (*++q == ':') {
+               if (*p == '\0' && (p = *optnext) == NULL) {
+                       if (optstr[0] == ':') {
+                               s[0] = c;
+                               s[1] = '\0';
+                               err |= setvarsafe("OPTARG", s, 0);
+                               c = ':';
+                       }
+                       else {
+                               outfmt(&errout, "No arg for -%c option\n", c);
+                               (void) unsetvar("OPTARG");
+                               c = '?';
+                       }
+                       goto bad;
+               }
+
+               if (p == *optnext)
+                       optnext++;
+               setvarsafe("OPTARG", p, 0);
+               p = NULL;
+       }
+       else
+               setvarsafe("OPTARG", "", 0);
+       *myoptind = optnext - optfirst + 1;
+       goto out;
+
+bad:
+       *myoptind = 1;
+       p = NULL;
+out:
+       *optoff = p ? p - *(optnext - 1) : -1;
+       fmtstr(s, sizeof(s), "%d", *myoptind);
+       err |= setvarsafe("OPTIND", s, VNOFUNC);
+       s[0] = c;
+       s[1] = '\0';
+       err |= setvarsafe(optvar, s, 0);
+       if (err) {
+               *myoptind = 1;
+               *optoff = -1;
+               flushall();
+               exraise(EXERROR);
+       }
+       return done;
+}
+#endif 
+
+/*
+ * XXX - should get rid of.  have all builtins use getopt(3).  the
+ * library getopt must have the BSD extension static variable "optreset"
+ * otherwise it can't be used within the shell safely.
+ *
+ * Standard option processing (a la getopt) for builtin routines.  The
+ * only argument that is passed to nextopt is the option string; the
+ * other arguments are unnecessary.  It return the character, or '\0' on
+ * end of input.
+ */
+
+static int
+nextopt(optstring)
+       const char *optstring;
+       {
+       char *p;
+       const char *q;
+       char c;
+
+       if ((p = optptr) == NULL || *p == '\0') {
+               p = *argptr;
+               if (p == NULL || *p != '-' || *++p == '\0')
+                       return '\0';
+               argptr++;
+               if (p[0] == '-' && p[1] == '\0')        /* check for "--" */
+                       return '\0';
+       }
+       c = *p++;
+       for (q = optstring ; *q != c ; ) {
+               if (*q == '\0')
+                       error("Illegal option -%c", c);
+               if (*++q == ':')
+                       q++;
+       }
+       if (*++q == ':') {
+               if (*p == '\0' && (p = *argptr++) == NULL)
+                       error("No arg for -%c option", c);
+               optionarg = p;
+               p = NULL;
+       }
+       optptr = p;
+       return c;
+}
+
+
+/*     $NetBSD: output.c,v 1.23 2001/01/07 23:39:07 lukem Exp $        */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Shell output routines.  We use our own output routines because:
+ *     When a builtin command is interrupted we have to discard
+ *             any pending output.
+ *     When a builtin command appears in back quotes, we want to
+ *             save the output of the command in a region obtained
+ *             via malloc, rather than doing a fork and reading the
+ *             output of the command via a pipe.
+ *     Our output routines may be smaller than the stdio routines.
+ */
+
+
+#define OUTBUFSIZ BUFSIZ
+#define MEM_OUT -3             /* output to dynamically allocated memory */
+
+
+#ifdef USE_GLIBC_STDIO
+struct output output = {NULL, NULL, 0, NULL, 0, 1, 0};
+struct output errout = {NULL, NULL, 0, NULL, 0, 2, 0};
+struct output memout = {NULL, NULL, 0, NULL, 0, MEM_OUT, 0};
+#else
+struct output output = {NULL, 0, NULL, OUTBUFSIZ, 1, 0};
+struct output errout = {NULL, 0, NULL, 0, 2, 0};
+struct output memout = {NULL, 0, NULL, 0, MEM_OUT, 0};
+#endif
+struct output *out1 = &output;
+struct output *out2 = &errout;
+
+
+#ifndef USE_GLIBC_STDIO
+static void __outstr __P((const char *, size_t, struct output*));
+#endif
+
+
+#ifdef mkinit
+
+INCLUDE "output.h"
+INCLUDE "memalloc.h"
+
+INIT {
+#ifdef USE_GLIBC_STDIO
+       initstreams();
+#endif
+}
+
+RESET {
+       out1 = &output;
+       out2 = &errout;
+#ifdef USE_GLIBC_STDIO
+       if (memout.stream != NULL)
+               __closememout();
+#endif
+       if (memout.buf != NULL) {
+               ckfree(memout.buf);
+               memout.buf = NULL;
+       }
+}
+
+#endif
+
+
+#ifndef USE_GLIBC_STDIO
+static void
+__outstr(const char *p, size_t len, struct output *dest) {
+       if (!dest->bufsize) {
+               dest->nleft = 0;
+       } else if (dest->buf == NULL) {
+               if (len > dest->bufsize && dest->fd == MEM_OUT) {
+                       dest->bufsize = len;
+               }
+               INTOFF;
+               dest->buf = ckmalloc(dest->bufsize);
+               dest->nextc = dest->buf;
+               dest->nleft = dest->bufsize;
+               INTON;
+       } else if (dest->fd == MEM_OUT) {
+               int offset;
+
+               offset = dest->bufsize;
+               INTOFF;
+               if (dest->bufsize >= len) {
+                       dest->bufsize <<= 1;
+               } else {
+                       dest->bufsize += len;
+               }
+               dest->buf = ckrealloc(dest->buf, dest->bufsize);
+               dest->nleft = dest->bufsize - offset;
+               dest->nextc = dest->buf + offset;
+               INTON;
+       } else {
+               flushout(dest);
+       }
+
+       if (len < dest->nleft) {
+               dest->nleft -= len;
+               memcpy(dest->nextc, p, len);
+               dest->nextc += len;
+               return;
+       }
+
+       if (xwrite(dest->fd, p, len) < len)
+               dest->flags |= OUTPUT_ERR;
+}
+#endif
+
+
+static void
+outstr(p, file)
+       const char *p;
+       struct output *file;
+       {
+#ifdef USE_GLIBC_STDIO
+       INTOFF;
+       fputs(p, file->stream);
+       INTON;
+#else
+       size_t len;
+
+       if (!*p) {
+               return;
+       }
+       len = strlen(p);
+       if ((file->nleft -= len) > 0) {
+               memcpy(file->nextc, p, len);
+               file->nextc += len;
+               return;
+       }
+       __outstr(p, len, file);
+#endif
+}
+
+
+#ifndef USE_GLIBC_STDIO
+
+
+static void
+outcslow(c, dest)
+       char c;
+       struct output *dest;
+       {
+       __outstr(&c, 1, dest);
+}
+#endif
+
+
+static void
+flushall() {
+       flushout(&output);
+#ifdef FLUSHERR
+       flushout(&errout);
+#endif
+}
+
+
+static void
+flushout(dest)
+       struct output *dest;
+       {
+#ifdef USE_GLIBC_STDIO
+       INTOFF;
+       fflush(dest->stream);
+       INTON;
+#else
+       size_t len;
+
+       len = dest->nextc - dest->buf;
+       if (dest->buf == NULL || !len || dest->fd < 0)
+               return;
+       dest->nextc = dest->buf;
+       dest->nleft = dest->bufsize;
+       if (xwrite(dest->fd, dest->buf, len) < len)
+               dest->flags |= OUTPUT_ERR;
+#endif
+}
+
+
+static void
+freestdout() {
+       if (output.buf) {
+               INTOFF;
+               ckfree(output.buf);
+               output.buf = NULL;
+               output.nleft = 0;
+               INTON;
+       }
+       output.flags = 0;
+}
+
+
+static void
+#ifdef __STDC__
+outfmt(struct output *file, const char *fmt, ...)
+#else
+static void
+outfmt(va_alist)
+       va_dcl
+#endif
+{
+       va_list ap;
+#ifndef __STDC__
+       struct output *file;
+       const char *fmt;
+
+       va_start(ap);
+       file = va_arg(ap, struct output *);
+       fmt = va_arg(ap, const char *);
+#else
+       va_start(ap, fmt);
+#endif
+       doformat(file, fmt, ap);
+       va_end(ap);
+}
+
+
+static void
+#ifdef __STDC__
+out1fmt(const char *fmt, ...)
+#else
+out1fmt(va_alist)
+       va_dcl
+#endif
+{
+       va_list ap;
+#ifndef __STDC__
+       const char *fmt;
+
+       va_start(ap);
+       fmt = va_arg(ap, const char *);
+#else
+       va_start(ap, fmt);
+#endif
+       doformat(out1, fmt, ap);
+       va_end(ap);
+}
+
+static void
+#ifdef __STDC__
+fmtstr(char *outbuf, size_t length, const char *fmt, ...)
+#else
+fmtstr(va_alist)
+       va_dcl
+#endif
+{
+       va_list ap;
+#ifndef __STDC__
+       char *outbuf;
+       size_t length;
+       const char *fmt;
+
+       va_start(ap);
+       outbuf = va_arg(ap, char *);
+       length = va_arg(ap, size_t);
+       fmt = va_arg(ap, const char *);
+#else
+       va_start(ap, fmt);
+#endif
+       INTOFF;
+       vsnprintf(outbuf, length, fmt, ap);
+       INTON;
+}
+
+#ifndef USE_GLIBC_STDIO
+/*
+ * Formatted output.  This routine handles a subset of the printf formats:
+ * - Formats supported: d, u, o, p, X, s, and c.
+ * - The x format is also accepted but is treated like X.
+ * - The l, ll and q modifiers are accepted.
+ * - The - and # flags are accepted; # only works with the o format.
+ * - Width and precision may be specified with any format except c.
+ * - An * may be given for the width or precision.
+ * - The obsolete practice of preceding the width with a zero to get
+ *   zero padding is not supported; use the precision field.
+ * - A % may be printed by writing %% in the format string.
+ */
+
+#define TEMPSIZE 24
+
+#ifdef BSD4_4
+#define HAVE_VASPRINTF 1
+#endif
+
+#if    !HAVE_VASPRINTF
+static const char digit[] = "0123456789ABCDEF";
+#endif
+
+
+static void
+doformat(dest, f, ap)
+       struct output *dest;
+       const char *f;          /* format string */
+       va_list ap;
+{
+#if    HAVE_VASPRINTF
+       char *s, *t;
+       int len;
+
+       INTOFF;
+       len = vasprintf(&t, f, ap);
+       if (len < 0) {
+               return;
+       }
+       s = stalloc(++len);
+       memcpy(s, t, len);
+       free(t);
+       INTON;
+       outstr(s, dest);
+       stunalloc(s);
+#else  /* !HAVE_VASPRINTF */
+       char c;
+       char temp[TEMPSIZE];
+       int flushleft;
+       int sharp;
+       int width;
+       int prec;
+       int islong;
+       int isquad;
+       char *p;
+       int sign;
+#ifdef BSD4_4
+       quad_t l;
+       u_quad_t num;
+#else
+       long l;
+       u_long num;
+#endif
+       unsigned base;
+       int len;
+       int size;
+       int pad;
+
+       while ((c = *f++) != '\0') {
+               if (c != '%') {
+                       outc(c, dest);
+                       continue;
+               }
+               flushleft = 0;
+               sharp = 0;
+               width = 0;
+               prec = -1;
+               islong = 0;
+               isquad = 0;
+               for (;;) {
+                       if (*f == '-')
+                               flushleft++;
+                       else if (*f == '#')
+                               sharp++;
+                       else
+                               break;
+                       f++;
+               }
+               if (*f == '*') {
+                       width = va_arg(ap, int);
+                       f++;
+               } else {
+                       while (is_digit(*f)) {
+                               width = 10 * width + digit_val(*f++);
+                       }
+               }
+               if (*f == '.') {
+                       if (*++f == '*') {
+                               prec = va_arg(ap, int);
+                               f++;
+                       } else {
+                               prec = 0;
+                               while (is_digit(*f)) {
+                                       prec = 10 * prec + digit_val(*f++);
+                               }
+                       }
+               }
+               if (*f == 'l') {
+                       f++;
+                       if (*f == 'l') {
+                               isquad++;
+                               f++;
+                       } else
+                               islong++;
+               } else if (*f == 'q') {
+                       isquad++;
+                       f++;
+               }
+               switch (*f) {
+               case 'd':
+#ifdef BSD4_4
+                       if (isquad)
+                               l = va_arg(ap, quad_t);
+                       else
+#endif
+                       if (islong)
+                               l = va_arg(ap, long);
+                       else
+                               l = va_arg(ap, int);
+                       sign = 0;
+                       num = l;
+                       if (l < 0) {
+                               num = -l;
+                               sign = 1;
+                       }
+                       base = 10;
+                       goto number;
+               case 'u':
+                       base = 10;
+                       goto uns_number;
+               case 'o':
+                       base = 8;
+                       goto uns_number;
+               case 'p':
+                       outc('0', dest);
+                       outc('x', dest);
+                       /*FALLTHROUGH*/
+               case 'x':
+                       /* we don't implement 'x'; treat like 'X' */
+               case 'X':
+                       base = 16;
+uns_number:      /* an unsigned number */
+                       sign = 0;
+#ifdef BSD4_4
+                       if (isquad)
+                               num = va_arg(ap, u_quad_t);
+                       else
+#endif
+                       if (islong)
+                               num = va_arg(ap, unsigned long);
+                       else
+                               num = va_arg(ap, unsigned int);
+number:                  /* process a number */
+                       p = temp + TEMPSIZE - 1;
+                       *p = '\0';
+                       while (num) {
+                               *--p = digit[num % base];
+                               num /= base;
+                       }
+                       len = (temp + TEMPSIZE - 1) - p;
+                       if (prec < 0)
+                               prec = 1;
+                       if (sharp && *f == 'o' && prec <= len)
+                               prec = len + 1;
+                       pad = 0;
+                       if (width) {
+                               size = len;
+                               if (size < prec)
+                                       size = prec;
+                               size += sign;
+                               pad = width - size;
+                               if (flushleft == 0) {
+                                       while (--pad >= 0)
+                                               outc(' ', dest);
+                               }
+                       }
+                       if (sign)
+                               outc('-', dest);
+                       prec -= len;
+                       while (--prec >= 0)
+                               outc('0', dest);
+                       while (*p)
+                               outc(*p++, dest);
+                       while (--pad >= 0)
+                               outc(' ', dest);
+                       break;
+               case 's':
+                       p = va_arg(ap, char *);
+                       pad = 0;
+                       if (width) {
+                               len = strlen(p);
+                               if (prec >= 0 && len > prec)
+                                       len = prec;
+                               pad = width - len;
+                               if (flushleft == 0) {
+                                       while (--pad >= 0)
+                                               outc(' ', dest);
+                               }
+                       }
+                       prec++;
+                       while (--prec != 0 && *p)
+                               outc(*p++, dest);
+                       while (--pad >= 0)
+                               outc(' ', dest);
+                       break;
+               case 'c':
+                       c = va_arg(ap, int);
+                       outc(c, dest);
+                       break;
+               default:
+                       outc(*f, dest);
+                       break;
+               }
+               f++;
+       }
+#endif /* !HAVE_VASPRINTF */
+}
+#endif
+
+
+
+/*
+ * Version of write which resumes after a signal is caught.
+ */
+
+static int
+xwrite(fd, buf, nbytes)
+       int fd;
+       const char *buf;
+       int nbytes;
+       {
+       int ntry;
+       int i;
+       int n;
+
+       n = nbytes;
+       ntry = 0;
+       for (;;) {
+               i = write(fd, buf, n);
+               if (i > 0) {
+                       if ((n -= i) <= 0)
+                               return nbytes;
+                       buf += i;
+                       ntry = 0;
+               } else if (i == 0) {
+                       if (++ntry > 10)
+                               return nbytes - n;
+               } else if (errno != EINTR) {
+                       return -1;
+               }
+       }
+}
+
+
+#ifdef notdef
+/*
+ * Version of ioctl that retries after a signal is caught.
+ * XXX unused function
+ */
+
+static int
+xioctl(fd, request, arg)
+       int fd;
+       unsigned long request;
+       char * arg;
+{
+       int i;
+
+       while ((i = ioctl(fd, request, arg)) == -1 && errno == EINTR);
+       return i;
+}
+#endif
+
+
+#ifdef USE_GLIBC_STDIO
+static void initstreams() {
+       output.stream = stdout;
+       errout.stream = stderr;
+}
+
+
+static void
+openmemout() {
+       INTOFF;
+       memout.stream = open_memstream(&memout.buf, &memout.bufsize);
+       INTON;
+}
+
+
+static int
+__closememout() {
+       int error;
+       error = fclose(memout.stream);
+       memout.stream = NULL;
+       return error;
+}
+#endif
+/*     $NetBSD: parser.c,v 1.46 2001/02/04 19:52:06 christos Exp $     */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+
+/*
+ * Shell command parser.
+ */
+
+#define EOFMARKLEN 79
+
+
+
+struct heredoc {
+       struct heredoc *next;   /* next here document in list */
+       union node *here;               /* redirection node */
+       char *eofmark;          /* string indicating end of input */
+       int striptabs;          /* if set, strip leading tabs */
+};
+
+
+
+struct heredoc *heredoclist;   /* list of here documents to read */
+static int parsebackquote;             /* nonzero if we are inside backquotes */
+static int doprompt;                   /* if set, prompt the user */
+static int needprompt;                 /* true if interactive and at start of line */
+static int lasttoken;                  /* last token read */
+static int tokpushback;                /* last token pushed back */
+static char *wordtext;                 /* text of last word returned by readtoken */
+static int checkkwd;            /* 1 == check for kwds, 2 == also eat newlines */
+/* 1 == check for aliases, 2 == also check for assignments */
+static int checkalias;
+struct nodelist *backquotelist;
+union node *redirnode;
+struct heredoc *heredoc;
+static int quoteflag;                  /* set if (part of) last token was quoted */
+static int startlinno;                 /* line # where last token started */
+
+
+static union node *list __P((int));
+static union node *andor __P((void));
+static union node *pipeline __P((void));
+static union node *command __P((void));
+static union node *simplecmd __P((void));
+static union node *makename __P((void));
+static void parsefname __P((void));
+static void parseheredoc __P((void));
+static int peektoken __P((void));
+static int readtoken __P((void));
+static int xxreadtoken __P((void));
+static int readtoken1 __P((int, char const *, char *, int));
+static int noexpand __P((char *));
+static void synexpect __P((int)) __attribute__((noreturn));
+static void synerror __P((const char *)) __attribute__((noreturn));
+static void setprompt __P((int));
+
+
+/*
+ * Read and parse a command.  Returns NEOF on end of file.  (NULL is a
+ * valid parse tree indicating a blank line.)
+ */
+
+union node *
+parsecmd(int interact)
+{
+       int t;
+
+       tokpushback = 0;
+       doprompt = interact;
+       if (doprompt)
+               setprompt(1);
+       else
+               setprompt(0);
+       needprompt = 0;
+       t = readtoken();
+       if (t == TEOF)
+               return NEOF;
+       if (t == TNL)
+               return NULL;
+       tokpushback++;
+       return list(1);
+}
+
+
+static union node *
+list(nlflag)
+       int nlflag;
+{
+       union node *n1, *n2, *n3;
+       int tok;
+
+       checkkwd = 2;
+       if (nlflag == 0 && tokendlist[peektoken()])
+               return NULL;
+       n1 = NULL;
+       for (;;) {
+               n2 = andor();
+               tok = readtoken();
+               if (tok == TBACKGND) {
+                       if (n2->type == NCMD || n2->type == NPIPE) {
+                               n2->ncmd.backgnd = 1;
+                       } else if (n2->type == NREDIR) {
+                               n2->type = NBACKGND;
+                       } else {
+                               n3 = (union node *)stalloc(sizeof (struct nredir));
+                               n3->type = NBACKGND;
+                               n3->nredir.n = n2;
+                               n3->nredir.redirect = NULL;
+                               n2 = n3;
+                       }
+               }
+               if (n1 == NULL) {
+                       n1 = n2;
+               }
+               else {
+                       n3 = (union node *)stalloc(sizeof (struct nbinary));
+                       n3->type = NSEMI;
+                       n3->nbinary.ch1 = n1;
+                       n3->nbinary.ch2 = n2;
+                       n1 = n3;
+               }
+               switch (tok) {
+               case TBACKGND:
+               case TSEMI:
+                       tok = readtoken();
+                       /* fall through */
+               case TNL:
+                       if (tok == TNL) {
+                               parseheredoc();
+                               if (nlflag)
+                                       return n1;
+                       } else {
+                               tokpushback++;
+                       }
+                       checkkwd = 2;
+                       if (tokendlist[peektoken()])
+                               return n1;
+                       break;
+               case TEOF:
+                       if (heredoclist)
+                               parseheredoc();
+                       else
+                               pungetc();              /* push back EOF on input */
+                       return n1;
+               default:
+                       if (nlflag)
+                               synexpect(-1);
+                       tokpushback++;
+                       return n1;
+               }
+       }
+}
+
+
+
+static union node *
+andor() {
+       union node *n1, *n2, *n3;
+       int t;
+
+       checkkwd = 1;
+       n1 = pipeline();
+       for (;;) {
+               if ((t = readtoken()) == TAND) {
+                       t = NAND;
+               } else if (t == TOR) {
+                       t = NOR;
+               } else {
+                       tokpushback++;
+                       return n1;
+               }
+               checkkwd = 2;
+               n2 = pipeline();
+               n3 = (union node *)stalloc(sizeof (struct nbinary));
+               n3->type = t;
+               n3->nbinary.ch1 = n1;
+               n3->nbinary.ch2 = n2;
+               n1 = n3;
+       }
+}
+
+
+
+static union node *
+pipeline() {
+       union node *n1, *n2, *pipenode;
+       struct nodelist *lp, *prev;
+       int negate;
+
+       negate = 0;
+       TRACE(("pipeline: entered\n"));
+       if (readtoken() == TNOT) {
+               negate = !negate;
+               checkkwd = 1;
+       } else
+               tokpushback++;
+       n1 = command();
+       if (readtoken() == TPIPE) {
+               pipenode = (union node *)stalloc(sizeof (struct npipe));
+               pipenode->type = NPIPE;
+               pipenode->npipe.backgnd = 0;
+               lp = (struct nodelist *)stalloc(sizeof (struct nodelist));
+               pipenode->npipe.cmdlist = lp;
+               lp->n = n1;
+               do {
+                       prev = lp;
+                       lp = (struct nodelist *)stalloc(sizeof (struct nodelist));
+                       checkkwd = 2;
+                       lp->n = command();
+                       prev->next = lp;
+               } while (readtoken() == TPIPE);
+               lp->next = NULL;
+               n1 = pipenode;
+       }
+       tokpushback++;
+       if (negate) {
+               n2 = (union node *)stalloc(sizeof (struct nnot));
+               n2->type = NNOT;
+               n2->nnot.com = n1;
+               return n2;
+       } else
+               return n1;
+}
+
+
+
+static union node *
+command() {
+       union node *n1, *n2;
+       union node *ap, **app;
+       union node *cp, **cpp;
+       union node *redir, **rpp;
+       int t;
+
+       redir = NULL;
+       n1 = NULL;
+       rpp = &redir;
+
+       switch (readtoken()) {
+       case TIF:
+               n1 = (union node *)stalloc(sizeof (struct nif));
+               n1->type = NIF;
+               n1->nif.test = list(0);
+               if (readtoken() != TTHEN)
+                       synexpect(TTHEN);
+               n1->nif.ifpart = list(0);
+               n2 = n1;
+               while (readtoken() == TELIF) {
+                       n2->nif.elsepart = (union node *)stalloc(sizeof (struct nif));
+                       n2 = n2->nif.elsepart;
+                       n2->type = NIF;
+                       n2->nif.test = list(0);
+                       if (readtoken() != TTHEN)
+                               synexpect(TTHEN);
+                       n2->nif.ifpart = list(0);
+               }
+               if (lasttoken == TELSE)
+                       n2->nif.elsepart = list(0);
+               else {
+                       n2->nif.elsepart = NULL;
+                       tokpushback++;
+               }
+               if (readtoken() != TFI)
+                       synexpect(TFI);
+               checkkwd = 1;
+               break;
+       case TWHILE:
+       case TUNTIL: {
+               int got;
+               n1 = (union node *)stalloc(sizeof (struct nbinary));
+               n1->type = (lasttoken == TWHILE)? NWHILE : NUNTIL;
+               n1->nbinary.ch1 = list(0);
+               if ((got=readtoken()) != TDO) {
+TRACE(("expecting DO got %s %s\n", tokname[got], got == TWORD ? wordtext : ""));
+                       synexpect(TDO);
+               }
+               n1->nbinary.ch2 = list(0);
+               if (readtoken() != TDONE)
+                       synexpect(TDONE);
+               checkkwd = 1;
+               break;
+       }
+       case TFOR:
+               if (readtoken() != TWORD || quoteflag || ! goodname(wordtext))
+                       synerror("Bad for loop variable");
+               n1 = (union node *)stalloc(sizeof (struct nfor));
+               n1->type = NFOR;
+               n1->nfor.var = wordtext;
+               checkkwd = 1;
+               if (readtoken() == TIN) {
+                       app = &ap;
+                       while (readtoken() == TWORD) {
+                               n2 = (union node *)stalloc(sizeof (struct narg));
+                               n2->type = NARG;
+                               n2->narg.text = wordtext;
+                               n2->narg.backquote = backquotelist;
+                               *app = n2;
+                               app = &n2->narg.next;
+                       }
+                       *app = NULL;
+                       n1->nfor.args = ap;
+                       if (lasttoken != TNL && lasttoken != TSEMI)
+                               synexpect(-1);
+               } else {
+                       static char argvars[5] = {CTLVAR, VSNORMAL|VSQUOTE,
+                                                                  '@', '=', '\0'};
+                       n2 = (union node *)stalloc(sizeof (struct narg));
+                       n2->type = NARG;
+                       n2->narg.text = argvars;
+                       n2->narg.backquote = NULL;
+                       n2->narg.next = NULL;
+                       n1->nfor.args = n2;
+                       /*
+                        * Newline or semicolon here is optional (but note
+                        * that the original Bourne shell only allowed NL).
+                        */
+                       if (lasttoken != TNL && lasttoken != TSEMI)
+                               tokpushback++;
+               }
+               checkkwd = 2;
+               if (readtoken() != TDO)
+                       synexpect(TDO);
+               n1->nfor.body = list(0);
+               if (readtoken() != TDONE)
+                       synexpect(TDONE);
+               checkkwd = 1;
+               break;
+       case TCASE:
+               n1 = (union node *)stalloc(sizeof (struct ncase));
+               n1->type = NCASE;
+               if (readtoken() != TWORD)
+                       synexpect(TWORD);
+               n1->ncase.expr = n2 = (union node *)stalloc(sizeof (struct narg));
+               n2->type = NARG;
+               n2->narg.text = wordtext;
+               n2->narg.backquote = backquotelist;
+               n2->narg.next = NULL;
+               do {
+                       checkkwd = 1;
+               } while (readtoken() == TNL);
+               if (lasttoken != TIN)
+                       synerror("expecting \"in\"");
+               cpp = &n1->ncase.cases;
+               checkkwd = 2, readtoken();
+               do {
+                       if (lasttoken == TLP)
+                               readtoken();
+                       *cpp = cp = (union node *)stalloc(sizeof (struct nclist));
+                       cp->type = NCLIST;
+                       app = &cp->nclist.pattern;
+                       for (;;) {
+                               *app = ap = (union node *)stalloc(sizeof (struct narg));
+                               ap->type = NARG;
+                               ap->narg.text = wordtext;
+                               ap->narg.backquote = backquotelist;
+                               if (checkkwd = 2, readtoken() != TPIPE)
+                                       break;
+                               app = &ap->narg.next;
+                               readtoken();
+                       }
+                       ap->narg.next = NULL;
+                       if (lasttoken != TRP)
+                               synexpect(TRP);
+                       cp->nclist.body = list(0);
+
+                       checkkwd = 2;
+                       if ((t = readtoken()) != TESAC) {
+                               if (t != TENDCASE)
+                                       synexpect(TENDCASE);
+                               else
+                                       checkkwd = 2, readtoken();
+                       }
+                       cpp = &cp->nclist.next;
+               } while(lasttoken != TESAC);
+               *cpp = NULL;
+               checkkwd = 1;
+               break;
+       case TLP:
+               n1 = (union node *)stalloc(sizeof (struct nredir));
+               n1->type = NSUBSHELL;
+               n1->nredir.n = list(0);
+               n1->nredir.redirect = NULL;
+               if (readtoken() != TRP)
+                       synexpect(TRP);
+               checkkwd = 1;
+               break;
+       case TBEGIN:
+               n1 = list(0);
+               if (readtoken() != TEND)
+                       synexpect(TEND);
+               checkkwd = 1;
+               break;
+       /* Handle an empty command like other simple commands.  */
+       case TSEMI:
+       case TAND:
+       case TOR:
+       case TNL:
+       case TEOF:
+       case TRP:
+       case TBACKGND:
+               /*
+                * An empty command before a ; doesn't make much sense, and
+                * should certainly be disallowed in the case of `if ;'.
+                */
+               if (!redir)
+                       synexpect(-1);
+       case TWORD:
+       case TREDIR:
+               tokpushback++;
+               n1 = simplecmd();
+               return n1;
+       default:
+               synexpect(-1);
+               /* NOTREACHED */
+       }
+
+       /* Now check for redirection which may follow command */
+       while (readtoken() == TREDIR) {
+               *rpp = n2 = redirnode;
+               rpp = &n2->nfile.next;
+               parsefname();
+       }
+       tokpushback++;
+       *rpp = NULL;
+       if (redir) {
+               if (n1->type != NSUBSHELL) {
+                       n2 = (union node *)stalloc(sizeof (struct nredir));
+                       n2->type = NREDIR;
+                       n2->nredir.n = n1;
+                       n1 = n2;
+               }
+               n1->nredir.redirect = redir;
+       }
+
+       return n1;
+}
+
+
+static union node *
+simplecmd() {
+       union node *args, **app;
+       union node *n = NULL;
+       union node *vars, **vpp;
+       union node **rpp, *redir;
+
+       args = NULL;
+       app = &args;
+       vars = NULL;
+       vpp = &vars;
+       redir = NULL;
+       rpp = &redir;
+
+       checkalias = 2;
+       for (;;) {
+               switch (readtoken()) {
+               case TWORD:
+               case TASSIGN:
+                       n = (union node *)stalloc(sizeof (struct narg));
+                       n->type = NARG;
+                       n->narg.text = wordtext;
+                       n->narg.backquote = backquotelist;
+                       if (lasttoken == TWORD) {
+                               *app = n;
+                               app = &n->narg.next;
+                       } else {
+                               *vpp = n;
+                               vpp = &n->narg.next;
+                       }
+                       break;
+               case TREDIR:
+                       *rpp = n = redirnode;
+                       rpp = &n->nfile.next;
+                       parsefname();   /* read name of redirection file */
+                       break;
+               case TLP:
+                       if (
+                               args && app == &args->narg.next &&
+                               !vars && !redir
+                       ) {
+                               /* We have a function */
+                               if (readtoken() != TRP)
+                                       synexpect(TRP);
+#ifdef notdef
+                               if (! goodname(n->narg.text))
+                                       synerror("Bad function name");
+#endif
+                               n->type = NDEFUN;
+                               checkkwd = 2;
+                               n->narg.next = command();
+                               return n;
+                       }
+                       /* fall through */
+               default:
+                       tokpushback++;
+                       goto out;
+               }
+       }
+out:
+       *app = NULL;
+       *vpp = NULL;
+       *rpp = NULL;
+       n = (union node *)stalloc(sizeof (struct ncmd));
+       n->type = NCMD;
+       n->ncmd.backgnd = 0;
+       n->ncmd.args = args;
+       n->ncmd.assign = vars;
+       n->ncmd.redirect = redir;
+       return n;
+}
+
+static union node *
+makename() {
+       union node *n;
+
+       n = (union node *)stalloc(sizeof (struct narg));
+       n->type = NARG;
+       n->narg.next = NULL;
+       n->narg.text = wordtext;
+       n->narg.backquote = backquotelist;
+       return n;
+}
+
+static void fixredir(union node *n, const char *text, int err)
+       {
+       TRACE(("Fix redir %s %d\n", text, err));
+       if (!err)
+               n->ndup.vname = NULL;
+
+       if (is_digit(text[0]) && text[1] == '\0')
+               n->ndup.dupfd = digit_val(text[0]);
+       else if (text[0] == '-' && text[1] == '\0')
+               n->ndup.dupfd = -1;
+       else {
+
+               if (err)
+                       synerror("Bad fd number");
+               else
+                       n->ndup.vname = makename();
+       }
+}
+
+
+static void
+parsefname() {
+       union node *n = redirnode;
+
+       if (readtoken() != TWORD)
+               synexpect(-1);
+       if (n->type == NHERE) {
+               struct heredoc *here = heredoc;
+               struct heredoc *p;
+               int i;
+
+               if (quoteflag == 0)
+                       n->type = NXHERE;
+               TRACE(("Here document %d\n", n->type));
+               if (here->striptabs) {
+                       while (*wordtext == '\t')
+                               wordtext++;
+               }
+               if (! noexpand(wordtext) || (i = strlen(wordtext)) == 0 || i > EOFMARKLEN)
+                       synerror("Illegal eof marker for << redirection");
+               rmescapes(wordtext);
+               here->eofmark = wordtext;
+               here->next = NULL;
+               if (heredoclist == NULL)
+                       heredoclist = here;
+               else {
+                       for (p = heredoclist ; p->next ; p = p->next);
+                       p->next = here;
+               }
+       } else if (n->type == NTOFD || n->type == NFROMFD) {
+               fixredir(n, wordtext, 0);
+       } else {
+               n->nfile.fname = makename();
+       }
+}
+
+
+/*
+ * Input any here documents.
+ */
+
+static void
+parseheredoc() {
+       struct heredoc *here;
+       union node *n;
+
+       while (heredoclist) {
+               here = heredoclist;
+               heredoclist = here->next;
+               if (needprompt) {
+                       setprompt(2);
+                       needprompt = 0;
+               }
+               readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX,
+                               here->eofmark, here->striptabs);
+               n = (union node *)stalloc(sizeof (struct narg));
+               n->narg.type = NARG;
+               n->narg.next = NULL;
+               n->narg.text = wordtext;
+               n->narg.backquote = backquotelist;
+               here->here->nhere.doc = n;
+       }
+}
+
+static int
+peektoken() {
+       int t;
+
+       t = readtoken();
+       tokpushback++;
+       return (t);
+}
+
+static int
+readtoken() {
+       int t;
+       int savecheckkwd = checkkwd;
+       int savecheckalias = checkalias;
+       struct alias *ap;
+#ifdef DEBUG
+       int alreadyseen = tokpushback;
+#endif
+
+top:
+       t = xxreadtoken();
+       checkalias = savecheckalias;
+
+       if (checkkwd) {
+               /*
+                * eat newlines
+                */
+               if (checkkwd == 2) {
+                       checkkwd = 0;
+                       while (t == TNL) {
+                               parseheredoc();
+                               t = xxreadtoken();
+                       }
+               }
+               checkkwd = 0;
+               /*
+                * check for keywords
+                */
+               if (t == TWORD && !quoteflag)
+               {
+                       const char *const *pp;
+
+                       if ((pp = findkwd(wordtext))) {
+                               lasttoken = t = pp - parsekwd + KWDOFFSET;
+                               TRACE(("keyword %s recognized\n", tokname[t]));
+                               goto out;
+                       }
+               }
+       }
+
+       if (t != TWORD) {
+               if (t != TREDIR) {
+                       checkalias = 0;
+               }
+       } else if (checkalias == 2 && isassignment(wordtext)) {
+               lasttoken = t = TASSIGN;
+       } else if (checkalias) {
+               if (!quoteflag && (ap = lookupalias(wordtext, 1)) != NULL) {
+                       if (*ap->val) {
+                               pushstring(ap->val, strlen(ap->val), ap);
+                       }
+                       checkkwd = savecheckkwd;
+                       goto top;
+               }
+               checkalias = 0;
+       }
+out:
+#ifdef DEBUG
+       if (!alreadyseen)
+           TRACE(("token %s %s\n", tokname[t], t == TWORD || t == TASSIGN ? wordtext : ""));
+       else
+           TRACE(("reread token %s %s\n", tokname[t], t == TWORD || t == TASSIGN ? wordtext : ""));
+#endif
+       return (t);
+}
+
+
+/*
+ * Read the next input token.
+ * If the token is a word, we set backquotelist to the list of cmds in
+ *     backquotes.  We set quoteflag to true if any part of the word was
+ *     quoted.
+ * If the token is TREDIR, then we set redirnode to a structure containing
+ *     the redirection.
+ * In all cases, the variable startlinno is set to the number of the line
+ *     on which the token starts.
+ *
+ * [Change comment:  here documents and internal procedures]
+ * [Readtoken shouldn't have any arguments.  Perhaps we should make the
+ *  word parsing code into a separate routine.  In this case, readtoken
+ *  doesn't need to have any internal procedures, but parseword does.
+ *  We could also make parseoperator in essence the main routine, and
+ *  have parseword (readtoken1?) handle both words and redirection.]
+ */
+
+#define RETURN(token)  return lasttoken = token
+
+static int
+xxreadtoken() {
+       int c;
+
+       if (tokpushback) {
+               tokpushback = 0;
+               return lasttoken;
+       }
+       if (needprompt) {
+               setprompt(2);
+               needprompt = 0;
+       }
+       startlinno = plinno;
+       for (;;) {      /* until token or start of word found */
+               c = pgetc_macro();
+               switch (c) {
+               case ' ': case '\t':
+               case PEOA:
+                       continue;
+               case '#':
+                       while ((c = pgetc()) != '\n' && c != PEOF);
+                       pungetc();
+                       continue;
+               case '\\':
+                       if (pgetc() == '\n') {
+                               startlinno = ++plinno;
+                               if (doprompt)
+                                       setprompt(2);
+                               else
+                                       setprompt(0);
+                               continue;
+                       }
+                       pungetc();
+                       goto breakloop;
+               case '\n':
+                       plinno++;
+                       needprompt = doprompt;
+                       RETURN(TNL);
+               case PEOF:
+                       RETURN(TEOF);
+               case '&':
+                       if (pgetc() == '&')
+                               RETURN(TAND);
+                       pungetc();
+                       RETURN(TBACKGND);
+               case '|':
+                       if (pgetc() == '|')
+                               RETURN(TOR);
+                       pungetc();
+                       RETURN(TPIPE);
+               case ';':
+                       if (pgetc() == ';')
+                               RETURN(TENDCASE);
+                       pungetc();
+                       RETURN(TSEMI);
+               case '(':
+                       RETURN(TLP);
+               case ')':
+                       RETURN(TRP);
+               default:
+                       goto breakloop;
+               }
+       }
+breakloop:
+       return readtoken1(c, BASESYNTAX, (char *)NULL, 0);
+#undef RETURN
+}
+
+
+
+/*
+ * If eofmark is NULL, read a word or a redirection symbol.  If eofmark
+ * is not NULL, read a here document.  In the latter case, eofmark is the
+ * word which marks the end of the document and striptabs is true if
+ * leading tabs should be stripped from the document.  The argument firstc
+ * is the first character of the input token or document.
+ *
+ * Because C does not have internal subroutines, I have simulated them
+ * using goto's to implement the subroutine linkage.  The following macros
+ * will run code that appears at the end of readtoken1.
+ */
+
+#define CHECKEND()     {goto checkend; checkend_return:;}
+#define PARSEREDIR()   {goto parseredir; parseredir_return:;}
+#define PARSESUB()     {goto parsesub; parsesub_return:;}
+#define PARSEBACKQOLD()        {oldstyle = 1; goto parsebackq; parsebackq_oldreturn:;}
+#define PARSEBACKQNEW()        {oldstyle = 0; goto parsebackq; parsebackq_newreturn:;}
+#define        PARSEARITH()    {goto parsearith; parsearith_return:;}
+
+static int
+readtoken1(firstc, syntax, eofmark, striptabs)
+       int firstc;
+       char const *syntax;
+       char *eofmark;
+       int striptabs;
+       {
+       int c = firstc;
+       char *out;
+       int len;
+       char line[EOFMARKLEN + 1];
+       struct nodelist *bqlist;
+       int quotef;
+       int dblquote;
+       int varnest;    /* levels of variables expansion */
+       int arinest;    /* levels of arithmetic expansion */
+       int parenlevel; /* levels of parens in arithmetic */
+       int dqvarnest;  /* levels of variables expansion within double quotes */
+       int oldstyle;
+       char const *prevsyntax; /* syntax before arithmetic */
+#if __GNUC__
+       /* Avoid longjmp clobbering */
+       (void) &out;
+       (void) &quotef;
+       (void) &dblquote;
+       (void) &varnest;
+       (void) &arinest;
+       (void) &parenlevel;
+       (void) &dqvarnest;
+       (void) &oldstyle;
+       (void) &prevsyntax;
+       (void) &syntax;
+#endif
+
+       startlinno = plinno;
+       dblquote = 0;
+       if (syntax == DQSYNTAX)
+               dblquote = 1;
+       quotef = 0;
+       bqlist = NULL;
+       varnest = 0;
+       arinest = 0;
+       parenlevel = 0;
+       dqvarnest = 0;
+
+       STARTSTACKSTR(out);
+       loop: { /* for each line, until end of word */
+#if ATTY
+               if (c == '\034' && doprompt
+                && attyset() && ! equal(termval(), "emacs")) {
+                       attyline();
+                       if (syntax == BASESYNTAX)
+                               return readtoken();
+                       c = pgetc();
+                       goto loop;
+               }
+#endif
+               CHECKEND();     /* set c to PEOF if at end of here document */
+               for (;;) {      /* until end of line or end of word */
+                       CHECKSTRSPACE(3, out);  /* permit 3 calls to USTPUTC */
+                       switch(syntax[c]) {
+                       case CNL:       /* '\n' */
+                               if (syntax == BASESYNTAX)
+                                       goto endword;   /* exit outer loop */
+                               USTPUTC(c, out);
+                               plinno++;
+                               if (doprompt)
+                                       setprompt(2);
+                               else
+                                       setprompt(0);
+                               c = pgetc();
+                               goto loop;              /* continue outer loop */
+                       case CWORD:
+                               USTPUTC(c, out);
+                               break;
+                       case CCTL:
+                               if ((eofmark == NULL || dblquote) &&
+                                   dqvarnest == 0)
+                                       USTPUTC(CTLESC, out);
+                               USTPUTC(c, out);
+                               break;
+                       case CBACK:     /* backslash */
+                               c = pgetc2();
+                               if (c == PEOF) {
+                                       USTPUTC('\\', out);
+                                       pungetc();
+                               } else if (c == '\n') {
+                                       if (doprompt)
+                                               setprompt(2);
+                                       else
+                                               setprompt(0);
+                               } else {
+                                       if (dblquote && c != '\\' && c != '`' && c != '$'
+                                                        && (c != '"' || eofmark != NULL))
+                                               USTPUTC('\\', out);
+                                       if (SQSYNTAX[c] == CCTL)
+                                               USTPUTC(CTLESC, out);
+                                       else if (eofmark == NULL)
+                                               USTPUTC(CTLQUOTEMARK, out);
+                                       USTPUTC(c, out);
+                                       quotef++;
+                               }
+                               break;
+                       case CSQUOTE:
+                               if (eofmark == NULL)
+                                       USTPUTC(CTLQUOTEMARK, out);
+                               syntax = SQSYNTAX;
+                               break;
+                       case CDQUOTE:
+                               if (eofmark == NULL)
+                                       USTPUTC(CTLQUOTEMARK, out);
+                               syntax = DQSYNTAX;
+                               dblquote = 1;
+                               break;
+                       case CENDQUOTE:
+                               if (eofmark != NULL && arinest == 0 &&
+                                   varnest == 0) {
+                                       USTPUTC(c, out);
+                               } else {
+                                       if (arinest) {
+                                               syntax = ARISYNTAX;
+                                               dblquote = 0;
+                                       } else if (eofmark == NULL &&
+                                                  dqvarnest == 0) {
+                                               syntax = BASESYNTAX;
+                                               dblquote = 0;
+                                       }
+                                       quotef++;
+                               }
+                               break;
+                       case CVAR:      /* '$' */
+                               PARSESUB();             /* parse substitution */
+                               break;
+                       case CENDVAR:   /* '}' */
+                               if (varnest > 0) {
+                                       varnest--;
+                                       if (dqvarnest > 0) {
+                                               dqvarnest--;
+                                       }
+                                       USTPUTC(CTLENDVAR, out);
+                               } else {
+                                       USTPUTC(c, out);
+                               }
+                               break;
+#ifdef ASH_MATH_SUPPORT
+                       case CLP:       /* '(' in arithmetic */
+                               parenlevel++;
+                               USTPUTC(c, out);
+                               break;
+                       case CRP:       /* ')' in arithmetic */
+                               if (parenlevel > 0) {
+                                       USTPUTC(c, out);
+                                       --parenlevel;
+                               } else {
+                                       if (pgetc() == ')') {
+                                               if (--arinest == 0) {
+                                                       USTPUTC(CTLENDARI, out);
+                                                       syntax = prevsyntax;
+                                                       if (syntax == DQSYNTAX)
+                                                               dblquote = 1;
+                                                       else
+                                                               dblquote = 0;
+                                               } else
+                                                       USTPUTC(')', out);
+                                       } else {
+                                               /*
+                                                * unbalanced parens
+                                                *  (don't 2nd guess - no error)
+                                                */
+                                               pungetc();
+                                               USTPUTC(')', out);
+                                       }
+                               }
+                               break;
+#endif
+                       case CBQUOTE:   /* '`' */
+                               PARSEBACKQOLD();
+                               break;
+                       case CEOF:
+                               goto endword;           /* exit outer loop */
+                       case CIGN:
+                               break;
+                       default:
+                               if (varnest == 0)
+                                       goto endword;   /* exit outer loop */
+                               if (c != PEOA) {
+                                       USTPUTC(c, out);
+                               }
+                       }
+                       c = pgetc_macro();
+               }
+       }
+endword:
+       if (syntax == ARISYNTAX)
+               synerror("Missing '))'");
+       if (syntax != BASESYNTAX && ! parsebackquote && eofmark == NULL)
+               synerror("Unterminated quoted string");
+       if (varnest != 0) {
+               startlinno = plinno;
+               synerror("Missing '}'");
+       }
+       USTPUTC('\0', out);
+       len = out - stackblock();
+       out = stackblock();
+       if (eofmark == NULL) {
+               if ((c == '>' || c == '<')
+                && quotef == 0
+                && len <= 2
+                && (*out == '\0' || is_digit(*out))) {
+                       PARSEREDIR();
+                       return lasttoken = TREDIR;
+               } else {
+                       pungetc();
+               }
+       }
+       quoteflag = quotef;
+       backquotelist = bqlist;
+       grabstackblock(len);
+       wordtext = out;
+       return lasttoken = TWORD;
+/* end of readtoken routine */
+
+
+
+/*
+ * Check to see whether we are at the end of the here document.  When this
+ * is called, c is set to the first character of the next input line.  If
+ * we are at the end of the here document, this routine sets the c to PEOF.
+ */
+
+checkend: {
+       if (eofmark) {
+               if (c == PEOA) {
+                       c = pgetc2();
+               }
+               if (striptabs) {
+                       while (c == '\t') {
+                               c = pgetc2();
+                       }
+               }
+               if (c == *eofmark) {
+                       if (pfgets(line, sizeof line) != NULL) {
+                               char *p, *q;
+
+                               p = line;
+                               for (q = eofmark + 1 ; *q && *p == *q ; p++, q++);
+                               if (*p == '\n' && *q == '\0') {
+                                       c = PEOF;
+                                       plinno++;
+                                       needprompt = doprompt;
+                               } else {
+                                       pushstring(line, strlen(line), NULL);
+                               }
+                       }
+               }
+       }
+       goto checkend_return;
+}
+
+
+/*
+ * Parse a redirection operator.  The variable "out" points to a string
+ * specifying the fd to be redirected.  The variable "c" contains the
+ * first character of the redirection operator.
+ */
+
+parseredir: {
+       char fd = *out;
+       union node *np;
+
+       np = (union node *)stalloc(sizeof (struct nfile));
+       if (c == '>') {
+               np->nfile.fd = 1;
+               c = pgetc();
+               if (c == '>')
+                       np->type = NAPPEND;
+               else if (c == '&')
+                       np->type = NTOFD;
+               else if (c == '|')
+                       np->type = NTOOV;
+               else {
+                       np->type = NTO;
+                       pungetc();
+               }
+       } else {        /* c == '<' */
+               np->nfile.fd = 0;
+               switch (c = pgetc()) {
+               case '<':
+                       if (sizeof (struct nfile) != sizeof (struct nhere)) {
+                               np = (union node *)stalloc(sizeof (struct nhere));
+                               np->nfile.fd = 0;
+                       }
+                       np->type = NHERE;
+                       heredoc = (struct heredoc *)stalloc(sizeof (struct heredoc));
+                       heredoc->here = np;
+                       if ((c = pgetc()) == '-') {
+                               heredoc->striptabs = 1;
+                       } else {
+                               heredoc->striptabs = 0;
+                               pungetc();
+                       }
+                       break;
+
+               case '&':
+                       np->type = NFROMFD;
+                       break;
+
+               case '>':
+                       np->type = NFROMTO;
+                       break;
+
+               default:
+                       np->type = NFROM;
+                       pungetc();
+                       break;
+               }
+       }
+       if (fd != '\0')
+               np->nfile.fd = digit_val(fd);
+       redirnode = np;
+       goto parseredir_return;
+}
+
+
+/*
+ * Parse a substitution.  At this point, we have read the dollar sign
+ * and nothing else.
+ */
+
+parsesub: {
+       int subtype;
+       int typeloc;
+       int flags;
+       char *p;
+       static const char types[] = "}-+?=";
+
+       c = pgetc();
+       if (
+               c <= PEOA  ||
+               (c != '(' && c != '{' && !is_name(c) && !is_special(c))
+       ) {
+               USTPUTC('$', out);
+               pungetc();
+       } else if (c == '(') {  /* $(command) or $((arith)) */
+               if (pgetc() == '(') {
+                       PARSEARITH();
+               } else {
+                       pungetc();
+                       PARSEBACKQNEW();
+               }
+       } else {
+               USTPUTC(CTLVAR, out);
+               typeloc = out - stackblock();
+               USTPUTC(VSNORMAL, out);
+               subtype = VSNORMAL;
+               if (c == '{') {
+                       c = pgetc();
+                       if (c == '#') {
+                               if ((c = pgetc()) == '}')
+                                       c = '#';
+                               else
+                                       subtype = VSLENGTH;
+                       }
+                       else
+                               subtype = 0;
+               }
+               if (c > PEOA && is_name(c)) {
+                       do {
+                               STPUTC(c, out);
+                               c = pgetc();
+                       } while (c > PEOA && is_in_name(c));
+               } else if (is_digit(c)) {
+                       do {
+                               USTPUTC(c, out);
+                               c = pgetc();
+                       } while (is_digit(c));
+               }
+               else if (is_special(c)) {
+                       USTPUTC(c, out);
+                       c = pgetc();
+               }
+               else
+badsub:                        synerror("Bad substitution");
+
+               STPUTC('=', out);
+               flags = 0;
+               if (subtype == 0) {
+                       switch (c) {
+                       case ':':
+                               flags = VSNUL;
+                               c = pgetc();
+                               /*FALLTHROUGH*/
+                       default:
+                               p = strchr(types, c);
+                               if (p == NULL)
+                                       goto badsub;
+                               subtype = p - types + VSNORMAL;
+                               break;
+                       case '%':
+                       case '#':
+                               {
+                                       int cc = c;
+                                       subtype = c == '#' ? VSTRIMLEFT :
+                                                            VSTRIMRIGHT;
+                                       c = pgetc();
+                                       if (c == cc)
+                                               subtype++;
+                                       else
+                                               pungetc();
+                                       break;
+                               }
+                       }
+               } else {
+                       pungetc();
+               }
+               if (dblquote || arinest)
+                       flags |= VSQUOTE;
+               *(stackblock() + typeloc) = subtype | flags;
+               if (subtype != VSNORMAL) {
+                       varnest++;
+                       if (dblquote) {
+                               dqvarnest++;
+                       }
+               }
+       }
+       goto parsesub_return;
+}
+
+
+/*
+ * Called to parse command substitutions.  Newstyle is set if the command
+ * is enclosed inside $(...); nlpp is a pointer to the head of the linked
+ * list of commands (passed by reference), and savelen is the number of
+ * characters on the top of the stack which must be preserved.
+ */
+
+parsebackq: {
+       struct nodelist **nlpp;
+       int savepbq;
+       union node *n;
+       char *volatile str;
+       struct jmploc jmploc;
+       struct jmploc *volatile savehandler;
+       int savelen;
+       int saveprompt;
+#ifdef __GNUC__
+       (void) &saveprompt;
+#endif
+
+       savepbq = parsebackquote;
+       if (setjmp(jmploc.loc)) {
+               if (str)
+                       ckfree(str);
+               parsebackquote = 0;
+               handler = savehandler;
+               longjmp(handler->loc, 1);
+       }
+       INTOFF;
+       str = NULL;
+       savelen = out - stackblock();
+       if (savelen > 0) {
+               str = ckmalloc(savelen);
+               memcpy(str, stackblock(), savelen);
+       }
+       savehandler = handler;
+       handler = &jmploc;
+       INTON;
+        if (oldstyle) {
+                /* We must read until the closing backquote, giving special
+                   treatment to some slashes, and then push the string and
+                   reread it as input, interpreting it normally.  */
+                char *pout;
+                int pc;
+                int psavelen;
+                char *pstr;
+
+
+                STARTSTACKSTR(pout);
+               for (;;) {
+                       if (needprompt) {
+                               setprompt(2);
+                               needprompt = 0;
+                       }
+                       switch (pc = pgetc()) {
+                       case '`':
+                               goto done;
+
+                       case '\\':
+                                if ((pc = pgetc()) == '\n') {
+                                       plinno++;
+                                       if (doprompt)
+                                               setprompt(2);
+                                       else
+                                               setprompt(0);
+                                       /*
+                                        * If eating a newline, avoid putting
+                                        * the newline into the new character
+                                        * stream (via the STPUTC after the
+                                        * switch).
+                                        */
+                                       continue;
+                               }
+                                if (pc != '\\' && pc != '`' && pc != '$'
+                                    && (!dblquote || pc != '"'))
+                                        STPUTC('\\', pout);
+                               if (pc > PEOA) {
+                                       break;
+                               }
+                               /* fall through */
+
+                       case PEOF:
+                       case PEOA:
+                               startlinno = plinno;
+                               synerror("EOF in backquote substitution");
+
+                       case '\n':
+                               plinno++;
+                               needprompt = doprompt;
+                               break;
+
+                       default:
+                               break;
+                       }
+                       STPUTC(pc, pout);
+                }
+done:
+                STPUTC('\0', pout);
+                psavelen = pout - stackblock();
+                if (psavelen > 0) {
+                       pstr = grabstackstr(pout);
+                       setinputstring(pstr);
+                }
+        }
+       nlpp = &bqlist;
+       while (*nlpp)
+               nlpp = &(*nlpp)->next;
+       *nlpp = (struct nodelist *)stalloc(sizeof (struct nodelist));
+       (*nlpp)->next = NULL;
+       parsebackquote = oldstyle;
+
+       if (oldstyle) {
+               saveprompt = doprompt;
+               doprompt = 0;
+       }
+
+       n = list(0);
+
+       if (oldstyle)
+               doprompt = saveprompt;
+       else {
+               if (readtoken() != TRP)
+                       synexpect(TRP);
+       }
+
+       (*nlpp)->n = n;
+        if (oldstyle) {
+               /*
+                * Start reading from old file again, ignoring any pushed back
+                * tokens left from the backquote parsing
+                */
+                popfile();
+               tokpushback = 0;
+       }
+       while (stackblocksize() <= savelen)
+               growstackblock();
+       STARTSTACKSTR(out);
+       if (str) {
+               memcpy(out, str, savelen);
+               STADJUST(savelen, out);
+               INTOFF;
+               ckfree(str);
+               str = NULL;
+               INTON;
+       }
+       parsebackquote = savepbq;
+       handler = savehandler;
+       if (arinest || dblquote)
+               USTPUTC(CTLBACKQ | CTLQUOTE, out);
+       else
+               USTPUTC(CTLBACKQ, out);
+       if (oldstyle)
+               goto parsebackq_oldreturn;
+       else
+               goto parsebackq_newreturn;
+}
+
+/*
+ * Parse an arithmetic expansion (indicate start of one and set state)
+ */
+parsearith: {
+
+       if (++arinest == 1) {
+               prevsyntax = syntax;
+               syntax = ARISYNTAX;
+               USTPUTC(CTLARI, out);
+               if (dblquote)
+                       USTPUTC('"',out);
+               else
+                       USTPUTC(' ',out);
+       } else {
+               /*
+                * we collapse embedded arithmetic expansion to
+                * parenthesis, which should be equivalent
+                */
+               USTPUTC('(', out);
+       }
+       goto parsearith_return;
+}
+
+} /* end of readtoken */
+
+
+
+#ifdef mkinit
+INCLUDE "parser.h"
+RESET {
+       tokpushback = 0;
+       checkkwd = 0;
+       checkalias = 0;
+}
+#endif
+
+/*
+ * Returns true if the text contains nothing to expand (no dollar signs
+ * or backquotes).
+ */
+
+static int
+noexpand(text)
+       char *text;
+       {
+       char *p;
+       char c;
+
+       p = text;
+       while ((c = *p++) != '\0') {
+               if (c == CTLQUOTEMARK)
+                       continue;
+               if (c == CTLESC)
+                       p++;
+               else if (BASESYNTAX[(int)c] == CCTL)
+                       return 0;
+       }
+       return 1;
+}
+
+
+/*
+ * Return true if the argument is a legal variable name (a letter or
+ * underscore followed by zero or more letters, underscores, and digits).
+ */
+
+static int
+goodname(char *name)
+       {
+       char *p;
+
+       p = name;
+       if (! is_name(*p))
+               return 0;
+       while (*++p) {
+               if (! is_in_name(*p))
+                       return 0;
+       }
+       return 1;
+}
+
+
+/*
+ * Called when an unexpected token is read during the parse.  The argument
+ * is the token that is expected, or -1 if more than one type of token can
+ * occur at this point.
+ */
+
+static void
+synexpect(token)
+       int token;
+{
+       char msg[64];
+
+       if (token >= 0) {
+               fmtstr(msg, 64, "%s unexpected (expecting %s)",
+                       tokname[lasttoken], tokname[token]);
+       } else {
+               fmtstr(msg, 64, "%s unexpected", tokname[lasttoken]);
+       }
+       synerror(msg);
+       /* NOTREACHED */
+}
+
+
+static void
+synerror(msg)
+       const char *msg;
+       {
+       if (commandname)
+               outfmt(&errout, "%s: %d: ", commandname, startlinno);
+       outfmt(&errout, "Syntax error: %s\n", msg);
+       error((char *)NULL);
+       /* NOTREACHED */
+}
+
+static void
+setprompt(int which)
+{
+    whichprompt = which;
+    putprompt(getprompt(NULL));
+}
+
+/*
+ * called by editline -- any expansions to the prompt
+ *    should be added here.
+ */
+static const char *
+getprompt(void *unused)
+       {
+       switch (whichprompt) {
+       case 0:
+               return "";
+       case 1:
+               return ps1val();
+       case 2:
+               return ps2val();
+       default:
+               return "<internal prompt error>";
+       }
+}
+
+static int
+isassignment(const char *word) {
+       if (!is_name(*word)) {
+               return 0;
+       }
+       do {
+               word++;
+       } while (is_in_name(*word));
+       return *word == '=';
+}
+
+static const char *const *
+findkwd(const char *s) {
+       return findstring(
+               s, parsekwd, sizeof(parsekwd) / sizeof(const char *)
+       );
+}
+/*     $NetBSD: redir.c,v 1.22 2000/05/22 10:18:47 elric Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Code for dealing with input/output redirection.
+ */
+
+#define EMPTY -2               /* marks an unused slot in redirtab */
+#ifndef PIPE_BUF
+# define PIPESIZE 4096         /* amount of buffering in a pipe */
+#else
+# define PIPESIZE PIPE_BUF
+#endif
+
+
+struct redirtab *redirlist;
+
+/*
+ * We keep track of whether or not fd0 has been redirected.  This is for
+ * background commands, where we want to redirect fd0 to /dev/null only
+ * if it hasn't already been redirected.
+*/
+static int fd0_redirected = 0;
+
+/*
+ * We also keep track of where fileno2 goes.
+ */
+static int fileno2 = 2;
+
+static int openredirect __P((union node *));
+static void dupredirect __P((union node *, int, char[10 ]));
+static int openhere __P((union node *));
+static int noclobberopen __P((const char *));
+
+
+/*
+ * 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(redir, flags)
+       union node *redir;
+       int flags;
+       {
+       union node *n;
+       struct redirtab *sv = NULL;
+       int i;
+       int fd;
+       int newfd;
+       int try;
+       char memory[10];        /* file descriptors to write to memory */
+
+       for (i = 10 ; --i >= 0 ; )
+               memory[i] = 0;
+       memory[1] = flags & REDIR_BACKQ;
+       if (flags & REDIR_PUSH) {
+               sv = ckmalloc(sizeof (struct redirtab));
+               for (i = 0 ; i < 10 ; i++)
+                       sv->renamed[i] = EMPTY;
+               sv->next = redirlist;
+               redirlist = sv;
+       }
+       for (n = redir ; n ; n = n->nfile.next) {
+               fd = n->nfile.fd;
+               try = 0;
+               if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) &&
+                   n->ndup.dupfd == fd)
+                       continue; /* redirect from/to same file descriptor */
+
+               INTOFF;
+               newfd = openredirect(n);
+               if (((flags & REDIR_PUSH) && sv->renamed[fd] == EMPTY) ||
+                   (fd == fileno2)) {
+                       if (newfd == fd) {
+                               try++;
+                       } else if ((i = fcntl(fd, F_DUPFD, 10)) == -1) {
+                               switch (errno) {
+                               case EBADF:
+                                       if (!try) {
+                                               dupredirect(n, newfd, memory);
+                                               try++;
+                                               break;
+                                       }
+                                       /* FALLTHROUGH*/
+                               default:
+                                       if (newfd >= 0) {
+                                               close(newfd);
+                                       }
+                                       INTON;
+                                       error("%d: %s", fd, strerror(errno));
+                                       /* NOTREACHED */
+                               }
+                       }
+                       if (!try) {
+                               close(fd);
+                               if (flags & REDIR_PUSH) {
+                                       sv->renamed[fd] = i;
+                               }
+                               if (fd == fileno2) {
+                                       fileno2 = i;
+                               }
+                       }
+               } else if (fd != newfd) {
+                       close(fd);
+               }
+                if (fd == 0)
+                        fd0_redirected++;
+               if (!try)
+                       dupredirect(n, newfd, memory);
+               INTON;
+       }
+       if (memory[1])
+               out1 = &memout;
+       if (memory[2])
+               out2 = &memout;
+}
+
+
+static int
+openredirect(redir)
+       union node *redir;
+       {
+       char *fname;
+       int f;
+
+       switch (redir->nfile.type) {
+       case NFROM:
+               fname = redir->nfile.expfname;
+               if ((f = open(fname, O_RDONLY)) < 0)
+                       goto eopen;
+               break;
+       case NFROMTO:
+               fname = redir->nfile.expfname;
+               if ((f = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0666)) < 0)
+                       goto ecreate;
+               break;
+       case NTO:
+               /* Take care of noclobber mode. */
+               if (Cflag) {
+                       fname = redir->nfile.expfname;
+                       if ((f = noclobberopen(fname)) < 0)
+                               goto ecreate;
+                       break;
+               }
+       case NTOOV:
+               fname = redir->nfile.expfname;
+#ifdef O_CREAT
+               if ((f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0)
+                       goto ecreate;
+#else
+               if ((f = creat(fname, 0666)) < 0)
+                       goto ecreate;
+#endif
+               break;
+       case NAPPEND:
+               fname = redir->nfile.expfname;
+#ifdef O_APPEND
+               if ((f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666)) < 0)
+                       goto ecreate;
+#else
+               if ((f = open(fname, O_WRONLY)) < 0
+                && (f = creat(fname, 0666)) < 0)
+                       goto ecreate;
+               lseek(f, (off_t)0, 2);
+#endif
+               break;
+       default:
+#ifdef 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:
+       error("cannot create %s: %s", fname, errmsg(errno, E_CREAT));
+eopen:
+       error("cannot open %s: %s", fname, errmsg(errno, E_OPEN));
+}
+
+
+static void
+dupredirect(redir, f, memory)
+       union node *redir;
+       int f;
+       char memory[10];
+       {
+       int fd = redir->nfile.fd;
+
+       memory[fd] = 0;
+       if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) {
+               if (redir->ndup.dupfd >= 0) {   /* if not ">&-" */
+                       if (memory[redir->ndup.dupfd])
+                               memory[fd] = 1;
+                       else
+                               dup_as_newfd(redir->ndup.dupfd, fd);
+               }
+               return;
+       }
+
+       if (f != fd) {
+               dup_as_newfd(f, fd);
+               close(f);
+       }
+       return;
+}
+
+
+/*
+ * Handle here documents.  Normally we fork off a process to write the
+ * data to a pipe.  If the document is short, we can stuff the data in
+ * the pipe without forking.
+ */
+
+static int
+openhere(redir)
+       union node *redir;
+       {
+       int pip[2];
+       int len = 0;
+
+       if (pipe(pip) < 0)
+               error("Pipe call failed");
+       if (redir->type == NHERE) {
+               len = strlen(redir->nhere.doc->narg.text);
+               if (len <= PIPESIZE) {
+                       xwrite(pip[1], redir->nhere.doc->narg.text, len);
+                       goto out;
+               }
+       }
+       if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) {
+               close(pip[0]);
+               signal(SIGINT, SIG_IGN);
+               signal(SIGQUIT, SIG_IGN);
+               signal(SIGHUP, SIG_IGN);
+#ifdef SIGTSTP
+               signal(SIGTSTP, SIG_IGN);
+#endif
+               signal(SIGPIPE, SIG_DFL);
+               if (redir->type == NHERE)
+                       xwrite(pip[1], redir->nhere.doc->narg.text, len);
+               else
+                       expandhere(redir->nhere.doc, pip[1]);
+               _exit(0);
+       }
+out:
+       close(pip[1]);
+       return pip[0];
+}
+
+
+
+/*
+ * Undo the effects of the last redirection.
+ */
+
+static void
+popredir() {
+       struct redirtab *rp = redirlist;
+       int i;
+
+       INTOFF;
+       for (i = 0 ; i < 10 ; i++) {
+               if (rp->renamed[i] != EMPTY) {
+                        if (i == 0)
+                                fd0_redirected--;
+                       close(i);
+                       if (rp->renamed[i] >= 0) {
+                               dup_as_newfd(rp->renamed[i], i);
+                               close(rp->renamed[i]);
+                       }
+                       if (rp->renamed[i] == fileno2) {
+                               fileno2 = i;
+                       }
+               }
+       }
+       redirlist = rp->next;
+       ckfree(rp);
+       INTON;
+}
+
+/*
+ * Undo all redirections.  Called on error or interrupt.
+ */
+
+#ifdef mkinit
+
+INCLUDE "redir.h"
+
+RESET {
+       while (redirlist)
+               popredir();
+}
+
+SHELLPROC {
+       clearredir();
+}
+
+#endif
+
+/* Return true if fd 0 has already been redirected at least once.  */
+static int
+fd0_redirected_p () {
+        return fd0_redirected != 0;
+}
+
+/*
+ * Discard all saved file descriptors.
+ */
+
+static void
+clearredir() {
+       struct redirtab *rp;
+       int i;
+
+       for (rp = redirlist ; rp ; rp = rp->next) {
+               for (i = 0 ; i < 10 ; i++) {
+                       if (rp->renamed[i] >= 0) {
+                               close(rp->renamed[i]);
+                               if (rp->renamed[i] == fileno2) {
+                                       fileno2 = -1;
+                               }
+                       }
+                       rp->renamed[i] = EMPTY;
+               }
+       }
+       if (fileno2 != 2 && fileno2 >= 0) {
+               close(fileno2);
+               fileno2 = -1;
+       }
+}
+
+
+
+/*
+ * 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
+dup_as_newfd(from, to)
+       int from;
+       int to;
+{
+       int newfd;
+
+       newfd = fcntl(from, F_DUPFD, to);
+       if (newfd < 0) {
+               if (errno == EMFILE)
+                       return EMPTY;
+               else
+                       error("%d: %s", from, strerror(errno));
+       }
+       return newfd;
+}
+
+/*
+ * Open a file in noclobber mode.
+ * The code was copied from bash.
+ */
+static int
+noclobberopen(fname)
+       const char *fname;
+{
+       int r, fd;
+       struct stat finfo, finfo2;
+
+       /*
+        * If the file exists and is a regular file, return an error
+        * immediately.
+        */
+       r = stat(fname, &finfo);
+       if (r == 0 && S_ISREG(finfo.st_mode)) {
+               errno = EEXIST;
+               return -1;
+       }
+
+       /*
+        * If the file was not present (r != 0), make sure we open it
+        * exclusively so that if it is created before we open it, our open
+        * will fail.  Make sure that we do not truncate an existing file.
+        * Note that we don't turn on O_EXCL unless the stat failed -- if the
+        * file was not a regular file, we leave O_EXCL off.
+        */
+       if (r != 0)
+               return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666);
+       fd = open(fname, O_WRONLY|O_CREAT, 0666);
+
+       /* If the open failed, return the file descriptor right away. */
+       if (fd < 0)
+               return fd;
+
+       /*
+        * OK, the open succeeded, but the file may have been changed from a
+        * non-regular file to a regular file between the stat and the open.
+        * We are assuming that the O_EXCL open handles the case where FILENAME
+        * did not exist and is symlinked to an existing file between the stat
+        * and open.
+        */
+
+       /*
+        * If we can open it and fstat the file descriptor, and neither check
+        * revealed that it was a regular file, and the file has not been
+        * replaced, return the file descriptor.
+        */
+        if (fstat(fd, &finfo2) == 0 && !S_ISREG(finfo2.st_mode) &&
+            finfo.st_dev == finfo2.st_dev && finfo.st_ino == finfo2.st_ino)
+               return fd;
+
+       /* The file has been replaced.  badness. */
+       close(fd);
+       errno = EEXIST;
+       return -1;
+}
+/*     $NetBSD: setmode.c,v 1.28 2000/01/25 15:43:43 enami Exp $       */
+
+/*
+ * Copyright (c) 1989, 1993, 1994
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Dave Borman at Cray Research, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef __weak_alias
+__weak_alias(getmode,_getmode)
+__weak_alias(setmode,_setmode)
+#endif
+
+#ifdef __GLIBC__
+#define S_ISTXT __S_ISVTX
+#endif
+
+#define        SET_LEN 6               /* initial # of bitcmd struct to malloc */
+#define        SET_LEN_INCR 4          /* # of bitcmd structs to add as needed */
+
+typedef struct bitcmd {
+       char    cmd;
+       char    cmd2;
+       mode_t  bits;
+} BITCMD;
+
+#define        CMD2_CLR        0x01
+#define        CMD2_SET        0x02
+#define        CMD2_GBITS      0x04
+#define        CMD2_OBITS      0x08
+#define        CMD2_UBITS      0x10
+
+static BITCMD  *addcmd __P((BITCMD *, int, int, int, u_int));
+static void     compress_mode __P((BITCMD *));
+#ifdef SETMODE_DEBUG
+static void     dumpmode __P((BITCMD *));
+#endif
+
+/*
+ * Given the old mode and an array of bitcmd structures, apply the operations
+ * described in the bitcmd structures to the old mode, and return the new mode.
+ * Note that there is no '=' command; a strict assignment is just a '-' (clear
+ * bits) followed by a '+' (set bits).
+ */
+mode_t
+getmode(bbox, omode)
+       const void *bbox;
+       mode_t omode;
+{
+       const BITCMD *set;
+       mode_t clrval, newmode, value;
+
+       _DIAGASSERT(bbox != NULL);
+
+       set = (const BITCMD *)bbox;
+       newmode = omode;
+       for (value = 0;; set++)
+               switch(set->cmd) {
+               /*
+                * When copying the user, group or other bits around, we "know"
+                * where the bits are in the mode so that we can do shifts to
+                * copy them around.  If we don't use shifts, it gets real
+                * grundgy with lots of single bit checks and bit sets.
+                */
+               case 'u':
+                       value = (newmode & S_IRWXU) >> 6;
+                       goto common;
+
+               case 'g':
+                       value = (newmode & S_IRWXG) >> 3;
+                       goto common;
+
+               case 'o':
+                       value = newmode & S_IRWXO;
+common:                        if (set->cmd2 & CMD2_CLR) {
+                               clrval =
+                                   (set->cmd2 & CMD2_SET) ?  S_IRWXO : value;
+                               if (set->cmd2 & CMD2_UBITS)
+                                       newmode &= ~((clrval<<6) & set->bits);
+                               if (set->cmd2 & CMD2_GBITS)
+                                       newmode &= ~((clrval<<3) & set->bits);
+                               if (set->cmd2 & CMD2_OBITS)
+                                       newmode &= ~(clrval & set->bits);
+                       }
+                       if (set->cmd2 & CMD2_SET) {
+                               if (set->cmd2 & CMD2_UBITS)
+                                       newmode |= (value<<6) & set->bits;
+                               if (set->cmd2 & CMD2_GBITS)
+                                       newmode |= (value<<3) & set->bits;
+                               if (set->cmd2 & CMD2_OBITS)
+                                       newmode |= value & set->bits;
+                       }
+                       break;
+
+               case '+':
+                       newmode |= set->bits;
+                       break;
+
+               case '-':
+                       newmode &= ~set->bits;
+                       break;
+
+               case 'X':
+                       if (omode & (S_IFDIR|S_IXUSR|S_IXGRP|S_IXOTH))
+                               newmode |= set->bits;
+                       break;
+
+               case '\0':
+               default:
+#ifdef SETMODE_DEBUG
+                       (void)printf("getmode:%04o -> %04o\n", omode, newmode);
+#endif
+                       return (newmode);
+               }
+}
+
+#define        ADDCMD(a, b, c, d) do {                                         \
+       if (set >= endset) {                                            \
+               BITCMD *newset;                                         \
+               setlen += SET_LEN_INCR;                                 \
+               newset = realloc(saveset, sizeof(BITCMD) * setlen);     \
+               if (newset == NULL) {                                   \
+                       free(saveset);                                  \
+                       return (NULL);                                  \
+               }                                                       \
+               set = newset + (set - saveset);                         \
+               saveset = newset;                                       \
+               endset = newset + (setlen - 2);                         \
+       }                                                               \
+       set = addcmd(set, (a), (b), (c), (d));                          \
+} while (/*CONSTCOND*/0)
+
+#define        STANDARD_BITS   (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO)
+
+static void *
+setmode(p)
+       const char *p;
+{
+       int perm, who;
+       char op, *ep;
+       BITCMD *set, *saveset, *endset;
+       sigset_t mysigset, sigoset;
+       mode_t mask;
+       int equalopdone = 0;    /* pacify gcc */
+       int permXbits, setlen;
+
+       if (!*p)
+               return (NULL);
+
+       /*
+        * Get a copy of the mask for the permissions that are mask relative.
+        * Flip the bits, we want what's not set.  Since it's possible that
+        * the caller is opening files inside a signal handler, protect them
+        * as best we can.
+        */
+       sigfillset(&mysigset);
+       (void)sigprocmask(SIG_BLOCK, &mysigset, &sigoset);
+       (void)umask(mask = umask(0));
+       mask = ~mask;
+       (void)sigprocmask(SIG_SETMASK, &sigoset, NULL);
+
+       setlen = SET_LEN + 2;
+       
+       if ((set = malloc((u_int)(sizeof(BITCMD) * setlen))) == NULL)
+               return (NULL);
+       saveset = set;
+       endset = set + (setlen - 2);
+
+       /*
+        * If an absolute number, get it and return; disallow non-octal digits
+        * or illegal bits.
+        */
+       if (isdigit((unsigned char)*p)) {
+               perm = (mode_t)strtol(p, &ep, 8);
+               if (*ep || perm & ~(STANDARD_BITS|S_ISTXT)) {
+                       free(saveset);
+                       return (NULL);
+               }
+               ADDCMD('=', (STANDARD_BITS|S_ISTXT), perm, mask);
+               set->cmd = 0;
+               return (saveset);
+       }
+
+       /*
+        * Build list of structures to set/clear/copy bits as described by
+        * each clause of the symbolic mode.
+        */
+       for (;;) {
+               /* First, find out which bits might be modified. */
+               for (who = 0;; ++p) {
+                       switch (*p) {
+                       case 'a':
+                               who |= STANDARD_BITS;
+                               break;
+                       case 'u':
+                               who |= S_ISUID|S_IRWXU;
+                               break;
+                       case 'g':
+                               who |= S_ISGID|S_IRWXG;
+                               break;
+                       case 'o':
+                               who |= S_IRWXO;
+                               break;
+                       default:
+                               goto getop;
+                       }
+               }
+
+getop:         if ((op = *p++) != '+' && op != '-' && op != '=') {
+                       free(saveset);
+                       return (NULL);
+               }
+               if (op == '=')
+                       equalopdone = 0;
+
+               who &= ~S_ISTXT;
+               for (perm = 0, permXbits = 0;; ++p) {
+                       switch (*p) {
+                       case 'r':
+                               perm |= S_IRUSR|S_IRGRP|S_IROTH;
+                               break;
+                       case 's':
+                               /*
+                                * If specific bits where requested and 
+                                * only "other" bits ignore set-id. 
+                                */
+                               if (who == 0 || (who & ~S_IRWXO))
+                                       perm |= S_ISUID|S_ISGID;
+                               break;
+                       case 't':
+                               /*
+                                * If specific bits where requested and 
+                                * only "other" bits ignore set-id. 
+                                */
+                               if (who == 0 || (who & ~S_IRWXO)) {
+                                       who |= S_ISTXT;
+                                       perm |= S_ISTXT;
+                               }
+                               break;
+                       case 'w':
+                               perm |= S_IWUSR|S_IWGRP|S_IWOTH;
+                               break;
+                       case 'X':
+                               permXbits = S_IXUSR|S_IXGRP|S_IXOTH;
+                               break;
+                       case 'x':
+                               perm |= S_IXUSR|S_IXGRP|S_IXOTH;
+                               break;
+                       case 'u':
+                       case 'g':
+                       case 'o':
+                               /*
+                                * When ever we hit 'u', 'g', or 'o', we have
+                                * to flush out any partial mode that we have,
+                                * and then do the copying of the mode bits.
+                                */
+                               if (perm) {
+                                       ADDCMD(op, who, perm, mask);
+                                       perm = 0;
+                               }
+                               if (op == '=')
+                                       equalopdone = 1;
+                               if (op == '+' && permXbits) {
+                                       ADDCMD('X', who, permXbits, mask);
+                                       permXbits = 0;
+                               }
+                               ADDCMD(*p, who, op, mask);
+                               break;
+
+                       default:
+                               /*
+                                * Add any permissions that we haven't already
+                                * done.
+                                */
+                               if (perm || (op == '=' && !equalopdone)) {
+                                       if (op == '=')
+                                               equalopdone = 1;
+                                       ADDCMD(op, who, perm, mask);
+                                       perm = 0;
+                               }
+                               if (permXbits) {
+                                       ADDCMD('X', who, permXbits, mask);
+                                       permXbits = 0;
+                               }
+                               goto apply;
+                       }
+               }
+
+apply:         if (!*p)
+                       break;
+               if (*p != ',')
+                       goto getop;
+               ++p;
+       }
+       set->cmd = 0;
+#ifdef SETMODE_DEBUG
+       (void)printf("Before compress_mode()\n");
+       dumpmode(saveset);
+#endif
+       compress_mode(saveset);
+#ifdef SETMODE_DEBUG
+       (void)printf("After compress_mode()\n");
+       dumpmode(saveset);
+#endif
+       return (saveset);
+}
+
+static BITCMD *
+addcmd(set, op, who, oparg, mask)
+       BITCMD *set;
+       int oparg, who;
+       int op;
+       u_int mask;
+{
+
+       _DIAGASSERT(set != NULL);
+
+       switch (op) {
+       case '=':
+               set->cmd = '-';
+               set->bits = who ? who : STANDARD_BITS;
+               set++;
+
+               op = '+';
+               /* FALLTHROUGH */
+       case '+':
+       case '-':
+       case 'X':
+               set->cmd = op;
+               set->bits = (who ? who : mask) & oparg;
+               break;
+
+       case 'u':
+       case 'g':
+       case 'o':
+               set->cmd = op;
+               if (who) {
+                       set->cmd2 = ((who & S_IRUSR) ? CMD2_UBITS : 0) |
+                                   ((who & S_IRGRP) ? CMD2_GBITS : 0) |
+                                   ((who & S_IROTH) ? CMD2_OBITS : 0);
+                       set->bits = (mode_t)~0;
+               } else {
+                       set->cmd2 = CMD2_UBITS | CMD2_GBITS | CMD2_OBITS;
+                       set->bits = mask;
+               }
+       
+               if (oparg == '+')
+                       set->cmd2 |= CMD2_SET;
+               else if (oparg == '-')
+                       set->cmd2 |= CMD2_CLR;
+               else if (oparg == '=')
+                       set->cmd2 |= CMD2_SET|CMD2_CLR;
+               break;
+       }
+       return (set + 1);
+}
+
+#ifdef SETMODE_DEBUG
+static void
+dumpmode(set)
+       BITCMD *set;
+{
+
+       _DIAGASSERT(set != NULL);
+
+       for (; set->cmd; ++set)
+               (void)printf("cmd: '%c' bits %04o%s%s%s%s%s%s\n",
+                   set->cmd, set->bits, set->cmd2 ? " cmd2:" : "",
+                   set->cmd2 & CMD2_CLR ? " CLR" : "",
+                   set->cmd2 & CMD2_SET ? " SET" : "",
+                   set->cmd2 & CMD2_UBITS ? " UBITS" : "",
+                   set->cmd2 & CMD2_GBITS ? " GBITS" : "",
+                   set->cmd2 & CMD2_OBITS ? " OBITS" : "");
+}
+#endif
+
+/*
+ * Given an array of bitcmd structures, compress by compacting consecutive
+ * '+', '-' and 'X' commands into at most 3 commands, one of each.  The 'u',
+ * 'g' and 'o' commands continue to be separate.  They could probably be 
+ * compacted, but it's not worth the effort.
+ */
+static void
+compress_mode(set)
+       BITCMD *set;
+{
+       BITCMD *nset;
+       int setbits, clrbits, Xbits, op;
+
+       _DIAGASSERT(set != NULL);
+
+       for (nset = set;;) {
+               /* Copy over any 'u', 'g' and 'o' commands. */
+               while ((op = nset->cmd) != '+' && op != '-' && op != 'X') {
+                       *set++ = *nset++;
+                       if (!op)
+                               return;
+               }
+
+               for (setbits = clrbits = Xbits = 0;; nset++) {
+                       if ((op = nset->cmd) == '-') {
+                               clrbits |= nset->bits;
+                               setbits &= ~nset->bits;
+                               Xbits &= ~nset->bits;
+                       } else if (op == '+') {
+                               setbits |= nset->bits;
+                               clrbits &= ~nset->bits;
+                               Xbits &= ~nset->bits;
+                       } else if (op == 'X')
+                               Xbits |= nset->bits & ~setbits;
+                       else
+                               break;
+               }
+               if (clrbits) {
+                       set->cmd = '-';
+                       set->cmd2 = 0;
+                       set->bits = clrbits;
+                       set++;
+               }
+               if (setbits) {
+                       set->cmd = '+';
+                       set->cmd2 = 0;
+                       set->bits = setbits;
+                       set++;
+               }
+               if (Xbits) {
+                       set->cmd = 'X';
+                       set->cmd2 = 0;
+                       set->bits = Xbits;
+                       set++;
+               }
+       }
+}
+/*     $NetBSD: show.c,v 1.18 1999/10/08 21:10:44 pk Exp $     */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+
+#ifdef DEBUG
+static void shtree __P((union node *, int, char *, FILE*));
+static void shcmd __P((union node *, FILE *));
+static void sharg __P((union node *, FILE *));
+static void indent __P((int, char *, FILE *));
+static void trstring __P((char *));
+
+
+static void
+showtree(n)
+       union node *n;
+{
+       trputs("showtree called\n");
+       shtree(n, 1, NULL, stdout);
+}
+
+
+static void
+shtree(n, ind, pfx, fp)
+       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(cmd, fp)
+       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 NAPPEND:   s = ">>"; dftfd = 1; break;
+                       case NTOFD:     s = ">&"; dftfd = 1; break;
+                       case NTOOV:     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(arg, fp)
+       union node *arg;
+       FILE *fp;
+       {
+       char *p;
+       struct nodelist *bqlist;
+       int subtype;
+
+       if (arg->type != NARG) {
+               printf("<node type %d>\n", arg->type);
+               fflush(stdout);
+               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:
+                               printf("<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;
+               }
+       }
+}
+
+
+static void
+indent(amount, pfx, fp)
+       int amount;
+       char *pfx;
+       FILE *fp;
+{
+       int i;
+
+       for (i = 0 ; i < amount ; i++) {
+               if (pfx && i == amount - 1)
+                       fputs(pfx, fp);
+               putc('\t', fp);
+       }
+}
+#endif
+
+
+
+/*
+ * Debugging stuff.
+ */
+
+
+#ifdef DEBUG
+FILE *tracefile;
+
+#if DEBUG == 2
+static int debug = 1;
+#else
+static int debug = 0;
+#endif
+
+
+static void
+trputc(c)
+       int c;
+{
+       if (tracefile == NULL)
+               return;
+       putc(c, tracefile);
+       if (c == '\n')
+               fflush(tracefile);
+}
+
+static void
+trace(const char *fmt, ...)
+{
+       va_list va;
+#ifdef __STDC__
+       va_start(va, fmt);
+#else
+       char *fmt;
+       va_start(va);
+       fmt = va_arg(va, char *);
+#endif
+       if (tracefile != NULL) {
+               (void) vfprintf(tracefile, fmt, va);
+               if (strchr(fmt, '\n'))
+                       (void) fflush(tracefile);
+       }
+       va_end(va);
+}
+
+
+static void
+trputs(s)
+       const char *s;
+{
+       if (tracefile == NULL)
+               return;
+       fputs(s, tracefile);
+       if (strchr(s, '\n'))
+               fflush(tracefile);
+}
+
+
+static void
+trstring(s)
+       char *s;
+{
+       char *p;
+       char c;
+
+       if (tracefile == NULL)
+               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;
+               }
+       }
+       putc('"', tracefile);
+}
+
+
+static void
+trargs(ap)
+       char **ap;
+{
+       if (tracefile == NULL)
+               return;
+       while (*ap) {
+               trstring(*ap++);
+               if (*ap)
+                       putc(' ', tracefile);
+               else
+                       putc('\n', tracefile);
+       }
+       fflush(tracefile);
+}
+
+
+static void
+opentrace() {
+       char s[100];
+#ifdef O_APPEND
+       int flags;
+#endif
+
+       if (!debug)
+               return;
+#ifdef not_this_way
+       {
+               char *p;
+               if ((p = getenv("HOME")) == NULL) {
+                       if (geteuid() == 0)
+                               p = "/";
+                       else
+                               p = "/tmp";
+               }
+               scopy(p, s);
+               strcat(s, "/trace");
+       }
+#else
+       scopy("./trace", s);
+#endif /* not_this_way */
+       if ((tracefile = fopen(s, "a")) == NULL) {
+               fprintf(stderr, "Can't open %s\n", s);
+               return;
+       }
+#ifdef O_APPEND
+       if ((flags = fcntl(fileno(tracefile), F_GETFL, 0)) >= 0)
+               fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND);
+#endif
+       fputs("\nTracing started.\n", tracefile);
+       fflush(tracefile);
+}
+#endif /* DEBUG */
+
+
+/*
+ * This file was generated by the mksyntax program.
+ */
+
+/* syntax table used when not in quotes */
+static const char basesyntax[257] = {
+      CEOF,    CSPCL,   CWORD,   CCTL,
+      CCTL,    CCTL,    CCTL,    CCTL,
+      CCTL,    CCTL,    CCTL,    CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CSPCL,
+      CNL,     CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CSPCL,   CWORD,
+      CDQUOTE, CWORD,   CVAR,    CWORD,
+      CSPCL,   CSQUOTE, CSPCL,   CSPCL,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CSPCL,   CSPCL,   CWORD,
+      CSPCL,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CBACK,   CWORD,
+      CWORD,   CWORD,   CBQUOTE, CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CSPCL,   CENDVAR,
+      CWORD
+};
+
+/* syntax table used when in double quotes */
+static const char dqsyntax[257] = {
+      CEOF,    CIGN,    CWORD,   CCTL,
+      CCTL,    CCTL,    CCTL,    CCTL,
+      CCTL,    CCTL,    CCTL,    CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CNL,     CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CCTL,
+      CENDQUOTE,CWORD,  CVAR,    CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CCTL,    CWORD,   CWORD,   CCTL,
+      CWORD,   CCTL,    CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CCTL,    CWORD,   CWORD,   CCTL,
+      CWORD,   CCTL,    CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CCTL,    CBACK,   CCTL,
+      CWORD,   CWORD,   CBQUOTE, CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CENDVAR,
+      CCTL
+};
+
+/* syntax table used when in single quotes */
+static const char sqsyntax[257] = {
+      CEOF,    CIGN,    CWORD,   CCTL,
+      CCTL,    CCTL,    CCTL,    CCTL,
+      CCTL,    CCTL,    CCTL,    CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CNL,     CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CCTL,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CENDQUOTE,CWORD,  CWORD,
+      CCTL,    CWORD,   CWORD,   CCTL,
+      CWORD,   CCTL,    CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CCTL,    CWORD,   CWORD,   CCTL,
+      CWORD,   CCTL,    CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CCTL,    CCTL,    CCTL,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CCTL
+};
+
+/* syntax table used when in arithmetic */
+static const char arisyntax[257] = {
+      CEOF,    CIGN,    CWORD,   CCTL,
+      CCTL,    CCTL,    CCTL,    CCTL,
+      CCTL,    CCTL,    CCTL,    CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CNL,     CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CDQUOTE, CWORD,   CVAR,    CWORD,
+      CWORD,   CSQUOTE, CLP,     CRP,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CBACK,   CWORD,
+      CWORD,   CWORD,   CBQUOTE, CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CENDVAR,
+      CWORD
+};
+
+/* character classification table */
+static const char is_type[257] = {
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       ISSPECL,
+      0,       ISSPECL, ISSPECL, 0,
+      0,       0,       0,       0,
+      ISSPECL, 0,       0,       ISSPECL,
+      0,       0,       ISDIGIT, ISDIGIT,
+      ISDIGIT, ISDIGIT, ISDIGIT, ISDIGIT,
+      ISDIGIT, ISDIGIT, ISDIGIT, ISDIGIT,
+      0,       0,       0,       0,
+      0,       ISSPECL, ISSPECL, ISUPPER,
+      ISUPPER, ISUPPER, ISUPPER, ISUPPER,
+      ISUPPER, ISUPPER, ISUPPER, ISUPPER,
+      ISUPPER, ISUPPER, ISUPPER, ISUPPER,
+      ISUPPER, ISUPPER, ISUPPER, ISUPPER,
+      ISUPPER, ISUPPER, ISUPPER, ISUPPER,
+      ISUPPER, ISUPPER, ISUPPER, ISUPPER,
+      ISUPPER, 0,       0,       0,
+      0,       ISUNDER, 0,       ISLOWER,
+      ISLOWER, ISLOWER, ISLOWER, ISLOWER,
+      ISLOWER, ISLOWER, ISLOWER, ISLOWER,
+      ISLOWER, ISLOWER, ISLOWER, ISLOWER,
+      ISLOWER, ISLOWER, ISLOWER, ISLOWER,
+      ISLOWER, ISLOWER, ISLOWER, ISLOWER,
+      ISLOWER, ISLOWER, ISLOWER, ISLOWER,
+      ISLOWER, 0,       0,       0,
+      0
+};
+/*     $NetBSD: trap.c,v 1.25 2001/02/04 19:52:07 christos Exp $       */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Sigmode records the current value of the signal handlers for the various
+ * modes.  A value of zero means that the current handler is not known.
+ * S_HARD_IGN indicates that the signal was ignored on entry to the shell,
+ */
+
+/*
+ * The trap builtin.
+ */
+
+static int
+trapcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       char *action;
+       char **ap;
+       int signo;
+
+       if (argc <= 1) {
+               for (signo = 0 ; signo < NSIG ; signo++) {
+                       if (trap[signo] != NULL) {
+                               char *p;
+
+                               p = single_quote(trap[signo]);
+                               out1fmt("trap -- %s %s\n", p,
+                                       signal_names[signo] + (signo ? 3 : 0)
+                               );
+                               stunalloc(p);
+                       }
+               }
+               return 0;
+       }
+       ap = argv + 1;
+       if (argc == 2)
+               action = NULL;
+       else
+               action = *ap++;
+       while (*ap) {
+               if ((signo = decode_signal(*ap, 0)) < 0)
+                       error("%s: bad trap", *ap);
+               INTOFF;
+               if (action) {
+                       if (action[0] == '-' && action[1] == '\0')
+                               action = NULL;
+                       else
+                               action = savestr(action);
+               }
+               if (trap[signo])
+                       ckfree(trap[signo]);
+               trap[signo] = action;
+               if (signo != 0)
+                       setsignal(signo);
+               INTON;
+               ap++;
+       }
+       return 0;
+}
+
+
+
+/*
+ * Clear traps on a fork.
+ */
+
+static void
+clear_traps() {
+       char **tp;
+
+       for (tp = trap ; tp < &trap[NSIG] ; tp++) {
+               if (*tp && **tp) {      /* trap not NULL or SIG_IGN */
+                       INTOFF;
+                       ckfree(*tp);
+                       *tp = NULL;
+                       if (tp != &trap[0])
+                               setsignal(tp - trap);
+                       INTON;
+               }
+       }
+}
+
+
+
+/*
+ * Set the signal handler for the specified signal.  The routine figures
+ * out what it should be set to.
+ */
+
+static void
+setsignal(signo)
+       int signo;
+{
+       int action;
+       char *t;
+       struct sigaction act;
+
+       if ((t = trap[signo]) == NULL)
+               action = S_DFL;
+       else if (*t != '\0')
+               action = S_CATCH;
+       else
+               action = S_IGN;
+       if (rootshell && action == S_DFL) {
+               switch (signo) {
+               case SIGINT:
+                       if (iflag || minusc || sflag == 0)
+                               action = S_CATCH;
+                       break;
+               case SIGQUIT:
+#ifdef DEBUG
+                       {
+                       extern int debug;
+
+                       if (debug)
+                               break;
+                       }
+#endif
+                       /* FALLTHROUGH */
+               case SIGTERM:
+                       if (iflag)
+                               action = S_IGN;
+                       break;
+#if JOBS
+               case SIGTSTP:
+               case SIGTTOU:
+                       if (mflag)
+                               action = S_IGN;
+                       break;
+#endif
+               }
+       }
+
+       t = &sigmode[signo - 1];
+       if (*t == 0) {
+               /*
+                * current setting unknown
+                */
+               if (sigaction(signo, 0, &act) == -1) {
+                       /*
+                        * Pretend it worked; maybe we should give a warning
+                        * here, but other shells don't. We don't alter
+                        * sigmode, so that we retry every time.
+                        */
+                       return;
+               }
+               if (act.sa_handler == SIG_IGN) {
+                       if (mflag && (signo == SIGTSTP ||
+                            signo == SIGTTIN || signo == SIGTTOU)) {
+                               *t = S_IGN;     /* don't hard ignore these */
+                       } else
+                               *t = S_HARD_IGN;
+               } else {
+                       *t = S_RESET;   /* force to be set */
+               }
+       }
+       if (*t == S_HARD_IGN || *t == action)
+               return;
+       switch (action) {
+       case S_CATCH:
+               act.sa_handler = onsig;
+               break;
+       case S_IGN:
+               act.sa_handler = SIG_IGN;
+               break;
+       default:
+               act.sa_handler = SIG_DFL;
+       }
+       *t = action;
+       act.sa_flags = 0;
+       sigemptyset(&act.sa_mask);
+       sigaction(signo, &act, 0);
+}
+
+/*
+ * Ignore a signal.
+ */
+
+static void
+ignoresig(signo)
+       int signo;
+{
+       if (sigmode[signo - 1] != S_IGN && sigmode[signo - 1] != S_HARD_IGN) {
+               signal(signo, SIG_IGN);
+       }
+       sigmode[signo - 1] = S_HARD_IGN;
+}
+
+
+#ifdef mkinit
+INCLUDE <signal.h>
+INCLUDE "trap.h"
+
+SHELLPROC {
+       char *sm;
+
+       clear_traps();
+       for (sm = sigmode ; sm < sigmode + NSIG - 1; sm++) {
+               if (*sm == S_IGN)
+                       *sm = S_HARD_IGN;
+       }
+}
+#endif
+
+
+
+/*
+ * Signal handler.
+ */
+
+static void
+onsig(signo)
+       int signo;
+{
+       if (signo == SIGINT && trap[SIGINT] == NULL) {
+               onint();
+               return;
+       }
+       gotsig[signo - 1] = 1;
+       pendingsigs++;
+}
+
+
+
+/*
+ * Called to execute a trap.  Perhaps we should avoid entering new trap
+ * handlers while we are executing a trap handler.
+ */
+
+static void
+dotrap() {
+       int i;
+       int savestatus;
+
+       for (;;) {
+               for (i = 1 ; ; i++) {
+                       if (gotsig[i - 1])
+                               break;
+                       if (i >= NSIG - 1)
+                               goto done;
+               }
+               gotsig[i - 1] = 0;
+               savestatus=exitstatus;
+               evalstring(trap[i], 0);
+               exitstatus=savestatus;
+       }
+done:
+       pendingsigs = 0;
+}
+
+
+
+/*
+ * Controls whether the shell is interactive or not.
+ */
+
+
+static void
+setinteractive(on)
+       int on;
+{
+       static int is_interactive;
+
+       if (on == is_interactive)
+               return;
+       setsignal(SIGINT);
+       setsignal(SIGQUIT);
+       setsignal(SIGTERM);
+       chkmail(1);
+       is_interactive = on;
+}
+
+
+
+/*
+ * Called to exit the shell.
+ */
+
+static void
+exitshell(status)
+       int status;
+{
+       struct jmploc loc1, loc2;
+       char *p;
+
+       TRACE(("exitshell(%d) pid=%d\n", status, getpid()));
+       if (setjmp(loc1.loc)) {
+               goto l1;
+       }
+       if (setjmp(loc2.loc)) {
+               goto l2;
+       }
+       handler = &loc1;
+       if ((p = trap[0]) != NULL && *p != '\0') {
+               trap[0] = NULL;
+               evalstring(p, 0);
+       }
+l1:   handler = &loc2;                 /* probably unnecessary */
+       flushall();
+#if JOBS
+       setjobctl(0);
+#endif
+l2:   _exit(status);
+       /* NOTREACHED */
+}
+
+static int decode_signal(const char *string, int minsig)
+{
+       int signo;
+
+       if (is_number(string)) {
+               signo = atoi(string);
+               if (signo >= NSIG) {
+                       return -1;
+               }
+               return signo;
+       }
+
+       signo = minsig;
+       if (!signo) {
+               goto zero;
+       }
+       for (; signo < NSIG; signo++) {
+               if (!strcasecmp(string, &(signal_names[signo])[3])) {
+                       return signo;
+               }
+zero:
+               if (!strcasecmp(string, signal_names[signo])) {
+                       return signo;
+               }
+       }
+
+       return -1;
+}
+/*     $NetBSD: var.c,v 1.27 2001/02/04 19:52:07 christos Exp $        */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+
+#define VTABSIZE 39
+
+
+struct varinit {
+       struct var *var;
+       int flags;
+       const char *text;
+       void (*func) __P((const char *));
+};
+
+struct localvar *localvars;
+
+#if ATTY
+struct var vatty;
+#endif
+struct var vifs;
+struct var vmail;
+struct var vmpath;
+struct var vpath;
+struct var vps1;
+struct var vps2;
+struct var vvers;
+struct var voptind;
+
+static const char defpathvar[] =
+       "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
+#ifdef IFS_BROKEN
+static const char defifsvar[] = "IFS= \t\n";
+#else
+static const char defifs[] = " \t\n";
+#endif
+
+static const struct varinit varinit[] = {
+#if ATTY
+       { &vatty,       VSTRFIXED|VTEXTFIXED|VUNSET,    "ATTY=",
+         NULL },
+#endif
+#ifdef IFS_BROKEN
+       { &vifs,        VSTRFIXED|VTEXTFIXED,           defifsvar,
+#else
+       { &vifs,        VSTRFIXED|VTEXTFIXED|VUNSET,    "IFS=",
+#endif
+         NULL },
+       { &vmail,       VSTRFIXED|VTEXTFIXED|VUNSET,    "MAIL=",
+         NULL },
+       { &vmpath,      VSTRFIXED|VTEXTFIXED|VUNSET,    "MAILPATH=",
+         NULL },
+       { &vpath,       VSTRFIXED|VTEXTFIXED,           defpathvar,
+         changepath },
+       /*
+        * vps1 depends on uid
+        */
+       { &vps2,        VSTRFIXED|VTEXTFIXED,           "PS2=> ",
+         NULL },
+       { &voptind,     VSTRFIXED|VTEXTFIXED,           "OPTIND=1",
+         getoptsreset },
+       { NULL, 0,                              NULL,
+         NULL }
+};
+
+struct var *vartab[VTABSIZE];
+
+static struct var **hashvar __P((const char *));
+static void showvars __P((const char *, int, int));
+static struct var **findvar __P((struct var **, const char *));
+
+/*
+ * Initialize the varable symbol tables and import the environment
+ */
+
+#ifdef mkinit
+INCLUDE <unistd.h>
+INCLUDE "output.h"
+INCLUDE "var.h"
+static char **environ;
+INIT {
+       char **envp;
+       char ppid[32];
+
+       initvar();
+       for (envp = environ ; *envp ; envp++) {
+               if (strchr(*envp, '=')) {
+                       setvareq(*envp, VEXPORT|VTEXTFIXED);
+               }
+       }
+
+       fmtstr(ppid, sizeof(ppid), "%d", (int) getppid());
+       setvar("PPID", ppid, 0);
+}
+#endif
+
+
+/*
+ * This routine initializes the builtin variables.  It is called when the
+ * shell is initialized and again when a shell procedure is spawned.
+ */
+
+static void
+initvar() {
+       const struct varinit *ip;
+       struct var *vp;
+       struct var **vpp;
+
+       for (ip = varinit ; (vp = ip->var) != NULL ; ip++) {
+               if ((vp->flags & VEXPORT) == 0) {
+                       vpp = hashvar(ip->text);
+                       vp->next = *vpp;
+                       *vpp = vp;
+                       vp->text = strdup(ip->text);
+                       vp->flags = ip->flags;
+                       vp->func = ip->func;
+               }
+       }
+       /*
+        * PS1 depends on uid
+        */
+       if ((vps1.flags & VEXPORT) == 0) {
+               vpp = hashvar("PS1=");
+               vps1.next = *vpp;
+               *vpp = &vps1;
+               vps1.text = strdup(geteuid() ? "PS1=$ " : "PS1=# ");
+               vps1.flags = VSTRFIXED|VTEXTFIXED;
+       }
+}
+
+/*
+ * 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(name, val, flags)
+       const char *name, *val;
+       int flags;
+{
+       const char *p;
+       int len;
+       int namelen;
+       char *nameeq;
+       int isbad;
+       int vallen = 0;
+
+       isbad = 0;
+       p = name;
+       if (! is_name(*p))
+               isbad = 1;
+       p++;
+       for (;;) {
+               if (! is_in_name(*p)) {
+                       if (*p == '\0' || *p == '=')
+                               break;
+                       isbad = 1;
+               }
+               p++;
+       }
+       namelen = p - name;
+       if (isbad)
+               error("%.*s: bad variable name", namelen, name);
+       len = namelen + 2;              /* 2 is space for '=' and '\0' */
+       if (val == NULL) {
+               flags |= VUNSET;
+       } else {
+               len += vallen = strlen(val);
+       }
+       INTOFF;
+       nameeq = ckmalloc(len);
+       memcpy(nameeq, name, namelen);
+       nameeq[namelen] = '=';
+       if (val) {
+               memcpy(nameeq + namelen + 1, val, vallen + 1);
+       } else {
+               nameeq[namelen + 1] = '\0';
+       }
+       setvareq(nameeq, flags);
+       INTON;
+}
+
+
+
+/*
+ * 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.
+ */
+
+static void
+setvareq(s, flags)
+       char *s;
+       int flags;
+{
+       struct var *vp, **vpp;
+
+       vpp = hashvar(s);
+       flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1));
+       if ((vp = *findvar(vpp, s))) {
+               if (vp->flags & VREADONLY) {
+                       size_t len = strchr(s, '=') - s;
+                       error("%.*s: is read only", len, s);
+               }
+               INTOFF;
+
+               if (vp->func && (flags & VNOFUNC) == 0)
+                       (*vp->func)(strchr(s, '=') + 1);
+
+               if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
+                       ckfree(vp->text);
+
+               vp->flags &= ~(VTEXTFIXED|VSTACK|VUNSET);
+               vp->flags |= flags;
+               vp->text = s;
+
+               /*
+                * We could roll this to a function, to handle it as
+                * a regular variable function callback, but why bother?
+                */
+               if (iflag && (vp == &vmpath || (vp == &vmail && !mpathset())))
+                       chkmail(1);
+               INTON;
+               return;
+       }
+       /* not found */
+       vp = ckmalloc(sizeof (*vp));
+       vp->flags = flags;
+       vp->text = s;
+       vp->next = *vpp;
+       vp->func = NULL;
+       *vpp = vp;
+}
+
+
+
+/*
+ * Process a linked list of variable assignments.
+ */
+
+static void
+listsetvar(mylist)
+       struct strlist *mylist;
+       {
+       struct strlist *lp;
+
+       INTOFF;
+       for (lp = mylist ; lp ; lp = lp->next) {
+               setvareq(savestr(lp->text), 0);
+       }
+       INTON;
+}
+
+
+
+/*
+ * Find the value of a variable.  Returns NULL if not set.
+ */
+
+static char *
+lookupvar(name)
+       const char *name;
+       {
+       struct var *v;
+
+       if ((v = *findvar(hashvar(name), name)) && !(v->flags & VUNSET)) {
+               return strchr(v->text, '=') + 1;
+       }
+       return NULL;
+}
+
+
+
+/*
+ * Search the environment of a builtin command.
+ */
+
+static char *
+bltinlookup(name)
+       const char *name;
+{
+       struct strlist *sp;
+
+       for (sp = cmdenviron ; sp ; sp = sp->next) {
+               if (varequal(sp->text, name))
+                       return strchr(sp->text, '=') + 1;
+       }
+       return lookupvar(name);
+}
+
+
+
+/*
+ * Generate a list of exported variables.  This routine is used to construct
+ * the third argument to execve when executing a program.
+ */
+
+static char **
+environment() {
+       int nenv;
+       struct var **vpp;
+       struct var *vp;
+       char **env;
+       char **ep;
+
+       nenv = 0;
+       for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
+               for (vp = *vpp ; vp ; vp = vp->next)
+                       if (vp->flags & VEXPORT)
+                               nenv++;
+       }
+       ep = env = stalloc((nenv + 1) * sizeof *env);
+       for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
+               for (vp = *vpp ; vp ; vp = vp->next)
+                       if (vp->flags & VEXPORT)
+                               *ep++ = vp->text;
+       }
+       *ep = NULL;
+       return env;
+}
+
+
+/*
+ * Called when a shell procedure is invoked to clear out nonexported
+ * variables.  It is also necessary to reallocate variables of with
+ * VSTACK set since these are currently allocated on the stack.
+ */
+
+#ifdef mkinit
+static void shprocvar __P((void));
+
+SHELLPROC {
+       shprocvar();
+}
+#endif
+
+static void
+shprocvar() {
+       struct var **vpp;
+       struct var *vp, **prev;
+
+       for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
+               for (prev = vpp ; (vp = *prev) != NULL ; ) {
+                       if ((vp->flags & VEXPORT) == 0) {
+                               *prev = vp->next;
+                               if ((vp->flags & VTEXTFIXED) == 0)
+                                       ckfree(vp->text);
+                               if ((vp->flags & VSTRFIXED) == 0)
+                                       ckfree(vp);
+                       } else {
+                               if (vp->flags & VSTACK) {
+                                       vp->text = savestr(vp->text);
+                                       vp->flags &=~ VSTACK;
+                               }
+                               prev = &vp->next;
+                       }
+               }
+       }
+       initvar();
+}
+
+
+
+/*
+ * Command to list all variables which are set.  Currently this command
+ * is invoked from the set command when the set command is called without
+ * any variables.
+ */
+
+static int
+showvarscmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       showvars(nullstr, VUNSET, VUNSET);
+       return 0;
+}
+
+
+
+/*
+ * The export and readonly commands.
+ */
+
+static int
+exportcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       struct var *vp;
+       char *name;
+       const char *p;
+       int flag = argv[0][0] == 'r'? VREADONLY : VEXPORT;
+       int pflag;
+
+       listsetvar(cmdenviron);
+       pflag = (nextopt("p") == 'p');
+       if (argc > 1 && !pflag) {
+               while ((name = *argptr++) != NULL) {
+                       if ((p = strchr(name, '=')) != NULL) {
+                               p++;
+                       } else {
+                               if ((vp = *findvar(hashvar(name), name))) {
+                                       vp->flags |= flag;
+                                       goto found;
+                               }
+                       }
+                       setvar(name, p, flag);
+found:;
+               }
+       } else {
+               showvars(argv[0], flag, 0);
+       }
+       return 0;
+}
+
+
+/*
+ * The "local" command.
+ */
+
+static int
+localcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       char *name;
+
+       if (! in_function())
+               error("Not in a function");
+       while ((name = *argptr++) != NULL) {
+               mklocal(name);
+       }
+       return 0;
+}
+
+
+/*
+ * Make a variable a local variable.  When a variable is made local, it's
+ * value and flags are saved in a localvar structure.  The saved values
+ * will be restored when the shell function returns.  We handle the name
+ * "-" as a special case.
+ */
+
+static void
+mklocal(name)
+       char *name;
+       {
+       struct localvar *lvp;
+       struct var **vpp;
+       struct var *vp;
+
+       INTOFF;
+       lvp = ckmalloc(sizeof (struct localvar));
+       if (name[0] == '-' && name[1] == '\0') {
+               char *p;
+               p = ckmalloc(sizeof optlist);
+               lvp->text = memcpy(p, optlist, sizeof optlist);
+               vp = NULL;
+       } else {
+               vpp = hashvar(name);
+               vp = *findvar(vpp, name);
+               if (vp == NULL) {
+                       if (strchr(name, '='))
+                               setvareq(savestr(name), VSTRFIXED);
+                       else
+                               setvar(name, NULL, VSTRFIXED);
+                       vp = *vpp;      /* the new variable */
+                       lvp->text = NULL;
+                       lvp->flags = VUNSET;
+               } else {
+                       lvp->text = vp->text;
+                       lvp->flags = vp->flags;
+                       vp->flags |= VSTRFIXED|VTEXTFIXED;
+                       if (strchr(name, '='))
+                               setvareq(savestr(name), 0);
+               }
+       }
+       lvp->vp = vp;
+       lvp->next = localvars;
+       localvars = lvp;
+       INTON;
+}
+
+
+/*
+ * Called after a function returns.
+ */
+
+static void
+poplocalvars() {
+       struct localvar *lvp;
+       struct var *vp;
+
+       while ((lvp = localvars) != NULL) {
+               localvars = lvp->next;
+               vp = lvp->vp;
+               if (vp == NULL) {       /* $- saved */
+                       memcpy(optlist, lvp->text, sizeof optlist);
+                       ckfree(lvp->text);
+               } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) {
+                       (void)unsetvar(vp->text);
+               } else {
+                       if ((vp->flags & VTEXTFIXED) == 0)
+                               ckfree(vp->text);
+                       vp->flags = lvp->flags;
+                       vp->text = lvp->text;
+               }
+               ckfree(lvp);
+       }
+}
+
+
+static int
+setvarcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       if (argc <= 2)
+               return unsetcmd(argc, argv);
+       else if (argc == 3)
+               setvar(argv[1], argv[2], 0);
+       else
+               error("List assignment not implemented");
+       return 0;
+}
+
+
+/*
+ * The unset builtin command.  We unset the function before we unset the
+ * variable to allow a function to be unset when there is a readonly variable
+ * with the same name.
+ */
+
+static int
+unsetcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       char **ap;
+       int i;
+       int flg_func = 0;
+       int flg_var = 0;
+       int ret = 0;
+
+       while ((i = nextopt("vf")) != '\0') {
+               if (i == 'f')
+                       flg_func = 1;
+               else
+                       flg_var = 1;
+       }
+       if (flg_func == 0 && flg_var == 0)
+               flg_var = 1;
+
+       for (ap = argptr; *ap ; ap++) {
+               if (flg_func)
+                       unsetfunc(*ap);
+               if (flg_var)
+                       ret |= unsetvar(*ap);
+       }
+       return ret;
+}
+
+
+/*
+ * Unset the specified variable.
+ */
+
+static int
+unsetvar(s)
+       const char *s;
+       {
+       struct var **vpp;
+       struct var *vp;
+
+       vpp = findvar(hashvar(s), s);
+       vp = *vpp;
+       if (vp) {
+               if (vp->flags & VREADONLY)
+                       return (1);
+               INTOFF;
+               if (*(strchr(vp->text, '=') + 1) != '\0')
+                       setvar(s, nullstr, 0);
+               vp->flags &= ~VEXPORT;
+               vp->flags |= VUNSET;
+               if ((vp->flags & VSTRFIXED) == 0) {
+                       if ((vp->flags & VTEXTFIXED) == 0)
+                               ckfree(vp->text);
+                       *vpp = vp->next;
+                       ckfree(vp);
+               }
+               INTON;
+               return (0);
+       }
+
+       return (0);
+}
+
+
+
+/*
+ * Find the appropriate entry in the hash table from the name.
+ */
+
+static struct var **
+hashvar(p)
+       const char *p;
+       {
+       unsigned int hashval;
+
+       hashval = ((unsigned char) *p) << 4;
+       while (*p && *p != '=')
+               hashval += (unsigned char) *p++;
+       return &vartab[hashval % VTABSIZE];
+}
+
+
+
+/*
+ * Returns true if the two strings specify the same varable.  The first
+ * variable name is terminated by '='; the second may be terminated by
+ * either '=' or '\0'.
+ */
+
+static int
+varequal(p, q)
+       const char *p, *q;
+       {
+       while (*p == *q++) {
+               if (*p++ == '=')
+                       return 1;
+       }
+       if (*p == '=' && *(q - 1) == '\0')
+               return 1;
+       return 0;
+}
+
+static void
+showvars(const char *myprefix, int mask, int xor)
+{
+       struct var **vpp;
+       struct var *vp;
+       const char *sep = myprefix == nullstr ? myprefix : spcstr;
+
+       for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
+               for (vp = *vpp ; vp ; vp = vp->next) {
+                       if ((vp->flags & mask) ^ xor) {
+                               char *p;
+                               int len;
+
+                               p = strchr(vp->text, '=') + 1;
+                               len = p - vp->text;
+                               p = single_quote(p);
+
+                               out1fmt(
+                                       "%s%s%.*s%s\n", myprefix, sep, len,
+                                       vp->text, p
+                               );
+                               stunalloc(p);
+                       }
+               }
+       }
+}
+
+static struct var **
+findvar(struct var **vpp, const char *name)
+{
+       for (; *vpp; vpp = &(*vpp)->next) {
+               if (varequal((*vpp)->text, name)) {
+                       break;
+               }
+       }
+       return vpp;
+}
+
+/*
+ * Copyright (c) 1999 Herbert Xu <herbert@debian.org>
+ * This file contains code for the times builtin.
+ * $Id: ash.c,v 1.1 2001/06/28 07:25:15 andersen Exp $
+ */
+static int timescmd (int argc, char **argv)
+{
+       struct tms buf;
+       long int clk_tck = sysconf(_SC_CLK_TCK);
+
+       times(&buf);
+       printf("%dm%fs %dm%fs\n%dm%fs %dm%fs\n",
+              (int) (buf.tms_utime / clk_tck / 60),
+              ((double) buf.tms_utime) / clk_tck,
+              (int) (buf.tms_stime / clk_tck / 60),
+              ((double) buf.tms_stime) / clk_tck,
+              (int) (buf.tms_cutime / clk_tck / 60),
+              ((double) buf.tms_cutime) / clk_tck,
+              (int) (buf.tms_cstime / clk_tck / 60),
+              ((double) buf.tms_cstime) / clk_tck);
+       return 0;
+}
+
diff --git a/ash.h b/ash.h
new file mode 100644 (file)
index 0000000..ac25ddb
--- /dev/null
+++ b/ash.h
@@ -0,0 +1,1225 @@
+/*-
+ * Copyright (c) 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*     $NetBSD: alias.h,v 1.4 1995/05/11 21:28:42 christos Exp $       */
+
+#define ALIASINUSE     1
+#define ALIASDEAD      2
+
+struct alias {
+       struct alias *next;
+       char *name;
+       char *val;
+       int flag;
+};
+
+struct alias *lookupalias __P((const char *, int));
+static int aliascmd __P((int, char **));
+static int unaliascmd __P((int, char **));
+static void rmaliases __P((void));
+static int unalias __P((char *));
+static void printalias __P((const struct alias *));
+#define ARITH_NUM 257
+#define ARITH_LPAREN 258
+#define ARITH_RPAREN 259
+#define ARITH_OR 260
+#define ARITH_AND 261
+#define ARITH_BOR 262
+#define ARITH_BXOR 263
+#define ARITH_BAND 264
+#define ARITH_EQ 265
+#define ARITH_NE 266
+#define ARITH_LT 267
+#define ARITH_GT 268
+#define ARITH_GE 269
+#define ARITH_LE 270
+#define ARITH_LSHIFT 271
+#define ARITH_RSHIFT 272
+#define ARITH_ADD 273
+#define ARITH_SUB 274
+#define ARITH_MUL 275
+#define ARITH_DIV 276
+#define ARITH_REM 277
+#define ARITH_UNARYMINUS 278
+#define ARITH_UNARYPLUS 279
+#define ARITH_NOT 280
+#define ARITH_BNOT 281
+
+/*
+ * This file was generated by the mkbuiltins program.
+ */
+
+
+#define BUILTIN_SPECIAL 0x1
+#define BUILTIN_REGULAR 0x2
+#define BUILTIN_ASSIGN 0x4
+
+struct builtincmd {
+       const char *name;
+       int (*const builtinfunc) __P((int, char **));
+       unsigned flags;
+};
+
+extern const struct builtincmd builtincmds[];
+
+
+
+/*     $NetBSD: cd.h,v 1.2 1997/07/04 21:01:52 christos Exp $  */
+static int     cdcmd __P((int, char **));
+static int     pwdcmd __P((int, char **));
+static void    setpwd __P((const char *, int));
+
+
+/*     $NetBSD: error.h,v 1.14 2001/02/04 19:52:06 christos Exp $      */
+
+/*
+ * Types of operations (passed to the errmsg routine).
+ */
+
+#define E_OPEN 01      /* opening a file */
+#define E_CREAT 02     /* creating a file */
+#define E_EXEC 04      /* executing a program */
+
+
+/*
+ * We enclose jmp_buf in a structure so that we can declare pointers to
+ * jump locations.  The global variable handler contains the location to
+ * jump to when an exception occurs, and the global variable exception
+ * contains a code identifying the exeception.  To implement nested
+ * exception handlers, the user should save the value of handler on entry
+ * to an inner scope, set handler to point to a jmploc structure for the
+ * inner scope, and restore handler on exit from the scope.
+ */
+
+struct jmploc {
+       jmp_buf loc;
+};
+
+extern struct jmploc *handler;
+extern int exception;
+
+/* exceptions */
+#define EXINT 0                /* SIGINT received */
+#define EXERROR 1      /* a generic error */
+#define EXSHELLPROC 2  /* execute a shell procedure */
+#define EXEXEC 3       /* command execution failed */
+
+
+/*
+ * These macros allow the user to suspend the handling of interrupt signals
+ * over a period of time.  This is similar to SIGHOLD to or sigblock, but
+ * much more efficient and portable.  (But hacking the kernel is so much
+ * more fun than worrying about efficiency and portability. :-))
+ */
+
+extern int suppressint;
+extern volatile int intpending;
+
+#define INTOFF suppressint++
+#ifdef REALLY_SMALL
+static void __inton __P((void));
+#define INTON __inton()
+#else
+#define INTON { if (--suppressint == 0 && intpending) onint(); }
+#endif
+#define FORCEINTON {suppressint = 0; if (intpending) onint();}
+#define CLEAR_PENDING_INT intpending = 0
+#define int_pending() intpending
+
+static void exraise __P((int)) __attribute__((__noreturn__));
+static void onint __P((void));
+static void error __P((const char *, ...)) __attribute__((__noreturn__));
+static void exerror __P((int, const char *, ...)) __attribute__((__noreturn__));
+static const char *errmsg __P((int, int));
+
+
+/*
+ * BSD setjmp saves the signal mask, which violates ANSI C and takes time,
+ * so we use _setjmp instead.
+ */
+
+#if defined(BSD) && !defined(__SVR4) && !defined(__GLIBC__)
+#define setjmp(jmploc) _setjmp(jmploc)
+#define longjmp(jmploc, val)   _longjmp(jmploc, val)
+#endif
+
+
+
+/*     $NetBSD: shell.h,v 1.13 2000/05/22 10:18:47 elric Exp $ */
+
+/*
+ * The follow should be set to reflect the type of system you have:
+ *     JOBS -> 1 if you have Berkeley job control, 0 otherwise.
+ *     SHORTNAMES -> 1 if your linker cannot handle long names.
+ *     define BSD if you are running 4.2 BSD or later.
+ *     define SYSV if you are running under System V.
+ *     define DEBUG=1 to compile in debugging (set global "debug" to turn on)
+ *     define DEBUG=2 to compile in and turn on debugging.
+ *
+ * When debugging is on, debugging info will be written to $HOME/trace and
+ * a quit signal will generate a core dump.
+ */
+
+
+#define JOBS 1
+#ifndef BSD
+#define BSD 1
+#endif
+
+#ifdef __STDC__
+typedef void *pointer;
+#ifndef NULL
+#define NULL (void *)0
+#endif
+#else /* not __STDC__ */
+typedef char *pointer;
+#ifndef NULL
+#define NULL 0
+#endif
+#endif /*  not __STDC__ */
+
+extern char nullstr[1];                /* null string */
+
+
+#ifdef DEBUG
+#define TRACE(param)   trace param
+#else
+#define TRACE(param)
+#endif
+
+
+
+
+/*
+ * This file was generated by the mknodes program.
+ */
+
+#define NSEMI 0
+#define NCMD 1
+#define NPIPE 2
+#define NREDIR 3
+#define NBACKGND 4
+#define NSUBSHELL 5
+#define NAND 6
+#define NOR 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 NFROM 17
+#define NFROMTO 18
+#define NAPPEND 19
+#define NTOOV 20
+#define NTOFD 21
+#define NFROMFD 22
+#define NHERE 23
+#define NXHERE 24
+#define NNOT 25
+
+
+
+struct nbinary {
+      int type;
+      union node *ch1;
+      union node *ch2;
+};
+
+
+struct ncmd {
+      int type;
+      int backgnd;
+      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 nif {
+      int type;
+      union node *test;
+      union node *ifpart;
+      union node *elsepart;
+};
+
+
+struct nfor {
+      int type;
+      union node *args;
+      union node *body;
+      char *var;
+};
+
+
+struct ncase {
+      int type;
+      union node *expr;
+      union node *cases;
+};
+
+
+struct nclist {
+      int type;
+      union node *next;
+      union node *pattern;
+      union node *body;
+};
+
+
+struct narg {
+      int type;
+      union node *next;
+      char *text;
+      struct nodelist *backquote;
+};
+
+
+struct nfile {
+      int type;
+      union node *next;
+      int fd;
+      union node *fname;
+      char *expfname;
+};
+
+
+struct ndup {
+      int type;
+      union node *next;
+      int fd;
+      int dupfd;
+      union node *vname;
+};
+
+
+struct nhere {
+      int type;
+      union node *next;
+      int fd;
+      union node *doc;
+};
+
+
+struct nnot {
+      int type;
+      union node *com;
+};
+
+
+union node {
+      int type;
+      struct nbinary nbinary;
+      struct ncmd ncmd;
+      struct npipe npipe;
+      struct nredir nredir;
+      struct nif nif;
+      struct nfor nfor;
+      struct ncase ncase;
+      struct nclist nclist;
+      struct narg narg;
+      struct nfile nfile;
+      struct ndup ndup;
+      struct nhere nhere;
+      struct nnot nnot;
+};
+
+
+struct nodelist {
+       struct nodelist *next;
+       union node *n;
+};
+
+
+#ifdef __STDC__
+union node *copyfunc(union node *);
+static void freefunc(union node *);
+#else
+union node *copyfunc();
+static void freefunc();
+#endif
+
+
+
+/*     $NetBSD: eval.h,v 1.10 2000/01/27 23:39:40 christos Exp $       */
+extern char *commandname;      /* currently executing command */
+extern int exitstatus;         /* exit status of last command */
+extern struct strlist *cmdenviron;  /* environment for builtin command */
+
+
+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 int evalcmd __P((int, char **));
+static void evalstring __P((char *, int));
+static void evaltree __P((union node *, int));
+static void evalbackcmd __P((union node *, struct backcmd *));
+static int bltincmd __P((int, char **));
+static int breakcmd __P((int, char **));
+static int returncmd __P((int, char **));
+static int execcmd __P((int, char **));
+
+/* in_function returns nonzero if we are currently evaluating a function */
+#define in_function()  funcnest
+extern int funcnest;
+extern int evalskip;
+
+/* reasons for skipping commands (see comment on breakcmd routine) */
+#define SKIPBREAK      1
+#define SKIPCONT       2
+#define SKIPFUNC       3
+#define SKIPFILE       4
+
+
+
+
+/*     $NetBSD: exec.h,v 1.17 2000/05/22 10:18:47 elric Exp $  */
+
+/* values of cmdtype */
+#define CMDUNKNOWN -1          /* no entry in table for command */
+#define CMDNORMAL 0            /* command is an executable program */
+#define CMDBUILTIN 1           /* command is a shell builtin */
+#define CMDFUNCTION 2          /* command is a shell function */
+
+
+struct cmdentry {
+       int cmdtype;
+       union param {
+               int index;
+               union node *func;
+               const struct builtincmd *cmd;
+       } u;
+};
+
+
+#define DO_ERR 1               /* find_command prints errors */
+#define DO_ABS 2               /* find_command checks absolute paths */
+#define DO_NOFUN       4       /* find_command ignores functions */
+#define DO_BRUTE       8       /* find_command ignores hash table */
+
+extern const char *pathopt;    /* set by padvance */
+extern int exerrno;            /* last exec error */
+
+static void shellexec __P((char **, char **, const char *, int))
+    __attribute__((noreturn));
+static char *padvance __P((const char **, const char *));
+static int hashcmd __P((int, char **));
+static void find_command __P((char *, struct cmdentry *, int, const char *));
+struct builtincmd *find_builtin __P((char *));
+static void hashcd __P((void));
+static void changepath __P((const char *));
+static void deletefuncs __P((void));
+#ifdef notdef
+static void getcmdentry __P((char *, struct cmdentry *));
+#endif
+static void addcmdentry __P((char *, struct cmdentry *));
+static void defun __P((char *, union node *));
+static void unsetfunc __P((char *));
+#ifdef ASH_TYPE
+static int typecmd __P((int, char **));
+#endif
+static int commandcmd __P((int, char **));
+
+
+
+/*     $NetBSD: expand.h,v 1.12 1999/07/09 03:05:50 christos Exp $     */
+struct strlist {
+       struct strlist *next;
+       char *text;
+};
+
+
+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 */
+
+
+static void expandhere __P((union node *, int));
+static void expandarg __P((union node *, struct arglist *, int));
+#ifdef ASH_MATH_SUPPORT
+static void expari __P((int));
+#endif
+#if !(defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN))
+static int patmatch __P((char *, char *, int));
+#endif
+#if defined(__GLIBC__) && !defined(FNMATCH_BROKEN)
+#define rmescapes(p) _rmescapes((p), 0)
+static char *_rmescapes __P((char *, int));
+#else
+static void rmescapes __P((char *));
+#endif
+static int casematch __P((union node *, char *));
+
+
+#ifdef ASH_MATH_SUPPORT
+/* From arith.y */
+static int arith __P((const char *));
+static int expcmd __P((int , char **));
+static void arith_lex_reset __P((void));
+static int yylex __P((void));
+#endif 
+
+
+
+
+/*     $NetBSD: init.h,v 1.8 1995/05/11 21:29:14 christos Exp $        */
+static void init __P((void));
+static void reset __P((void));
+static void initshellproc __P((void));
+
+
+
+/*     $NetBSD: input.h,v 1.12 2000/05/22 10:18:47 elric Exp $ */
+
+/* PEOF (the end of file marker) is defined in syntax.h */
+/*
+ * The input line number.  Input.c just defines this variable, and saves
+ * and restores it when files are pushed and popped.  The user of this
+ * package must set its value.
+ */
+extern int plinno;
+extern int parsenleft;         /* number of characters left in input buffer */
+extern char *parsenextc;       /* next character in input buffer */
+
+static char *pfgets __P((char *, int));
+static int pgetc __P((void));
+static int pgetc2 __P((void));
+static int preadbuffer __P((void));
+static void pungetc __P((void));
+static void pushstring __P((char *, int, void *));
+static void popstring __P((void));
+static void setinputfile __P((const char *, int));
+static void setinputfd __P((int, int));
+static void setinputstring __P((char *));
+static void popfile __P((void));
+static void popallfiles __P((void));
+static void closescript __P((void));
+
+#define pgetc_macro()  (--parsenleft >= 0? *parsenextc++ : preadbuffer())
+
+
+
+/*     $NetBSD: jobs.h,v 1.12 2000/05/22 10:18:47 elric Exp $  */
+
+/* Mode argument to forkshell.  Don't change FORK_FG or FORK_BG. */
+#define FORK_FG 0
+#define FORK_BG 1
+#define FORK_NOJOB 2
+
+
+/*
+ * A job structure contains information about a job.  A job is either a
+ * single process or a set of processes contained in a pipeline.  In the
+ * latter case, pidlist will be non-NULL, and will point to a -1 terminated
+ * array of pids.
+ */
+
+struct procstat {
+       pid_t pid;              /* process id */
+       int status;             /* status flags (defined above) */
+       char *cmd;              /* text of command being run */
+};
+
+
+/* states */
+#define JOBSTOPPED 1           /* all procs are stopped */
+#define JOBDONE 2              /* all procs are completed */
+
+
+struct job {
+       struct procstat ps0;    /* status of process */
+       struct procstat *ps;    /* status or processes when more than one */
+       short nprocs;           /* number of processes */
+       short pgrp;             /* process group of this job */
+       char state;             /* true if job is finished */
+       char used;              /* true if this entry is in used */
+       char changed;           /* true if status has changed */
+#if JOBS
+       char jobctl;            /* job running under job control */
+#endif
+};
+
+extern short backgndpid;       /* pid of last background process */
+extern int job_warning;                /* user was warned about stopped jobs */
+
+static void setjobctl __P((int));
+static int killcmd __P((int, char **));
+static int fgcmd __P((int, char **));
+static int bgcmd __P((int, char **));
+static int jobscmd __P((int, char **));
+static void showjobs __P((int));
+static int waitcmd __P((int, char **));
+struct job *makejob __P((union node *, int));
+static int forkshell __P((struct job *, union node *, int));
+static int waitforjob __P((struct job *));
+static int stoppedjobs __P((void));
+static char *commandtext __P((union node *));
+
+#if ! JOBS
+#define setjobctl(on)  /* do nothing */
+#endif
+
+
+
+/*     $NetBSD: machdep.h,v 1.8 1995/05/11 21:29:21 christos Exp $     */
+
+/*
+ * Most machines require the value returned from malloc to be aligned
+ * in some way.  The following macro will get this right on many machines.
+ */
+
+#ifndef ALIGN
+union align {
+       int i;
+       char *cp;
+};
+
+#define ALIGN(nbytes)  (((nbytes) + sizeof(union align) - 1) & ~(sizeof(union align) - 1))
+#endif
+
+
+
+/*     $NetBSD: mail.h,v 1.8 1995/05/11 21:29:23 christos Exp $        */
+
+static void chkmail __P((int));
+
+
+
+/*     $NetBSD: main.h,v 1.8 1995/05/11 21:29:27 christos Exp $        */
+extern int rootpid;    /* pid of main shell */
+extern int rootshell;  /* true if we aren't a child of the main shell */
+
+static void readcmdfile __P((char *));
+static void cmdloop __P((int));
+static int dotcmd __P((int, char **));
+static int exitcmd __P((int, char **));
+
+
+
+/*     $NetBSD: memalloc.h,v 1.11 2000/11/01 19:56:01 christos Exp $   */
+struct stackmark {
+       struct stack_block *stackp;
+       char *stacknxt;
+       int stacknleft;
+       struct stackmark *marknext;
+};
+
+
+extern char *stacknxt;
+extern int stacknleft;
+extern int sstrnleft;
+extern int herefd;
+
+static inline pointer  ckmalloc (int sz)          { return xmalloc(sz);     }
+static inline pointer  ckrealloc(void *p, int sz) { return xrealloc(p, sz); }
+static inline char *   savestr  (const char *s)   { return xstrdup(s);      }
+
+pointer stalloc __P((int));
+static void stunalloc __P((pointer));
+static void setstackmark __P((struct stackmark *));
+static void popstackmark __P((struct stackmark *));
+static void growstackblock __P((void));
+static void grabstackblock __P((int));
+static char *growstackstr __P((void));
+static char *makestrspace __P((size_t));
+static void ungrabstackstr __P((char *, char *));
+
+
+
+#define stackblock() stacknxt
+#define stackblocksize() stacknleft
+#define STARTSTACKSTR(p)       p = stackblock(), sstrnleft = stackblocksize()
+#define STPUTC(c, p)   (--sstrnleft >= 0? (*p++ = (c)) : (p = growstackstr(), *p++ = (c)))
+#define CHECKSTRSPACE(n, p)    { if (sstrnleft < n) p = makestrspace(n); }
+#define USTPUTC(c, p)  (--sstrnleft, *p++ = (c))
+#define STACKSTRNUL(p) (sstrnleft == 0? (p = growstackstr(), *p = '\0') : (*p = '\0'))
+#define STUNPUTC(p)    (++sstrnleft, --p)
+#define STTOPC(p)      p[-1]
+#define STADJUST(amount, p)    (p += (amount), sstrnleft -= (amount))
+#define grabstackstr(p)        stalloc(stackblocksize() - sstrnleft)
+
+#define ckfree(p)      free((pointer)(p))
+
+
+
+/*     $NetBSD: miscbltin.h,v 1.1 1997/07/04 21:02:10 christos Exp $   */
+static int readcmd __P((int, char **));
+static int umaskcmd __P((int, char **));
+static int ulimitcmd __P((int, char **));
+
+
+
+/*     $NetBSD: mystring.h,v 1.9 1995/05/11 21:29:42 christos Exp $    */
+
+extern const char snlfmt[];
+extern const char spcstr[];
+
+#if 0
+static void scopyn __P((const char *, char *, int));
+#endif
+static int prefix __P((const char *, const char *));
+static int number __P((const char *));
+static int is_number __P((const char *));
+static char *single_quote __P((const char *));
+static char *sstrdup __P((const char *));
+static int pstrcmp __P((const void *, const void *));
+static const char *const *findstring __P((const char *, const char *const *, size_t));
+
+#define equal(s1, s2)  (strcmp(s1, s2) == 0)
+#define scopy(s1, s2)  ((void)strcpy(s2, s1))
+
+
+/*     $NetBSD: options.h,v 1.14 2001/02/04 19:52:06 christos Exp $    */
+
+struct shparam {
+       int nparam;             /* # of positional parameters (without $0) */
+       unsigned char malloc;   /* if parameter list dynamically allocated */
+       char **p;               /* parameter list */
+       int optind;             /* next parameter to be processed by getopts */
+       int optoff;             /* used by getopts */
+};
+
+
+
+#define eflag optlist[0].val
+#define fflag optlist[1].val
+#define Iflag optlist[2].val
+#define iflag optlist[3].val
+#define mflag optlist[4].val
+#define nflag optlist[5].val
+#define sflag optlist[6].val
+#define xflag optlist[7].val
+#define vflag optlist[8].val
+#define Vflag optlist[9].val
+#define        Eflag optlist[10].val
+#define        Cflag optlist[11].val
+#define        aflag optlist[12].val
+#define        bflag optlist[13].val
+#define        uflag optlist[14].val
+#define        qflag optlist[15].val
+
+#define NOPTS  16
+
+struct optent {
+       const char *name;
+       const char letter;
+       char val;
+};
+
+extern struct optent optlist[NOPTS];
+
+
+extern char *minusc;           /* argument to -c option */
+extern char *arg0;             /* $0 */
+extern struct shparam shellparam;  /* $@ */
+extern char **argptr;          /* argument list for builtin commands */
+extern char *optionarg;                /* set by nextopt */
+extern char *optptr;           /* used by nextopt */
+
+static void procargs __P((int, char **));
+static void optschanged __P((void));
+static void setparam __P((char **));
+static void freeparam __P((volatile struct shparam *));
+static int shiftcmd __P((int, char **));
+static int setcmd __P((int, char **));
+#ifdef ASH_GETOPTS
+static int getoptscmd __P((int, char **));
+static int setvarsafe __P((const char *, const char *, int));
+#endif
+static int nextopt __P((const char *));
+static void getoptsreset __P((const char *));
+
+
+
+/*     $NetBSD: output.h,v 1.14 1998/01/31 12:37:55 christos Exp $     */
+struct output {
+#ifdef USE_GLIBC_STDIO
+       FILE *stream;
+#endif
+       char *nextc;
+       int nleft;
+       char *buf;
+       int bufsize;
+       int fd;
+       short flags;
+};
+
+extern struct output output;
+extern struct output errout;
+extern struct output memout;
+extern struct output *out1;
+extern struct output *out2;
+
+static void outstr __P((const char *, struct output *));
+#ifndef USE_GLIBC_STDIO
+static void outcslow __P((char, struct output *));
+#endif
+static void flushall __P((void));
+static void flushout __P((struct output *));
+static void freestdout __P((void));
+static void outfmt __P((struct output *, const char *, ...))
+    __attribute__((__format__(__printf__,2,3)));
+static void out1fmt __P((const char *, ...))
+    __attribute__((__format__(__printf__,1,2)));
+static void fmtstr __P((char *, size_t, const char *, ...))
+    __attribute__((__format__(__printf__,3,4)));
+#ifndef USE_GLIBC_STDIO
+static void doformat __P((struct output *, const char *, va_list));
+#endif
+static int xwrite __P((int, const char *, int));
+#ifdef USE_GLIBC_STDIO
+static void initstreams __P((void));
+static void openmemout __P((void));
+static int __closememout __P((void));
+#endif
+
+#define OUTPUT_ERR 01          /* error occurred on output */
+
+#ifdef USE_GLIBC_STDIO
+#define outc(c, o)     putc((c), (o)->stream)
+#define doformat(d, f, a)      vfprintf((d)->stream, (f), (a))
+#else
+#define outc(c, file)  (--(file)->nleft < 0? outcslow((c), (file)) : (*(file)->nextc = (c), (file)->nextc++))
+#endif
+#define out1c(c)       outc((c), out1)
+#define out2c(c)       outc((c), out2)
+#define out1str(s)     outstr((s), out1)
+#define out2str(s)     outstr((s), out2)
+#define outerr(f)      ((f)->flags & OUTPUT_ERR)
+
+
+
+/*     $NetBSD: parser.h,v 1.14 2000/07/27 04:09:28 cgd Exp $  */
+/* control characters in argument strings */
+#define CTLESC '\201'
+#define CTLVAR '\202'
+#define CTLENDVAR '\203'
+#define CTLBACKQ '\204'
+#define CTLQUOTE 01            /* ored with CTLBACKQ code if in quotes */
+/*     CTLBACKQ | CTLQUOTE == '\205' */
+#define        CTLARI  '\206'
+#define        CTLENDARI '\207'
+#define        CTLQUOTEMARK '\210'
+
+/* variable substitution byte (follows CTLVAR) */
+#define VSTYPE 0x0f            /* type of variable substitution */
+#define VSNUL  0x10            /* colon--treat the empty string as unset */
+#define VSQUOTE 0x80           /* inside double quotes--suppress splitting */
+
+/* values of VSTYPE field */
+#define VSNORMAL       0x1             /* normal variable:  $var or ${var} */
+#define VSMINUS                0x2             /* ${var-text} */
+#define VSPLUS         0x3             /* ${var+text} */
+#define VSQUESTION     0x4             /* ${var?message} */
+#define VSASSIGN       0x5             /* ${var=text} */
+#define VSTRIMLEFT     0x6             /* ${var#pattern} */
+#define VSTRIMLEFTMAX  0x7             /* ${var##pattern} */
+#define VSTRIMRIGHT    0x8             /* ${var%pattern} */
+#define VSTRIMRIGHTMAX         0x9             /* ${var%%pattern} */
+#define VSLENGTH       0xa             /* ${#var} */
+
+
+/*
+ * 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.
+ */
+extern int tokpushback;
+#define NEOF ((union node *)&tokpushback)
+extern int whichprompt;                /* 1 == PS1, 2 == PS2 */
+extern int checkalias;
+
+
+union node *parsecmd(int);
+static void fixredir(union node *, const char *, int);
+static int goodname(char *);
+static const char *getprompt(void *);
+static int isassignment __P((const char *));
+static const char *const *findkwd __P((const char *));
+
+
+/*     $NetBSD: redir.h,v 1.12 2000/05/22 10:18:47 elric Exp $ */
+/* flags passed to redirect */
+#define REDIR_PUSH 01          /* save previous values of file descriptors */
+#define REDIR_BACKQ 02         /* save the command output in memory */
+
+extern int fileno2;
+
+static void redirect __P((union node *, int));
+static void popredir __P((void));
+static int fd0_redirected_p __P((void));
+static void clearredir __P((void));
+static int dup_as_newfd __P((int, int));
+
+
+
+
+/*     $NetBSD: show.h,v 1.4 1999/10/08 21:10:44 pk Exp $      */
+#ifdef DEBUG
+static void trace __P((const char *, ...));
+static void trargs __P((char **));
+static void showtree __P((union node *));
+static void trputc __P((int));
+static void trputs __P((const char *));
+static void opentrace __P((void));
+#endif
+/*
+ * This file was generated by the mksyntax program.
+ */
+
+#ifdef CEOF
+#undef CEOF
+#endif
+
+/* Syntax classes */
+#define CWORD 0                        /* character is nothing special */
+#define CNL 1                  /* newline character */
+#define CBACK 2                        /* a backslash character */
+#define CSQUOTE 3              /* single quote */
+#define CDQUOTE 4              /* double quote */
+#define CENDQUOTE 5            /* a terminating quote */
+#define CBQUOTE 6              /* backwards single quote */
+#define CVAR 7                 /* a dollar sign */
+#define CENDVAR 8              /* a '}' character */
+#define CLP 9                  /* a left paren in arithmetic */
+#define CRP 10                 /* a right paren in arithmetic */
+#define CEOF 11                        /* end of file */
+#define CCTL 12                        /* like CWORD, except it must be escaped */
+#define CSPCL 13               /* these terminate a word */
+#define CIGN 14                        /* character should be ignored */
+
+/* Syntax classes for is_ functions */
+#define ISDIGIT 01             /* a digit */
+#define ISUPPER 02             /* an upper case letter */
+#define ISLOWER 04             /* a lower case letter */
+#define ISUNDER 010            /* an underscore */
+#define ISSPECL 020            /* the name of a special parameter */
+
+#define SYNBASE 130
+#define PEOF -130
+
+#define PEOA -129
+
+
+#define BASESYNTAX (basesyntax + SYNBASE)
+#define DQSYNTAX (dqsyntax + SYNBASE)
+#define SQSYNTAX (sqsyntax + SYNBASE)
+#define ARISYNTAX (arisyntax + SYNBASE)
+
+#define is_digit(c)    ((unsigned)((c) - '0') <= 9)
+#define is_alpha(c)    (((c) < CTLESC || (c) > CTLENDARI) && isalpha((unsigned char) (c)))
+#define is_name(c)     (((c) < CTLESC || (c) > CTLENDARI) && ((c) == '_' || isalpha((unsigned char) (c))))
+#define is_in_name(c)  (((c) < CTLESC || (c) > CTLENDARI) && ((c) == '_' || isalnum((unsigned char) (c))))
+#define is_special(c)  ((is_type+SYNBASE)[c] & (ISSPECL|ISDIGIT))
+#define digit_val(c)   ((c) - '0')
+
+extern const char basesyntax[];
+extern const char dqsyntax[];
+extern const char sqsyntax[];
+extern const char arisyntax[];
+extern const char is_type[];
+#define TEOF 0
+#define TNL 1
+#define TSEMI 2
+#define TBACKGND 3
+#define TAND 4
+#define TOR 5
+#define TPIPE 6
+#define TLP 7
+#define TRP 8
+#define TENDCASE 9
+#define TENDBQUOTE 10
+#define TREDIR 11
+#define TWORD 12
+#define TASSIGN 13
+#define TNOT 14
+#define TCASE 15
+#define TDO 16
+#define TDONE 17
+#define TELIF 18
+#define TELSE 19
+#define TESAC 20
+#define TFI 21
+#define TFOR 22
+#define TIF 23
+#define TIN 24
+#define TTHEN 25
+#define TUNTIL 26
+#define TWHILE 27
+#define TBEGIN 28
+#define TEND 29
+
+/* Array indicating which tokens mark the end of a list */
+static const char tokendlist[] = {
+       1,
+       0,
+       0,
+       0,
+       0,
+       0,
+       0,
+       0,
+       1,
+       1,
+       1,
+       0,
+       0,
+       0,
+       0,
+       0,
+       1,
+       1,
+       1,
+       1,
+       1,
+       1,
+       0,
+       0,
+       0,
+       1,
+       0,
+       0,
+       0,
+       1,
+};
+
+static const char *const tokname[] = {
+       "end of file",
+       "newline",
+       "\";\"",
+       "\"&\"",
+       "\"&&\"",
+       "\"||\"",
+       "\"|\"",
+       "\"(\"",
+       "\")\"",
+       "\";;\"",
+       "\"`\"",
+       "redirection",
+       "word",
+       "assignment",
+       "\"!\"",
+       "\"case\"",
+       "\"do\"",
+       "\"done\"",
+       "\"elif\"",
+       "\"else\"",
+       "\"esac\"",
+       "\"fi\"",
+       "\"for\"",
+       "\"if\"",
+       "\"in\"",
+       "\"then\"",
+       "\"until\"",
+       "\"while\"",
+       "\"{\"",
+       "\"}\"",
+};
+
+#define KWDOFFSET 14
+
+static const char *const parsekwd[] = {
+       "!",
+       "case",
+       "do",
+       "done",
+       "elif",
+       "else",
+       "esac",
+       "fi",
+       "for",
+       "if",
+       "in",
+       "then",
+       "until",
+       "while",
+       "{",
+       "}"
+};
+
+
+
+
+/*     $NetBSD: trap.h,v 1.14 2000/05/22 10:18:47 elric Exp $  */
+extern int pendingsigs;
+
+static int trapcmd __P((int, char **));
+static void clear_traps __P((void));
+static void setsignal __P((int));
+static void ignoresig __P((int));
+static void onsig __P((int));
+static void dotrap __P((void));
+static void setinteractive __P((int));
+static void exitshell __P((int)) __attribute__((noreturn));
+static int decode_signal __P((const char *, int));
+
+
+
+/*     $NetBSD: var.h,v 1.18 2000/05/22 10:18:47 elric Exp $   */
+
+/*
+ * Shell variables.
+ */
+
+/* flags */
+#define VEXPORT                0x01    /* variable is exported */
+#define VREADONLY      0x02    /* variable cannot be modified */
+#define VSTRFIXED      0x04    /* variable struct is staticly allocated */
+#define VTEXTFIXED     0x08    /* text is staticly 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 */
+
+
+struct var {
+       struct var *next;               /* next entry in hash list */
+       int flags;                      /* flags are defined above */
+       char *text;                     /* name=value */
+       void (*func) __P((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 */
+       char *text;                     /* saved text */
+};
+
+
+extern struct localvar *localvars;
+
+#if ATTY
+extern struct var vatty;
+#endif
+extern struct var vifs;
+extern struct var vmail;
+extern struct var vmpath;
+extern struct var vpath;
+extern struct var vps1;
+extern struct var vps2;
+#ifndef SMALL
+extern struct var vterm;
+extern struct var vtermcap;
+extern struct var vhistsize;
+#endif
+
+#ifdef IFS_BROKEN
+extern const char defifsvar[];
+#define defifs (defifsvar + 4)
+#else
+extern const char defifs[];
+#endif
+extern const char defpathvar[];
+#define defpath (defpathvar + 5)
+
+/*
+ * The following macros access the values of the above variables.
+ * They have to skip over the name.  They return the null string
+ * for unset variables.
+ */
+
+#define ifsval()       (vifs.text + 4)
+#define ifsset()       ((vifs.flags & VUNSET) == 0)
+#define mailval()      (vmail.text + 5)
+#define mpathval()     (vmpath.text + 9)
+#define pathval()      (vpath.text + 5)
+#define ps1val()       (vps1.text + 4)
+#define ps2val()       (vps2.text + 4)
+#define optindval()    (voptind.text + 7)
+#ifndef SMALL
+#define histsizeval()  (vhistsize.text + 9)
+#define termval()      (vterm.text + 5)
+#endif
+
+#if ATTY
+#define attyset()      ((vatty.flags & VUNSET) == 0)
+#endif
+#define mpathset()     ((vmpath.flags & VUNSET) == 0)
+
+static void initvar __P((void));
+static void setvar __P((const char *, const char *, int));
+static void setvareq __P((char *, int));
+struct strlist;
+static void listsetvar __P((struct strlist *));
+static char *lookupvar __P((const char *));
+static char *bltinlookup __P((const char *));
+static char **environment __P((void));
+static void shprocvar __P((void));
+static int showvarscmd __P((int, char **));
+static int exportcmd __P((int, char **));
+static int localcmd __P((int, char **));
+static void mklocal __P((char *));
+static void poplocalvars __P((void));
+static int setvarcmd __P((int, char **));
+static int unsetcmd __P((int, char **));
+static int unsetvar __P((const char *));
+static int varequal __P((const char *, const char *));
+
diff --git a/shell/ash.c b/shell/ash.c
new file mode 100644 (file)
index 0000000..bab12b9
--- /dev/null
@@ -0,0 +1,14242 @@
+/*-
+ * Copyright (c) 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#undef _GNU_SOURCE
+#undef ASH_TYPE
+#undef ASH_GETOPTS
+#undef ASH_MATH_SUPPORT
+
+#include <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <paths.h>
+#include <pwd.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/cdefs.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/times.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+
+#if !defined(FNMATCH_BROKEN)
+#include <fnmatch.h>
+#endif
+#if !defined(GLOB_BROKEN)
+#include <glob.h>
+#endif
+
+#if JOBS
+#include <termios.h>
+#undef CEOF                    /* syntax.h redefines this */
+#endif
+
+#include "cmdedit.h"
+#include "busybox.h"
+#include "ash.h"
+
+
+#define _DIAGASSERT(x)
+
+#define ATABSIZE 39
+
+#define S_DFL 1                        /* default signal handling (SIG_DFL) */
+#define S_CATCH 2              /* signal is caught */
+#define S_IGN 3                        /* signal is ignored (SIG_IGN) */
+#define S_HARD_IGN 4           /* signal is ignored permenantly */
+#define S_RESET 5              /* temporary - to reset a hard ignored sig */
+
+
+
+struct alias *atab[ATABSIZE];
+
+static void setalias __P((char *, char *));
+static struct alias **hashalias __P((const char *));
+static struct alias *freealias __P((struct alias *));
+static struct alias **__lookupalias __P((const char *));
+static char *trap[NSIG];               /* trap handler commands */
+static char sigmode[NSIG - 1]; /* current value of signal */
+static char gotsig[NSIG - 1];          /* indicates specified signal received */
+static int pendingsigs;                        /* indicates some signal received */
+
+
+static void
+setalias(name, val)
+       char *name, *val;
+{
+       struct alias *ap, **app;
+
+       app = __lookupalias(name);
+       ap = *app;
+       INTOFF;
+       if (ap) {
+               if (!(ap->flag & ALIASINUSE)) {
+                       ckfree(ap->val);
+               }
+               ap->val = savestr(val);
+               ap->flag &= ~ALIASDEAD;
+       } else {
+               /* not found */
+               ap = ckmalloc(sizeof (struct alias));
+               ap->name = savestr(name);
+               ap->val = savestr(val);
+               ap->flag = 0;
+               ap->next = 0;
+               *app = ap;
+       }
+       INTON;
+}
+
+static int
+unalias(name)
+       char *name;
+       {
+       struct alias **app;
+
+       app = __lookupalias(name);
+
+       if (*app) {
+               INTOFF;
+               *app = freealias(*app);
+               INTON;
+               return (0);
+       }
+
+       return (1);
+}
+
+#ifdef mkinit
+static void rmaliases __P((void));
+
+SHELLPROC {
+       rmaliases();
+}
+#endif
+
+static void
+rmaliases() {
+       struct alias *ap, **app;
+       int i;
+
+       INTOFF;
+       for (i = 0; i < ATABSIZE; i++) {
+               app = &atab[i];
+               for (ap = *app; ap; ap = *app) {
+                       *app = freealias(*app);
+                       if (ap == *app) {
+                               app = &ap->next;
+                       }
+               }
+       }
+       INTON;
+}
+
+struct alias *
+lookupalias(name, check)
+       const char *name;
+       int check;
+{
+       struct alias *ap = *__lookupalias(name);
+
+       if (check && ap && (ap->flag & ALIASINUSE))
+               return (NULL);
+       return (ap);
+}
+
+
+/*
+ * TODO - sort output
+ */
+static int
+aliascmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       char *n, *v;
+       int ret = 0;
+       struct alias *ap;
+
+       if (argc == 1) {
+               int i;
+
+               for (i = 0; i < ATABSIZE; i++)
+                       for (ap = atab[i]; ap; ap = ap->next) {
+                               printalias(ap);
+                       }
+               return (0);
+       }
+       while ((n = *++argv) != NULL) {
+               if ((v = strchr(n+1, '=')) == NULL) { /* n+1: funny ksh stuff */
+                       if ((ap = *__lookupalias(n)) == NULL) {
+                               outfmt(out2, "%s: %s not found\n", "alias", n);
+                               ret = 1;
+                       } else
+                               printalias(ap);
+               }
+               else {
+                       *v++ = '\0';
+                       setalias(n, v);
+               }
+       }
+
+       return (ret);
+}
+
+static int
+unaliascmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       int i;
+
+       while ((i = nextopt("a")) != '\0') {
+               if (i == 'a') {
+                       rmaliases();
+                       return (0);
+               }
+       }
+       for (i = 0; *argptr; argptr++) {
+               if (unalias(*argptr)) {
+                       outfmt(out2, "%s: %s not found\n", "unalias", *argptr);
+                       i = 1;
+               }
+       }
+
+       return (i);
+}
+
+static struct alias **
+hashalias(p)
+       const char *p;
+       {
+       unsigned int hashval;
+
+       hashval = *p << 4;
+       while (*p)
+               hashval+= *p++;
+       return &atab[hashval % ATABSIZE];
+}
+
+static struct alias *
+freealias(struct alias *ap) {
+       struct alias *next;
+
+       if (ap->flag & ALIASINUSE) {
+               ap->flag |= ALIASDEAD;
+               return ap;
+       }
+
+       next = ap->next;
+       ckfree(ap->name);
+       ckfree(ap->val);
+       ckfree(ap);
+       return next;
+}
+
+static void
+printalias(const struct alias *ap) {
+       char *p;
+
+       p = single_quote(ap->val);
+       out1fmt("alias %s=%s\n", ap->name, p);
+       stunalloc(p);
+}
+
+static struct alias **
+__lookupalias(const char *name) {
+       struct alias **app = hashalias(name);
+
+       for (; *app; app = &(*app)->next) {
+               if (equal(name, (*app)->name)) {
+                       break;
+               }
+       }
+
+       return app;
+}
+
+#ifdef ASH_MATH_SUPPORT
+/* The generated file arith.c has been snipped.  If you want this
+ * stuff back in, feel free to add it to your own copy.  */
+#endif
+
+/*
+ * This file was generated by the mkbuiltins program.
+ */
+
+static int bgcmd __P((int, char **));
+static int breakcmd __P((int, char **));
+static int cdcmd __P((int, char **));
+static int commandcmd __P((int, char **));
+static int dotcmd __P((int, char **));
+static int evalcmd __P((int, char **));
+static int execcmd __P((int, char **));
+static int exitcmd __P((int, char **));
+static int exportcmd __P((int, char **));
+static int histcmd __P((int, char **));
+static int fgcmd __P((int, char **));
+static int hashcmd __P((int, char **));
+static int jobscmd __P((int, char **));
+static int killcmd __P((int, char **));
+static int localcmd __P((int, char **));
+static int pwdcmd __P((int, char **));
+static int readcmd __P((int, char **));
+static int returncmd __P((int, char **));
+static int setcmd __P((int, char **));
+static int setvarcmd __P((int, char **));
+static int shiftcmd __P((int, char **));
+static int trapcmd __P((int, char **));
+static int umaskcmd __P((int, char **));
+static int unaliascmd __P((int, char **));
+static int unsetcmd __P((int, char **));
+static int waitcmd __P((int, char **));
+static int aliascmd __P((int, char **));
+static int ulimitcmd __P((int, char **));
+static int timescmd __P((int, char **));
+#ifdef ASH_MATH_SUPPORT
+static int expcmd __P((int, char **));
+#endif
+#ifdef ASH_TYPE
+static int typecmd __P((int, char **));
+#endif
+#ifdef ASH_GETOPTS
+static int getoptscmd __P((int, char **));
+#endif
+#ifndef BB_TRUE_FALSE
+static int true_main __P((int, char **));
+static int false_main __P((int, char **));
+#endif
+
+static struct builtincmd *DOTCMD;
+static struct builtincmd *BLTINCMD;
+static struct builtincmd *COMMANDCMD;
+static struct builtincmd *EXECCMD;
+static struct builtincmd *EVALCMD;
+
+/* It is CRUCIAL that this listing be kept in ascii order, otherwise
+ * the binary search in find_builtin() will stop working. If you value
+ * your kneecaps, you'll be sure to *make sure* that any changes made
+ * to this array result in the listing remaining in ascii order. You
+ * have been warned.
+ */
+static const struct builtincmd builtincmds[] = {
+       { ".", dotcmd, 1 },
+       { ":", true_main, 1 },
+       { "alias", aliascmd, 6 },
+       { "bg", bgcmd, 2 },
+       { "break", breakcmd, 1 },
+       { "builtin", bltincmd, 1 },
+       { "cd", cdcmd, 2 },
+       { "chdir", cdcmd, 0 },
+       { "command", commandcmd, 2 },
+       { "continue", breakcmd, 1 },
+       { "eval", evalcmd, 1 },
+       { "exec", execcmd, 1 },
+       { "exit", exitcmd, 1 },
+#ifdef ASH_MATH_SUPPORT
+       { "exp", expcmd, 0 },
+#endif
+       { "export", exportcmd, 5 },
+       { "false", false_main, 2 },
+       { "fc", histcmd, 2 },
+       { "fg", fgcmd, 2 },
+#ifdef ASH_GETOPTS
+       { "getopts", getoptscmd, 2 },
+#endif 
+       { "hash", hashcmd, 0 },
+       { "jobs", jobscmd, 2 },
+       { "kill", killcmd, 2 },
+#ifdef ASH_MATH_SUPPORT
+       { "let", expcmd, 0 },
+#endif
+       { "local", localcmd, 4 },
+       { "pwd", pwdcmd, 0 },
+       { "read", readcmd, 2 },
+       { "readonly", exportcmd, 5 },
+       { "return", returncmd, 1 },
+       { "set", setcmd, 1 },
+       { "setvar", setvarcmd, 0 },
+       { "shift", shiftcmd, 1 },
+       { "times", timescmd, 1 },
+       { "trap", trapcmd, 1 },
+       { "true", true_main, 2 },
+#ifdef ASH_TYPE
+       { "type", typecmd, 0 },
+#endif
+       { "ulimit", ulimitcmd, 0 },
+       { "umask", umaskcmd, 2 },
+       { "unalias", unaliascmd, 2 },
+       { "unset", unsetcmd, 1 },
+       { "wait", waitcmd, 2 },
+};
+#define NUMBUILTINS  (sizeof (builtincmds) / sizeof (struct builtincmd) )
+
+
+/*     $NetBSD: cd.c,v 1.27 1999/07/09 03:05:49 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+static int docd __P((char *, int));
+static char *getcomponent __P((void));
+static void updatepwd __P((char *));
+static void getpwd __P((void));
+
+static char *curdir = nullstr;         /* current working directory */
+static char *cdcomppath;
+
+#ifdef mkinit
+INCLUDE "cd.h"
+INIT {
+       setpwd(0, 0);
+}
+#endif
+
+static int
+cdcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       const char *dest;
+       const char *path;
+       char *p;
+       struct stat statb;
+       int print = 0;
+
+       nextopt(nullstr);
+       if ((dest = *argptr) == NULL && (dest = bltinlookup("HOME")) == NULL)
+               error("HOME not set");
+       if (*dest == '\0')
+               dest = ".";
+       if (dest[0] == '-' && dest[1] == '\0') {
+               dest = bltinlookup("OLDPWD");
+               if (!dest || !*dest) {
+                       dest = curdir;
+               }
+               print = 1;
+               if (dest)
+                       print = 1;
+               else
+                       dest = ".";
+       }
+       if (*dest == '/' || (path = bltinlookup("CDPATH")) == NULL)
+               path = nullstr;
+       while ((p = padvance(&path, dest)) != NULL) {
+               if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
+                       if (!print) {
+                               /*
+                                * XXX - rethink
+                                */
+                               if (p[0] == '.' && p[1] == '/' && p[2] != '\0')
+                                       p += 2;
+                               print = strcmp(p, dest);
+                       }
+                       if (docd(p, print) >= 0)
+                               return 0;
+
+               }
+       }
+       error("can't cd to %s", dest);
+       /* NOTREACHED */
+}
+
+
+/*
+ * Actually do the chdir.  In an interactive shell, print the
+ * directory name if "print" is nonzero.
+ */
+
+static int
+docd(dest, print)
+       char *dest;
+       int print;
+{
+       char *p;
+       char *q;
+       char *component;
+       struct stat statb;
+       int first;
+       int badstat;
+
+       TRACE(("docd(\"%s\", %d) called\n", dest, print));
+
+       /*
+        *  Check each component of the path. If we find a symlink or
+        *  something we can't stat, clear curdir to force a getcwd()
+        *  next time we get the value of the current directory.
+        */
+       badstat = 0;
+       cdcomppath = sstrdup(dest);
+       STARTSTACKSTR(p);
+       if (*dest == '/') {
+               STPUTC('/', p);
+               cdcomppath++;
+       }
+       first = 1;
+       while ((q = getcomponent()) != NULL) {
+               if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0'))
+                       continue;
+               if (! first)
+                       STPUTC('/', p);
+               first = 0;
+               component = q;
+               while (*q)
+                       STPUTC(*q++, p);
+               if (equal(component, ".."))
+                       continue;
+               STACKSTRNUL(p);
+               if ((lstat(stackblock(), &statb) < 0)
+                   || (S_ISLNK(statb.st_mode)))  {
+                       /* print = 1; */
+                       badstat = 1;
+                       break;
+               }
+       }
+
+       INTOFF;
+       if (chdir(dest) < 0) {
+               INTON;
+               return -1;
+       }
+       updatepwd(badstat ? NULL : dest);
+       INTON;
+       if (print && iflag)
+               out1fmt(snlfmt, curdir);
+       return 0;
+}
+
+
+/*
+ * Get the next component of the path name pointed to by cdcomppath.
+ * This routine overwrites the string pointed to by cdcomppath.
+ */
+
+static char *
+getcomponent() {
+       char *p;
+       char *start;
+
+       if ((p = cdcomppath) == NULL)
+               return NULL;
+       start = cdcomppath;
+       while (*p != '/' && *p != '\0')
+               p++;
+       if (*p == '\0') {
+               cdcomppath = NULL;
+       } else {
+               *p++ = '\0';
+               cdcomppath = p;
+       }
+       return start;
+}
+
+
+
+/*
+ * Update curdir (the name of the current directory) in response to a
+ * cd command.  We also call hashcd to let the routines in exec.c know
+ * that the current directory has changed.
+ */
+
+static void
+updatepwd(dir)
+       char *dir;
+       {
+       char *new;
+       char *p;
+       size_t len;
+
+       hashcd();                               /* update command hash table */
+
+       /*
+        * If our argument is NULL, we don't know the current directory
+        * any more because we traversed a symbolic link or something
+        * we couldn't stat().
+        */
+       if (dir == NULL || curdir == nullstr)  {
+               setpwd(0, 1);
+               return;
+       }
+       len = strlen(dir);
+       cdcomppath = sstrdup(dir);
+       STARTSTACKSTR(new);
+       if (*dir != '/') {
+               p = curdir;
+               while (*p)
+                       STPUTC(*p++, new);
+               if (p[-1] == '/')
+                       STUNPUTC(new);
+       }
+       while ((p = getcomponent()) != NULL) {
+               if (equal(p, "..")) {
+                       while (new > stackblock() && (STUNPUTC(new), *new) != '/');
+               } else if (*p != '\0' && ! equal(p, ".")) {
+                       STPUTC('/', new);
+                       while (*p)
+                               STPUTC(*p++, new);
+               }
+       }
+       if (new == stackblock())
+               STPUTC('/', new);
+       STACKSTRNUL(new);
+       setpwd(stackblock(), 1);
+}
+
+
+
+static int
+pwdcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       out1fmt(snlfmt, curdir);
+       return 0;
+}
+
+
+
+
+#define MAXPWD 256
+
+/*
+ * Find out what the current directory is. If we already know the current
+ * directory, this routine returns immediately.
+ */
+static void
+getpwd()
+{
+       char buf[MAXPWD];
+
+       /*
+        * Things are a bit complicated here; we could have just used
+        * getcwd, but traditionally getcwd is implemented using popen
+        * to /bin/pwd. This creates a problem for us, since we cannot
+        * keep track of the job if it is being ran behind our backs.
+        * So we re-implement getcwd(), and we suppress interrupts
+        * throughout the process. This is not completely safe, since
+        * the user can still break out of it by killing the pwd program.
+        * We still try to use getcwd for systems that we know have a
+        * c implementation of getcwd, that does not open a pipe to
+        * /bin/pwd.
+        */
+#if defined(__NetBSD__) || defined(__SVR4) || defined(__GLIBC__)
+               
+       if (getcwd(buf, sizeof(buf)) == NULL) {
+               char *pwd = getenv("PWD");
+               struct stat stdot, stpwd;
+
+               if (pwd && *pwd == '/' && stat(".", &stdot) != -1 &&
+                   stat(pwd, &stpwd) != -1 &&
+                   stdot.st_dev == stpwd.st_dev &&
+                   stdot.st_ino == stpwd.st_ino) {
+                       curdir = savestr(pwd);
+                       return;
+               }
+               error("getcwd() failed: %s", strerror(errno));
+       }
+       curdir = savestr(buf);
+#else
+       {
+               char *p;
+               int i;
+               int status;
+               struct job *jp;
+               int pip[2];
+
+               if (pipe(pip) < 0)
+                       error("Pipe call failed");
+               jp = makejob((union node *)NULL, 1);
+               if (forkshell(jp, (union node *)NULL, FORK_NOJOB) == 0) {
+                       (void) close(pip[0]);
+                       if (pip[1] != 1) {
+                               close(1);
+                               dup_as_newfd(pip[1], 1);
+                               close(pip[1]);
+                       }
+                       (void) execl("/bin/pwd", "pwd", (char *)0);
+                       error("Cannot exec /bin/pwd");
+               }
+               (void) close(pip[1]);
+               pip[1] = -1;
+               p = buf;
+               while ((i = read(pip[0], p, buf + MAXPWD - p)) > 0
+                    || (i == -1 && errno == EINTR)) {
+                       if (i > 0)
+                               p += i;
+               }
+               (void) close(pip[0]);
+               pip[0] = -1;
+               status = waitforjob(jp);
+               if (status != 0)
+                       error((char *)0);
+               if (i < 0 || p == buf || p[-1] != '\n')
+                       error("pwd command failed");
+               p[-1] = '\0';
+       }
+       curdir = savestr(buf);
+#endif
+}
+
+static void
+setpwd(const char *val, int setold)
+{
+       if (setold) {
+               setvar("OLDPWD", curdir, VEXPORT);
+       }
+       INTOFF;
+       if (curdir != nullstr) {
+               free(curdir);
+               curdir = nullstr;
+       }
+       if (!val) {
+               getpwd();
+       } else {
+               curdir = savestr(val);
+       }
+       INTON;
+       setvar("PWD", curdir, VEXPORT);
+}
+
+/*     $NetBSD: error.c,v 1.23 2000/07/03 03:26:19 matt Exp $  */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Errors and exceptions.
+ */
+
+/*
+ * Code to handle exceptions in C.
+ */
+
+struct jmploc *handler;
+static int exception;
+volatile int suppressint;
+volatile int intpending;
+
+
+static void exverror __P((int, const char *, va_list))
+    __attribute__((__noreturn__));
+
+/*
+ * Called to raise an exception.  Since C doesn't include exceptions, we
+ * just do a longjmp to the exception handler.  The type of exception is
+ * stored in the global variable "exception".
+ */
+
+static void
+exraise(e)
+       int e;
+{
+#ifdef DEBUG
+       if (handler == NULL)
+               abort();
+#endif
+       exception = e;
+       longjmp(handler->loc, 1);
+}
+
+
+/*
+ * Called from trap.c when a SIGINT is received.  (If the user specifies
+ * that SIGINT is to be trapped or ignored using the trap builtin, then
+ * this routine is not called.)  Suppressint is nonzero when interrupts
+ * are held using the INTOFF macro.  The call to _exit is necessary because
+ * there is a short period after a fork before the signal handlers are
+ * set to the appropriate value for the child.  (The test for iflag is
+ * just defensive programming.)
+ */
+
+static void
+onint() {
+       sigset_t mysigset;
+
+       if (suppressint) {
+               intpending++;
+               return;
+       }
+       intpending = 0;
+       sigemptyset(&mysigset);
+       sigprocmask(SIG_SETMASK, &mysigset, NULL);
+       if (rootshell && iflag)
+               exraise(EXINT);
+       else {
+               signal(SIGINT, SIG_DFL);
+               raise(SIGINT);
+       }
+       /* NOTREACHED */
+}
+
+
+/*
+ * Exverror is called to raise the error exception.  If the first argument
+ * is not NULL then error prints an error message using printf style
+ * formatting.  It then raises the error exception.
+ */
+static void
+exverror(cond, msg, ap)
+       int cond;
+       const char *msg;
+       va_list ap;
+{
+       CLEAR_PENDING_INT;
+       INTOFF;
+
+#ifdef DEBUG
+       if (msg)
+               TRACE(("exverror(%d, \"%s\") pid=%d\n", cond, msg, getpid()));
+       else
+               TRACE(("exverror(%d, NULL) pid=%d\n", cond, getpid()));
+#endif
+       if (msg) {
+               if (commandname)
+                       outfmt(&errout, "%s: ", commandname);
+               doformat(&errout, msg, ap);
+#if FLUSHERR
+               outc('\n', &errout);
+#else
+               outcslow('\n', &errout);
+#endif
+       }
+       flushall();
+       exraise(cond);
+       /* NOTREACHED */
+}
+
+
+#ifdef __STDC__
+static void
+error(const char *msg, ...)
+#else
+static void
+error(va_alist)
+       va_dcl
+#endif
+{
+#ifndef __STDC__
+       const char *msg;
+#endif
+       va_list ap;
+#ifdef __STDC__
+       va_start(ap, msg);
+#else
+       va_start(ap);
+       msg = va_arg(ap, const char *);
+#endif
+       exverror(EXERROR, msg, ap);
+       /* NOTREACHED */
+       va_end(ap);
+}
+
+
+#ifdef __STDC__
+static void
+exerror(int cond, const char *msg, ...)
+#else
+static void
+exerror(va_alist)
+       va_dcl
+#endif
+{
+#ifndef __STDC__
+       int cond;
+       const char *msg;
+#endif
+       va_list ap;
+#ifdef __STDC__
+       va_start(ap, msg);
+#else
+       va_start(ap);
+       cond = va_arg(ap, int);
+       msg = va_arg(ap, const char *);
+#endif
+       exverror(cond, msg, ap);
+       /* NOTREACHED */
+       va_end(ap);
+}
+
+
+
+/*
+ * Table of error messages.
+ */
+
+struct errname {
+       short errcode;          /* error number */
+       short action;           /* operation which encountered the error */
+       const char *msg;        /* text describing the error */
+};
+
+
+#define ALL (E_OPEN|E_CREAT|E_EXEC)
+
+static const struct errname errormsg[] = {
+       { EINTR,        ALL,    "interrupted" },
+       { EACCES,       ALL,    "permission denied" },
+       { EIO,          ALL,    "I/O error" },
+       { ENOENT,       E_OPEN, "no such file" },
+       { ENOENT,       E_CREAT,"directory nonexistent" },
+       { ENOENT,       E_EXEC, "not found" },
+       { ENOTDIR,      E_OPEN, "no such file" },
+       { ENOTDIR,      E_CREAT,"directory nonexistent" },
+       { ENOTDIR,      E_EXEC, "not found" },
+       { EISDIR,       ALL,    "is a directory" },
+       { EEXIST,       E_CREAT,"file exists" },
+#ifdef notdef
+       { EMFILE,       ALL,    "too many open files" },
+#endif
+       { ENFILE,       ALL,    "file table overflow" },
+       { ENOSPC,       ALL,    "file system full" },
+#ifdef EDQUOT
+       { EDQUOT,       ALL,    "disk quota exceeded" },
+#endif
+#ifdef ENOSR
+       { ENOSR,        ALL,    "no streams resources" },
+#endif
+       { ENXIO,        ALL,    "no such device or address" },
+       { EROFS,        ALL,    "read-only file system" },
+       { ETXTBSY,      ALL,    "text busy" },
+#ifdef SYSV
+       { EAGAIN,       E_EXEC, "not enough memory" },
+#endif
+       { ENOMEM,       ALL,    "not enough memory" },
+#ifdef ENOLINK
+       { ENOLINK,      ALL,    "remote access failed" },
+#endif
+#ifdef EMULTIHOP
+       { EMULTIHOP,    ALL,    "remote access failed" },
+#endif
+#ifdef ECOMM
+       { ECOMM,        ALL,    "remote access failed" },
+#endif
+#ifdef ESTALE
+       { ESTALE,       ALL,    "remote access failed" },
+#endif
+#ifdef ETIMEDOUT
+       { ETIMEDOUT,    ALL,    "remote access failed" },
+#endif
+#ifdef ELOOP
+       { ELOOP,        ALL,    "symbolic link loop" },
+#endif
+       { E2BIG,        E_EXEC, "argument list too long" },
+#ifdef ELIBACC
+       { ELIBACC,      E_EXEC, "shared library missing" },
+#endif
+       { 0,            0,      NULL },
+};
+
+
+/*
+ * 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(e, action)
+       int e;
+       int action;
+{
+       struct errname const *ep;
+       static char buf[12];
+
+       for (ep = errormsg ; ep->errcode ; ep++) {
+               if (ep->errcode == e && (ep->action & action) != 0)
+                       return ep->msg;
+       }
+       fmtstr(buf, sizeof buf, "error %d", e);
+       return buf;
+}
+
+
+#ifdef REALLY_SMALL
+static void
+__inton() {
+       if (--suppressint == 0 && intpending) {
+               onint();
+       }
+}
+#endif
+/*     $NetBSD: eval.c,v 1.57 2001/02/04 19:52:06 christos Exp $       */
+
+/*-
+ * Copyright (c) 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+
+/* flags in argument to evaltree */
+#define EV_EXIT 01             /* exit after evaluating tree */
+#define EV_TESTED 02           /* exit status is checked; ignore -e flag */
+#define EV_BACKCMD 04          /* command executing within back quotes */
+
+static int evalskip;                   /* set if we are skipping commands */
+static int skipcount;          /* number of levels to skip */
+static int loopnest;           /* current loop nesting level */
+static int funcnest;                   /* depth of function calls */
+
+
+static char *commandname;
+struct strlist *cmdenviron;
+static int exitstatus;                 /* exit status of last command */
+static int oexitstatus;                /* saved exit status */
+
+
+static void evalloop __P((union node *, int));
+static void evalfor __P((union node *, int));
+static void evalcase __P((union node *, int));
+static void evalsubshell __P((union node *, int));
+static void expredir __P((union node *));
+static void evalpipe __P((union node *));
+#ifdef notyet
+static void evalcommand __P((union node *, int, struct backcmd *));
+#else
+static void evalcommand __P((union node *, int));
+#endif
+static void prehash __P((union node *));
+static void eprintlist __P((struct strlist *));
+
+
+/*
+ * Called to reset things after an exception.
+ */
+
+#ifdef mkinit
+INCLUDE "eval.h"
+
+RESET {
+       evalskip = 0;
+       loopnest = 0;
+       funcnest = 0;
+}
+
+SHELLPROC {
+       exitstatus = 0;
+}
+#endif
+
+
+
+/*
+ * The eval commmand.
+ */
+
+static int
+evalcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+        char *p;
+        char *concat;
+        char **ap;
+
+        if (argc > 1) {
+                p = argv[1];
+                if (argc > 2) {
+                        STARTSTACKSTR(concat);
+                        ap = argv + 2;
+                        for (;;) {
+                                while (*p)
+                                        STPUTC(*p++, concat);
+                                if ((p = *ap++) == NULL)
+                                        break;
+                                STPUTC(' ', concat);
+                        }
+                        STPUTC('\0', concat);
+                        p = grabstackstr(concat);
+                }
+                evalstring(p, EV_TESTED);
+        }
+        return exitstatus;
+}
+
+
+/*
+ * Execute a command or commands contained in a string.
+ */
+
+static void
+evalstring(s, flag)
+       char *s;
+       int flag;
+       {
+       union node *n;
+       struct stackmark smark;
+
+       setstackmark(&smark);
+       setinputstring(s);
+       while ((n = parsecmd(0)) != NEOF) {
+               evaltree(n, flag);
+               popstackmark(&smark);
+       }
+       popfile();
+       popstackmark(&smark);
+}
+
+
+
+/*
+ * Evaluate a parse tree.  The value is left in the global variable
+ * exitstatus.
+ */
+
+static void
+evaltree(n, flags)
+       union node *n;
+       int flags;
+{
+       int checkexit = 0;
+       if (n == NULL) {
+               TRACE(("evaltree(NULL) called\n"));
+               goto out;
+       }
+       TRACE(("evaltree(0x%lx: %d) called\n", (long)n, n->type));
+       switch (n->type) {
+       case NSEMI:
+               evaltree(n->nbinary.ch1, flags & EV_TESTED);
+               if (evalskip)
+                       goto out;
+               evaltree(n->nbinary.ch2, flags);
+               break;
+       case NAND:
+               evaltree(n->nbinary.ch1, EV_TESTED);
+               if (evalskip || exitstatus != 0)
+                       goto out;
+               evaltree(n->nbinary.ch2, flags);
+               break;
+       case NOR:
+               evaltree(n->nbinary.ch1, EV_TESTED);
+               if (evalskip || exitstatus == 0)
+                       goto out;
+               evaltree(n->nbinary.ch2, flags);
+               break;
+       case NREDIR:
+               expredir(n->nredir.redirect);
+               redirect(n->nredir.redirect, REDIR_PUSH);
+               evaltree(n->nredir.n, flags);
+               popredir();
+               break;
+       case NSUBSHELL:
+               evalsubshell(n, flags);
+               break;
+       case NBACKGND:
+               evalsubshell(n, flags);
+               break;
+       case NIF: {
+               evaltree(n->nif.test, EV_TESTED);
+               if (evalskip)
+                       goto out;
+               if (exitstatus == 0)
+                       evaltree(n->nif.ifpart, flags);
+               else if (n->nif.elsepart)
+                       evaltree(n->nif.elsepart, flags);
+               else
+                       exitstatus = 0;
+               break;
+       }
+       case NWHILE:
+       case NUNTIL:
+               evalloop(n, flags);
+               break;
+       case NFOR:
+               evalfor(n, flags);
+               break;
+       case NCASE:
+               evalcase(n, flags);
+               break;
+       case NDEFUN: {
+               struct builtincmd *bcmd;
+               if (
+                       (bcmd = find_builtin(n->narg.text)) &&
+                       bcmd->flags & BUILTIN_SPECIAL
+               ) {
+                       outfmt(out2, "%s is a special built-in\n", n->narg.text);
+                       exitstatus = 1;
+                       break;
+               }
+               defun(n->narg.text, n->narg.next);
+               exitstatus = 0;
+               break;
+       }
+       case NNOT:
+               evaltree(n->nnot.com, EV_TESTED);
+               exitstatus = !exitstatus;
+               break;
+
+       case NPIPE:
+               evalpipe(n);
+               checkexit = 1;
+               break;
+       case NCMD:
+#ifdef notyet
+               evalcommand(n, flags, (struct backcmd *)NULL);
+#else
+               evalcommand(n, flags);
+#endif
+               checkexit = 1;
+               break;
+#ifdef DEBUG
+       default:
+               out1fmt("Node type = %d\n", n->type);
+#ifndef USE_GLIBC_STDIO
+               flushout(out1);
+#endif
+               break;
+#endif
+       }
+out:
+       if (pendingsigs)
+               dotrap();
+       if (
+               flags & EV_EXIT ||
+               (checkexit && eflag && exitstatus && !(flags & EV_TESTED))
+       )
+               exitshell(exitstatus);
+}
+
+
+static void
+evalloop(n, flags)
+       union node *n;
+       int flags;
+{
+       int status;
+
+       loopnest++;
+       status = 0;
+       for (;;) {
+               evaltree(n->nbinary.ch1, EV_TESTED);
+               if (evalskip) {
+skipping:        if (evalskip == SKIPCONT && --skipcount <= 0) {
+                               evalskip = 0;
+                               continue;
+                       }
+                       if (evalskip == SKIPBREAK && --skipcount <= 0)
+                               evalskip = 0;
+                       break;
+               }
+               if (n->type == NWHILE) {
+                       if (exitstatus != 0)
+                               break;
+               } else {
+                       if (exitstatus == 0)
+                               break;
+               }
+               evaltree(n->nbinary.ch2, flags & EV_TESTED);
+               status = exitstatus;
+               if (evalskip)
+                       goto skipping;
+       }
+       loopnest--;
+       exitstatus = status;
+}
+
+
+
+static void
+evalfor(n, flags)
+    union node *n;
+    int flags;
+{
+       struct arglist arglist;
+       union node *argp;
+       struct strlist *sp;
+       struct stackmark smark;
+
+       setstackmark(&smark);
+       arglist.lastp = &arglist.list;
+       for (argp = n->nfor.args ; argp ; argp = argp->narg.next) {
+               oexitstatus = exitstatus;
+               expandarg(argp, &arglist, EXP_FULL | EXP_TILDE | EXP_RECORD);
+               if (evalskip)
+                       goto out;
+       }
+       *arglist.lastp = NULL;
+
+       exitstatus = 0;
+       loopnest++;
+       for (sp = arglist.list ; sp ; sp = sp->next) {
+               setvar(n->nfor.var, sp->text, 0);
+               evaltree(n->nfor.body, flags & EV_TESTED);
+               if (evalskip) {
+                       if (evalskip == SKIPCONT && --skipcount <= 0) {
+                               evalskip = 0;
+                               continue;
+                       }
+                       if (evalskip == SKIPBREAK && --skipcount <= 0)
+                               evalskip = 0;
+                       break;
+               }
+       }
+       loopnest--;
+out:
+       popstackmark(&smark);
+}
+
+
+
+static void
+evalcase(n, flags)
+       union node *n;
+       int flags;
+{
+       union node *cp;
+       union node *patp;
+       struct arglist arglist;
+       struct stackmark smark;
+
+       setstackmark(&smark);
+       arglist.lastp = &arglist.list;
+       oexitstatus = exitstatus;
+       expandarg(n->ncase.expr, &arglist, EXP_TILDE);
+       for (cp = n->ncase.cases ; cp && evalskip == 0 ; cp = cp->nclist.next) {
+               for (patp = cp->nclist.pattern ; patp ; patp = patp->narg.next) {
+                       if (casematch(patp, arglist.list->text)) {
+                               if (evalskip == 0) {
+                                       evaltree(cp->nclist.body, flags);
+                               }
+                               goto out;
+                       }
+               }
+       }
+out:
+       popstackmark(&smark);
+}
+
+
+
+/*
+ * Kick off a subshell to evaluate a tree.
+ */
+
+static void
+evalsubshell(n, flags)
+       union node *n;
+       int flags;
+{
+       struct job *jp;
+       int backgnd = (n->type == NBACKGND);
+
+       expredir(n->nredir.redirect);
+       jp = makejob(n, 1);
+       if (forkshell(jp, n, backgnd) == 0) {
+               if (backgnd)
+                       flags &=~ EV_TESTED;
+               redirect(n->nredir.redirect, 0);
+               evaltree(n->nredir.n, flags | EV_EXIT); /* never returns */
+       }
+       if (! backgnd) {
+               INTOFF;
+               exitstatus = waitforjob(jp);
+               INTON;
+       }
+}
+
+
+
+/*
+ * Compute the names of the files in a redirection list.
+ */
+
+static void
+expredir(n)
+       union node *n;
+{
+       union node *redir;
+
+       for (redir = n ; redir ; redir = redir->nfile.next) {
+               struct arglist fn;
+               fn.lastp = &fn.list;
+               oexitstatus = exitstatus;
+               switch (redir->type) {
+               case NFROMTO:
+               case NFROM:
+               case NTO:
+               case NAPPEND:
+               case NTOOV:
+                       expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR);
+                       redir->nfile.expfname = fn.list->text;
+                       break;
+               case NFROMFD:
+               case NTOFD:
+                       if (redir->ndup.vname) {
+                               expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE);
+                               fixredir(redir, fn.list->text, 1);
+                       }
+                       break;
+               }
+       }
+}
+
+
+
+/*
+ * Evaluate a pipeline.  All the processes in the pipeline are children
+ * of the process creating the pipeline.  (This differs from some versions
+ * of the shell, which make the last process in a pipeline the parent
+ * of all the rest.)
+ */
+
+static void
+evalpipe(n)
+       union node *n;
+{
+       struct job *jp;
+       struct nodelist *lp;
+       int pipelen;
+       int prevfd;
+       int pip[2];
+
+       TRACE(("evalpipe(0x%lx) called\n", (long)n));
+       pipelen = 0;
+       for (lp = n->npipe.cmdlist ; lp ; lp = lp->next)
+               pipelen++;
+       INTOFF;
+       jp = makejob(n, pipelen);
+       prevfd = -1;
+       for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
+               prehash(lp->n);
+               pip[1] = -1;
+               if (lp->next) {
+                       if (pipe(pip) < 0) {
+                               close(prevfd);
+                               error("Pipe call failed");
+                       }
+               }
+               if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) {
+                       INTON;
+                       if (prevfd > 0) {
+                               close(0);
+                               dup_as_newfd(prevfd, 0);
+                               close(prevfd);
+                               if (pip[0] == 0) {
+                                       pip[0] = -1;
+                               }
+                       }
+                       if (pip[1] >= 0) {
+                               if (pip[0] >= 0) {
+                                       close(pip[0]);
+                               }
+                               if (pip[1] != 1) {
+                                       close(1);
+                                       dup_as_newfd(pip[1], 1);
+                                       close(pip[1]);
+                               }
+                       }
+                       evaltree(lp->n, EV_EXIT);
+               }
+               if (prevfd >= 0)
+                       close(prevfd);
+               prevfd = pip[0];
+               close(pip[1]);
+       }
+       INTON;
+       if (n->npipe.backgnd == 0) {
+               INTOFF;
+               exitstatus = waitforjob(jp);
+               TRACE(("evalpipe:  job done exit status %d\n", exitstatus));
+               INTON;
+       }
+}
+
+
+
+/*
+ * Execute a command inside back quotes.  If it's a builtin command, we
+ * want to save its output in a block obtained from malloc.  Otherwise
+ * we fork off a subprocess and get the output of the command via a pipe.
+ * Should be called with interrupts off.
+ */
+
+static void
+evalbackcmd(n, result)
+       union node *n;
+       struct backcmd *result;
+{
+       int pip[2];
+       struct job *jp;
+       struct stackmark smark;         /* unnecessary */
+
+       setstackmark(&smark);
+       result->fd = -1;
+       result->buf = NULL;
+       result->nleft = 0;
+       result->jp = NULL;
+       if (n == NULL) {
+               exitstatus = 0;
+               goto out;
+       }
+#ifdef notyet
+       /*
+        * For now we disable executing builtins in the same
+        * context as the shell, because we are not keeping
+        * enough state to recover from changes that are
+        * supposed only to affect subshells. eg. echo "`cd /`"
+        */
+       if (n->type == NCMD) {
+               exitstatus = oexitstatus;
+               evalcommand(n, EV_BACKCMD, result);
+       } else
+#endif
+       {
+               exitstatus = 0;
+               if (pipe(pip) < 0)
+                       error("Pipe call failed");
+               jp = makejob(n, 1);
+               if (forkshell(jp, n, FORK_NOJOB) == 0) {
+                       FORCEINTON;
+                       close(pip[0]);
+                       if (pip[1] != 1) {
+                               close(1);
+                               dup_as_newfd(pip[1], 1);
+                               close(pip[1]);
+                       }
+                       eflag = 0;
+                       evaltree(n, EV_EXIT);
+               }
+               close(pip[1]);
+               result->fd = pip[0];
+               result->jp = jp;
+       }
+out:
+       popstackmark(&smark);
+       TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n",
+               result->fd, result->buf, result->nleft, result->jp));
+}
+
+
+
+/*
+ * Execute a simple command.
+ */
+
+static void
+#ifdef notyet
+evalcommand(cmd, flags, backcmd)
+       union node *cmd;
+       int flags;
+       struct backcmd *backcmd;
+#else
+evalcommand(cmd, flags)
+       union node *cmd;
+       int flags;
+#endif
+{
+       struct stackmark smark;
+       union node *argp;
+       struct arglist arglist;
+       struct arglist varlist;
+       char **argv;
+       int argc;
+       char **envp;
+       struct strlist *sp;
+       int mode;
+#ifdef notyet
+       int pip[2];
+#endif
+       struct cmdentry cmdentry;
+       struct job *jp;
+       char *volatile savecmdname;
+       volatile struct shparam saveparam;
+       struct localvar *volatile savelocalvars;
+       volatile int e;
+       char *lastarg;
+       const char *path;
+       const struct builtincmd *firstbltin;
+       struct jmploc *volatile savehandler;
+       struct jmploc jmploc;
+#if __GNUC__
+       /* Avoid longjmp clobbering */
+       (void) &argv;
+       (void) &argc;
+       (void) &lastarg;
+       (void) &flags;
+#endif
+
+       /* First expand the arguments. */
+       TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
+       setstackmark(&smark);
+       arglist.lastp = &arglist.list;
+       varlist.lastp = &varlist.list;
+       arglist.list = 0;
+       oexitstatus = exitstatus;
+       exitstatus = 0;
+       path = pathval();
+       for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) {
+               expandarg(argp, &varlist, EXP_VARTILDE);
+       }
+       for (
+               argp = cmd->ncmd.args; argp && !arglist.list;
+               argp = argp->narg.next
+       ) {
+               expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
+       }
+       if (argp) {
+               struct builtincmd *bcmd;
+               bool pseudovarflag;
+               bcmd = find_builtin(arglist.list->text);
+               pseudovarflag = bcmd && bcmd->flags & BUILTIN_ASSIGN;
+               for (; argp; argp = argp->narg.next) {
+                       if (pseudovarflag && isassignment(argp->narg.text)) {
+                               expandarg(argp, &arglist, EXP_VARTILDE);
+                               continue;
+                       }
+                       expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
+               }
+       }
+       *arglist.lastp = NULL;
+       *varlist.lastp = NULL;
+       expredir(cmd->ncmd.redirect);
+       argc = 0;
+       for (sp = arglist.list ; sp ; sp = sp->next)
+               argc++;
+       argv = stalloc(sizeof (char *) * (argc + 1));
+
+       for (sp = arglist.list ; sp ; sp = sp->next) {
+               TRACE(("evalcommand arg: %s\n", sp->text));
+               *argv++ = sp->text;
+       }
+       *argv = NULL;
+       lastarg = NULL;
+       if (iflag && funcnest == 0 && argc > 0)
+               lastarg = argv[-1];
+       argv -= argc;
+
+       /* Print the command if xflag is set. */
+       if (xflag) {
+#ifdef FLUSHERR
+               outc('+', &errout);
+#else
+               outcslow('+', &errout);
+#endif
+               eprintlist(varlist.list);
+               eprintlist(arglist.list);
+#ifdef FLUSHERR
+               outc('\n', &errout);
+               flushout(&errout);
+#else
+               outcslow('\n', &errout);
+#endif
+       }
+
+       /* Now locate the command. */
+       if (argc == 0) {
+               cmdentry.cmdtype = CMDBUILTIN;
+               firstbltin = cmdentry.u.cmd = BLTINCMD;
+       } else {
+               const char *oldpath;
+               int findflag = DO_ERR;
+               int oldfindflag;
+
+               /*
+                * Modify the command lookup path, if a PATH= assignment
+                * is present
+                */
+               for (sp = varlist.list ; sp ; sp = sp->next)
+                       if (varequal(sp->text, defpathvar)) {
+                               path = sp->text + 5;
+                               findflag |= DO_BRUTE;
+                       }
+               oldpath = path;
+               oldfindflag = findflag;
+               firstbltin = 0;
+               for(;;) {
+                       find_command(argv[0], &cmdentry, findflag, path);
+                       if (cmdentry.cmdtype == CMDUNKNOWN) {   /* command not found */
+                               exitstatus = 127;
+#ifdef FLUSHERR
+                               flushout(&errout);
+#endif
+                               goto out;
+                       }
+                       /* implement bltin and command here */
+                       if (cmdentry.cmdtype != CMDBUILTIN) {
+                               break;
+                       }
+                       if (!firstbltin) {
+                               firstbltin = cmdentry.u.cmd;
+                       }
+                       if (cmdentry.u.cmd == BLTINCMD) {
+                               for(;;) {
+                                       struct builtincmd *bcmd;
+
+                                       argv++;
+                                       if (--argc == 0)
+                                               goto found;
+                                       if (!(bcmd = find_builtin(*argv))) {
+                                               outfmt(&errout, "%s: not found\n", *argv);
+                                               exitstatus = 127;
+#ifdef FLUSHERR
+                                               flushout(&errout);
+#endif
+                                               goto out;
+                                       }
+                                       cmdentry.u.cmd = bcmd;
+                                       if (bcmd != BLTINCMD)
+                                               break;
+                               }
+                       }
+                       if (cmdentry.u.cmd == COMMANDCMD) {
+                               argv++;
+                               if (--argc == 0) {
+                                       goto found;
+                               }
+                               if (*argv[0] == '-') {
+                                       if (!equal(argv[0], "-p")) {
+                                               argv--;
+                                               argc++;
+                                               break;
+                                       }
+                                       argv++;
+                                       if (--argc == 0) {
+                                               goto found;
+                                       }
+                                       path = defpath;
+                                       findflag |= DO_BRUTE;
+                               } else {
+                                       path = oldpath;
+                                       findflag = oldfindflag;
+                               }
+                               findflag |= DO_NOFUN;
+                               continue;
+                       }
+found:
+                       break;
+               }
+       }
+
+       /* Fork off a child process if necessary. */
+       if (cmd->ncmd.backgnd
+        || (cmdentry.cmdtype == CMDNORMAL && (flags & EV_EXIT) == 0)
+#ifdef notyet
+        || ((flags & EV_BACKCMD) != 0
+           && (cmdentry.cmdtype != CMDBUILTIN
+                || cmdentry.u.bcmd == DOTCMD
+                || cmdentry.u.bcmd == EVALCMD))
+#endif
+       ) {
+               jp = makejob(cmd, 1);
+               mode = cmd->ncmd.backgnd;
+#ifdef notyet
+               if (flags & EV_BACKCMD) {
+                       mode = FORK_NOJOB;
+                       if (pipe(pip) < 0)
+                               error("Pipe call failed");
+               }
+#endif
+               if (forkshell(jp, cmd, mode) != 0)
+                       goto parent;    /* at end of routine */
+#ifdef notyet
+               if (flags & EV_BACKCMD) {
+                       FORCEINTON;
+                       close(pip[0]);
+                       if (pip[1] != 1) {
+                               close(1);
+                               dup_as_newfd(pip[1], 1);
+                               close(pip[1]);
+                       }
+               }
+#endif
+               flags |= EV_EXIT;
+       }
+
+       /* This is the child process if a fork occurred. */
+       /* Execute the command. */
+       if (cmdentry.cmdtype == CMDFUNCTION) {
+#ifdef DEBUG
+               trputs("Shell function:  ");  trargs(argv);
+#endif
+               exitstatus = oexitstatus;
+               redirect(cmd->ncmd.redirect, REDIR_PUSH);
+               saveparam = shellparam;
+               shellparam.malloc = 0;
+               shellparam.nparam = argc - 1;
+               shellparam.p = argv + 1;
+               INTOFF;
+               savelocalvars = localvars;
+               localvars = NULL;
+               INTON;
+               if (setjmp(jmploc.loc)) {
+                       if (exception == EXSHELLPROC) {
+                               freeparam((volatile struct shparam *)
+                                   &saveparam);
+                       } else {
+                               saveparam.optind = shellparam.optind;
+                               saveparam.optoff = shellparam.optoff;
+                               freeparam(&shellparam);
+                               shellparam = saveparam;
+                       }
+                       poplocalvars();
+                       localvars = savelocalvars;
+                       handler = savehandler;
+                       longjmp(handler->loc, 1);
+               }
+               savehandler = handler;
+               handler = &jmploc;
+               for (sp = varlist.list ; sp ; sp = sp->next)
+                       mklocal(sp->text);
+               funcnest++;
+               evaltree(cmdentry.u.func, flags & EV_TESTED);
+               funcnest--;
+               INTOFF;
+               poplocalvars();
+               localvars = savelocalvars;
+               saveparam.optind = shellparam.optind;
+               saveparam.optoff = shellparam.optoff;
+               freeparam(&shellparam);
+               shellparam = saveparam;
+               handler = savehandler;
+               popredir();
+               INTON;
+               if (evalskip == SKIPFUNC) {
+                       evalskip = 0;
+                       skipcount = 0;
+               }
+               if (flags & EV_EXIT)
+                       exitshell(exitstatus);
+       } else if (cmdentry.cmdtype == CMDBUILTIN) {
+#ifdef DEBUG
+               trputs("builtin command:  ");  trargs(argv);
+#endif
+               mode = (cmdentry.u.cmd == EXECCMD)? 0 : REDIR_PUSH;
+#ifdef notyet
+               if (flags == EV_BACKCMD) {
+#ifdef USE_GLIBC_STDIO
+                       openmemout();
+#else
+                       memout.nleft = 0;
+                       memout.nextc = memout.buf;
+                       memout.bufsize = 64;
+#endif
+                       mode |= REDIR_BACKQ;
+               }
+#endif
+               redirect(cmd->ncmd.redirect, mode);
+               savecmdname = commandname;
+               if (firstbltin->flags & BUILTIN_SPECIAL) {
+                       listsetvar(varlist.list);
+               } else {
+                       cmdenviron = varlist.list;
+               }
+               e = -1;
+               if (setjmp(jmploc.loc)) {
+                       e = exception;
+                       exitstatus = (e == EXINT)? SIGINT+128 : 2;
+                       goto cmddone;
+               }
+               savehandler = handler;
+               handler = &jmploc;
+               commandname = argv[0];
+               argptr = argv + 1;
+               optptr = NULL;                  /* initialize nextopt */
+               exitstatus = (*cmdentry.u.cmd->builtinfunc)(argc, argv);
+               flushall();
+cmddone:
+               exitstatus |= outerr(out1);
+               out1 = &output;
+               out2 = &errout;
+               freestdout();
+               cmdenviron = NULL;
+               if (e != EXSHELLPROC) {
+                       commandname = savecmdname;
+                       if (flags & EV_EXIT)
+                               exitshell(exitstatus);
+               }
+               handler = savehandler;
+               if (e != -1) {
+                       if ((e != EXERROR && e != EXEXEC)
+                          || cmdentry.u.cmd == BLTINCMD
+                          || cmdentry.u.cmd == DOTCMD
+                          || cmdentry.u.cmd == EVALCMD
+                          || cmdentry.u.cmd == EXECCMD)
+                               exraise(e);
+                       FORCEINTON;
+               }
+               if (cmdentry.u.cmd != EXECCMD)
+                       popredir();
+#ifdef notyet
+               if (flags == EV_BACKCMD) {
+                       INTOFF;
+#ifdef USE_GLIBC_STDIO
+                       if (__closememout()) {
+                               error(
+                                       "__closememout() failed: %s",
+                                       strerror(errno)
+                               );
+                       }
+#endif
+                       backcmd->buf = memout.buf;
+#ifdef USE_GLIBC_STDIO
+                       backcmd->nleft = memout.bufsize;
+#else
+                       backcmd->nleft = memout.nextc - memout.buf;
+#endif
+                       memout.buf = NULL;
+                       INTON;
+               }
+#endif
+       } else {
+#ifdef DEBUG
+               trputs("normal command:  ");  trargs(argv);
+#endif
+               redirect(cmd->ncmd.redirect, 0);
+               clearredir();
+               for (sp = varlist.list ; sp ; sp = sp->next)
+                       setvareq(sp->text, VEXPORT|VSTACK);
+               envp = environment();
+               shellexec(argv, envp, path, cmdentry.u.index);
+       }
+       goto out;
+
+parent:        /* parent process gets here (if we forked) */
+       if (mode == 0) {        /* argument to fork */
+               INTOFF;
+               exitstatus = waitforjob(jp);
+               INTON;
+#ifdef notyet
+       } else if (mode == 2) {
+               backcmd->fd = pip[0];
+               close(pip[1]);
+               backcmd->jp = jp;
+#endif
+       }
+
+out:
+       if (lastarg)
+               setvar("_", lastarg, 0);
+       popstackmark(&smark);
+}
+
+
+
+/*
+ * Search for a command.  This is called before we fork so that the
+ * location of the command will be available in the parent as well as
+ * the child.  The check for "goodname" is an overly conservative
+ * check that the name will not be subject to expansion.
+ */
+
+static void
+prehash(n)
+       union node *n;
+{
+       struct cmdentry entry;
+
+       if (n->type == NCMD && n->ncmd.args)
+               if (goodname(n->ncmd.args->narg.text))
+                       find_command(n->ncmd.args->narg.text, &entry, 0,
+                                    pathval());
+}
+
+
+
+/*
+ * Builtin commands.  Builtin commands whose functions are closely
+ * tied to evaluation are implemented here.
+ */
+
+/*
+ * No command given, or a bltin command with no arguments.  Set the
+ * specified variables.
+ */
+
+int
+bltincmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       /*
+        * Preserve exitstatus of a previous possible redirection
+        * as POSIX mandates
+        */
+       return exitstatus;
+}
+
+
+/*
+ * Handle break and continue commands.  Break, continue, and return are
+ * all handled by setting the evalskip flag.  The evaluation routines
+ * above all check this flag, and if it is set they start skipping
+ * commands rather than executing them.  The variable skipcount is
+ * the number of loops to break/continue, or the number of function
+ * levels to return.  (The latter is always 1.)  It should probably
+ * be an error to break out of more loops than exist, but it isn't
+ * in the standard shell so we don't make it one here.
+ */
+
+static int
+breakcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       int n = argc > 1 ? number(argv[1]) : 1;
+
+       if (n > loopnest)
+               n = loopnest;
+       if (n > 0) {
+               evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK;
+               skipcount = n;
+       }
+       return 0;
+}
+
+
+/*
+ * The return command.
+ */
+
+static int
+returncmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       int ret = argc > 1 ? number(argv[1]) : oexitstatus;
+
+       if (funcnest) {
+               evalskip = SKIPFUNC;
+               skipcount = 1;
+               return ret;
+       }
+       else {
+               /* Do what ksh does; skip the rest of the file */
+               evalskip = SKIPFILE;
+               skipcount = 1;
+               return ret;
+       }
+}
+
+
+#ifndef BB_TRUE_FALSE
+static int
+false_main(argc, argv)
+       int argc;
+       char **argv;
+{
+       return 1;
+}
+
+
+static int
+true_main(argc, argv)
+       int argc;
+       char **argv;
+{
+       return 0;
+}
+#endif
+
+static int
+execcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       if (argc > 1) {
+               struct strlist *sp;
+
+               iflag = 0;              /* exit on error */
+               mflag = 0;
+               optschanged();
+               for (sp = cmdenviron; sp ; sp = sp->next)
+                       setvareq(sp->text, VEXPORT|VSTACK);
+               shellexec(argv + 1, environment(), pathval(), 0);
+       }
+       return 0;
+}
+
+static void
+eprintlist(struct strlist *sp)
+{
+       for (; sp; sp = sp->next) {
+               outfmt(&errout, " %s",sp->text);
+       }
+}
+/*     $NetBSD: exec.c,v 1.32 2001/02/04 19:52:06 christos Exp $       */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * When commands are first encountered, they are entered in a hash table.
+ * This ensures that a full path search will not have to be done for them
+ * on each invocation.
+ *
+ * We should investigate converting to a linear search, even though that
+ * would make the command name "hash" a misnomer.
+ */
+#define CMDTABLESIZE 31                /* should be prime */
+#define ARB 1                  /* actual size determined at run time */
+
+
+
+struct tblentry {
+       struct tblentry *next;  /* next entry in hash chain */
+       union param param;      /* definition of builtin function */
+       short cmdtype;          /* index identifying command */
+       char rehash;            /* if set, cd done since entry created */
+       char cmdname[ARB];      /* name of command */
+};
+
+
+static struct tblentry *cmdtable[CMDTABLESIZE];
+static int builtinloc = -1;            /* index in path of %builtin, or -1 */
+static int exerrno = 0;                        /* Last exec error */
+
+
+static void tryexec __P((char *, char **, char **));
+#if !defined(BSD) && !defined(linux)
+static void execinterp __P((char **, char **));
+#endif
+static void printentry __P((struct tblentry *, int));
+static void clearcmdentry __P((int));
+static struct tblentry *cmdlookup __P((char *, int));
+static void delete_cmd_entry __P((void));
+#ifdef ASH_TYPE
+static int describe_command __P((char *, int));
+#endif
+static int path_change __P((const char *, int *));
+
+
+/*
+ * Exec a program.  Never returns.  If you change this routine, you may
+ * have to change the find_command routine as well.
+ */
+
+static void
+shellexec(argv, envp, path, idx)
+       char **argv, **envp;
+       const char *path;
+       int idx;
+{
+       char *cmdname;
+       int e;
+
+       if (strchr(argv[0], '/') != NULL) {
+               tryexec(argv[0], argv, envp);
+               e = errno;
+       } else {
+               e = ENOENT;
+               while ((cmdname = padvance(&path, argv[0])) != NULL) {
+                       if (--idx < 0 && pathopt == NULL) {
+                               tryexec(cmdname, argv, envp);
+                               if (errno != ENOENT && errno != ENOTDIR)
+                                       e = errno;
+                       }
+                       stunalloc(cmdname);
+               }
+       }
+
+       /* Map to POSIX errors */
+       switch (e) {
+       case EACCES:
+               exerrno = 126;
+               break;
+       case ENOENT:
+               exerrno = 127;
+               break;
+       default:
+               exerrno = 2;
+               break;
+       }
+       exerror(EXEXEC, "%s: %s", argv[0], errmsg(e, E_EXEC));
+       /* NOTREACHED */
+}
+
+
+static void
+tryexec(cmd, argv, envp)
+       char *cmd;
+       char **argv;
+       char **envp;
+       {
+       int e;
+#if !defined(BSD) && !defined(linux)
+       char *p;
+#endif
+
+#ifdef SYSV
+       do {
+               execve(cmd, argv, envp);
+       } while (errno == EINTR);
+#else
+       execve(cmd, argv, envp);
+#endif
+       e = errno;
+       if (e == ENOEXEC) {
+               INTOFF;
+               initshellproc();
+               setinputfile(cmd, 0);
+               commandname = arg0 = savestr(argv[0]);
+#if !defined(BSD) && !defined(linux)
+               INTON;
+               pgetc(); pungetc();             /* fill up input buffer */
+               p = parsenextc;
+               if (parsenleft > 2 && p[0] == '#' && p[1] == '!') {
+                       argv[0] = cmd;
+                       execinterp(argv, envp);
+               }
+               INTOFF;
+#endif
+               setparam(argv + 1);
+               exraise(EXSHELLPROC);
+       }
+       errno = e;
+}
+
+
+#if !defined(BSD) && !defined(linux)
+/*
+ * Execute an interpreter introduced by "#!", for systems where this
+ * feature has not been built into the kernel.  If the interpreter is
+ * the shell, return (effectively ignoring the "#!").  If the execution
+ * of the interpreter fails, exit.
+ *
+ * This code peeks inside the input buffer in order to avoid actually
+ * reading any input.  It would benefit from a rewrite.
+ */
+
+#define NEWARGS 5
+
+static void
+execinterp(argv, envp)
+       char **argv, **envp;
+       {
+       int n;
+       char *inp;
+       char *outp;
+       char c;
+       char *p;
+       char **ap;
+       char *newargs[NEWARGS];
+       int i;
+       char **ap2;
+       char **new;
+
+       n = parsenleft - 2;
+       inp = parsenextc + 2;
+       ap = newargs;
+       for (;;) {
+               while (--n >= 0 && (*inp == ' ' || *inp == '\t'))
+                       inp++;
+               if (n < 0)
+                       goto bad;
+               if ((c = *inp++) == '\n')
+                       break;
+               if (ap == &newargs[NEWARGS])
+bad:             error("Bad #! line");
+               STARTSTACKSTR(outp);
+               do {
+                       STPUTC(c, outp);
+               } while (--n >= 0 && (c = *inp++) != ' ' && c != '\t' && c != '\n');
+               STPUTC('\0', outp);
+               n++, inp--;
+               *ap++ = grabstackstr(outp);
+       }
+       if (ap == newargs + 1) {        /* if no args, maybe no exec is needed */
+               p = newargs[0];
+               for (;;) {
+                       if (equal(p, "sh") || equal(p, "ash")) {
+                               return;
+                       }
+                       while (*p != '/') {
+                               if (*p == '\0')
+                                       goto break2;
+                               p++;
+                       }
+                       p++;
+               }
+break2:;
+       }
+       i = (char *)ap - (char *)newargs;               /* size in bytes */
+       if (i == 0)
+               error("Bad #! line");
+       for (ap2 = argv ; *ap2++ != NULL ; );
+       new = ckmalloc(i + ((char *)ap2 - (char *)argv));
+       ap = newargs, ap2 = new;
+       while ((i -= sizeof (char **)) >= 0)
+               *ap2++ = *ap++;
+       ap = argv;
+       while (*ap2++ = *ap++);
+       shellexec(new, envp, pathval(), 0);
+       /* NOTREACHED */
+}
+#endif
+
+
+
+/*
+ * Do a path search.  The variable path (passed by reference) should be
+ * set to the start of the path before the first call; padvance will update
+ * this value as it proceeds.  Successive calls to padvance will return
+ * the possible path expansions in sequence.  If an option (indicated by
+ * a percent sign) appears in the path entry then the global variable
+ * pathopt will be set to point to it; otherwise pathopt will be set to
+ * NULL.
+ */
+
+static const char *pathopt;
+
+static char *
+padvance(path, name)
+       const char **path;
+       const char *name;
+       {
+       const char *p;
+       char *q;
+       const char *start;
+       int len;
+
+       if (*path == NULL)
+               return NULL;
+       start = *path;
+       for (p = start ; *p && *p != ':' && *p != '%' ; p++);
+       len = p - start + strlen(name) + 2;     /* "2" is for '/' and '\0' */
+       while (stackblocksize() < len)
+               growstackblock();
+       q = stackblock();
+       if (p != start) {
+               memcpy(q, start, p - start);
+               q += p - start;
+               *q++ = '/';
+       }
+       strcpy(q, name);
+       pathopt = NULL;
+       if (*p == '%') {
+               pathopt = ++p;
+               while (*p && *p != ':')  p++;
+       }
+       if (*p == ':')
+               *path = p + 1;
+       else
+               *path = NULL;
+       return stalloc(len);
+}
+
+
+
+/*** Command hashing code ***/
+
+
+static int
+hashcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       struct tblentry **pp;
+       struct tblentry *cmdp;
+       int c;
+       int verbose;
+       struct cmdentry entry;
+       char *name;
+
+       verbose = 0;
+       while ((c = nextopt("rv")) != '\0') {
+               if (c == 'r') {
+                       clearcmdentry(0);
+                       return 0;
+               } else if (c == 'v') {
+                       verbose++;
+               }
+       }
+       if (*argptr == NULL) {
+               for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) {
+                       for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
+                               if (cmdp->cmdtype != CMDBUILTIN) {
+                                       printentry(cmdp, verbose);
+                               }
+                       }
+               }
+               return 0;
+       }
+       c = 0;
+       while ((name = *argptr) != NULL) {
+               if ((cmdp = cmdlookup(name, 0)) != NULL
+                && (cmdp->cmdtype == CMDNORMAL
+                    || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0)))
+                       delete_cmd_entry();
+               find_command(name, &entry, DO_ERR, pathval());
+               if (entry.cmdtype == CMDUNKNOWN) c = 1;
+               else if (verbose) {
+                       cmdp = cmdlookup(name, 0);
+                       if (cmdp) printentry(cmdp, verbose);
+                       flushall();
+               }
+               argptr++;
+       }
+       return c;
+}
+
+
+static void
+printentry(cmdp, verbose)
+       struct tblentry *cmdp;
+       int verbose;
+       {
+       int idx;
+       const char *path;
+       char *name;
+
+       if (cmdp->cmdtype == CMDNORMAL) {
+               idx = cmdp->param.index;
+               path = pathval();
+               do {
+                       name = padvance(&path, cmdp->cmdname);
+                       stunalloc(name);
+               } while (--idx >= 0);
+               out1str(name);
+       } else if (cmdp->cmdtype == CMDBUILTIN) {
+               out1fmt("builtin %s", cmdp->cmdname);
+       } else if (cmdp->cmdtype == CMDFUNCTION) {
+               out1fmt("function %s", cmdp->cmdname);
+               if (verbose) {
+                       INTOFF;
+                       name = commandtext(cmdp->param.func);
+                       out1fmt(" %s", name);
+                       ckfree(name);
+                       INTON;
+               }
+#ifdef DEBUG
+       } else {
+               error("internal error: cmdtype %d", cmdp->cmdtype);
+#endif
+       }
+       out1fmt(snlfmt, cmdp->rehash ? "*" : nullstr);
+}
+
+
+
+/*
+ * Resolve a command name.  If you change this routine, you may have to
+ * change the shellexec routine as well.
+ */
+
+static void
+find_command(name, entry, act, path)
+       char *name;
+       struct cmdentry *entry;
+       int act;
+       const char *path;
+{
+       struct tblentry *cmdp;
+       int idx;
+       int prev;
+       char *fullname;
+       struct stat statb;
+       int e;
+       int bltin;
+       int firstchange;
+       int updatetbl;
+       bool regular;
+       struct builtincmd *bcmd;
+
+       /* If name contains a slash, don't use the hash table */
+       if (strchr(name, '/') != NULL) {
+               if (act & DO_ABS) {
+                       while (stat(name, &statb) < 0) {
+       #ifdef SYSV
+                               if (errno == EINTR)
+                                       continue;
+       #endif
+                               if (errno != ENOENT && errno != ENOTDIR)
+                                       e = errno;
+                               entry->cmdtype = CMDUNKNOWN;
+                               entry->u.index = -1;
+                               return;
+                       }
+                       entry->cmdtype = CMDNORMAL;
+                       entry->u.index = -1;
+                       return;
+               }
+               entry->cmdtype = CMDNORMAL;
+               entry->u.index = 0;
+               return;
+       }
+
+       updatetbl = 1;
+       if (act & DO_BRUTE) {
+               firstchange = path_change(path, &bltin);
+       } else {
+               bltin = builtinloc;
+               firstchange = 9999;
+       }
+
+       /* If name is in the table, and not invalidated by cd, we're done */
+       if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->rehash == 0) {
+               if (cmdp->cmdtype == CMDFUNCTION) {
+                       if (act & DO_NOFUN) {
+                               updatetbl = 0;
+                       } else {
+                               goto success;
+                       }
+               } else if (act & DO_BRUTE) {
+                       if ((cmdp->cmdtype == CMDNORMAL &&
+                            cmdp->param.index >= firstchange) ||
+                           (cmdp->cmdtype == CMDBUILTIN &&
+                            ((builtinloc < 0 && bltin >= 0) ?
+                             bltin : builtinloc) >= firstchange)) {
+                               /* need to recompute the entry */
+                       } else {
+                               goto success;
+                       }
+               } else {
+                       goto success;
+               }
+       }
+
+       bcmd = find_builtin(name);
+       regular = bcmd && bcmd->flags & BUILTIN_REGULAR;
+
+       if (regular) {
+               if (cmdp && (cmdp->cmdtype == CMDBUILTIN)) {
+                       goto success;
+               }
+       } else if (act & DO_BRUTE) {
+               if (firstchange == 0) {
+                       updatetbl = 0;
+               }
+       }
+
+       /* If %builtin not in path, check for builtin next */
+       if (regular || (bltin < 0 && bcmd)) {
+builtin:
+               if (!updatetbl) {
+                       entry->cmdtype = CMDBUILTIN;
+                       entry->u.cmd = bcmd;
+                       return;
+               }
+               INTOFF;
+               cmdp = cmdlookup(name, 1);
+               cmdp->cmdtype = CMDBUILTIN;
+               cmdp->param.cmd = bcmd;
+               INTON;
+               goto success;
+       }
+
+       /* We have to search path. */
+       prev = -1;              /* where to start */
+       if (cmdp && cmdp->rehash) {     /* doing a rehash */
+               if (cmdp->cmdtype == CMDBUILTIN)
+                       prev = builtinloc;
+               else
+                       prev = cmdp->param.index;
+       }
+
+       e = ENOENT;
+       idx = -1;
+loop:
+       while ((fullname = padvance(&path, name)) != NULL) {
+               stunalloc(fullname);
+               idx++;
+               if (idx >= firstchange) {
+                       updatetbl = 0;
+               }
+               if (pathopt) {
+                       if (prefix("builtin", pathopt)) {
+                               if ((bcmd = find_builtin(name))) {
+                                       goto builtin;
+                               }
+                               continue;
+                       } else if (!(act & DO_NOFUN) &&
+                                  prefix("func", pathopt)) {
+                               /* handled below */
+                       } else {
+                               continue;       /* ignore unimplemented options */
+                       }
+               }
+               /* if rehash, don't redo absolute path names */
+               if (fullname[0] == '/' && idx <= prev &&
+                   idx < firstchange) {
+                       if (idx < prev)
+                               continue;
+                       TRACE(("searchexec \"%s\": no change\n", name));
+                       goto success;
+               }
+               while (stat(fullname, &statb) < 0) {
+#ifdef SYSV
+                       if (errno == EINTR)
+                               continue;
+#endif
+                       if (errno != ENOENT && errno != ENOTDIR)
+                               e = errno;
+                       goto loop;
+               }
+               e = EACCES;     /* if we fail, this will be the error */
+               if (!S_ISREG(statb.st_mode))
+                       continue;
+               if (pathopt) {          /* this is a %func directory */
+                       stalloc(strlen(fullname) + 1);
+                       readcmdfile(fullname);
+                       if ((cmdp = cmdlookup(name, 0)) == NULL || cmdp->cmdtype != CMDFUNCTION)
+                               error("%s not defined in %s", name, fullname);
+                       stunalloc(fullname);
+                       goto success;
+               }
+#ifdef notdef
+               if (statb.st_uid == geteuid()) {
+                       if ((statb.st_mode & 0100) == 0)
+                               goto loop;
+               } else if (statb.st_gid == getegid()) {
+                       if ((statb.st_mode & 010) == 0)
+                               goto loop;
+               } else {
+                       if ((statb.st_mode & 01) == 0)
+                               goto loop;
+               }
+#endif
+               TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname));
+               /* If we aren't called with DO_BRUTE and cmdp is set, it must
+                  be a function and we're being called with DO_NOFUN */
+               if (!updatetbl) {
+                       entry->cmdtype = CMDNORMAL;
+                       entry->u.index = idx;
+                       return;
+               }
+               INTOFF;
+               cmdp = cmdlookup(name, 1);
+               cmdp->cmdtype = CMDNORMAL;
+               cmdp->param.index = idx;
+               INTON;
+               goto success;
+       }
+
+       /* We failed.  If there was an entry for this command, delete it */
+       if (cmdp && updatetbl)
+               delete_cmd_entry();
+       if (act & DO_ERR)
+               outfmt(out2, "%s: %s\n", name, errmsg(e, E_EXEC));
+       entry->cmdtype = CMDUNKNOWN;
+       return;
+
+success:
+       cmdp->rehash = 0;
+       entry->cmdtype = cmdp->cmdtype;
+       entry->u = cmdp->param;
+}
+
+
+
+/*
+ * Search the table of builtin commands.
+ */
+
+struct builtincmd *
+find_builtin(name)
+       char *name;
+{
+       struct builtincmd *bp;
+
+       bp = bsearch( &name, builtincmds, NUMBUILTINS, sizeof(struct builtincmd),
+               pstrcmp
+       );
+       return bp;
+}
+
+
+/*
+ * Called when a cd is done.  Marks all commands so the next time they
+ * are executed they will be rehashed.
+ */
+
+static void
+hashcd() {
+       struct tblentry **pp;
+       struct tblentry *cmdp;
+
+       for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) {
+               for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
+                       if (cmdp->cmdtype == CMDNORMAL
+                        || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0))
+                               cmdp->rehash = 1;
+               }
+       }
+}
+
+
+
+/*
+ * Called before PATH is changed.  The argument is the new value of PATH;
+ * pathval() still returns the old value at this point.  Called with
+ * interrupts off.
+ */
+
+static void
+changepath(newval)
+       const char *newval;
+{
+       int firstchange;
+       int bltin;
+
+       firstchange = path_change(newval, &bltin);
+       if (builtinloc < 0 && bltin >= 0)
+               builtinloc = bltin;             /* zap builtins */
+       clearcmdentry(firstchange);
+       builtinloc = bltin;
+}
+
+
+/*
+ * Clear out command entries.  The argument specifies the first entry in
+ * PATH which has changed.
+ */
+
+static void
+clearcmdentry(firstchange)
+       int firstchange;
+{
+       struct tblentry **tblp;
+       struct tblentry **pp;
+       struct tblentry *cmdp;
+
+       INTOFF;
+       for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) {
+               pp = tblp;
+               while ((cmdp = *pp) != NULL) {
+                       if ((cmdp->cmdtype == CMDNORMAL &&
+                            cmdp->param.index >= firstchange)
+                        || (cmdp->cmdtype == CMDBUILTIN &&
+                            builtinloc >= firstchange)) {
+                               *pp = cmdp->next;
+                               ckfree(cmdp);
+                       } else {
+                               pp = &cmdp->next;
+                       }
+               }
+       }
+       INTON;
+}
+
+
+/*
+ * Delete all functions.
+ */
+
+#ifdef mkinit
+static void deletefuncs __P((void));
+
+SHELLPROC {
+       deletefuncs();
+}
+#endif
+
+static void
+deletefuncs() {
+       struct tblentry **tblp;
+       struct tblentry **pp;
+       struct tblentry *cmdp;
+
+       INTOFF;
+       for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) {
+               pp = tblp;
+               while ((cmdp = *pp) != NULL) {
+                       if (cmdp->cmdtype == CMDFUNCTION) {
+                               *pp = cmdp->next;
+                               freefunc(cmdp->param.func);
+                               ckfree(cmdp);
+                       } else {
+                               pp = &cmdp->next;
+                       }
+               }
+       }
+       INTON;
+}
+
+
+
+/*
+ * Locate a command in the command hash table.  If "add" is nonzero,
+ * add the command to the table if it is not already present.  The
+ * variable "lastcmdentry" is set to point to the address of the link
+ * pointing to the entry, so that delete_cmd_entry can delete the
+ * entry.
+ */
+
+struct tblentry **lastcmdentry;
+
+
+static struct tblentry *
+cmdlookup(name, add)
+       char *name;
+       int add;
+{
+       int hashval;
+       char *p;
+       struct tblentry *cmdp;
+       struct tblentry **pp;
+
+       p = name;
+       hashval = *p << 4;
+       while (*p)
+               hashval += *p++;
+       hashval &= 0x7FFF;
+       pp = &cmdtable[hashval % CMDTABLESIZE];
+       for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
+               if (equal(cmdp->cmdname, name))
+                       break;
+               pp = &cmdp->next;
+       }
+       if (add && cmdp == NULL) {
+               INTOFF;
+               cmdp = *pp = ckmalloc(sizeof (struct tblentry) - ARB
+                                       + strlen(name) + 1);
+               cmdp->next = NULL;
+               cmdp->cmdtype = CMDUNKNOWN;
+               cmdp->rehash = 0;
+               strcpy(cmdp->cmdname, name);
+               INTON;
+       }
+       lastcmdentry = pp;
+       return cmdp;
+}
+
+/*
+ * Delete the command entry returned on the last lookup.
+ */
+
+static void
+delete_cmd_entry() {
+       struct tblentry *cmdp;
+
+       INTOFF;
+       cmdp = *lastcmdentry;
+       *lastcmdentry = cmdp->next;
+       ckfree(cmdp);
+       INTON;
+}
+
+
+
+#ifdef notdef
+static void
+getcmdentry(name, entry)
+       char *name;
+       struct cmdentry *entry;
+       {
+       struct tblentry *cmdp = cmdlookup(name, 0);
+
+       if (cmdp) {
+               entry->u = cmdp->param;
+               entry->cmdtype = cmdp->cmdtype;
+       } else {
+               entry->cmdtype = CMDUNKNOWN;
+               entry->u.index = 0;
+       }
+}
+#endif
+
+
+/*
+ * Add a new command entry, replacing any existing command entry for
+ * the same name.
+ */
+
+static void
+addcmdentry(name, entry)
+       char *name;
+       struct cmdentry *entry;
+       {
+       struct tblentry *cmdp;
+
+       INTOFF;
+       cmdp = cmdlookup(name, 1);
+       if (cmdp->cmdtype == CMDFUNCTION) {
+               freefunc(cmdp->param.func);
+       }
+       cmdp->cmdtype = entry->cmdtype;
+       cmdp->param = entry->u;
+       INTON;
+}
+
+
+/*
+ * Define a shell function.
+ */
+
+static void
+defun(name, func)
+       char *name;
+       union node *func;
+       {
+       struct cmdentry entry;
+
+       entry.cmdtype = CMDFUNCTION;
+       entry.u.func = copyfunc(func);
+       addcmdentry(name, &entry);
+}
+
+
+/*
+ * Delete a function if it exists.
+ */
+
+static void
+unsetfunc(name)
+       char *name;
+       {
+       struct tblentry *cmdp;
+
+       if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->cmdtype == CMDFUNCTION) {
+               freefunc(cmdp->param.func);
+               delete_cmd_entry();
+       }
+}
+
+#ifdef ASH_TYPE
+/*
+ * Locate and print what a word is...
+ */
+
+static int
+typecmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       int i;
+       int err = 0;
+
+       for (i = 1; i < argc; i++) {
+               err |= describe_command(argv[i], 1);
+       }
+       return err;
+}
+
+static int
+describe_command(command, verbose)
+       char *command;
+       int verbose;
+{
+       struct cmdentry entry;
+       struct tblentry *cmdp;
+       const struct alias *ap;
+       const char *path = pathval();
+
+       if (verbose) {
+               out1str(command);
+       }
+
+       /* First look at the keywords */
+       if (findkwd(command)) {
+               out1str(verbose ? " is a shell keyword" : command);
+               goto out;
+       }
+
+       /* Then look at the aliases */
+       if ((ap = lookupalias(command, 0)) != NULL) {
+               if (verbose) {
+                       out1fmt(" is an alias for %s", ap->val);
+               } else {
+                       printalias(ap);
+               }
+               goto out;
+       }
+
+       /* Then check if it is a tracked alias */
+       if ((cmdp = cmdlookup(command, 0)) != NULL) {
+               entry.cmdtype = cmdp->cmdtype;
+               entry.u = cmdp->param;
+       } else {
+               /* Finally use brute force */
+               find_command(command, &entry, DO_ABS, path);
+       }
+
+       switch (entry.cmdtype) {
+       case CMDNORMAL: {
+               int j = entry.u.index;
+               char *p;
+               if (j == -1) {
+                       p = command;
+               } else {
+                       do {
+                               p = padvance(&path, command);
+                               stunalloc(p);
+                       } while (--j >= 0);
+               }
+               if (verbose) {
+                       out1fmt(
+                               " is%s %s",
+                               cmdp ? " a tracked alias for" : nullstr, p
+                       );
+               } else {
+                       out1str(p);
+               }
+               break;
+       }
+
+       case CMDFUNCTION:
+               if (verbose) {
+                       out1str(" is a shell function");
+               } else {
+                       out1str(command);
+               }
+               break;
+
+       case CMDBUILTIN:
+               if (verbose) {
+                       out1fmt(
+                               " is a %sshell builtin",
+                               entry.u.cmd->flags & BUILTIN_SPECIAL ?
+                                       "special " : nullstr
+                       );
+               } else {
+                       out1str(command);
+               }
+               break;
+
+       default:
+               if (verbose) {
+                       out1str(": not found\n");
+               }
+               return 127;
+       }
+
+out:
+       out1c('\n');
+       return 0;
+}
+#endif 
+
+static int
+commandcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       int c;
+       int default_path = 0;
+       int verify_only = 0;
+       int verbose_verify_only = 0;
+
+       while ((c = nextopt("pvV")) != '\0')
+               switch (c) {
+               case 'p':
+                       default_path = 1;
+                       break;
+               case 'v':
+                       verify_only = 1;
+                       break;
+               case 'V':
+                       verbose_verify_only = 1;
+                       break;
+               default:
+                       outfmt(out2,
+"command: nextopt returned character code 0%o\n", c);
+                       return EX_SOFTWARE;
+               }
+
+       if (default_path + verify_only + verbose_verify_only > 1 ||
+           !*argptr) {
+                       outfmt(out2,
+"command [-p] command [arg ...]\n");
+                       outfmt(out2,
+"command {-v|-V} command\n");
+                       return EX_USAGE;
+       }
+
+#ifdef ASH_TYPE
+       if (verify_only || verbose_verify_only) {
+               return describe_command(*argptr, verbose_verify_only);
+       }
+#endif 
+
+       return 0;
+}
+
+static int
+path_change(newval, bltin)
+       const char *newval;
+       int *bltin;
+{
+       const char *old, *new;
+       int idx;
+       int firstchange;
+
+       old = pathval();
+       new = newval;
+       firstchange = 9999;     /* assume no change */
+       idx = 0;
+       *bltin = -1;
+       for (;;) {
+               if (*old != *new) {
+                       firstchange = idx;
+                       if ((*old == '\0' && *new == ':')
+                        || (*old == ':' && *new == '\0'))
+                               firstchange++;
+                       old = new;      /* ignore subsequent differences */
+               }
+               if (*new == '\0')
+                       break;
+               if (*new == '%' && *bltin < 0 && prefix("builtin", new + 1))
+                       *bltin = idx;
+               if (*new == ':') {
+                       idx++;
+               }
+               new++, old++;
+       }
+       if (builtinloc >= 0 && *bltin < 0)
+               firstchange = 0;
+       return firstchange;
+}
+/*     $NetBSD: expand.c,v 1.50 2001/02/04 19:52:06 christos Exp $     */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Routines to expand arguments to commands.  We have to deal with
+ * backquotes, shell variables, and file metacharacters.
+ */
+/*
+ * _rmescape() flags
+ */
+#define RMESCAPE_ALLOC 0x1     /* Allocate a new string */
+#define RMESCAPE_GLOB  0x2     /* Add backslashes for glob */
+
+/*
+ * Structure specifying which parts of the string should be searched
+ * for IFS characters.
+ */
+
+struct ifsregion {
+       struct ifsregion *next; /* next region in list */
+       int begoff;             /* offset of start of region */
+       int endoff;             /* offset of end of region */
+       int nulonly;            /* search for nul bytes only */
+};
+
+
+static char *expdest;                  /* output of current string */
+struct nodelist *argbackq;     /* list of back quote expressions */
+struct ifsregion ifsfirst;     /* first struct in list of ifs regions */
+struct ifsregion *ifslastp;    /* last struct in list */
+struct arglist exparg;         /* holds expanded arg list */
+
+static void argstr __P((char *, int));
+static char *exptilde __P((char *, int));
+static void expbackq __P((union node *, int, int));
+static int subevalvar __P((char *, char *, int, int, int, int, int));
+static char *evalvar __P((char *, int));
+static int varisset __P((char *, int));
+static void strtodest __P((const char *, const char *, int));
+static void varvalue __P((char *, int, int));
+static void recordregion __P((int, int, int));
+static void removerecordregions __P((int)); 
+static void ifsbreakup __P((char *, struct arglist *));
+static void ifsfree __P((void));
+static void expandmeta __P((struct strlist *, int));
+#if defined(__GLIBC__) && !defined(FNMATCH_BROKEN)
+#define preglob(p) _rmescapes((p), RMESCAPE_ALLOC | RMESCAPE_GLOB)
+#if !defined(GLOB_BROKEN)
+static void addglob __P((const glob_t *));
+#endif
+#endif
+#if !(defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN))
+static void expmeta __P((char *, char *));
+#endif
+static void addfname __P((char *));
+#if !(defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN))
+static struct strlist *expsort __P((struct strlist *));
+static struct strlist *msort __P((struct strlist *, int));
+#endif
+#if defined(__GLIBC__) && !defined(FNMATCH_BROKEN)
+static int patmatch __P((char *, char *, int));
+static int patmatch2 __P((char *, char *, int));
+#else
+static int pmatch __P((char *, char *, int));
+#define patmatch2 patmatch
+#endif
+static char *cvtnum __P((int, char *));
+
+extern int oexitstatus;
+
+/*
+ * Expand shell variables and backquotes inside a here document.
+ */
+
+static void
+expandhere(arg, fd)
+       union node *arg;        /* the document */
+       int fd;                 /* where to write the expanded version */
+       {
+       herefd = fd;
+       expandarg(arg, (struct arglist *)NULL, 0);
+       xwrite(fd, stackblock(), expdest - stackblock());
+}
+
+
+/*
+ * Perform variable substitution and command substitution on an argument,
+ * placing the resulting list of arguments in arglist.  If EXP_FULL is true,
+ * perform splitting and file name expansion.  When arglist is NULL, perform
+ * here document expansion.
+ */
+
+static void
+expandarg(arg, arglist, flag)
+       union node *arg;
+       struct arglist *arglist;
+       int flag;
+{
+       struct strlist *sp;
+       char *p;
+
+       argbackq = arg->narg.backquote;
+       STARTSTACKSTR(expdest);
+       ifsfirst.next = NULL;
+       ifslastp = NULL;
+       argstr(arg->narg.text, flag);
+       if (arglist == NULL) {
+               return;                 /* here document expanded */
+       }
+       STPUTC('\0', expdest);
+       p = grabstackstr(expdest);
+       exparg.lastp = &exparg.list;
+       /*
+        * TODO - EXP_REDIR
+        */
+       if (flag & EXP_FULL) {
+               ifsbreakup(p, &exparg);
+               *exparg.lastp = NULL;
+               exparg.lastp = &exparg.list;
+               expandmeta(exparg.list, flag);
+       } else {
+               if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */
+                       rmescapes(p);
+               sp = (struct strlist *)stalloc(sizeof (struct strlist));
+               sp->text = p;
+               *exparg.lastp = sp;
+               exparg.lastp = &sp->next;
+       }
+       ifsfree();
+       *exparg.lastp = NULL;
+       if (exparg.list) {
+               *arglist->lastp = exparg.list;
+               arglist->lastp = exparg.lastp;
+       }
+}
+
+
+
+/*
+ * Perform variable and command substitution.  If EXP_FULL is set, output CTLESC
+ * characters to allow for further processing.  Otherwise treat
+ * $@ like $* since no splitting will be performed.
+ */
+
+static void
+argstr(p, flag)
+       char *p;
+       int flag;
+{
+       char c;
+       int quotes = flag & (EXP_FULL | EXP_CASE);      /* do CTLESC */
+       int firsteq = 1;
+
+       if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE)))
+               p = exptilde(p, flag);
+       for (;;) {
+               switch (c = *p++) {
+               case '\0':
+               case CTLENDVAR: /* ??? */
+                       goto breakloop;
+               case CTLQUOTEMARK:
+                       /* "$@" syntax adherence hack */
+                       if (p[0] == CTLVAR && p[2] == '@' && p[3] == '=')
+                               break;
+                       if ((flag & EXP_FULL) != 0)
+                               STPUTC(c, expdest);
+                       break;
+               case CTLESC:
+                       if (quotes)
+                               STPUTC(c, expdest);
+                       c = *p++;
+                       STPUTC(c, expdest);
+                       break;
+               case CTLVAR:
+                       p = evalvar(p, flag);
+                       break;
+               case CTLBACKQ:
+               case CTLBACKQ|CTLQUOTE:
+                       expbackq(argbackq->n, c & CTLQUOTE, flag);
+                       argbackq = argbackq->next;
+                       break;
+#ifdef ASH_MATH_SUPPORT
+               case CTLENDARI:
+                       expari(flag);
+                       break;
+#endif 
+               case ':':
+               case '=':
+                       /*
+                        * sort of a hack - expand tildes in variable
+                        * assignments (after the first '=' and after ':'s).
+                        */
+                       STPUTC(c, expdest);
+                       if (flag & EXP_VARTILDE && *p == '~') {
+                               if (c == '=') {
+                                       if (firsteq)
+                                               firsteq = 0;
+                                       else
+                                               break;
+                               }
+                               p = exptilde(p, flag);
+                       }
+                       break;
+               default:
+                       STPUTC(c, expdest);
+               }
+       }
+breakloop:;
+       return;
+}
+
+static char *
+exptilde(p, flag)
+       char *p;
+       int flag;
+{
+       char c, *startp = p;
+       struct passwd *pw;
+       const char *home;
+       int quotes = flag & (EXP_FULL | EXP_CASE);
+
+       while ((c = *p) != '\0') {
+               switch(c) {
+               case CTLESC:
+                       return (startp);
+               case CTLQUOTEMARK:
+                       return (startp);
+               case ':':
+                       if (flag & EXP_VARTILDE)
+                               goto done;
+                       break;
+               case '/':
+                       goto done;
+               }
+               p++;
+       }
+done:
+       *p = '\0';
+       if (*(startp+1) == '\0') {
+               if ((home = lookupvar("HOME")) == NULL)
+                       goto lose;
+       } else {
+               if ((pw = getpwnam(startp+1)) == NULL)
+                       goto lose;
+               home = pw->pw_dir;
+       }
+       if (*home == '\0')
+               goto lose;
+       *p = c;
+       strtodest(home, SQSYNTAX, quotes);
+       return (p);
+lose:
+       *p = c;
+       return (startp);
+}
+
+
+static void 
+removerecordregions(endoff)
+       int endoff;
+{
+       if (ifslastp == NULL)
+               return;
+
+       if (ifsfirst.endoff > endoff) {
+               while (ifsfirst.next != NULL) {
+                       struct ifsregion *ifsp;
+                       INTOFF;
+                       ifsp = ifsfirst.next->next;
+                       ckfree(ifsfirst.next);
+                       ifsfirst.next = ifsp;
+                       INTON;
+               }
+               if (ifsfirst.begoff > endoff)
+                       ifslastp = NULL;
+               else {
+                       ifslastp = &ifsfirst;
+                       ifsfirst.endoff = endoff;
+               }
+               return;
+       }
+       
+       ifslastp = &ifsfirst;
+       while (ifslastp->next && ifslastp->next->begoff < endoff)
+               ifslastp=ifslastp->next;
+       while (ifslastp->next != NULL) {
+               struct ifsregion *ifsp;
+               INTOFF;
+               ifsp = ifslastp->next->next;
+               ckfree(ifslastp->next);
+               ifslastp->next = ifsp;
+               INTON;
+       }
+       if (ifslastp->endoff > endoff)
+               ifslastp->endoff = endoff;
+}
+
+
+#ifdef ASH_MATH_SUPPORT
+/*
+ * Expand arithmetic expression.  Backup to start of expression,
+ * evaluate, place result in (backed up) result, adjust string position.
+ */
+static void
+expari(flag)
+       int flag;
+{
+       char *p, *start;
+       int result;
+       int begoff;
+       int quotes = flag & (EXP_FULL | EXP_CASE);
+       int quoted;
+
+       /*      ifsfree(); */
+
+       /*
+        * This routine is slightly over-complicated for
+        * efficiency.  First we make sure there is
+        * enough space for the result, which may be bigger
+        * than the expression if we add exponentation.  Next we
+        * scan backwards looking for the start of arithmetic.  If the
+        * next previous character is a CTLESC character, then we
+        * have to rescan starting from the beginning since CTLESC
+        * characters have to be processed left to right.
+        */
+       CHECKSTRSPACE(10, expdest);
+       USTPUTC('\0', expdest);
+       start = stackblock();
+       p = expdest - 1;
+       while (*p != CTLARI && p >= start)
+               --p;
+       if (*p != CTLARI)
+               error("missing CTLARI (shouldn't happen)");
+       if (p > start && *(p-1) == CTLESC)
+               for (p = start; *p != CTLARI; p++)
+                       if (*p == CTLESC)
+                               p++;
+
+       if (p[1] == '"')
+               quoted=1;
+       else
+               quoted=0;
+       begoff = p - start;
+       removerecordregions(begoff);
+       if (quotes)
+               rmescapes(p+2);
+       result = arith(p+2);
+       fmtstr(p, 12, "%d", result);
+
+       while (*p++)
+               ;
+
+       if (quoted == 0)
+               recordregion(begoff, p - 1 - start, 0);
+       result = expdest - p + 1;
+       STADJUST(-result, expdest);
+}
+#endif 
+
+
+/*
+ * Expand stuff in backwards quotes.
+ */
+
+static void
+expbackq(cmd, quoted, flag)
+       union node *cmd;
+       int quoted;
+       int flag;
+{
+       volatile struct backcmd in;
+       int i;
+       char buf[128];
+       char *p;
+       char *dest = expdest;
+       volatile struct ifsregion saveifs;
+       struct ifsregion *volatile savelastp;
+       struct nodelist *volatile saveargbackq;
+       char lastc;
+       int startloc = dest - stackblock();
+       char const *syntax = quoted? DQSYNTAX : BASESYNTAX;
+       volatile int saveherefd;
+       int quotes = flag & (EXP_FULL | EXP_CASE);
+       struct jmploc jmploc;
+       struct jmploc *volatile savehandler;
+       int ex;
+
+#if __GNUC__
+       /* Avoid longjmp clobbering */
+       (void) &dest;
+       (void) &syntax;
+#endif
+
+       in.fd = -1;
+       in.buf = 0;
+       in.jp = 0;
+
+       INTOFF;
+       saveifs = ifsfirst;
+       savelastp = ifslastp;
+       saveargbackq = argbackq;
+       saveherefd = herefd;
+       herefd = -1;
+       if ((ex = setjmp(jmploc.loc))) {
+               goto err1;
+       }
+       savehandler = handler;
+       handler = &jmploc;
+       INTON;
+       p = grabstackstr(dest);
+       evalbackcmd(cmd, (struct backcmd *) &in);
+       ungrabstackstr(p, dest);
+err1:
+       INTOFF;
+       ifsfirst = saveifs;
+       ifslastp = savelastp;
+       argbackq = saveargbackq;
+       herefd = saveherefd;
+       if (ex) {
+               goto err2;
+       }
+
+       p = in.buf;
+       lastc = '\0';
+       for (;;) {
+               if (--in.nleft < 0) {
+                       if (in.fd < 0)
+                               break;
+                       while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR);
+                       TRACE(("expbackq: read returns %d\n", i));
+                       if (i <= 0)
+                               break;
+                       p = buf;
+                       in.nleft = i - 1;
+               }
+               lastc = *p++;
+               if (lastc != '\0') {
+                       if (quotes && syntax[(int)lastc] == CCTL)
+                               STPUTC(CTLESC, dest);
+                       STPUTC(lastc, dest);
+               }
+       }
+
+       /* Eat all trailing newlines */
+       for (; dest > stackblock() && dest[-1] == '\n';)
+               STUNPUTC(dest);
+
+err2:
+       if (in.fd >= 0)
+               close(in.fd);
+       if (in.buf)
+               ckfree(in.buf);
+       if (in.jp)
+               exitstatus = waitforjob(in.jp);
+       handler = savehandler;
+       if (ex) {
+               longjmp(handler->loc, 1);
+       }
+       if (quoted == 0)
+               recordregion(startloc, dest - stackblock(), 0);
+       TRACE(("evalbackq: size=%d: \"%.*s\"\n",
+               (dest - stackblock()) - startloc,
+               (dest - stackblock()) - startloc,
+               stackblock() + startloc));
+       expdest = dest;
+       INTON;
+}
+
+
+
+static int
+subevalvar(p, str, strloc, subtype, startloc, varflags, quotes)
+       char *p;
+       char *str;
+       int strloc;
+       int subtype;
+       int startloc;
+       int varflags;
+       int quotes;
+{
+       char *startp;
+       char *loc = NULL;
+       char *q;
+       int c = 0;
+       int saveherefd = herefd;
+       struct nodelist *saveargbackq = argbackq;
+       int amount;
+
+       herefd = -1;
+       argstr(p, subtype != VSASSIGN && subtype != VSQUESTION ? EXP_CASE : 0);
+       STACKSTRNUL(expdest);
+       herefd = saveherefd;
+       argbackq = saveargbackq;
+       startp = stackblock() + startloc;
+       if (str == NULL)
+           str = stackblock() + strloc;
+
+       switch (subtype) {
+       case VSASSIGN:
+               setvar(str, startp, 0);
+               amount = startp - expdest;
+               STADJUST(amount, expdest);
+               varflags &= ~VSNUL;
+               if (c != 0)
+                       *loc = c;
+               return 1;
+
+       case VSQUESTION:
+               if (*p != CTLENDVAR) {
+                       outfmt(&errout, snlfmt, startp);
+                       error((char *)NULL);
+               }
+               error("%.*s: parameter %snot set", p - str - 1,
+                     str, (varflags & VSNUL) ? "null or "
+                                             : nullstr);
+               /* NOTREACHED */
+
+       case VSTRIMLEFT:
+               for (loc = startp; loc < str; loc++) {
+                       c = *loc;
+                       *loc = '\0';
+                       if (patmatch2(str, startp, quotes))
+                               goto recordleft;
+                       *loc = c;
+                       if (quotes && *loc == CTLESC)
+                               loc++;
+               }
+               return 0;
+
+       case VSTRIMLEFTMAX:
+               for (loc = str - 1; loc >= startp;) {
+                       c = *loc;
+                       *loc = '\0';
+                       if (patmatch2(str, startp, quotes))
+                               goto recordleft;
+                       *loc = c;
+                       loc--;
+                       if (quotes && loc > startp && *(loc - 1) == CTLESC) {
+                               for (q = startp; q < loc; q++)
+                                       if (*q == CTLESC)
+                                               q++;
+                               if (q > loc)
+                                       loc--;
+                       }
+               }
+               return 0;
+
+       case VSTRIMRIGHT:
+               for (loc = str - 1; loc >= startp;) {
+                       if (patmatch2(str, loc, quotes))
+                               goto recordright;
+                       loc--;
+                       if (quotes && loc > startp && *(loc - 1) == CTLESC) { 
+                               for (q = startp; q < loc; q++)
+                                       if (*q == CTLESC)
+                                               q++;
+                               if (q > loc)
+                                       loc--;
+                       }
+               }
+               return 0;
+
+       case VSTRIMRIGHTMAX:
+               for (loc = startp; loc < str - 1; loc++) {
+                       if (patmatch2(str, loc, quotes))
+                               goto recordright;
+                       if (quotes && *loc == CTLESC)
+                               loc++;
+               }
+               return 0;
+
+#ifdef DEBUG
+       default:
+               abort();
+#endif
+       }
+
+recordleft:
+       *loc = c;
+       amount = ((str - 1) - (loc - startp)) - expdest;
+       STADJUST(amount, expdest);
+       while (loc != str - 1)
+               *startp++ = *loc++;
+       return 1;
+
+recordright:
+       amount = loc - expdest;
+       STADJUST(amount, expdest);
+       STPUTC('\0', expdest);
+       STADJUST(-1, expdest);
+       return 1;
+}
+
+
+/*
+ * Expand a variable, and return a pointer to the next character in the
+ * input string.
+ */
+
+static char *
+evalvar(p, flag)
+       char *p;
+       int flag;
+{
+       int subtype;
+       int varflags;
+       char *var;
+       char *val;
+       int patloc;
+       int c;
+       int set;
+       int special;
+       int startloc;
+       int varlen;
+       int easy;
+       int quotes = flag & (EXP_FULL | EXP_CASE);
+
+       varflags = *p++;
+       subtype = varflags & VSTYPE;
+       var = p;
+       special = 0;
+       if (! is_name(*p))
+               special = 1;
+       p = strchr(p, '=') + 1;
+again: /* jump here after setting a variable with ${var=text} */
+       if (special) {
+               set = varisset(var, varflags & VSNUL);
+               val = NULL;
+       } else {
+               val = lookupvar(var);
+               if (val == NULL || ((varflags & VSNUL) && val[0] == '\0')) {
+                       val = NULL;
+                       set = 0;
+               } else
+                       set = 1;
+       }
+       varlen = 0;
+       startloc = expdest - stackblock();
+       if (set && subtype != VSPLUS) {
+               /* insert the value of the variable */
+               if (special) {
+                       varvalue(var, varflags & VSQUOTE, flag);
+                       if (subtype == VSLENGTH) {
+                               varlen = expdest - stackblock() - startloc;
+                               STADJUST(-varlen, expdest);
+                       }
+               } else {
+                       if (subtype == VSLENGTH) {
+                               varlen = strlen(val);
+                       } else {
+                               strtodest(
+                                       val,
+                                       varflags & VSQUOTE ?
+                                               DQSYNTAX : BASESYNTAX,
+                                       quotes
+                               );
+                       }
+               }
+       }
+
+       if (subtype == VSPLUS)
+               set = ! set;
+
+       easy = ((varflags & VSQUOTE) == 0 ||
+               (*var == '@' && shellparam.nparam != 1));
+
+
+       switch (subtype) {
+       case VSLENGTH:
+               expdest = cvtnum(varlen, expdest);
+               goto record;
+
+       case VSNORMAL:
+               if (!easy)
+                       break;
+record:
+               recordregion(startloc, expdest - stackblock(),
+                            varflags & VSQUOTE);
+               break;
+
+       case VSPLUS:
+       case VSMINUS:
+               if (!set) {
+                       argstr(p, flag);
+                       break;
+               }
+               if (easy)
+                       goto record;
+               break;
+
+       case VSTRIMLEFT:
+       case VSTRIMLEFTMAX:
+       case VSTRIMRIGHT:
+       case VSTRIMRIGHTMAX:
+               if (!set)
+                       break;
+               /*
+                * Terminate the string and start recording the pattern
+                * right after it
+                */
+               STPUTC('\0', expdest);
+               patloc = expdest - stackblock();
+               if (subevalvar(p, NULL, patloc, subtype,
+                              startloc, varflags, quotes) == 0) {
+                       int amount = (expdest - stackblock() - patloc) + 1;
+                       STADJUST(-amount, expdest);
+               }
+               /* Remove any recorded regions beyond start of variable */
+               removerecordregions(startloc);
+               goto record;
+
+       case VSASSIGN:
+       case VSQUESTION:
+               if (!set) {
+                       if (subevalvar(p, var, 0, subtype, startloc,
+                                      varflags, quotes)) {
+                               varflags &= ~VSNUL;
+                               /* 
+                                * Remove any recorded regions beyond 
+                                * start of variable 
+                                */
+                               removerecordregions(startloc);
+                               goto again;
+                       }
+                       break;
+               }
+               if (easy)
+                       goto record;
+               break;
+
+#ifdef DEBUG
+       default:
+               abort();
+#endif
+       }
+
+       if (subtype != VSNORMAL) {      /* skip to end of alternative */
+               int nesting = 1;
+               for (;;) {
+                       if ((c = *p++) == CTLESC)
+                               p++;
+                       else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) {
+                               if (set)
+                                       argbackq = argbackq->next;
+                       } else if (c == CTLVAR) {
+                               if ((*p++ & VSTYPE) != VSNORMAL)
+                                       nesting++;
+                       } else if (c == CTLENDVAR) {
+                               if (--nesting == 0)
+                                       break;
+                       }
+               }
+       }
+       return p;
+}
+
+
+
+/*
+ * Test whether a specialized variable is set.
+ */
+
+static int
+varisset(name, nulok)
+       char *name;
+       int nulok;
+{
+       if (*name == '!')
+               return backgndpid != -1;
+       else if (*name == '@' || *name == '*') {
+               if (*shellparam.p == NULL)
+                       return 0;
+
+               if (nulok) {
+                       char **av;
+
+                       for (av = shellparam.p; *av; av++)
+                               if (**av != '\0')
+                                       return 1;
+                       return 0;
+               }
+       } else if (is_digit(*name)) {
+               char *ap;
+               int num = atoi(name);
+
+               if (num > shellparam.nparam)
+                       return 0;
+
+               if (num == 0)
+                       ap = arg0;
+               else
+                       ap = shellparam.p[num - 1];
+
+               if (nulok && (ap == NULL || *ap == '\0'))
+                       return 0;
+       }
+       return 1;
+}
+
+
+
+/*
+ * Put a string on the stack.
+ */
+
+static void
+strtodest(p, syntax, quotes)
+       const char *p;
+       const char *syntax;
+       int quotes;
+{
+       while (*p) {
+               if (quotes && syntax[(int) *p] == CCTL)
+                       STPUTC(CTLESC, expdest);
+               STPUTC(*p++, expdest);
+       }
+}
+
+
+
+/*
+ * Add the value of a specialized variable to the stack string.
+ */
+
+static void
+varvalue(name, quoted, flags)
+       char *name;
+       int quoted;
+       int flags;
+{
+       int num;
+       char *p;
+       int i;
+       int sep;
+       int sepq = 0;
+       char **ap;
+       char const *syntax;
+       int allow_split = flags & EXP_FULL;
+       int quotes = flags & (EXP_FULL | EXP_CASE);
+
+       syntax = quoted ? DQSYNTAX : BASESYNTAX;
+       switch (*name) {
+       case '$':
+               num = rootpid;
+               goto numvar;
+       case '?':
+               num = oexitstatus;
+               goto numvar;
+       case '#':
+               num = shellparam.nparam;
+               goto numvar;
+       case '!':
+               num = backgndpid;
+numvar:
+               expdest = cvtnum(num, expdest);
+               break;
+       case '-':
+               for (i = 0 ; i < NOPTS ; i++) {
+                       if (optlist[i].val)
+                               STPUTC(optlist[i].letter, expdest);
+               }
+               break;
+       case '@':
+               if (allow_split && quoted) {
+                       sep = 1 << CHAR_BIT;
+                       goto param;
+               }
+               /* fall through */
+       case '*':
+               sep = ifsset() ? ifsval()[0] : ' ';
+               if (quotes) {
+                       sepq = syntax[(int) sep] == CCTL;
+               }
+param:
+               for (ap = shellparam.p ; (p = *ap++) != NULL ; ) {
+                       strtodest(p, syntax, quotes);
+                       if (*ap && sep) {
+                               if (sepq)
+                                       STPUTC(CTLESC, expdest);
+                               STPUTC(sep, expdest);
+                       }
+               }
+               break;
+       case '0':
+               strtodest(arg0, syntax, quotes);
+               break;
+       default:
+               num = atoi(name);
+               if (num > 0 && num <= shellparam.nparam) {
+                       strtodest(shellparam.p[num - 1], syntax, quotes);
+               }
+               break;
+       }
+}
+
+
+
+/*
+ * Record the fact that we have to scan this region of the
+ * string for IFS characters.
+ */
+
+static void
+recordregion(start, end, nulonly)
+       int start;
+       int end;
+       int nulonly;
+{
+       struct ifsregion *ifsp;
+
+       if (ifslastp == NULL) {
+               ifsp = &ifsfirst;
+       } else {
+               INTOFF;
+               ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion));
+               ifsp->next = NULL;
+               ifslastp->next = ifsp;
+               INTON;
+       }
+       ifslastp = ifsp;
+       ifslastp->begoff = start;
+       ifslastp->endoff = end;
+       ifslastp->nulonly = nulonly;
+}
+
+
+
+/*
+ * Break the argument string into pieces based upon IFS and add the
+ * strings to the argument list.  The regions of the string to be
+ * searched for IFS characters have been stored by recordregion.
+ */
+static void
+ifsbreakup(string, arglist)
+       char *string;
+       struct arglist *arglist;
+       {
+       struct ifsregion *ifsp;
+       struct strlist *sp;
+       char *start;
+       char *p;
+       char *q;
+       const char *ifs, *realifs;
+       int ifsspc;
+       int nulonly;
+
+
+       start = string;
+       ifsspc = 0;
+       nulonly = 0;
+       realifs = ifsset() ? ifsval() : defifs;
+       if (ifslastp != NULL) {
+               ifsp = &ifsfirst;
+               do {
+                       p = string + ifsp->begoff;
+                       nulonly = ifsp->nulonly;
+                       ifs = nulonly ? nullstr : realifs;
+                       ifsspc = 0;
+                       while (p < string + ifsp->endoff) {
+                               q = p;
+                               if (*p == CTLESC)
+                                       p++;
+                               if (strchr(ifs, *p)) {
+                                       if (!nulonly)
+                                               ifsspc = (strchr(defifs, *p) != NULL);
+                                       /* Ignore IFS whitespace at start */
+                                       if (q == start && ifsspc) {
+                                               p++;
+                                               start = p;
+                                               continue;
+                                       }
+                                       *q = '\0';
+                                       sp = (struct strlist *)stalloc(sizeof *sp);
+                                       sp->text = start;
+                                       *arglist->lastp = sp;
+                                       arglist->lastp = &sp->next;
+                                       p++;
+                                       if (!nulonly) {
+                                               for (;;) {
+                                                       if (p >= string + ifsp->endoff) {
+                                                               break;
+                                                       }
+                                                       q = p;
+                                                       if (*p == CTLESC)
+                                                               p++;
+                                                       if (strchr(ifs, *p) == NULL ) {
+                                                               p = q;
+                                                               break;
+                                                       } else if (strchr(defifs, *p) == NULL) {
+                                                               if (ifsspc) {
+                                                                       p++;
+                                                                       ifsspc = 0;
+                                                               } else {
+                                                                       p = q;
+                                                                       break;
+                                                               }
+                                                       } else
+                                                               p++;
+                                               }
+                                       }
+                                       start = p;
+                               } else
+                                       p++;
+                       }
+               } while ((ifsp = ifsp->next) != NULL);
+               if (!(*start || (!ifsspc && start > string && nulonly))) {
+                       return;
+               }
+       }
+
+       sp = (struct strlist *)stalloc(sizeof *sp);
+       sp->text = start;
+       *arglist->lastp = sp;
+       arglist->lastp = &sp->next;
+}
+
+static void
+ifsfree()
+{
+       while (ifsfirst.next != NULL) {
+               struct ifsregion *ifsp;
+               INTOFF;
+               ifsp = ifsfirst.next->next;
+               ckfree(ifsfirst.next);
+               ifsfirst.next = ifsp;
+               INTON;
+       }
+       ifslastp = NULL;
+       ifsfirst.next = NULL;
+}
+
+
+
+/*
+ * Expand shell metacharacters.  At this point, the only control characters
+ * should be escapes.  The results are stored in the list exparg.
+ */
+
+#if defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN)
+static void
+expandmeta(str, flag)
+       struct strlist *str;
+       int flag;
+{
+       const char *p;
+       glob_t pglob;
+       /* TODO - EXP_REDIR */
+
+       while (str) {
+               if (fflag)
+                       goto nometa;
+               p = preglob(str->text);
+               INTOFF;
+               switch (glob(p, GLOB_NOMAGIC, 0, &pglob)) {
+               case 0:
+                       if (!(pglob.gl_flags & GLOB_MAGCHAR))
+                               goto nometa2;
+                       addglob(&pglob);
+                       globfree(&pglob);
+                       INTON;
+                       break;
+               case GLOB_NOMATCH:
+nometa2:
+                       globfree(&pglob);
+                       INTON;
+nometa:
+                       *exparg.lastp = str;
+                       rmescapes(str->text);
+                       exparg.lastp = &str->next;
+                       break;
+               default:        /* GLOB_NOSPACE */
+                       error("Out of space");
+               }
+               str = str->next;
+       }
+}
+
+
+/*
+ * Add the result of glob(3) to the list.
+ */
+
+static void
+addglob(pglob)
+       const glob_t *pglob;
+{
+       char **p = pglob->gl_pathv;
+
+       do {
+               addfname(*p);
+       } while (*++p);
+}
+
+
+#else  /* defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN) */
+static char *expdir;
+
+
+static void
+expandmeta(str, flag)
+       struct strlist *str;
+       int flag;
+{
+       char *p;
+       struct strlist **savelastp;
+       struct strlist *sp;
+       char c;
+       /* TODO - EXP_REDIR */
+
+       while (str) {
+               if (fflag)
+                       goto nometa;
+               p = str->text;
+               for (;;) {                      /* fast check for meta chars */
+                       if ((c = *p++) == '\0')
+                               goto nometa;
+                       if (c == '*' || c == '?' || c == '[' || c == '!')
+                               break;
+               }
+               savelastp = exparg.lastp;
+               INTOFF;
+               if (expdir == NULL) {
+                       int i = strlen(str->text);
+                       expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */
+               }
+
+               expmeta(expdir, str->text);
+               ckfree(expdir);
+               expdir = NULL;
+               INTON;
+               if (exparg.lastp == savelastp) {
+                       /*
+                        * no matches
+                        */
+nometa:
+                       *exparg.lastp = str;
+                       rmescapes(str->text);
+                       exparg.lastp = &str->next;
+               } else {
+                       *exparg.lastp = NULL;
+                       *savelastp = sp = expsort(*savelastp);
+                       while (sp->next != NULL)
+                               sp = sp->next;
+                       exparg.lastp = &sp->next;
+               }
+               str = str->next;
+       }
+}
+
+
+/*
+ * Do metacharacter (i.e. *, ?, [...]) expansion.
+ */
+
+static void
+expmeta(enddir, name)
+       char *enddir;
+       char *name;
+       {
+       char *p;
+       const char *cp;
+       char *q;
+       char *start;
+       char *endname;
+       int metaflag;
+       struct stat statb;
+       DIR *dirp;
+       struct dirent *dp;
+       int atend;
+       int matchdot;
+
+       metaflag = 0;
+       start = name;
+       for (p = name ; ; p++) {
+               if (*p == '*' || *p == '?')
+                       metaflag = 1;
+               else if (*p == '[') {
+                       q = p + 1;
+                       if (*q == '!')
+                               q++;
+                       for (;;) {
+                               while (*q == CTLQUOTEMARK)
+                                       q++;
+                               if (*q == CTLESC)
+                                       q++;
+                               if (*q == '/' || *q == '\0')
+                                       break;
+                               if (*++q == ']') {
+                                       metaflag = 1;
+                                       break;
+                               }
+                       }
+               } else if (*p == '!' && p[1] == '!'     && (p == name || p[-1] == '/')) {
+                       metaflag = 1;
+               } else if (*p == '\0')
+                       break;
+               else if (*p == CTLQUOTEMARK)
+                       continue;
+               else if (*p == CTLESC)
+                       p++;
+               if (*p == '/') {
+                       if (metaflag)
+                               break;
+                       start = p + 1;
+               }
+       }
+       if (metaflag == 0) {    /* we've reached the end of the file name */
+               if (enddir != expdir)
+                       metaflag++;
+               for (p = name ; ; p++) {
+                       if (*p == CTLQUOTEMARK)
+                               continue;
+                       if (*p == CTLESC)
+                               p++;
+                       *enddir++ = *p;
+                       if (*p == '\0')
+                               break;
+               }
+               if (metaflag == 0 || lstat(expdir, &statb) >= 0)
+                       addfname(expdir);
+               return;
+       }
+       endname = p;
+       if (start != name) {
+               p = name;
+               while (p < start) {
+                       while (*p == CTLQUOTEMARK)
+                               p++;
+                       if (*p == CTLESC)
+                               p++;
+                       *enddir++ = *p++;
+               }
+       }
+       if (enddir == expdir) {
+               cp = ".";
+       } else if (enddir == expdir + 1 && *expdir == '/') {
+               cp = "/";
+       } else {
+               cp = expdir;
+               enddir[-1] = '\0';
+       }
+       if ((dirp = opendir(cp)) == NULL)
+               return;
+       if (enddir != expdir)
+               enddir[-1] = '/';
+       if (*endname == 0) {
+               atend = 1;
+       } else {
+               atend = 0;
+               *endname++ = '\0';
+       }
+       matchdot = 0;
+       p = start;
+       while (*p == CTLQUOTEMARK)
+               p++;
+       if (*p == CTLESC)
+               p++;
+       if (*p == '.')
+               matchdot++;
+       while (! int_pending() && (dp = readdir(dirp)) != NULL) {
+               if (dp->d_name[0] == '.' && ! matchdot)
+                       continue;
+               if (patmatch(start, dp->d_name, 0)) {
+                       if (atend) {
+                               scopy(dp->d_name, enddir);
+                               addfname(expdir);
+                       } else {
+                               for (p = enddir, cp = dp->d_name;
+                                    (*p++ = *cp++) != '\0';)
+                                       continue;
+                               p[-1] = '/';
+                               expmeta(p, endname);
+                       }
+               }
+       }
+       closedir(dirp);
+       if (! atend)
+               endname[-1] = '/';
+}
+#endif /* defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN) */
+
+
+/*
+ * Add a file name to the list.
+ */
+
+static void
+addfname(name)
+       char *name;
+       {
+       char *p;
+       struct strlist *sp;
+
+       p = sstrdup(name);
+       sp = (struct strlist *)stalloc(sizeof *sp);
+       sp->text = p;
+       *exparg.lastp = sp;
+       exparg.lastp = &sp->next;
+}
+
+
+#if !(defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN))
+/*
+ * Sort the results of file name expansion.  It calculates the number of
+ * strings to sort and then calls msort (short for merge sort) to do the
+ * work.
+ */
+
+static struct strlist *
+expsort(str)
+       struct strlist *str;
+       {
+       int len;
+       struct strlist *sp;
+
+       len = 0;
+       for (sp = str ; sp ; sp = sp->next)
+               len++;
+       return msort(str, len);
+}
+
+
+static struct strlist *
+msort(list, len)
+       struct strlist *list;
+       int len;
+{
+       struct strlist *p, *q = NULL;
+       struct strlist **lpp;
+       int half;
+       int n;
+
+       if (len <= 1)
+               return list;
+       half = len >> 1;
+       p = list;
+       for (n = half ; --n >= 0 ; ) {
+               q = p;
+               p = p->next;
+       }
+       q->next = NULL;                 /* terminate first half of list */
+       q = msort(list, half);          /* sort first half of list */
+       p = msort(p, len - half);               /* sort second half */
+       lpp = &list;
+       for (;;) {
+               if (strcmp(p->text, q->text) < 0) {
+                       *lpp = p;
+                       lpp = &p->next;
+                       if ((p = *lpp) == NULL) {
+                               *lpp = q;
+                               break;
+                       }
+               } else {
+                       *lpp = q;
+                       lpp = &q->next;
+                       if ((q = *lpp) == NULL) {
+                               *lpp = p;
+                               break;
+                       }
+               }
+       }
+       return list;
+}
+#endif
+
+
+
+/*
+ * Returns true if the pattern matches the string.
+ */
+
+#if defined(__GLIBC__) && !defined(FNMATCH_BROKEN)
+static int
+patmatch(pattern, string, squoted)
+       char *pattern;
+       char *string;
+       int squoted;    /* string might have quote chars */
+       {
+       const char *p;
+       char *q;
+
+       p = preglob(pattern);
+       q = squoted ? _rmescapes(string, RMESCAPE_ALLOC) : string;
+
+       return !fnmatch(p, q, 0);
+}
+
+
+static int
+patmatch2(pattern, string, squoted)
+       char *pattern;
+       char *string;
+       int squoted;    /* string might have quote chars */
+       {
+       char *p;
+       int res;
+
+       sstrnleft--;
+       p = grabstackstr(expdest);
+       res = patmatch(pattern, string, squoted);
+       ungrabstackstr(p, expdest);
+       return res;
+}
+#else
+static int
+patmatch(pattern, string, squoted)
+       char *pattern;
+       char *string;
+       int squoted;    /* string might have quote chars */
+       {
+#ifdef notdef
+       if (pattern[0] == '!' && pattern[1] == '!')
+               return 1 - pmatch(pattern + 2, string);
+       else
+#endif
+               return pmatch(pattern, string, squoted);
+}
+
+
+static int
+pmatch(pattern, string, squoted)
+       char *pattern;
+       char *string;
+       int squoted;
+       {
+       char *p, *q;
+       char c;
+
+       p = pattern;
+       q = string;
+       for (;;) {
+               switch (c = *p++) {
+               case '\0':
+                       goto breakloop;
+               case CTLESC:
+                       if (squoted && *q == CTLESC)
+                               q++;
+                       if (*q++ != *p++)
+                               return 0;
+                       break;
+               case CTLQUOTEMARK:
+                       continue;
+               case '?':
+                       if (squoted && *q == CTLESC)
+                               q++;
+                       if (*q++ == '\0')
+                               return 0;
+                       break;
+               case '*':
+                       c = *p;
+                       while (c == CTLQUOTEMARK || c == '*')
+                               c = *++p;
+                       if (c != CTLESC &&  c != CTLQUOTEMARK &&
+                           c != '?' && c != '*' && c != '[') {
+                               while (*q != c) {
+                                       if (squoted && *q == CTLESC &&
+                                           q[1] == c)
+                                               break;
+                                       if (*q == '\0')
+                                               return 0;
+                                       if (squoted && *q == CTLESC)
+                                               q++;
+                                       q++;
+                               }
+                       }
+                       do {
+                               if (pmatch(p, q, squoted))
+                                       return 1;
+                               if (squoted && *q == CTLESC)
+                                       q++;
+                       } while (*q++ != '\0');
+                       return 0;
+               case '[': {
+                       char *endp;
+                       int invert, found;
+                       char chr;
+
+                       endp = p;
+                       if (*endp == '!')
+                               endp++;
+                       for (;;) {
+                               while (*endp == CTLQUOTEMARK)
+                                       endp++;
+                               if (*endp == '\0')
+                                       goto dft;               /* no matching ] */
+                               if (*endp == CTLESC)
+                                       endp++;
+                               if (*++endp == ']')
+                                       break;
+                       }
+                       invert = 0;
+                       if (*p == '!') {
+                               invert++;
+                               p++;
+                       }
+                       found = 0;
+                       chr = *q++;
+                       if (squoted && chr == CTLESC)
+                               chr = *q++;
+                       if (chr == '\0')
+                               return 0;
+                       c = *p++;
+                       do {
+                               if (c == CTLQUOTEMARK)
+                                       continue;
+                               if (c == CTLESC)
+                                       c = *p++;
+                               if (*p == '-' && p[1] != ']') {
+                                       p++;
+                                       while (*p == CTLQUOTEMARK)
+                                               p++;
+                                       if (*p == CTLESC)
+                                               p++;
+                                       if (chr >= c && chr <= *p)
+                                               found = 1;
+                                       p++;
+                               } else {
+                                       if (chr == c)
+                                               found = 1;
+                               }
+                       } while ((c = *p++) != ']');
+                       if (found == invert)
+                               return 0;
+                       break;
+               }
+dft:           default:
+                       if (squoted && *q == CTLESC)
+                               q++;
+                       if (*q++ != c)
+                               return 0;
+                       break;
+               }
+       }
+breakloop:
+       if (*q != '\0')
+               return 0;
+       return 1;
+}
+#endif
+
+
+
+/*
+ * Remove any CTLESC characters from a string.
+ */
+
+#if defined(__GLIBC__) && !defined(FNMATCH_BROKEN)
+static char *
+_rmescapes(str, flag)
+       char *str;
+       int flag;
+{
+       char *p, *q, *r;
+       static const char qchars[] = { CTLESC, CTLQUOTEMARK, 0 };
+
+       p = strpbrk(str, qchars);
+       if (!p) {
+               return str;
+       }
+       q = p;
+       r = str;
+       if (flag & RMESCAPE_ALLOC) {
+               size_t len = p - str;
+               q = r = stalloc(strlen(p) + len + 1);
+               if (len > 0) {
+#ifdef _GNU_SOURCE
+                       q = mempcpy(q, str, len);
+#else
+                       memcpy(q, str, len);
+                       q += len;
+#endif
+               }
+       }
+       while (*p) {
+               if (*p == CTLQUOTEMARK) {
+                       p++;
+                       continue;
+               }
+               if (*p == CTLESC) {
+                       p++;
+                       if (flag & RMESCAPE_GLOB && *p != '/') {
+                               *q++ = '\\';
+                       }
+               }
+               *q++ = *p++;
+       }
+       *q = '\0';
+       return r;
+}
+#else
+static void
+rmescapes(str)
+       char *str;
+{
+       char *p, *q;
+
+       p = str;
+       while (*p != CTLESC && *p != CTLQUOTEMARK) {
+               if (*p++ == '\0')
+                       return;
+       }
+       q = p;
+       while (*p) {
+               if (*p == CTLQUOTEMARK) {
+                       p++;
+                       continue;
+               }
+               if (*p == CTLESC)
+                       p++;
+               *q++ = *p++;
+       }
+       *q = '\0';
+}
+#endif
+
+
+
+/*
+ * See if a pattern matches in a case statement.
+ */
+
+static int
+casematch(pattern, val)
+       union node *pattern;
+       char *val;
+       {
+       struct stackmark smark;
+       int result;
+       char *p;
+
+       setstackmark(&smark);
+       argbackq = pattern->narg.backquote;
+       STARTSTACKSTR(expdest);
+       ifslastp = NULL;
+       argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
+       STPUTC('\0', expdest);
+       p = grabstackstr(expdest);
+       result = patmatch(p, val, 0);
+       popstackmark(&smark);
+       return result;
+}
+
+/*
+ * Our own itoa().
+ */
+
+static char *
+cvtnum(num, buf)
+       int num;
+       char *buf;
+       {
+       int len;
+
+       CHECKSTRSPACE(32, buf);
+       len = sprintf(buf, "%d", num);
+       STADJUST(len, buf);
+       return buf;
+}
+/*     $NetBSD: histedit.c,v 1.25 2001/02/04 19:52:06 christos Exp $   */
+
+/*-
+ * Copyright (c) 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Editline and history functions (and glue).
+ */
+static int histcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       error("not compiled with history support");
+       /* NOTREACHED */
+}
+
+
+/*
+ * This file was generated by the mkinit program.
+ */
+
+extern void rmaliases __P((void));
+
+extern int loopnest;           /* current loop nesting level */
+
+extern void deletefuncs __P((void));
+
+struct strpush {
+       struct strpush *prev;   /* preceding string on stack */
+       char *prevstring;
+       int prevnleft;
+       struct alias *ap;       /* if push was associated with an alias */
+       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 */
+};
+
+extern int parselleft;         /* copy of parsefile->lleft */
+extern struct parsefile basepf;        /* top level input file */
+extern char basebuf[BUFSIZ];   /* buffer for top level input file */
+
+extern short backgndpid;       /* pid of last background process */
+extern int jobctl;
+
+extern int tokpushback;                /* last token pushed back */
+extern int checkkwd;            /* 1 == check for kwds, 2 == also eat newlines */
+
+struct redirtab {
+       struct redirtab *next;
+       short renamed[10];
+};
+
+extern struct redirtab *redirlist;
+
+extern char sigmode[NSIG - 1]; /* current value of signal */
+
+extern char **environ;
+
+
+
+/*
+ * Initialization code.
+ */
+
+static void
+init() {
+
+      /* from cd.c: */
+      {
+             setpwd(0, 0);
+      }
+
+      /* from input.c: */
+      {
+             basepf.nextc = basepf.buf = basebuf;
+      }
+
+      /* from output.c: */
+      {
+#ifdef USE_GLIBC_STDIO
+             initstreams();
+#endif
+      }
+
+      /* from var.c: */
+      {
+             char **envp;
+             char ppid[32];
+
+             initvar();
+             for (envp = environ ; *envp ; envp++) {
+                     if (strchr(*envp, '=')) {
+                             setvareq(*envp, VEXPORT|VTEXTFIXED);
+                     }
+             }
+
+             fmtstr(ppid, sizeof(ppid), "%d", (int) getppid());
+             setvar("PPID", ppid, 0);
+      }
+}
+
+
+
+/*
+ * This routine is called when an error or an interrupt occurs in an
+ * interactive shell and control is returned to the main command loop.
+ */
+
+static void
+reset() {
+
+      /* from eval.c: */
+      {
+             evalskip = 0;
+             loopnest = 0;
+             funcnest = 0;
+      }
+
+      /* from input.c: */
+      {
+             if (exception != EXSHELLPROC)
+                     parselleft = parsenleft = 0;      /* clear input buffer */
+             popallfiles();
+      }
+
+      /* from parser.c: */
+      {
+             tokpushback = 0;
+             checkkwd = 0;
+             checkalias = 0;
+      }
+
+      /* from redir.c: */
+      {
+             while (redirlist)
+                     popredir();
+      }
+
+      /* from output.c: */
+      {
+             out1 = &output;
+             out2 = &errout;
+#ifdef USE_GLIBC_STDIO
+             if (memout.stream != NULL)
+                     __closememout();
+#endif
+             if (memout.buf != NULL) {
+                     ckfree(memout.buf);
+                     memout.buf = NULL;
+             }
+      }
+}
+
+
+
+/*
+ * This routine is called to initialize the shell to run a shell procedure.
+ */
+
+static void
+initshellproc() {
+
+      /* from alias.c: */
+      {
+             rmaliases();
+      }
+
+      /* from eval.c: */
+      {
+             exitstatus = 0;
+      }
+
+      /* from exec.c: */
+      {
+             deletefuncs();
+      }
+
+      /* from jobs.c: */
+      {
+             backgndpid = -1;
+#if JOBS
+             jobctl = 0;
+#endif
+      }
+
+      /* from options.c: */
+      {
+             int i;
+
+             for (i = 0; i < NOPTS; i++)
+                     optlist[i].val = 0;
+             optschanged();
+
+      }
+
+      /* from redir.c: */
+      {
+             clearredir();
+      }
+
+      /* from trap.c: */
+      {
+             char *sm;
+
+             clear_traps();
+             for (sm = sigmode ; sm < sigmode + NSIG - 1; sm++) {
+                     if (*sm == S_IGN)
+                             *sm = S_HARD_IGN;
+             }
+      }
+
+      /* from var.c: */
+      {
+             shprocvar();
+      }
+}
+/*     $NetBSD: input.c,v 1.35 2001/02/04 19:52:06 christos Exp $      */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file implements the input routines used by the parser.
+ */
+
+#ifdef BB_FEATURE_COMMAND_EDITING
+unsigned int shell_context;
+static const char * cmdedit_prompt;
+static inline void putprompt(const char *s) {
+    cmdedit_prompt = s;
+}
+#else
+static inline void putprompt(const char *s) {
+    out2str(s);
+}
+#endif
+
+#define EOF_NLEFT -99          /* value of parsenleft when EOF pushed back */
+
+static int plinno = 1;                 /* input line number */
+static int parsenleft;                 /* copy of parsefile->nleft */
+static int parselleft;         /* copy of parsefile->lleft */
+static char *parsenextc;               /* copy of parsefile->nextc */
+struct parsefile basepf;       /* top level input file */
+static char basebuf[BUFSIZ];   /* buffer for top level input file */
+struct parsefile *parsefile = &basepf; /* current input file */
+static int whichprompt;                /* 1 == PS1, 2 == PS2 */
+
+static void pushfile __P((void));
+static int preadfd __P((void));
+
+#ifdef mkinit
+INCLUDE <stdio.h>
+INCLUDE "input.h"
+INCLUDE "error.h"
+
+INIT {
+       basepf.nextc = basepf.buf = basebuf;
+}
+
+RESET {
+       if (exception != EXSHELLPROC)
+               parselleft = parsenleft = 0;    /* clear input buffer */
+       popallfiles();
+}
+#endif
+
+
+/*
+ * Read a line from the script.
+ */
+
+static char *
+pfgets(line, len)
+       char *line;
+       int len;
+{
+       char *p = line;
+       int nleft = len;
+       int c;
+
+       while (--nleft > 0) {
+               c = pgetc2();
+               if (c == PEOF) {
+                       if (p == line)
+                               return NULL;
+                       break;
+               }
+               *p++ = c;
+               if (c == '\n')
+                       break;
+       }
+       *p = '\0';
+       return line;
+}
+
+
+/*
+ * Read a character from the script, returning PEOF on end of file.
+ * Nul characters in the input are silently discarded.
+ */
+
+static int
+pgetc()
+{
+       return pgetc_macro();
+}
+
+
+/*
+ * Same as pgetc(), but ignores PEOA.
+ */
+
+static int
+pgetc2()
+{
+       int c;
+       do {
+               c = pgetc_macro();
+       } while (c == PEOA);
+       return c;
+}
+
+
+static int
+preadfd()
+{
+    int nr;
+    char *buf =  parsefile->buf;
+    parsenextc = buf;
+
+retry:
+#ifdef BB_FEATURE_COMMAND_EDITING
+       {
+           if (parsefile->fd)
+               nr = read(parsefile->fd, buf, BUFSIZ - 1);
+           else { 
+               do {
+                   cmdedit_read_input((char*)cmdedit_prompt, buf);
+                   nr = strlen(buf);
+               } while (nr <=0 || shell_context);
+               cmdedit_terminate();
+           }
+       }
+#else
+       nr = read(parsefile->fd, buf, BUFSIZ - 1);
+#endif
+
+       if (nr < 0) {
+               if (errno == EINTR)
+                       goto retry;
+               if (parsefile->fd == 0 && errno == EWOULDBLOCK) {
+                       int flags = fcntl(0, F_GETFL, 0);
+                       if (flags >= 0 && flags & O_NONBLOCK) {
+                               flags &=~ O_NONBLOCK;
+                               if (fcntl(0, F_SETFL, flags) >= 0) {
+                                       out2str("sh: turning off NDELAY mode\n");
+                                       goto retry;
+                               }
+                       }
+               }
+       }
+       return nr;
+}
+
+/*
+ * Refill the input buffer and return the next input character:
+ *
+ * 1) If a string was pushed back on the input, pop it;
+ * 2) If an EOF was pushed back (parsenleft == EOF_NLEFT) or we are reading
+ *    from a string so we can't refill the buffer, return EOF.
+ * 3) If the is more stuff in this buffer, use it else call read to fill it.
+ * 4) Process input up to the next newline, deleting nul characters.
+ */
+
+static int
+preadbuffer()
+{
+       char *p, *q;
+       int more;
+       char savec;
+
+       while (parsefile->strpush) {
+               if (
+                       parsenleft == -1 && parsefile->strpush->ap &&
+                       parsenextc[-1] != ' ' && parsenextc[-1] != '\t'
+               ) {
+                       return PEOA;
+               }
+               popstring();
+               if (--parsenleft >= 0)
+                       return (*parsenextc++);
+       }
+       if (parsenleft == EOF_NLEFT || parsefile->buf == NULL)
+               return PEOF;
+       flushout(&output);
+#ifdef FLUSHERR
+       flushout(&errout);
+#endif
+
+again:
+       if (parselleft <= 0) {
+               if ((parselleft = preadfd()) <= 0) {
+                       parselleft = parsenleft = EOF_NLEFT;
+                       return PEOF;
+               }
+       }
+
+       q = p = parsenextc;
+
+       /* delete nul characters */
+       for (more = 1; more;) {
+               switch (*p) {
+               case '\0':
+                       p++;    /* Skip nul */
+                       goto check;
+
+
+               case '\n':
+                       parsenleft = q - parsenextc;
+                       more = 0; /* Stop processing here */
+                       break;
+               }
+
+               *q++ = *p++;
+check:
+               if (--parselleft <= 0 && more) {
+                       parsenleft = q - parsenextc - 1;
+                       if (parsenleft < 0)
+                               goto again;
+                       more = 0;
+               }
+       }
+
+       savec = *q;
+       *q = '\0';
+
+       if (vflag) {
+               out2str(parsenextc);
+#ifdef FLUSHERR
+               flushout(out2);
+#endif
+       }
+
+       *q = savec;
+
+       return *parsenextc++;
+}
+
+/*
+ * Undo the last call to pgetc.  Only one character may be pushed back.
+ * PEOF may be pushed back.
+ */
+
+static void
+pungetc() {
+       parsenleft++;
+       parsenextc--;
+}
+
+/*
+ * Push a string back onto the input at this current parsefile level.
+ * We handle aliases this way.
+ */
+static void
+pushstring(s, len, ap)
+       char *s;
+       int len;
+       void *ap;
+       {
+       struct strpush *sp;
+
+       INTOFF;
+/*dprintf("*** calling pushstring: %s, %d\n", s, len);*/
+       if (parsefile->strpush) {
+               sp = ckmalloc(sizeof (struct strpush));
+               sp->prev = parsefile->strpush;
+               parsefile->strpush = sp;
+       } else
+               sp = parsefile->strpush = &(parsefile->basestrpush);
+       sp->prevstring = parsenextc;
+       sp->prevnleft = parsenleft;
+       sp->ap = (struct alias *)ap;
+       if (ap) {
+               ((struct alias *)ap)->flag |= ALIASINUSE;
+               sp->string = s;
+       }
+       parsenextc = s;
+       parsenleft = len;
+       INTON;
+}
+
+static void
+popstring()
+{
+       struct strpush *sp = parsefile->strpush;
+
+       INTOFF;
+       if (sp->ap) {
+               if (parsenextc[-1] == ' ' || parsenextc[-1] == '\t') {
+                       if (!checkalias) {
+                               checkalias = 1;
+                       }
+               }
+               if (sp->string != sp->ap->val) {
+                       ckfree(sp->string);
+               }
+               sp->ap->flag &= ~ALIASINUSE;
+               if (sp->ap->flag & ALIASDEAD) {
+                       unalias(sp->ap->name);
+               }
+       }
+       parsenextc = sp->prevstring;
+       parsenleft = sp->prevnleft;
+/*dprintf("*** calling popstring: restoring to '%s'\n", parsenextc);*/
+       parsefile->strpush = sp->prev;
+       if (sp != &(parsefile->basestrpush))
+               ckfree(sp);
+       INTON;
+}
+
+/*
+ * Set the input to take input from a file.  If push is set, push the
+ * old input onto the stack first.
+ */
+
+static void
+setinputfile(fname, push)
+       const char *fname;
+       int push;
+{
+       int fd;
+       int myfileno2;
+
+       INTOFF;
+       if ((fd = open(fname, O_RDONLY)) < 0)
+               error("Can't open %s", fname);
+       if (fd < 10) {
+               myfileno2 = dup_as_newfd(fd, 10);
+               close(fd);
+               if (myfileno2 < 0)
+                       error("Out of file descriptors");
+               fd = myfileno2;
+       }
+       setinputfd(fd, push);
+       INTON;
+}
+
+
+/*
+ * Like setinputfile, but takes an open file descriptor.  Call this with
+ * interrupts off.
+ */
+
+static void
+setinputfd(fd, push)
+       int fd, push;
+{
+       (void) fcntl(fd, F_SETFD, FD_CLOEXEC);
+       if (push) {
+               pushfile();
+               parsefile->buf = 0;
+       } else {
+               closescript();
+               while (parsefile->strpush)
+                       popstring();
+       }
+       parsefile->fd = fd;
+       if (parsefile->buf == NULL)
+               parsefile->buf = ckmalloc(BUFSIZ);
+       parselleft = parsenleft = 0;
+       plinno = 1;
+}
+
+
+/*
+ * Like setinputfile, but takes input from a string.
+ */
+
+static void
+setinputstring(string)
+       char *string;
+       {
+       INTOFF;
+       pushfile();
+       parsenextc = string;
+       parsenleft = strlen(string);
+       parsefile->buf = NULL;
+       plinno = 1;
+       INTON;
+}
+
+
+
+/*
+ * To handle the "." command, a stack of input files is used.  Pushfile
+ * adds a new entry to the stack and popfile restores the previous level.
+ */
+
+static void
+pushfile() {
+       struct parsefile *pf;
+
+       parsefile->nleft = parsenleft;
+       parsefile->lleft = parselleft;
+       parsefile->nextc = parsenextc;
+       parsefile->linno = plinno;
+       pf = (struct parsefile *)ckmalloc(sizeof (struct parsefile));
+       pf->prev = parsefile;
+       pf->fd = -1;
+       pf->strpush = NULL;
+       pf->basestrpush.prev = NULL;
+       parsefile = pf;
+}
+
+
+static void
+popfile() {
+       struct parsefile *pf = parsefile;
+
+       INTOFF;
+       if (pf->fd >= 0)
+               close(pf->fd);
+       if (pf->buf)
+               ckfree(pf->buf);
+       while (pf->strpush)
+               popstring();
+       parsefile = pf->prev;
+       ckfree(pf);
+       parsenleft = parsefile->nleft;
+       parselleft = parsefile->lleft;
+       parsenextc = parsefile->nextc;
+       plinno = parsefile->linno;
+       INTON;
+}
+
+
+/*
+ * Return to top level.
+ */
+
+static void
+popallfiles() {
+       while (parsefile != &basepf)
+               popfile();
+}
+
+
+
+/*
+ * Close the file(s) that the shell is reading commands from.  Called
+ * after a fork is done.
+ */
+
+static void
+closescript() {
+       popallfiles();
+       if (parsefile->fd > 0) {
+               close(parsefile->fd);
+               parsefile->fd = 0;
+       }
+}
+/*     $NetBSD: jobs.c,v 1.36 2000/05/22 10:18:47 elric Exp $  */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+
+struct job *jobtab;            /* array of jobs */
+static int njobs;                      /* size of array */
+short backgndpid = -1; /* pid of last background process */
+#if JOBS
+static int initialpgrp;                /* pgrp of shell on invocation */
+short curjob;                  /* current job */
+#endif
+static int intreceived;
+
+static void restartjob __P((struct job *));
+static void freejob __P((struct job *));
+static struct job *getjob __P((char *));
+static int dowait __P((int, struct job *));
+#ifdef SYSV
+static int onsigchild __P((void));
+#endif
+static int waitproc __P((int, int *));
+static void cmdtxt __P((union node *));
+static void cmdputs __P((const char *));
+static void waitonint(int);
+
+
+#if JOBS
+/*
+ * Turn job control on and off.
+ *
+ * Note:  This code assumes that the third arg to ioctl is a character
+ * pointer, which is true on Berkeley systems but not System V.  Since
+ * System V doesn't have job control yet, this isn't a problem now.
+ */
+
+static int jobctl;
+
+static void setjobctl(int enable)
+{
+#ifdef OLD_TTY_DRIVER
+       int ldisc;
+#endif
+
+       if (enable == jobctl || rootshell == 0)
+               return;
+       if (enable) {
+               do { /* while we are in the background */
+#ifdef OLD_TTY_DRIVER
+                       if (ioctl(fileno2, TIOCGPGRP, (char *)&initialpgrp) < 0) {
+#else
+                       initialpgrp = tcgetpgrp(fileno2);
+                       if (initialpgrp < 0) {
+#endif
+                               out2str("sh: can't access tty; job cenabletrol turned off\n");
+                               mflag = 0;
+                               return;
+                       }
+                       if (initialpgrp == -1)
+                               initialpgrp = getpgrp();
+                       else if (initialpgrp != getpgrp()) {
+                               killpg(initialpgrp, SIGTTIN);
+                               continue;
+                       }
+               } while (0);
+#ifdef OLD_TTY_DRIVER
+               if (ioctl(fileno2, TIOCGETD, (char *)&ldisc) < 0 || ldisc != NTTYDISC) {
+                       out2str("sh: need new tty driver to run job cenabletrol; job cenabletrol turned off\n");
+                       mflag = 0;
+                       return;
+               }
+#endif
+               setsignal(SIGTSTP);
+               setsignal(SIGTTOU);
+               setsignal(SIGTTIN);
+               setpgid(0, rootpid);
+#ifdef OLD_TTY_DRIVER
+               ioctl(fileno2, TIOCSPGRP, (char *)&rootpid);
+#else
+               tcsetpgrp(fileno2, rootpid);
+#endif
+       } else { /* turning job cenabletrol off */
+               setpgid(0, initialpgrp);
+#ifdef OLD_TTY_DRIVER
+               ioctl(fileno2, TIOCSPGRP, (char *)&initialpgrp);
+#else
+               tcsetpgrp(fileno2, initialpgrp);
+#endif
+               setsignal(SIGTSTP);
+               setsignal(SIGTTOU);
+               setsignal(SIGTTIN);
+       }
+       jobctl = enable;
+}
+#endif
+
+
+#ifdef mkinit
+INCLUDE <stdlib.h>
+
+SHELLPROC {
+       backgndpid = -1;
+#if JOBS
+       jobctl = 0;
+#endif
+}
+
+#endif
+
+
+/* This file was automatically created by ./mksignames.
+   Do not edit.  Edit support/mksignames.c instead. */
+
+/* A translation list so we can be polite to our users. */
+static char *signal_names[NSIG + 2] = {
+    "EXIT",
+    "SIGHUP",
+    "SIGINT",
+    "SIGQUIT",
+    "SIGILL",
+    "SIGTRAP",
+    "SIGABRT",
+    "SIGBUS",
+    "SIGFPE",
+    "SIGKILL",
+    "SIGUSR1",
+    "SIGSEGV",
+    "SIGUSR2",
+    "SIGPIPE",
+    "SIGALRM",
+    "SIGTERM",
+    "SIGJUNK(16)",
+    "SIGCHLD",
+    "SIGCONT",
+    "SIGSTOP",
+    "SIGTSTP",
+    "SIGTTIN",
+    "SIGTTOU",
+    "SIGURG",
+    "SIGXCPU",
+    "SIGXFSZ",
+    "SIGVTALRM",
+    "SIGPROF",
+    "SIGWINCH",
+    "SIGIO",
+    "SIGPWR",
+    "SIGSYS",
+    "SIGRTMIN",
+    "SIGRTMIN+1",
+    "SIGRTMIN+2",
+    "SIGRTMIN+3",
+    "SIGRTMIN+4",
+    "SIGRTMIN+5",
+    "SIGRTMIN+6",
+    "SIGRTMIN+7",
+    "SIGRTMIN+8",
+    "SIGRTMIN+9",
+    "SIGRTMIN+10",
+    "SIGRTMIN+11",
+    "SIGRTMIN+12",
+    "SIGRTMIN+13",
+    "SIGRTMIN+14",
+    "SIGRTMIN+15",
+    "SIGRTMAX-15",
+    "SIGRTMAX-14",
+    "SIGRTMAX-13",
+    "SIGRTMAX-12",
+    "SIGRTMAX-11",
+    "SIGRTMAX-10",
+    "SIGRTMAX-9",
+    "SIGRTMAX-8",
+    "SIGRTMAX-7",
+    "SIGRTMAX-6",
+    "SIGRTMAX-5",
+    "SIGRTMAX-4",
+    "SIGRTMAX-3",
+    "SIGRTMAX-2",
+    "SIGRTMAX-1",
+    "SIGRTMAX",
+    "DEBUG",
+    (char *)0x0,
+};
+
+
+
+#if JOBS
+static int
+killcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       int signo = -1;
+       int list = 0;
+       int i;
+       pid_t pid;
+       struct job *jp;
+
+       if (argc <= 1) {
+usage:
+               error(
+"Usage: kill [-s sigspec | -signum | -sigspec] [pid | job]... or\n"
+"kill -l [exitstatus]"
+               );
+       }
+
+       if (*argv[1] == '-') {
+               signo = decode_signal(argv[1] + 1, 1);
+               if (signo < 0) {
+                       int c;
+
+                       while ((c = nextopt("ls:")) != '\0')
+                               switch (c) {
+                               case 'l':
+                                       list = 1;
+                                       break;
+                               case 's':
+                                       signo = decode_signal(optionarg, 1);
+                                       if (signo < 0) {
+                                               error(
+                                                       "invalid signal number or name: %s",
+                                                       optionarg
+                                               );
+                                       }
+                                       break;
+#ifdef DEBUG
+                               default:
+                                       error(
+       "nextopt returned character code 0%o", c);
+#endif
+                       }
+               } else
+                       argptr++;
+       }
+
+       if (!list && signo < 0)
+               signo = SIGTERM;
+
+       if ((signo < 0 || !*argptr) ^ list) {
+               goto usage;
+       }
+
+       if (list) {
+               if (!*argptr) {
+                       out1str("0\n");
+                       for (i = 1; i < NSIG; i++) {
+                               out1fmt(snlfmt, signal_names[i] + 3);
+                       }
+                       return 0;
+               }
+               signo = atoi(*argptr);
+               if (signo > 128)
+                       signo -= 128;
+               if (0 < signo && signo < NSIG)
+                               out1fmt(snlfmt, signal_names[signo] + 3);
+               else
+                       error("invalid signal number or exit status: %s",
+                             *argptr);
+               return 0;
+       }
+
+       do {
+               if (**argptr == '%') {
+                       jp = getjob(*argptr);
+                       if (jp->jobctl == 0)
+                               error("job %s not created under job control",
+                                     *argptr);
+                       pid = -jp->ps[0].pid;
+               } else
+                       pid = atoi(*argptr);
+               if (kill(pid, signo) != 0)
+                       error("%s: %s", *argptr, strerror(errno));
+       } while (*++argptr);
+
+       return 0;
+}
+
+static int
+fgcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       struct job *jp;
+       int pgrp;
+       int status;
+
+       jp = getjob(argv[1]);
+       if (jp->jobctl == 0)
+               error("job not created under job control");
+       pgrp = jp->ps[0].pid;
+#ifdef OLD_TTY_DRIVER
+       ioctl(fileno2, TIOCSPGRP, (char *)&pgrp);
+#else
+       tcsetpgrp(fileno2, pgrp);
+#endif
+       restartjob(jp);
+       INTOFF;
+       status = waitforjob(jp);
+       INTON;
+       return status;
+}
+
+
+static int
+bgcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       struct job *jp;
+
+       do {
+               jp = getjob(*++argv);
+               if (jp->jobctl == 0)
+                       error("job not created under job control");
+               restartjob(jp);
+       } while (--argc > 1);
+       return 0;
+}
+
+
+static void
+restartjob(jp)
+       struct job *jp;
+{
+       struct procstat *ps;
+       int i;
+
+       if (jp->state == JOBDONE)
+               return;
+       INTOFF;
+       killpg(jp->ps[0].pid, SIGCONT);
+       for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) {
+               if (WIFSTOPPED(ps->status)) {
+                       ps->status = -1;
+                       jp->state = 0;
+               }
+       }
+       INTON;
+}
+#endif
+
+
+static int
+jobscmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       showjobs(0);
+       return 0;
+}
+
+
+/*
+ * Print a list of jobs.  If "change" is nonzero, only print jobs whose
+ * statuses have changed since the last call to showjobs.
+ *
+ * If the shell is interrupted in the process of creating a job, the
+ * result may be a job structure containing zero processes.  Such structures
+ * will be freed here.
+ */
+
+static void
+showjobs(change)
+       int change;
+{
+       int jobno;
+       int procno;
+       int i;
+       struct job *jp;
+       struct procstat *ps;
+       int col;
+       char s[64];
+
+       TRACE(("showjobs(%d) called\n", change));
+       while (dowait(0, (struct job *)NULL) > 0);
+       for (jobno = 1, jp = jobtab ; jobno <= njobs ; jobno++, jp++) {
+               if (! jp->used)
+                       continue;
+               if (jp->nprocs == 0) {
+                       freejob(jp);
+                       continue;
+               }
+               if (change && ! jp->changed)
+                       continue;
+               procno = jp->nprocs;
+               for (ps = jp->ps ; ; ps++) {    /* for each process */
+                       if (ps == jp->ps)
+                               fmtstr(s, 64, "[%d] %ld ", jobno, 
+                                   (long)ps->pid);
+                       else
+                               fmtstr(s, 64, "    %ld ", 
+                                   (long)ps->pid);
+                       out1str(s);
+                       col = strlen(s);
+                       s[0] = '\0';
+                       if (ps->status == -1) {
+                               /* don't print anything */
+                       } else if (WIFEXITED(ps->status)) {
+                               fmtstr(s, 64, "Exit %d", 
+                                      WEXITSTATUS(ps->status));
+                       } else {
+#if JOBS
+                               if (WIFSTOPPED(ps->status)) 
+                                       i = WSTOPSIG(ps->status);
+                               else /* WIFSIGNALED(ps->status) */
+#endif
+                                       i = WTERMSIG(ps->status);
+                               if ((i & 0x7F) < NSIG && sys_siglist[i & 0x7F])
+                                       scopy(sys_siglist[i & 0x7F], s);
+                               else
+                                       fmtstr(s, 64, "Signal %d", i & 0x7F);
+                               if (WCOREDUMP(ps->status))
+                                       strcat(s, " (core dumped)");
+                       }
+                       out1str(s);
+                       col += strlen(s);
+                       out1fmt(
+                               "%*c%s\n", 30 - col >= 0 ? 30 - col : 0, ' ',
+                               ps->cmd
+                       );
+                       if (--procno <= 0)
+                               break;
+               }
+               jp->changed = 0;
+               if (jp->state == JOBDONE) {
+                       freejob(jp);
+               }
+       }
+}
+
+
+/*
+ * Mark a job structure as unused.
+ */
+
+static void
+freejob(jp)
+       struct job *jp;
+       {
+       struct procstat *ps;
+       int i;
+
+       INTOFF;
+       for (i = jp->nprocs, ps = jp->ps ; --i >= 0 ; ps++) {
+               if (ps->cmd != nullstr)
+                       ckfree(ps->cmd);
+       }
+       if (jp->ps != &jp->ps0)
+               ckfree(jp->ps);
+       jp->used = 0;
+#if JOBS
+       if (curjob == jp - jobtab + 1)
+               curjob = 0;
+#endif
+       INTON;
+}
+
+
+
+static int
+waitcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       struct job *job;
+       int status, retval;
+       struct job *jp;
+
+       if (--argc > 0) {
+start:
+               job = getjob(*++argv);
+       } else {
+               job = NULL;
+       }
+       for (;;) {      /* loop until process terminated or stopped */
+               if (job != NULL) {
+                       if (job->state) {
+                               status = job->ps[job->nprocs - 1].status;
+                               if (! iflag)
+                                       freejob(job);
+                               if (--argc) {
+                                       goto start;
+                               }
+                               if (WIFEXITED(status))
+                                       retval = WEXITSTATUS(status);
+#if JOBS
+                               else if (WIFSTOPPED(status))
+                                       retval = WSTOPSIG(status) + 128;
+#endif
+                               else {
+                                       /* XXX: limits number of signals */
+                                       retval = WTERMSIG(status) + 128;
+                               }
+                               return retval;
+                       }
+               } else {
+                       for (jp = jobtab ; ; jp++) {
+                               if (jp >= jobtab + njobs) {     /* no running procs */
+                                       return 0;
+                               }
+                               if (jp->used && jp->state == 0)
+                                       break;
+                       }
+               }
+               if (dowait(2, 0) < 0 && errno == EINTR) {
+                       return 129;
+               }
+       }
+}
+
+
+
+/*
+ * Convert a job name to a job structure.
+ */
+
+static struct job *
+getjob(name)
+       char *name;
+       {
+       int jobno;
+       struct job *jp;
+       int pid;
+       int i;
+
+       if (name == NULL) {
+#if JOBS
+currentjob:
+               if ((jobno = curjob) == 0 || jobtab[jobno - 1].used == 0)
+                       error("No current job");
+               return &jobtab[jobno - 1];
+#else
+               error("No current job");
+#endif
+       } else if (name[0] == '%') {
+               if (is_digit(name[1])) {
+                       jobno = number(name + 1);
+                       if (jobno > 0 && jobno <= njobs
+                        && jobtab[jobno - 1].used != 0)
+                               return &jobtab[jobno - 1];
+#if JOBS
+               } else if (name[1] == '%' && name[2] == '\0') {
+                       goto currentjob;
+#endif
+               } else {
+                       struct job *found = NULL;
+                       for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) {
+                               if (jp->used && jp->nprocs > 0
+                                && prefix(name + 1, jp->ps[0].cmd)) {
+                                       if (found)
+                                               error("%s: ambiguous", name);
+                                       found = jp;
+                               }
+                       }
+                       if (found)
+                               return found;
+               }
+       } else if (is_number(name)) {
+               pid = number(name);
+               for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) {
+                       if (jp->used && jp->nprocs > 0
+                        && jp->ps[jp->nprocs - 1].pid == pid)
+                               return jp;
+               }
+       }
+       error("No such job: %s", name);
+       /* NOTREACHED */
+}
+
+
+
+/*
+ * Return a new job structure,
+ */
+
+struct job *
+makejob(node, nprocs)
+       union node *node;
+       int nprocs;
+{
+       int i;
+       struct job *jp;
+
+       for (i = njobs, jp = jobtab ; ; jp++) {
+               if (--i < 0) {
+                       INTOFF;
+                       if (njobs == 0) {
+                               jobtab = ckmalloc(4 * sizeof jobtab[0]);
+                       } else {
+                               jp = ckmalloc((njobs + 4) * sizeof jobtab[0]);
+                               memcpy(jp, jobtab, njobs * sizeof jp[0]);
+                               /* Relocate `ps' pointers */
+                               for (i = 0; i < njobs; i++)
+                                       if (jp[i].ps == &jobtab[i].ps0)
+                                               jp[i].ps = &jp[i].ps0;
+                               ckfree(jobtab);
+                               jobtab = jp;
+                       }
+                       jp = jobtab + njobs;
+                       for (i = 4 ; --i >= 0 ; jobtab[njobs++].used = 0);
+                       INTON;
+                       break;
+               }
+               if (jp->used == 0)
+                       break;
+       }
+       INTOFF;
+       jp->state = 0;
+       jp->used = 1;
+       jp->changed = 0;
+       jp->nprocs = 0;
+#if JOBS
+       jp->jobctl = jobctl;
+#endif
+       if (nprocs > 1) {
+               jp->ps = ckmalloc(nprocs * sizeof (struct procstat));
+       } else {
+               jp->ps = &jp->ps0;
+       }
+       INTON;
+       TRACE(("makejob(0x%lx, %d) returns %%%d\n", (long)node, nprocs,
+           jp - jobtab + 1));
+       return jp;
+}
+
+
+/*
+ * Fork of a subshell.  If we are doing job control, give the subshell its
+ * own process group.  Jp is a job structure that the job is to be added to.
+ * N is the command that will be evaluated by the child.  Both jp and n may
+ * be NULL.  The mode parameter can be one of the following:
+ *     FORK_FG - Fork off a foreground process.
+ *     FORK_BG - Fork off a background process.
+ *     FORK_NOJOB - Like FORK_FG, but don't give the process its own
+ *                  process group even if job control is on.
+ *
+ * When job control is turned off, background processes have their standard
+ * input redirected to /dev/null (except for the second and later processes
+ * in a pipeline).
+ */
+
+static int
+forkshell(jp, n, mode)
+       union node *n;
+       struct job *jp;
+       int mode;
+{
+       int pid;
+       int pgrp;
+       const char *devnull = _PATH_DEVNULL;
+       const char *nullerr = "Can't open %s";
+
+       TRACE(("forkshell(%%%d, 0x%lx, %d) called\n", jp - jobtab, (long)n,
+           mode));
+       INTOFF;
+       pid = fork();
+       if (pid == -1) {
+               TRACE(("Fork failed, errno=%d\n", errno));
+               INTON;
+               error("Cannot fork");
+       }
+       if (pid == 0) {
+               struct job *p;
+               int wasroot;
+               int i;
+
+               TRACE(("Child shell %d\n", getpid()));
+               wasroot = rootshell;
+               rootshell = 0;
+               closescript();
+               INTON;
+               clear_traps();
+#if JOBS
+               jobctl = 0;             /* do job control only in root shell */
+               if (wasroot && mode != FORK_NOJOB && mflag) {
+                       if (jp == NULL || jp->nprocs == 0)
+                               pgrp = getpid();
+                       else
+                               pgrp = jp->ps[0].pid;
+                       setpgid(0, pgrp);
+                       if (mode == FORK_FG) {
+                               /*** this causes superfluous TIOCSPGRPS ***/
+#ifdef OLD_TTY_DRIVER
+                               if (ioctl(fileno2, TIOCSPGRP, (char *)&pgrp) < 0)
+                                       error("TIOCSPGRP failed, errno=%d", errno);
+#else
+                               if (tcsetpgrp(fileno2, pgrp) < 0)
+                                       error("tcsetpgrp failed, errno=%d", errno);
+#endif
+                       }
+                       setsignal(SIGTSTP);
+                       setsignal(SIGTTOU);
+               } else if (mode == FORK_BG) {
+                       ignoresig(SIGINT);
+                       ignoresig(SIGQUIT);
+                       if ((jp == NULL || jp->nprocs == 0) &&
+                           ! fd0_redirected_p ()) {
+                               close(0);
+                               if (open(devnull, O_RDONLY) != 0)
+                                       error(nullerr, devnull);
+                       }
+               }
+#else
+               if (mode == FORK_BG) {
+                       ignoresig(SIGINT);
+                       ignoresig(SIGQUIT);
+                       if ((jp == NULL || jp->nprocs == 0) &&
+                           ! fd0_redirected_p ()) {
+                               close(0);
+                               if (open(devnull, O_RDONLY) != 0)
+                                       error(nullerr, devnull);
+                       }
+               }
+#endif
+               for (i = njobs, p = jobtab ; --i >= 0 ; p++)
+                       if (p->used)
+                               freejob(p);
+               if (wasroot && iflag) {
+                       setsignal(SIGINT);
+                       setsignal(SIGQUIT);
+                       setsignal(SIGTERM);
+               }
+               return pid;
+       }
+       if (rootshell && mode != FORK_NOJOB && mflag) {
+               if (jp == NULL || jp->nprocs == 0)
+                       pgrp = pid;
+               else
+                       pgrp = jp->ps[0].pid;
+               setpgid(pid, pgrp);
+       }
+       if (mode == FORK_BG)
+               backgndpid = pid;               /* set $! */
+       if (jp) {
+               struct procstat *ps = &jp->ps[jp->nprocs++];
+               ps->pid = pid;
+               ps->status = -1;
+               ps->cmd = nullstr;
+               if (iflag && rootshell && n)
+                       ps->cmd = commandtext(n);
+       }
+       INTON;
+       TRACE(("In parent shell:  child = %d\n", pid));
+       return pid;
+}
+
+
+
+/*
+ * Wait for job to finish.
+ *
+ * Under job control we have the problem that while a child process is
+ * running interrupts generated by the user are sent to the child but not
+ * to the shell.  This means that an infinite loop started by an inter-
+ * active user may be hard to kill.  With job control turned off, an
+ * interactive user may place an interactive program inside a loop.  If
+ * the interactive program catches interrupts, the user doesn't want
+ * these interrupts to also abort the loop.  The approach we take here
+ * is to have the shell ignore interrupt signals while waiting for a
+ * forground process to terminate, and then send itself an interrupt
+ * signal if the child process was terminated by an interrupt signal.
+ * Unfortunately, some programs want to do a bit of cleanup and then
+ * exit on interrupt; unless these processes terminate themselves by
+ * sending a signal to themselves (instead of calling exit) they will
+ * confuse this approach.
+ */
+
+static int
+waitforjob(jp)
+       struct job *jp;
+       {
+#if JOBS
+       int mypgrp = getpgrp();
+#endif
+       int status;
+       int st;
+       struct sigaction act, oact;
+
+       INTOFF;
+       intreceived = 0;
+#if JOBS
+       if (!jobctl) {
+#else
+       if (!iflag) {
+#endif
+               sigaction(SIGINT, 0, &act);
+               act.sa_handler = waitonint;
+               sigaction(SIGINT, &act, &oact);
+       }
+       TRACE(("waitforjob(%%%d) called\n", jp - jobtab + 1));
+       while (jp->state == 0) {
+               dowait(1, jp);
+       }
+#if JOBS
+       if (!jobctl) {
+#else
+       if (!iflag) {
+#endif
+               sigaction(SIGINT, &oact, 0);
+               if (intreceived && trap[SIGINT]) kill(getpid(), SIGINT);
+       }
+#if JOBS
+       if (jp->jobctl) {
+#ifdef OLD_TTY_DRIVER
+               if (ioctl(fileno2, TIOCSPGRP, (char *)&mypgrp) < 0)
+                       error("TIOCSPGRP failed, errno=%d\n", errno);
+#else
+               if (tcsetpgrp(fileno2, mypgrp) < 0)
+                       error("tcsetpgrp failed, errno=%d\n", errno);
+#endif
+       }
+       if (jp->state == JOBSTOPPED)
+               curjob = jp - jobtab + 1;
+#endif
+       status = jp->ps[jp->nprocs - 1].status;
+       /* convert to 8 bits */
+       if (WIFEXITED(status))
+               st = WEXITSTATUS(status);
+#if JOBS
+       else if (WIFSTOPPED(status))
+               st = WSTOPSIG(status) + 128;
+#endif
+       else
+               st = WTERMSIG(status) + 128;
+#if JOBS
+       if (jp->jobctl) {
+               /*
+                * This is truly gross.
+                * If we're doing job control, then we did a TIOCSPGRP which
+                * caused us (the shell) to no longer be in the controlling
+                * session -- so we wouldn't have seen any ^C/SIGINT.  So, we
+                * intuit from the subprocess exit status whether a SIGINT
+                * occured, and if so interrupt ourselves.  Yuck.  - mycroft
+                */
+               if (WIFSIGNALED(status) && WTERMSIG(status) == SIGINT)
+                       raise(SIGINT);
+       }
+#endif
+       if (! JOBS || jp->state == JOBDONE)
+               freejob(jp);
+       INTON;
+       return st;
+}
+
+
+
+/*
+ * Wait for a process to terminate.
+ */
+
+static int
+dowait(block, job)
+       int block;
+       struct job *job;
+{
+       int pid;
+       int status;
+       struct procstat *sp;
+       struct job *jp;
+       struct job *thisjob;
+       int done;
+       int stopped;
+       int core;
+       int sig;
+
+       TRACE(("dowait(%d) called\n", block));
+       do {
+               pid = waitproc(block, &status);
+               TRACE(("wait returns %d, status=%d\n", pid, status));
+       } while (!(block & 2) && pid == -1 && errno == EINTR);
+       if (pid <= 0)
+               return pid;
+       INTOFF;
+       thisjob = NULL;
+       for (jp = jobtab ; jp < jobtab + njobs ; jp++) {
+               if (jp->used) {
+                       done = 1;
+                       stopped = 1;
+                       for (sp = jp->ps ; sp < jp->ps + jp->nprocs ; sp++) {
+                               if (sp->pid == -1)
+                                       continue;
+                               if (sp->pid == pid) {
+                                       TRACE(("Changing status of proc %d from 0x%x to 0x%x\n", pid, sp->status, status));
+                                       sp->status = status;
+                                       thisjob = jp;
+                               }
+                               if (sp->status == -1)
+                                       stopped = 0;
+                               else if (WIFSTOPPED(sp->status))
+                                       done = 0;
+                       }
+                       if (stopped) {          /* stopped or done */
+                               int state = done? JOBDONE : JOBSTOPPED;
+                               if (jp->state != state) {
+                                       TRACE(("Job %d: changing state from %d to %d\n", jp - jobtab + 1, jp->state, state));
+                                       jp->state = state;
+#if JOBS
+                                       if (done && curjob == jp - jobtab + 1)
+                                               curjob = 0;             /* no current job */
+#endif
+                               }
+                       }
+               }
+       }
+       INTON;
+       if (! rootshell || ! iflag || (job && thisjob == job)) {
+               core = WCOREDUMP(status);
+#if JOBS
+               if (WIFSTOPPED(status)) sig = WSTOPSIG(status);
+               else
+#endif
+               if (WIFEXITED(status)) sig = 0;
+               else sig = WTERMSIG(status);
+
+               if (sig != 0 && sig != SIGINT && sig != SIGPIPE) {
+                       if (thisjob != job)
+                               outfmt(out2, "%d: ", pid);
+#if JOBS
+                       if (sig == SIGTSTP && rootshell && iflag)
+                               outfmt(out2, "%%%ld ",
+                                   (long)(job - jobtab + 1));
+#endif
+                       if (sig < NSIG && sys_siglist[sig])
+                               out2str(sys_siglist[sig]);
+                       else
+                               outfmt(out2, "Signal %d", sig);
+                       if (core)
+                               out2str(" - core dumped");
+                       out2c('\n');
+#ifdef FLUSHERR
+                       flushout(&errout);
+#endif
+               } else {
+                       TRACE(("Not printing status: status=%d, sig=%d\n", 
+                              status, sig));
+               }
+       } else {
+               TRACE(("Not printing status, rootshell=%d, job=0x%x\n", rootshell, job));
+               if (thisjob)
+                       thisjob->changed = 1;
+       }
+       return pid;
+}
+
+
+
+/*
+ * Do a wait system call.  If job control is compiled in, we accept
+ * stopped processes.  If block is zero, we return a value of zero
+ * rather than blocking.
+ *
+ * System V doesn't have a non-blocking wait system call.  It does
+ * have a SIGCLD signal that is sent to a process when one of it's
+ * children dies.  The obvious way to use SIGCLD would be to install
+ * a handler for SIGCLD which simply bumped a counter when a SIGCLD
+ * was received, and have waitproc bump another counter when it got
+ * the status of a process.  Waitproc would then know that a wait
+ * system call would not block if the two counters were different.
+ * This approach doesn't work because if a process has children that
+ * have not been waited for, System V will send it a SIGCLD when it
+ * installs a signal handler for SIGCLD.  What this means is that when
+ * a child exits, the shell will be sent SIGCLD signals continuously
+ * until is runs out of stack space, unless it does a wait call before
+ * restoring the signal handler.  The code below takes advantage of
+ * this (mis)feature by installing a signal handler for SIGCLD and
+ * then checking to see whether it was called.  If there are any
+ * children to be waited for, it will be.
+ *
+ * If neither SYSV nor BSD is defined, we don't implement nonblocking
+ * waits at all.  In this case, the user will not be informed when
+ * a background process until the next time she runs a real program
+ * (as opposed to running a builtin command or just typing return),
+ * and the jobs command may give out of date information.
+ */
+
+#ifdef SYSV
+static int gotsigchild;
+
+static int onsigchild() {
+       gotsigchild = 1;
+}
+#endif
+
+
+static int
+waitproc(block, status)
+       int block;
+       int *status;
+{
+#ifdef BSD
+       int flags;
+
+       flags = 0;
+#if JOBS
+       if (jobctl)
+               flags |= WUNTRACED;
+#endif
+       if (block == 0)
+               flags |= WNOHANG;
+       return wait3(status, flags, (struct rusage *)NULL);
+#else
+#ifdef SYSV
+       int (*save)();
+
+       if (block == 0) {
+               gotsigchild = 0;
+               save = signal(SIGCLD, onsigchild);
+               signal(SIGCLD, save);
+               if (gotsigchild == 0)
+                       return 0;
+       }
+       return wait(status);
+#else
+       if (block == 0)
+               return 0;
+       return wait(status);
+#endif
+#endif
+}
+
+/*
+ * return 1 if there are stopped jobs, otherwise 0
+ */
+static int job_warning = 0;
+static int
+stoppedjobs()
+{
+       int jobno;
+       struct job *jp;
+
+       if (job_warning)
+               return (0);
+       for (jobno = 1, jp = jobtab; jobno <= njobs; jobno++, jp++) {
+               if (jp->used == 0)
+                       continue;
+               if (jp->state == JOBSTOPPED) {
+                       out2str("You have stopped jobs.\n");
+                       job_warning = 2;
+                       return (1);
+               }
+       }
+
+       return (0);
+}
+
+/*
+ * Return a string identifying a command (to be printed by the
+ * jobs command.
+ */
+
+static char *cmdnextc;
+static int cmdnleft;
+#define MAXCMDTEXT     200
+
+static char *
+commandtext(n)
+       union node *n;
+       {
+       char *name;
+
+       cmdnextc = name = ckmalloc(MAXCMDTEXT);
+       cmdnleft = MAXCMDTEXT - 4;
+       cmdtxt(n);
+       *cmdnextc = '\0';
+       return name;
+}
+
+
+static void
+cmdtxt(n)
+       union node *n;
+       {
+       union node *np;
+       struct nodelist *lp;
+       const char *p;
+       int i;
+       char s[2];
+
+       if (n == NULL)
+               return;
+       switch (n->type) {
+       case NSEMI:
+               cmdtxt(n->nbinary.ch1);
+               cmdputs("; ");
+               cmdtxt(n->nbinary.ch2);
+               break;
+       case NAND:
+               cmdtxt(n->nbinary.ch1);
+               cmdputs(" && ");
+               cmdtxt(n->nbinary.ch2);
+               break;
+       case NOR:
+               cmdtxt(n->nbinary.ch1);
+               cmdputs(" || ");
+               cmdtxt(n->nbinary.ch2);
+               break;
+       case NPIPE:
+               for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
+                       cmdtxt(lp->n);
+                       if (lp->next)
+                               cmdputs(" | ");
+               }
+               break;
+       case NSUBSHELL:
+               cmdputs("(");
+               cmdtxt(n->nredir.n);
+               cmdputs(")");
+               break;
+       case NREDIR:
+       case NBACKGND:
+               cmdtxt(n->nredir.n);
+               break;
+       case NIF:
+               cmdputs("if ");
+               cmdtxt(n->nif.test);
+               cmdputs("; then ");
+               cmdtxt(n->nif.ifpart);
+               cmdputs("...");
+               break;
+       case NWHILE:
+               cmdputs("while ");
+               goto until;
+       case NUNTIL:
+               cmdputs("until ");
+until:
+               cmdtxt(n->nbinary.ch1);
+               cmdputs("; do ");
+               cmdtxt(n->nbinary.ch2);
+               cmdputs("; done");
+               break;
+       case NFOR:
+               cmdputs("for ");
+               cmdputs(n->nfor.var);
+               cmdputs(" in ...");
+               break;
+       case NCASE:
+               cmdputs("case ");
+               cmdputs(n->ncase.expr->narg.text);
+               cmdputs(" in ...");
+               break;
+       case NDEFUN:
+               cmdputs(n->narg.text);
+               cmdputs("() ...");
+               break;
+       case NCMD:
+               for (np = n->ncmd.args ; np ; np = np->narg.next) {
+                       cmdtxt(np);
+                       if (np->narg.next)
+                               cmdputs(spcstr);
+               }
+               for (np = n->ncmd.redirect ; np ; np = np->nfile.next) {
+                       cmdputs(spcstr);
+                       cmdtxt(np);
+               }
+               break;
+       case NARG:
+               cmdputs(n->narg.text);
+               break;
+       case NTO:
+               p = ">";  i = 1;  goto redir;
+       case NAPPEND:
+               p = ">>";  i = 1;  goto redir;
+       case NTOFD:
+               p = ">&";  i = 1;  goto redir;
+       case NTOOV:
+               p = ">|";  i = 1;  goto redir;
+       case NFROM:
+               p = "<";  i = 0;  goto redir;
+       case NFROMFD:
+               p = "<&";  i = 0;  goto redir;
+       case NFROMTO:
+               p = "<>";  i = 0;  goto redir;
+redir:
+               if (n->nfile.fd != i) {
+                       s[0] = n->nfile.fd + '0';
+                       s[1] = '\0';
+                       cmdputs(s);
+               }
+               cmdputs(p);
+               if (n->type == NTOFD || n->type == NFROMFD) {
+                       s[0] = n->ndup.dupfd + '0';
+                       s[1] = '\0';
+                       cmdputs(s);
+               } else {
+                       cmdtxt(n->nfile.fname);
+               }
+               break;
+       case NHERE:
+       case NXHERE:
+               cmdputs("<<...");
+               break;
+       default:
+               cmdputs("???");
+               break;
+       }
+}
+
+
+
+static void
+cmdputs(s)
+       const char *s;
+       {
+       const char *p;
+       char *q;
+       char c;
+       int subtype = 0;
+
+       if (cmdnleft <= 0)
+               return;
+       p = s;
+       q = cmdnextc;
+       while ((c = *p++) != '\0') {
+               if (c == CTLESC)
+                       *q++ = *p++;
+               else if (c == CTLVAR) {
+                       *q++ = '$';
+                       if (--cmdnleft > 0)
+                               *q++ = '{';
+                       subtype = *p++;
+               } else if (c == '=' && subtype != 0) {
+                       *q++ = "}-+?="[(subtype & VSTYPE) - VSNORMAL];
+                       subtype = 0;
+               } else if (c == CTLENDVAR) {
+                       *q++ = '}';
+               } else if (c == CTLBACKQ || c == CTLBACKQ+CTLQUOTE)
+                       cmdnleft++;             /* ignore it */
+               else
+                       *q++ = c;
+               if (--cmdnleft <= 0) {
+                       *q++ = '.';
+                       *q++ = '.';
+                       *q++ = '.';
+                       break;
+               }
+       }
+       cmdnextc = q;
+}
+
+static void waitonint(int sig) {
+       intreceived = 1;
+       return;
+}
+/*     $NetBSD: mail.c,v 1.14 2000/07/03 03:26:19 matt Exp $   */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Routines to check for mail.  (Perhaps make part of main.c?)
+ */
+
+
+#define MAXMBOXES 10
+
+
+static int nmboxes;                    /* number of mailboxes */
+static time_t mailtime[MAXMBOXES];     /* times of mailboxes */
+
+
+
+/*
+ * Print appropriate message(s) if mail has arrived.  If the argument is
+ * nozero, then the value of MAIL has changed, so we just update the
+ * values.
+ */
+
+static void
+chkmail(silent)
+       int silent;
+{
+       int i;
+       const char *mpath;
+       char *p;
+       char *q;
+       struct stackmark smark;
+       struct stat statb;
+
+       if (silent)
+               nmboxes = 10;
+       if (nmboxes == 0)
+               return;
+       setstackmark(&smark);
+       mpath = mpathset()? mpathval() : mailval();
+       for (i = 0 ; i < nmboxes ; i++) {
+               p = padvance(&mpath, nullstr);
+               if (p == NULL)
+                       break;
+               if (*p == '\0')
+                       continue;
+               for (q = p ; *q ; q++);
+#ifdef DEBUG
+               if (q[-1] != '/')
+                       abort();
+#endif
+               q[-1] = '\0';                   /* delete trailing '/' */
+#ifdef notdef /* this is what the System V shell claims to do (it lies) */
+               if (stat(p, &statb) < 0)
+                       statb.st_mtime = 0;
+               if (statb.st_mtime > mailtime[i] && ! silent) {
+                       outfmt(
+                               &errout, snlfmt,
+                               pathopt? pathopt : "you have mail"
+                       );
+               }
+               mailtime[i] = statb.st_mtime;
+#else /* this is what it should do */
+               if (stat(p, &statb) < 0)
+                       statb.st_size = 0;
+               if (statb.st_size > mailtime[i] && ! silent) {
+                       outfmt(
+                               &errout, snlfmt,
+                               pathopt? pathopt : "you have mail"
+                       );
+               }
+               mailtime[i] = statb.st_size;
+#endif
+       }
+       nmboxes = i;
+       popstackmark(&smark);
+}
+/*     $NetBSD: main.c,v 1.40 2001/02/04 19:52:06 christos Exp $       */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+
+#define PROFILE 0
+
+static int rootpid;
+static int rootshell;
+#if PROFILE
+short profile_buf[16384];
+extern int etext();
+#endif
+
+static void read_profile __P((const char *));
+static char *find_dot_file __P((char *));
+int shell_main __P((int, char **));
+
+extern int oexitstatus;
+/*
+ * Main routine.  We initialize things, parse the arguments, execute
+ * profiles if we're a login shell, and then call cmdloop to execute
+ * commands.  The setjmp call sets up the location to jump to when an
+ * exception occurs.  When an exception occurs the variable "state"
+ * is used to figure out how far we had gotten.
+ */
+
+int
+shell_main(argc, argv)
+       int argc;
+       char **argv;
+{
+       struct jmploc jmploc;
+       struct stackmark smark;
+       volatile int state;
+       char *shinit;
+
+       DOTCMD = find_builtin(".");
+       BLTINCMD = find_builtin("builtin");
+       COMMANDCMD = find_builtin("command");
+       EXECCMD = find_builtin("exec");
+       EVALCMD = find_builtin("eval");
+
+#if PROFILE
+       monitor(4, etext, profile_buf, sizeof profile_buf, 50);
+#endif
+#if defined(linux) || defined(__GNU__)
+       signal(SIGCHLD, SIG_DFL);
+#endif
+       state = 0;
+       if (setjmp(jmploc.loc)) {
+               INTOFF;
+               /*
+                * When a shell procedure is executed, we raise the
+                * exception EXSHELLPROC to clean up before executing
+                * the shell procedure.
+                */
+               switch (exception) {
+               case EXSHELLPROC:
+                       rootpid = getpid();
+                       rootshell = 1;
+                       minusc = NULL;
+                       state = 3;
+                       break;
+
+               case EXEXEC:
+                       exitstatus = exerrno;
+                       break;
+
+               case EXERROR:
+                       exitstatus = 2;
+                       break;
+
+               default:
+                       break;
+               }
+
+               if (exception != EXSHELLPROC) {
+                   if (state == 0 || iflag == 0 || ! rootshell)
+                           exitshell(exitstatus);
+               }
+               reset();
+               if (exception == EXINT
+#if ATTY
+                && (! attyset() || equal(termval(), "emacs"))
+#endif
+                ) {
+                       out2c('\n');
+#ifdef FLUSHERR
+                       flushout(out2);
+#endif
+               }
+               popstackmark(&smark);
+               FORCEINTON;                             /* enable interrupts */
+               if (state == 1)
+                       goto state1;
+               else if (state == 2)
+                       goto state2;
+               else if (state == 3)
+                       goto state3;
+               else
+                       goto state4;
+       }
+       handler = &jmploc;
+#ifdef DEBUG
+       opentrace();
+       trputs("Shell args:  ");  trargs(argv);
+#endif
+       rootpid = getpid();
+       rootshell = 1;
+       init();
+       setstackmark(&smark);
+       procargs(argc, argv);
+       if (argv[0] && argv[0][0] == '-') {
+               state = 1;
+               read_profile("/etc/profile");
+state1:
+               state = 2;
+               read_profile(".profile");
+       }
+state2:
+       state = 3;
+#ifndef linux
+       if (getuid() == geteuid() && getgid() == getegid()) {
+#endif
+               if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') {
+                       state = 3;
+                       read_profile(shinit);
+               }
+#ifndef linux
+       }
+#endif
+state3:
+       state = 4;
+       if (sflag == 0 || minusc) {
+               static int sigs[] =  {
+                   SIGINT, SIGQUIT, SIGHUP, 
+#ifdef SIGTSTP
+                   SIGTSTP,
+#endif
+                   SIGPIPE
+               };
+#define SIGSSIZE (sizeof(sigs)/sizeof(sigs[0]))
+               int i;
+
+               for (i = 0; i < SIGSSIZE; i++)
+                   setsignal(sigs[i]);
+       }
+
+       if (minusc)
+               evalstring(minusc, 0);
+
+       if (sflag || minusc == NULL) {
+state4:        /* XXX ??? - why isn't this before the "if" statement */
+               cmdloop(1);
+       }
+#if PROFILE
+       monitor(0);
+#endif
+       exitshell(exitstatus);
+       /* NOTREACHED */
+}
+
+
+/*
+ * Read and execute commands.  "Top" is nonzero for the top level command
+ * loop; it turns on prompting if the shell is interactive.
+ */
+
+static void
+cmdloop(top)
+       int top;
+{
+       union node *n;
+       struct stackmark smark;
+       int inter;
+       int numeof = 0;
+
+       TRACE(("cmdloop(%d) called\n", top));
+       setstackmark(&smark);
+       for (;;) {
+               if (pendingsigs)
+                       dotrap();
+               inter = 0;
+               if (iflag && top) {
+                       inter++;
+                       showjobs(1);
+                       chkmail(0);
+                       flushout(&output);
+               }
+               n = parsecmd(inter);
+               /* showtree(n); DEBUG */
+               if (n == NEOF) {
+                       if (!top || numeof >= 50)
+                               break;
+                       if (!stoppedjobs()) {
+                               if (!Iflag)
+                                       break;
+                               out2str("\nUse \"exit\" to leave shell.\n");
+                       }
+                       numeof++;
+               } else if (n != NULL && nflag == 0) {
+                       job_warning = (job_warning == 2) ? 1 : 0;
+                       numeof = 0;
+                       evaltree(n, 0);
+               }
+               popstackmark(&smark);
+               setstackmark(&smark);
+               if (evalskip == SKIPFILE) {
+                       evalskip = 0;
+                       break;
+               }
+       }
+       popstackmark(&smark);
+}
+
+
+
+/*
+ * Read /etc/profile or .profile.  Return on error.
+ */
+
+static void
+read_profile(name)
+       const char *name;
+{
+       int fd;
+       int xflag_set = 0;
+       int vflag_set = 0;
+
+       INTOFF;
+       if ((fd = open(name, O_RDONLY)) >= 0)
+               setinputfd(fd, 1);
+       INTON;
+       if (fd < 0)
+               return;
+       /* -q turns off -x and -v just when executing init files */
+       if (qflag)  {
+           if (xflag)
+                   xflag = 0, xflag_set = 1;
+           if (vflag)
+                   vflag = 0, vflag_set = 1;
+       }
+       cmdloop(0);
+       if (qflag)  {
+           if (xflag_set)
+                   xflag = 1;
+           if (vflag_set)
+                   vflag = 1;
+       }
+       popfile();
+}
+
+
+
+/*
+ * Read a file containing shell functions.
+ */
+
+static void
+readcmdfile(name)
+       char *name;
+{
+       int fd;
+
+       INTOFF;
+       if ((fd = open(name, O_RDONLY)) >= 0)
+               setinputfd(fd, 1);
+       else
+               error("Can't open %s", name);
+       INTON;
+       cmdloop(0);
+       popfile();
+}
+
+
+
+/*
+ * Take commands from a file.  To be compatable we should do a path
+ * search for the file, which is necessary to find sub-commands.
+ */
+
+
+static char *
+find_dot_file(mybasename)
+       char *mybasename;
+{
+       char *fullname;
+       const char *path = pathval();
+       struct stat statb;
+
+       /* don't try this for absolute or relative paths */
+       if (strchr(mybasename, '/'))
+               return mybasename;
+
+       while ((fullname = padvance(&path, mybasename)) != NULL) {
+               if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) {
+                       /*
+                        * Don't bother freeing here, since it will
+                        * be freed by the caller.
+                        */
+                       return fullname;
+               }
+               stunalloc(fullname);
+       }
+
+       /* not found in the PATH */
+       error("%s: not found", mybasename);
+       /* NOTREACHED */
+}
+
+static int
+dotcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       struct strlist *sp;
+       exitstatus = 0;
+
+       for (sp = cmdenviron; sp ; sp = sp->next)
+               setvareq(savestr(sp->text), VSTRFIXED|VTEXTFIXED);
+
+       if (argc >= 2) {                /* That's what SVR2 does */
+               char *fullname;
+               struct stackmark smark;
+
+               setstackmark(&smark);
+               fullname = find_dot_file(argv[1]);
+               setinputfile(fullname, 1);
+               commandname = fullname;
+               cmdloop(0);
+               popfile();
+               popstackmark(&smark);
+       }
+       return exitstatus;
+}
+
+
+static int
+exitcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       if (stoppedjobs())
+               return 0;
+       if (argc > 1)
+               exitstatus = number(argv[1]);
+       else
+               exitstatus = oexitstatus;
+       exitshell(exitstatus);
+       /* NOTREACHED */
+}
+/*     $NetBSD: memalloc.c,v 1.23 2000/11/01 19:56:01 christos Exp $   */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+
+/*
+ * 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.
+ */
+
+#define MINSIZE 504            /* minimum size of a block */
+
+
+struct stack_block {
+       struct stack_block *prev;
+       char space[MINSIZE];
+};
+
+struct stack_block stackbase;
+struct stack_block *stackp = &stackbase;
+struct stackmark *markp;
+static char *stacknxt = stackbase.space;
+static int stacknleft = MINSIZE;
+static int sstrnleft;
+static int herefd = -1;
+
+
+
+pointer
+stalloc(nbytes)
+       int nbytes;
+{
+       char *p;
+
+       nbytes = ALIGN(nbytes);
+       if (nbytes > stacknleft) {
+               int blocksize;
+               struct stack_block *sp;
+
+               blocksize = nbytes;
+               if (blocksize < MINSIZE)
+                       blocksize = MINSIZE;
+               INTOFF;
+               sp = ckmalloc(sizeof(struct stack_block) - MINSIZE + blocksize);
+               sp->prev = stackp;
+               stacknxt = sp->space;
+               stacknleft = blocksize;
+               stackp = sp;
+               INTON;
+       }
+       p = stacknxt;
+       stacknxt += nbytes;
+       stacknleft -= nbytes;
+       return p;
+}
+
+
+static void
+stunalloc(p)
+       pointer p;
+       {
+#ifdef DEBUG
+       if (p == NULL) {                /*DEBUG */
+               write(2, "stunalloc\n", 10);
+               abort();
+       }
+#endif
+       if (!(stacknxt >= (char *)p && (char *)p >= stackp->space)) {
+               p = stackp->space;
+       }
+       stacknleft += stacknxt - (char *)p;
+       stacknxt = p;
+}
+
+
+
+static void
+setstackmark(mark)
+       struct stackmark *mark;
+       {
+       mark->stackp = stackp;
+       mark->stacknxt = stacknxt;
+       mark->stacknleft = stacknleft;
+       mark->marknext = markp;
+       markp = mark;
+}
+
+
+static void
+popstackmark(mark)
+       struct stackmark *mark;
+       {
+       struct stack_block *sp;
+
+       INTOFF;
+       markp = mark->marknext;
+       while (stackp != mark->stackp) {
+               sp = stackp;
+               stackp = sp->prev;
+               ckfree(sp);
+       }
+       stacknxt = mark->stacknxt;
+       stacknleft = mark->stacknleft;
+       INTON;
+}
+
+
+/*
+ * 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() {
+       char *p;
+       int newlen = ALIGN(stacknleft * 2 + 100);
+       char *oldspace = stacknxt;
+       int oldlen = stacknleft;
+       struct stack_block *sp;
+       struct stack_block *oldstackp;
+
+       if (stacknxt == stackp->space && stackp != &stackbase) {
+               INTOFF;
+               oldstackp = stackp;
+               sp = stackp;
+               stackp = sp->prev;
+               sp = ckrealloc((pointer)sp, sizeof(struct stack_block) - MINSIZE + newlen);
+               sp->prev = stackp;
+               stackp = sp;
+               stacknxt = sp->space;
+               stacknleft = newlen;
+               {
+                 /* Stack marks pointing to the start of the old block
+                  * must be relocated to point to the new block 
+                  */
+                 struct stackmark *xmark;
+                 xmark = markp;
+                 while (xmark != NULL && xmark->stackp == oldstackp) {
+                   xmark->stackp = stackp;
+                   xmark->stacknxt = stacknxt;
+                   xmark->stacknleft = stacknleft;
+                   xmark = xmark->marknext;
+                 }
+               }
+               INTON;
+       } else {
+               p = stalloc(newlen);
+               memcpy(p, oldspace, oldlen);
+               stacknxt = p;                   /* free the space */
+               stacknleft += newlen;           /* we just allocated */
+       }
+}
+
+
+
+static void
+grabstackblock(len)
+       int len;
+{
+       len = ALIGN(len);
+       stacknxt += len;
+       stacknleft -= len;
+}
+
+
+
+/*
+ * The following routines are somewhat easier to use that 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 char *
+growstackstr() {
+       int len = stackblocksize();
+       if (herefd >= 0 && len >= 1024) {
+               xwrite(herefd, stackblock(), len);
+               sstrnleft = len - 1;
+               return stackblock();
+       }
+       growstackblock();
+       sstrnleft = stackblocksize() - len - 1;
+       return stackblock() + len;
+}
+
+
+/*
+ * Called from CHECKSTRSPACE.
+ */
+
+static char *
+makestrspace(size_t newlen) {
+       int len = stackblocksize() - sstrnleft;
+       do {
+               growstackblock();
+               sstrnleft = stackblocksize() - len;
+       } while (sstrnleft < newlen);
+       return stackblock() + len;
+}
+
+
+
+static void
+ungrabstackstr(s, p)
+       char *s;
+       char *p;
+       {
+       stacknleft += stacknxt - s;
+       stacknxt = s;
+       sstrnleft = stacknleft - (p - s);
+}
+/*     $NetBSD: miscbltin.c,v 1.30 2001/02/04 19:52:06 christos Exp $  */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Miscelaneous builtins.
+ */
+
+
+#undef rflag
+
+#ifdef __GLIBC__
+mode_t getmode(const void *, mode_t);
+static void *setmode(const char *);
+
+#if !defined(__GLIBC__) || __GLIBC__ == 2 && __GLIBC_MINOR__ < 1
+typedef enum __rlimit_resource rlim_t;
+#endif
+#endif
+
+
+
+/*
+ * The read builtin.  The -e option causes backslashes to escape the
+ * following character.
+ *
+ * This uses unbuffered input, which may be avoidable in some cases.
+ */
+
+static int
+readcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       char **ap;
+       int backslash;
+       char c;
+       int rflag;
+       char *prompt;
+       const char *ifs;
+       char *p;
+       int startword;
+       int status;
+       int i;
+
+       rflag = 0;
+       prompt = NULL;
+       while ((i = nextopt("p:r")) != '\0') {
+               if (i == 'p')
+                       prompt = optionarg;
+               else
+                       rflag = 1;
+       }
+       if (prompt && isatty(0)) {
+               putprompt(prompt);
+               flushall();
+       }
+       if (*(ap = argptr) == NULL)
+               error("arg count");
+       if ((ifs = bltinlookup("IFS")) == NULL)
+               ifs = defifs;
+       status = 0;
+       startword = 1;
+       backslash = 0;
+       STARTSTACKSTR(p);
+       for (;;) {
+               if (read(0, &c, 1) != 1) {
+                       status = 1;
+                       break;
+               }
+               if (c == '\0')
+                       continue;
+               if (backslash) {
+                       backslash = 0;
+                       if (c != '\n')
+                               STPUTC(c, p);
+                       continue;
+               }
+               if (!rflag && c == '\\') {
+                       backslash++;
+                       continue;
+               }
+               if (c == '\n')
+                       break;
+               if (startword && *ifs == ' ' && strchr(ifs, c)) {
+                       continue;
+               }
+               startword = 0;
+               if (backslash && c == '\\') {
+                       if (read(0, &c, 1) != 1) {
+                               status = 1;
+                               break;
+                       }
+                       STPUTC(c, p);
+               } else if (ap[1] != NULL && strchr(ifs, c) != NULL) {
+                       STACKSTRNUL(p);
+                       setvar(*ap, stackblock(), 0);
+                       ap++;
+                       startword = 1;
+                       STARTSTACKSTR(p);
+               } else {
+                       STPUTC(c, p);
+               }
+       }
+       STACKSTRNUL(p);
+       /* Remove trailing blanks */
+       while (stackblock() <= --p && strchr(ifs, *p) != NULL)
+               *p = '\0';
+       setvar(*ap, stackblock(), 0);
+       while (*++ap != NULL)
+               setvar(*ap, nullstr, 0);
+       return status;
+}
+
+
+
+static int
+umaskcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       char *ap;
+       int mask;
+       int i;
+       int symbolic_mode = 0;
+
+       while ((i = nextopt("S")) != '\0') {
+               symbolic_mode = 1;
+       }
+
+       INTOFF;
+       mask = umask(0);
+       umask(mask);
+       INTON;
+
+       if ((ap = *argptr) == NULL) {
+               if (symbolic_mode) {
+                       char u[4], g[4], o[4];
+
+                       i = 0;
+                       if ((mask & S_IRUSR) == 0)
+                               u[i++] = 'r';
+                       if ((mask & S_IWUSR) == 0)
+                               u[i++] = 'w';
+                       if ((mask & S_IXUSR) == 0)
+                               u[i++] = 'x';
+                       u[i] = '\0';
+
+                       i = 0;
+                       if ((mask & S_IRGRP) == 0)
+                               g[i++] = 'r';
+                       if ((mask & S_IWGRP) == 0)
+                               g[i++] = 'w';
+                       if ((mask & S_IXGRP) == 0)
+                               g[i++] = 'x';
+                       g[i] = '\0';
+
+                       i = 0;
+                       if ((mask & S_IROTH) == 0)
+                               o[i++] = 'r';
+                       if ((mask & S_IWOTH) == 0)
+                               o[i++] = 'w';
+                       if ((mask & S_IXOTH) == 0)
+                               o[i++] = 'x';
+                       o[i] = '\0';
+
+                       out1fmt("u=%s,g=%s,o=%s\n", u, g, o);
+               } else {
+                       out1fmt("%.4o\n", mask);
+               }
+       } else {
+               if (isdigit((unsigned char)*ap)) {
+                       mask = 0;
+                       do {
+                               if (*ap >= '8' || *ap < '0')
+                                       error("Illegal number: %s", argv[1]);
+                               mask = (mask << 3) + (*ap - '0');
+                       } while (*++ap != '\0');
+                       umask(mask);
+               } else {
+                       void *set;
+
+                       INTOFF;
+                       if ((set = setmode(ap)) != 0) {
+                               mask = getmode(set, ~mask & 0777);
+                               ckfree(set);
+                       }
+                       INTON;
+                       if (!set)
+                               error("Illegal mode: %s", ap);
+
+                       umask(~mask & 0777);
+               }
+       }
+       return 0;
+}
+
+/*
+ * ulimit builtin
+ *
+ * This code, originally by Doug Gwyn, Doug Kingston, Eric Gisin, and
+ * Michael Rendell was ripped from pdksh 5.0.8 and hacked for use with
+ * ash by J.T. Conklin.
+ *
+ * Public domain.
+ */
+
+struct limits {
+       const char *name;
+       int     cmd;
+       int     factor; /* multiply by to get rlim_{cur,max} values */
+       char    option;
+};
+
+static const struct limits limits[] = {
+#ifdef RLIMIT_CPU
+       { "time(seconds)",              RLIMIT_CPU,        1, 't' },
+#endif
+#ifdef RLIMIT_FSIZE
+       { "file(blocks)",               RLIMIT_FSIZE,    512, 'f' },
+#endif
+#ifdef RLIMIT_DATA
+       { "data(kbytes)",               RLIMIT_DATA,    1024, 'd' },
+#endif
+#ifdef RLIMIT_STACK
+       { "stack(kbytes)",              RLIMIT_STACK,   1024, 's' },
+#endif
+#ifdef  RLIMIT_CORE
+       { "coredump(blocks)",           RLIMIT_CORE,     512, 'c' },
+#endif
+#ifdef RLIMIT_RSS
+       { "memory(kbytes)",             RLIMIT_RSS,     1024, 'm' },
+#endif
+#ifdef RLIMIT_MEMLOCK
+       { "locked memory(kbytes)",      RLIMIT_MEMLOCK, 1024, 'l' },
+#endif
+#ifdef RLIMIT_NPROC
+       { "process(processes)",         RLIMIT_NPROC,      1, 'p' },
+#endif
+#ifdef RLIMIT_NOFILE
+       { "nofiles(descriptors)",       RLIMIT_NOFILE,     1, 'n' },
+#endif
+#ifdef RLIMIT_VMEM
+       { "vmemory(kbytes)",            RLIMIT_VMEM,    1024, 'v' },
+#endif
+#ifdef RLIMIT_SWAP
+       { "swap(kbytes)",               RLIMIT_SWAP,    1024, 'w' },
+#endif
+       { (char *) 0,                   0,                 0,  '\0' }
+};
+
+static int
+ulimitcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       int     c;
+       rlim_t val = 0;
+       enum { SOFT = 0x1, HARD = 0x2 }
+                       how = SOFT | HARD;
+       const struct limits     *l;
+       int             set, all = 0;
+       int             optc, what;
+       struct rlimit   limit;
+
+       what = 'f';
+       while ((optc = nextopt("HSatfdsmcnpl")) != '\0')
+               switch (optc) {
+               case 'H':
+                       how = HARD;
+                       break;
+               case 'S':
+                       how = SOFT;
+                       break;
+               case 'a':
+                       all = 1;
+                       break;
+               default:
+                       what = optc;
+               }
+
+       for (l = limits; l->name && l->option != what; l++)
+               ;
+       if (!l->name)
+               error("internal error (%c)", what);
+
+       set = *argptr ? 1 : 0;
+       if (set) {
+               char *p = *argptr;
+
+               if (all || argptr[1])
+                       error("too many arguments");
+               if (strcmp(p, "unlimited") == 0)
+                       val = RLIM_INFINITY;
+               else {
+                       val = (rlim_t) 0;
+
+                       while ((c = *p++) >= '0' && c <= '9')
+                       {
+                               val = (val * 10) + (long)(c - '0');
+                               if (val < (rlim_t) 0)
+                                       break;
+                       }
+                       if (c)
+                               error("bad number");
+                       val *= l->factor;
+               }
+       }
+       if (all) {
+               for (l = limits; l->name; l++) {
+                       getrlimit(l->cmd, &limit);
+                       if (how & SOFT)
+                               val = limit.rlim_cur;
+                       else if (how & HARD)
+                               val = limit.rlim_max;
+
+                       out1fmt("%-20s ", l->name);
+                       if (val == RLIM_INFINITY)
+                               out1fmt("unlimited\n");
+                       else
+                       {
+                               val /= l->factor;
+#ifdef BSD4_4
+                               out1fmt("%lld\n", (long long) val);
+#else
+                               out1fmt("%ld\n", (long) val);
+#endif
+                       }
+               }
+               return 0;
+       }
+
+       getrlimit(l->cmd, &limit);
+       if (set) {
+               if (how & HARD)
+                       limit.rlim_max = val;
+               if (how & SOFT)
+                       limit.rlim_cur = val;
+               if (setrlimit(l->cmd, &limit) < 0)
+                       error("error setting limit (%s)", strerror(errno));
+       } else {
+               if (how & SOFT)
+                       val = limit.rlim_cur;
+               else if (how & HARD)
+                       val = limit.rlim_max;
+
+               if (val == RLIM_INFINITY)
+                       out1fmt("unlimited\n");
+               else
+               {
+                       val /= l->factor;
+#ifdef BSD4_4
+                       out1fmt("%lld\n", (long long) val);
+#else
+                       out1fmt("%ld\n", (long) val);
+#endif
+               }
+       }
+       return 0;
+}
+/*     $NetBSD: mystring.c,v 1.14 1999/07/09 03:05:50 christos Exp $   */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * String functions.
+ *
+ *     equal(s1, s2)           Return true if strings are equal.
+ *     scopy(from, to)         Copy a string.
+ *     scopyn(from, to, n)     Like scopy, but checks for overflow.
+ *     number(s)               Convert a string of digits to an integer.
+ *     is_number(s)            Return true if s is a string of digits.
+ */
+
+static char nullstr[1];                /* zero length string */
+static const char spcstr[] = " ";
+static const char snlfmt[] = "%s\n";
+
+/*
+ * equal - #defined in mystring.h
+ */
+
+/*
+ * scopy - #defined in mystring.h
+ */
+
+
+#if 0
+/*
+ * scopyn - copy a string from "from" to "to", truncating the string
+ *             if necessary.  "To" is always nul terminated, even if
+ *             truncation is performed.  "Size" is the size of "to".
+ */
+
+static void
+scopyn(from, to, size)
+       char const *from;
+       char *to;
+       int size;
+       {
+
+       while (--size > 0) {
+               if ((*to++ = *from++) == '\0')
+                       return;
+       }
+       *to = '\0';
+}
+#endif
+
+
+/*
+ * prefix -- see if pfx is a prefix of string.
+ */
+
+static int
+prefix(pfx, string)
+       char const *pfx;
+       char const *string;
+       {
+       while (*pfx) {
+               if (*pfx++ != *string++)
+                       return 0;
+       }
+       return 1;
+}
+
+
+/*
+ * Convert a string of digits to an integer, printing an error message on
+ * failure.
+ */
+
+static int
+number(s)
+       const char *s;
+       {
+
+       if (! is_number(s))
+               error("Illegal number: %s", s);
+       return atoi(s);
+}
+
+
+
+/*
+ * Check for a valid number.  This should be elsewhere.
+ */
+
+static int
+is_number(p)
+       const char *p;
+       {
+       do {
+               if (! is_digit(*p))
+                       return 0;
+       } while (*++p != '\0');
+       return 1;
+}
+
+
+/*
+ * Produce a possibly single quoted string suitable as input to the shell.
+ * The return string is allocated on the stack.
+ */
+
+static char *
+single_quote(const char *s) {
+       char *p;
+
+       STARTSTACKSTR(p);
+
+       do {
+               char *q = p;
+               size_t len1, len1p, len2, len2p;
+
+               len1 = strcspn(s, "'");
+               len2 = strspn(s + len1, "'");
+
+               len1p = len1 ? len1 + 2 : len1;
+               switch (len2) {
+               case 0:
+                       len2p = 0;
+                       break;
+               case 1:
+                       len2p = 2;
+                       break;
+               default:
+                       len2p = len2 + 2;
+               }
+
+               CHECKSTRSPACE(len1p + len2p + 1, p);
+
+               if (len1) {
+                       *p = '\'';
+#ifdef _GNU_SOURCE
+                       q = mempcpy(p + 1, s, len1);
+#else
+                       q = p + 1 + len1;
+                       memcpy(p + 1, s, len1);
+#endif
+                       *q++ = '\'';
+                       s += len1;
+               }
+
+               switch (len2) {
+               case 0:
+                       break;
+               case 1:
+                       *q++ = '\\';
+                       *q = '\'';
+                       s++;
+                       break;
+               default:
+                       *q = '"';
+#ifdef _GNU_SOURCE
+                       *(char *) mempcpy(q + 1, s, len2) = '"';
+#else
+                       q += 1 + len2;
+                       memcpy(q + 1, s, len2);
+                       *q = '"';
+#endif
+                       s += len2;
+               }
+
+               STADJUST(len1p + len2p, p);
+       } while (*s);
+
+       USTPUTC(0, p);
+
+       return grabstackstr(p);
+}
+
+/*
+ * Like strdup but works with the ash stack.
+ */
+
+static char *
+sstrdup(const char *p)
+{
+       size_t len = strlen(p) + 1;
+       return memcpy(stalloc(len), p, len);
+}
+
+/*
+ * Wrapper around strcmp for qsort/bsearch/...
+ */
+static int
+pstrcmp(const void *a, const void *b)
+{
+       return strcmp(*(const char *const *) a, *(const char *const *) b);
+}
+
+/*
+ * Find a string is in a sorted array.
+ */
+static const char *const *
+findstring(const char *s, const char *const *array, size_t nmemb)
+{
+       return bsearch(&s, array, nmemb, sizeof(const char *), pstrcmp);
+}
+/*
+ * This file was generated by the mknodes program.
+ */
+
+/*     $NetBSD: nodes.c.pat,v 1.8 1997/04/11 23:03:09 christos Exp $   */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)nodes.c.pat 8.2 (Berkeley) 5/4/95
+ */
+
+/*
+ * Routine for dealing with parsed shell commands.
+ */
+
+
+static int     funcblocksize;          /* size of structures in function */
+static int     funcstringsize;         /* size of strings in node */
+pointer funcblock;             /* block to allocate function from */
+static char   *funcstring;             /* block to allocate strings from */
+
+static const short nodesize[26] = {
+      ALIGN(sizeof (struct nbinary)),
+      ALIGN(sizeof (struct ncmd)),
+      ALIGN(sizeof (struct npipe)),
+      ALIGN(sizeof (struct nredir)),
+      ALIGN(sizeof (struct nredir)),
+      ALIGN(sizeof (struct nredir)),
+      ALIGN(sizeof (struct nbinary)),
+      ALIGN(sizeof (struct nbinary)),
+      ALIGN(sizeof (struct nif)),
+      ALIGN(sizeof (struct nbinary)),
+      ALIGN(sizeof (struct nbinary)),
+      ALIGN(sizeof (struct nfor)),
+      ALIGN(sizeof (struct ncase)),
+      ALIGN(sizeof (struct nclist)),
+      ALIGN(sizeof (struct narg)),
+      ALIGN(sizeof (struct narg)),
+      ALIGN(sizeof (struct nfile)),
+      ALIGN(sizeof (struct nfile)),
+      ALIGN(sizeof (struct nfile)),
+      ALIGN(sizeof (struct nfile)),
+      ALIGN(sizeof (struct nfile)),
+      ALIGN(sizeof (struct ndup)),
+      ALIGN(sizeof (struct ndup)),
+      ALIGN(sizeof (struct nhere)),
+      ALIGN(sizeof (struct nhere)),
+      ALIGN(sizeof (struct nnot)),
+};
+
+
+static void calcsize __P((union node *));
+static void sizenodelist __P((struct nodelist *));
+static union node *copynode __P((union node *));
+static struct nodelist *copynodelist __P((struct nodelist *));
+static char *nodesavestr __P((char *));
+
+
+
+/*
+ * Make a copy of a parse tree.
+ */
+
+union node *
+copyfunc(n)
+       union node *n;
+{
+       if (n == NULL)
+               return NULL;
+       funcblocksize = 0;
+       funcstringsize = 0;
+       calcsize(n);
+       funcblock = ckmalloc(funcblocksize + funcstringsize);
+       funcstring = (char *) funcblock + funcblocksize;
+       return copynode(n);
+}
+
+
+
+static void
+calcsize(n)
+       union node *n;
+{
+      if (n == NULL)
+           return;
+      funcblocksize += nodesize[n->type];
+      switch (n->type) {
+      case NSEMI:
+      case NAND:
+      case NOR:
+      case NWHILE:
+      case NUNTIL:
+           calcsize(n->nbinary.ch2);
+           calcsize(n->nbinary.ch1);
+           break;
+      case NCMD:
+           calcsize(n->ncmd.redirect);
+           calcsize(n->ncmd.args);
+           calcsize(n->ncmd.assign);
+           break;
+      case NPIPE:
+           sizenodelist(n->npipe.cmdlist);
+           break;
+      case NREDIR:
+      case NBACKGND:
+      case NSUBSHELL:
+           calcsize(n->nredir.redirect);
+           calcsize(n->nredir.n);
+           break;
+      case NIF:
+           calcsize(n->nif.elsepart);
+           calcsize(n->nif.ifpart);
+           calcsize(n->nif.test);
+           break;
+      case NFOR:
+           funcstringsize += strlen(n->nfor.var) + 1;
+           calcsize(n->nfor.body);
+           calcsize(n->nfor.args);
+           break;
+      case NCASE:
+           calcsize(n->ncase.cases);
+           calcsize(n->ncase.expr);
+           break;
+      case NCLIST:
+           calcsize(n->nclist.body);
+           calcsize(n->nclist.pattern);
+           calcsize(n->nclist.next);
+           break;
+      case NDEFUN:
+      case NARG:
+           sizenodelist(n->narg.backquote);
+           funcstringsize += strlen(n->narg.text) + 1;
+           calcsize(n->narg.next);
+           break;
+      case NTO:
+      case NFROM:
+      case NFROMTO:
+      case NAPPEND:
+      case NTOOV:
+           calcsize(n->nfile.fname);
+           calcsize(n->nfile.next);
+           break;
+      case NTOFD:
+      case NFROMFD:
+           calcsize(n->ndup.vname);
+           calcsize(n->ndup.next);
+           break;
+      case NHERE:
+      case NXHERE:
+           calcsize(n->nhere.doc);
+           calcsize(n->nhere.next);
+           break;
+      case NNOT:
+           calcsize(n->nnot.com);
+           break;
+      };
+}
+
+
+
+static void
+sizenodelist(lp)
+       struct nodelist *lp;
+{
+       while (lp) {
+               funcblocksize += ALIGN(sizeof(struct nodelist));
+               calcsize(lp->n);
+               lp = lp->next;
+       }
+}
+
+
+
+static union node *
+copynode(n)
+       union node *n;
+{
+       union node *new;
+
+      if (n == NULL)
+           return NULL;
+      new = funcblock;
+      funcblock = (char *) funcblock + nodesize[n->type];
+      switch (n->type) {
+      case NSEMI:
+      case NAND:
+      case NOR:
+      case NWHILE:
+      case NUNTIL:
+           new->nbinary.ch2 = copynode(n->nbinary.ch2);
+           new->nbinary.ch1 = copynode(n->nbinary.ch1);
+           break;
+      case NCMD:
+           new->ncmd.redirect = copynode(n->ncmd.redirect);
+           new->ncmd.args = copynode(n->ncmd.args);
+           new->ncmd.assign = copynode(n->ncmd.assign);
+           new->ncmd.backgnd = n->ncmd.backgnd;
+           break;
+      case NPIPE:
+           new->npipe.cmdlist = copynodelist(n->npipe.cmdlist);
+           new->npipe.backgnd = n->npipe.backgnd;
+           break;
+      case NREDIR:
+      case NBACKGND:
+      case NSUBSHELL:
+           new->nredir.redirect = copynode(n->nredir.redirect);
+           new->nredir.n = copynode(n->nredir.n);
+           break;
+      case NIF:
+           new->nif.elsepart = copynode(n->nif.elsepart);
+           new->nif.ifpart = copynode(n->nif.ifpart);
+           new->nif.test = copynode(n->nif.test);
+           break;
+      case NFOR:
+           new->nfor.var = nodesavestr(n->nfor.var);
+           new->nfor.body = copynode(n->nfor.body);
+           new->nfor.args = copynode(n->nfor.args);
+           break;
+      case NCASE:
+           new->ncase.cases = copynode(n->ncase.cases);
+           new->ncase.expr = copynode(n->ncase.expr);
+           break;
+      case NCLIST:
+           new->nclist.body = copynode(n->nclist.body);
+           new->nclist.pattern = copynode(n->nclist.pattern);
+           new->nclist.next = copynode(n->nclist.next);
+           break;
+      case NDEFUN:
+      case NARG:
+           new->narg.backquote = copynodelist(n->narg.backquote);
+           new->narg.text = nodesavestr(n->narg.text);
+           new->narg.next = copynode(n->narg.next);
+           break;
+      case NTO:
+      case NFROM:
+      case NFROMTO:
+      case NAPPEND:
+      case NTOOV:
+           new->nfile.fname = copynode(n->nfile.fname);
+           new->nfile.fd = n->nfile.fd;
+           new->nfile.next = copynode(n->nfile.next);
+           break;
+      case NTOFD:
+      case NFROMFD:
+           new->ndup.vname = copynode(n->ndup.vname);
+           new->ndup.dupfd = n->ndup.dupfd;
+           new->ndup.fd = n->ndup.fd;
+           new->ndup.next = copynode(n->ndup.next);
+           break;
+      case NHERE:
+      case NXHERE:
+           new->nhere.doc = copynode(n->nhere.doc);
+           new->nhere.fd = n->nhere.fd;
+           new->nhere.next = copynode(n->nhere.next);
+           break;
+      case NNOT:
+           new->nnot.com = copynode(n->nnot.com);
+           break;
+      };
+      new->type = n->type;
+       return new;
+}
+
+
+static struct nodelist *
+copynodelist(lp)
+       struct nodelist *lp;
+{
+       struct nodelist *start;
+       struct nodelist **lpp;
+
+       lpp = &start;
+       while (lp) {
+               *lpp = funcblock;
+               funcblock = (char *) funcblock + ALIGN(sizeof(struct nodelist));
+               (*lpp)->n = copynode(lp->n);
+               lp = lp->next;
+               lpp = &(*lpp)->next;
+       }
+       *lpp = NULL;
+       return start;
+}
+
+
+
+static char *
+nodesavestr(s)
+       char   *s;
+{
+#ifdef _GNU_SOURCE
+       char   *rtn = funcstring;
+
+       funcstring = stpcpy(funcstring, s) + 1;
+       return rtn;
+#else
+       register char *p = s;
+       register char *q = funcstring;
+       char   *rtn = funcstring;
+
+       while ((*q++ = *p++) != '\0')
+               continue;
+       funcstring = q;
+       return rtn;
+#endif
+}
+
+
+
+/*
+ * Free a parse tree.
+ */
+
+static void
+freefunc(n)
+       union node *n;
+{
+       if (n)
+               ckfree(n);
+}
+/*     $NetBSD: options.c,v 1.31 2001/02/26 13:06:43 wiz Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+
+struct optent optlist[NOPTS] = {
+       { "errexit",    'e',    0 },
+       { "noglob",     'f',    0 },
+       { "ignoreeof",  'I',    0 },
+       { "interactive",'i',    0 },
+       { "monitor",    'm',    0 },
+       { "noexec",     'n',    0 },
+       { "stdin",      's',    0 },
+       { "xtrace",     'x',    0 },
+       { "verbose",    'v',    0 },
+       { "vi",         'V',    0 },
+       { "emacs",      'E',    0 },
+       { "noclobber",  'C',    0 },
+       { "allexport",  'a',    0 },
+       { "notify",     'b',    0 },
+       { "nounset",    'u',    0 },
+       { "quietprofile", 'q',  0 },
+};
+static char *arg0;                     /* value of $0 */
+struct shparam shellparam;     /* current positional parameters */
+static char **argptr;                  /* argument list for builtin commands */
+static char *optionarg;                /* set by nextopt (like getopt) */
+static char *optptr;                   /* used by nextopt */
+
+static char *minusc;                   /* argument to -c option */
+
+
+static void options __P((int));
+static void minus_o __P((char *, int));
+static void setoption __P((int, int));
+#ifdef ASH_GETOPTS
+static int getopts __P((char *, char *, char **, int *, int *));
+#endif
+
+
+/*
+ * Process the shell command line arguments.
+ */
+
+static void
+procargs(argc, argv)
+       int argc;
+       char **argv;
+{
+       int i;
+
+       argptr = argv;
+       if (argc > 0)
+               argptr++;
+       for (i = 0; i < NOPTS; i++)
+               optlist[i].val = 2;
+       options(1);
+       if (*argptr == NULL && minusc == NULL)
+               sflag = 1;
+       if (iflag == 2 && sflag == 1 && isatty(0) && isatty(1))
+               iflag = 1;
+       if (mflag == 2)
+               mflag = iflag;
+       for (i = 0; i < NOPTS; i++)
+               if (optlist[i].val == 2)
+                       optlist[i].val = 0;
+       arg0 = argv[0];
+       if (sflag == 0 && minusc == NULL) {
+               commandname = argv[0];
+               arg0 = *argptr++;
+               setinputfile(arg0, 0);
+               commandname = arg0;
+       }
+       /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */
+       if (argptr && minusc && *argptr)
+               arg0 = *argptr++;
+
+       shellparam.p = argptr;
+       shellparam.optind = 1;
+       shellparam.optoff = -1;
+       /* assert(shellparam.malloc == 0 && shellparam.nparam == 0); */
+       while (*argptr) {
+               shellparam.nparam++;
+               argptr++;
+       }
+       optschanged();
+}
+
+
+static void
+optschanged()
+{
+       setinteractive(iflag);
+       setjobctl(mflag);
+}
+
+/*
+ * Process shell options.  The global variable argptr contains a pointer
+ * to the argument list; we advance it past the options.
+ */
+
+static void
+options(cmdline)
+       int cmdline;
+{
+       char *p;
+       int val;
+       int c;
+
+       if (cmdline)
+               minusc = NULL;
+       while ((p = *argptr) != NULL) {
+               argptr++;
+               if ((c = *p++) == '-') {
+                       val = 1;
+                        if (p[0] == '\0' || (p[0] == '-' && p[1] == '\0')) {
+                                if (!cmdline) {
+                                        /* "-" means turn off -x and -v */
+                                        if (p[0] == '\0')
+                                                xflag = vflag = 0;
+                                        /* "--" means reset params */
+                                        else if (*argptr == NULL)
+                                               setparam(argptr);
+                                }
+                               break;    /* "-" or  "--" terminates options */
+                       }
+               } else if (c == '+') {
+                       val = 0;
+               } else {
+                       argptr--;
+                       break;
+               }
+               while ((c = *p++) != '\0') {
+                       if (c == 'c' && cmdline) {
+                               char *q;
+#ifdef NOHACK  /* removing this code allows sh -ce 'foo' for compat */
+                               if (*p == '\0')
+#endif
+                                       q = *argptr++;
+                               if (q == NULL || minusc != NULL)
+                                       error("Bad -c option");
+                               minusc = q;
+#ifdef NOHACK
+                               break;
+#endif
+                       } else if (c == 'o') {
+                               minus_o(*argptr, val);
+                               if (*argptr)
+                                       argptr++;
+                       } else {
+                               setoption(c, val);
+                       }
+               }
+       }
+}
+
+static void
+minus_o(name, val)
+       char *name;
+       int val;
+{
+       int i;
+
+       if (name == NULL) {
+               out1str("Current option settings\n");
+               for (i = 0; i < NOPTS; i++)
+                       out1fmt("%-16s%s\n", optlist[i].name,
+                               optlist[i].val ? "on" : "off");
+       } else {
+               for (i = 0; i < NOPTS; i++)
+                       if (equal(name, optlist[i].name)) {
+                               setoption(optlist[i].letter, val);
+                               return;
+                       }
+               error("Illegal option -o %s", name);
+       }
+}
+
+
+static void
+setoption(flag, val)
+       char flag;
+       int val;
+       {
+       int i;
+
+       for (i = 0; i < NOPTS; i++)
+               if (optlist[i].letter == flag) {
+                       optlist[i].val = val;
+                       if (val) {
+                               /* #%$ hack for ksh semantics */
+                               if (flag == 'V')
+                                       Eflag = 0;
+                               else if (flag == 'E')
+                                       Vflag = 0;
+                       }
+                       return;
+               }
+       error("Illegal option -%c", flag);
+       /* NOTREACHED */
+}
+
+
+
+#ifdef mkinit
+SHELLPROC {
+       int i;
+
+       for (i = 0; i < NOPTS; i++)
+               optlist[i].val = 0;
+       optschanged();
+
+}
+#endif
+
+
+/*
+ * Set the shell parameters.
+ */
+
+static void
+setparam(argv)
+       char **argv;
+       {
+       char **newparam;
+       char **ap;
+       int nparam;
+
+       for (nparam = 0 ; argv[nparam] ; nparam++);
+       ap = newparam = ckmalloc((nparam + 1) * sizeof *ap);
+       while (*argv) {
+               *ap++ = savestr(*argv++);
+       }
+       *ap = NULL;
+       freeparam(&shellparam);
+       shellparam.malloc = 1;
+       shellparam.nparam = nparam;
+       shellparam.p = newparam;
+       shellparam.optind = 1;
+       shellparam.optoff = -1;
+}
+
+
+/*
+ * Free the list of positional parameters.
+ */
+
+static void
+freeparam(param)
+       volatile struct shparam *param;
+       {
+       char **ap;
+
+       if (param->malloc) {
+               for (ap = param->p ; *ap ; ap++)
+                       ckfree(*ap);
+               ckfree(param->p);
+       }
+}
+
+
+
+/*
+ * The shift builtin command.
+ */
+
+static int
+shiftcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       int n;
+       char **ap1, **ap2;
+
+       n = 1;
+       if (argc > 1)
+               n = number(argv[1]);
+       if (n > shellparam.nparam)
+               error("can't shift that many");
+       INTOFF;
+       shellparam.nparam -= n;
+       for (ap1 = shellparam.p ; --n >= 0 ; ap1++) {
+               if (shellparam.malloc)
+                       ckfree(*ap1);
+       }
+       ap2 = shellparam.p;
+       while ((*ap2++ = *ap1++) != NULL);
+       shellparam.optind = 1;
+       shellparam.optoff = -1;
+       INTON;
+       return 0;
+}
+
+
+
+/*
+ * The set command builtin.
+ */
+
+static int
+setcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       if (argc == 1)
+               return showvarscmd(argc, argv);
+       INTOFF;
+       options(0);
+       optschanged();
+       if (*argptr != NULL) {
+               setparam(argptr);
+       }
+       INTON;
+       return 0;
+}
+
+
+static void
+getoptsreset(value)
+       const char *value;
+{
+       shellparam.optind = number(value);
+       shellparam.optoff = -1;
+}
+
+#ifdef ASH_GETOPTS
+/*
+ * The getopts builtin.  Shellparam.optnext points to the next argument
+ * to be processed.  Shellparam.optptr points to the next character to
+ * be processed in the current argument.  If shellparam.optnext is NULL,
+ * then it's the first time getopts has been called.
+ */
+
+static int
+getoptscmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       char **optbase;
+
+       if (argc < 3)
+               error("Usage: getopts optstring var [arg]");
+       else if (argc == 3) {
+               optbase = shellparam.p;
+               if (shellparam.optind > shellparam.nparam + 1) {
+                       shellparam.optind = 1;
+                       shellparam.optoff = -1;
+               }
+       }
+       else {
+               optbase = &argv[3];
+               if (shellparam.optind > argc - 2) {
+                       shellparam.optind = 1;
+                       shellparam.optoff = -1;
+               }
+       }
+
+       return getopts(argv[1], argv[2], optbase, &shellparam.optind,
+                      &shellparam.optoff);
+}
+
+/*
+ * Safe version of setvar, returns 1 on success 0 on failure.
+ */
+
+static int
+setvarsafe(name, val, flags)
+       const char *name, *val;
+       int flags;
+{
+       struct jmploc jmploc;
+       struct jmploc *volatile savehandler = handler;
+       int err = 0;
+#ifdef __GNUC__
+       (void) &err;
+#endif
+
+       if (setjmp(jmploc.loc))
+               err = 1;
+       else {
+               handler = &jmploc;
+               setvar(name, val, flags);
+       }
+       handler = savehandler;
+       return err;
+}
+
+static int
+getopts(optstr, optvar, optfirst, myoptind, optoff)
+       char *optstr;
+       char *optvar;
+       char **optfirst;
+       int *myoptind;
+       int *optoff;
+{
+       char *p, *q;
+       char c = '?';
+       int done = 0;
+       int err = 0;
+       char s[10];
+       char **optnext = optfirst + *myoptind - 1;
+
+       if (*myoptind <= 1 || *optoff < 0 || !(*(optnext - 1)) ||
+           strlen(*(optnext - 1)) < *optoff)
+               p = NULL;
+       else
+               p = *(optnext - 1) + *optoff;
+       if (p == NULL || *p == '\0') {
+               /* Current word is done, advance */
+               if (optnext == NULL)
+                       return 1;
+               p = *optnext;
+               if (p == NULL || *p != '-' || *++p == '\0') {
+atend:
+                       *myoptind = optnext - optfirst + 1;
+                       p = NULL;
+                       done = 1;
+                       goto out;
+               }
+               optnext++;
+               if (p[0] == '-' && p[1] == '\0')        /* check for "--" */
+                       goto atend;
+       }
+
+       c = *p++;
+       for (q = optstr; *q != c; ) {
+               if (*q == '\0') {
+                       if (optstr[0] == ':') {
+                               s[0] = c;
+                               s[1] = '\0';
+                               err |= setvarsafe("OPTARG", s, 0);
+                       }
+                       else {
+                               outfmt(&errout, "Illegal option -%c\n", c);
+                               (void) unsetvar("OPTARG");
+                       }
+                       c = '?';
+                       goto bad;
+               }
+               if (*++q == ':')
+                       q++;
+       }
+
+       if (*++q == ':') {
+               if (*p == '\0' && (p = *optnext) == NULL) {
+                       if (optstr[0] == ':') {
+                               s[0] = c;
+                               s[1] = '\0';
+                               err |= setvarsafe("OPTARG", s, 0);
+                               c = ':';
+                       }
+                       else {
+                               outfmt(&errout, "No arg for -%c option\n", c);
+                               (void) unsetvar("OPTARG");
+                               c = '?';
+                       }
+                       goto bad;
+               }
+
+               if (p == *optnext)
+                       optnext++;
+               setvarsafe("OPTARG", p, 0);
+               p = NULL;
+       }
+       else
+               setvarsafe("OPTARG", "", 0);
+       *myoptind = optnext - optfirst + 1;
+       goto out;
+
+bad:
+       *myoptind = 1;
+       p = NULL;
+out:
+       *optoff = p ? p - *(optnext - 1) : -1;
+       fmtstr(s, sizeof(s), "%d", *myoptind);
+       err |= setvarsafe("OPTIND", s, VNOFUNC);
+       s[0] = c;
+       s[1] = '\0';
+       err |= setvarsafe(optvar, s, 0);
+       if (err) {
+               *myoptind = 1;
+               *optoff = -1;
+               flushall();
+               exraise(EXERROR);
+       }
+       return done;
+}
+#endif 
+
+/*
+ * XXX - should get rid of.  have all builtins use getopt(3).  the
+ * library getopt must have the BSD extension static variable "optreset"
+ * otherwise it can't be used within the shell safely.
+ *
+ * Standard option processing (a la getopt) for builtin routines.  The
+ * only argument that is passed to nextopt is the option string; the
+ * other arguments are unnecessary.  It return the character, or '\0' on
+ * end of input.
+ */
+
+static int
+nextopt(optstring)
+       const char *optstring;
+       {
+       char *p;
+       const char *q;
+       char c;
+
+       if ((p = optptr) == NULL || *p == '\0') {
+               p = *argptr;
+               if (p == NULL || *p != '-' || *++p == '\0')
+                       return '\0';
+               argptr++;
+               if (p[0] == '-' && p[1] == '\0')        /* check for "--" */
+                       return '\0';
+       }
+       c = *p++;
+       for (q = optstring ; *q != c ; ) {
+               if (*q == '\0')
+                       error("Illegal option -%c", c);
+               if (*++q == ':')
+                       q++;
+       }
+       if (*++q == ':') {
+               if (*p == '\0' && (p = *argptr++) == NULL)
+                       error("No arg for -%c option", c);
+               optionarg = p;
+               p = NULL;
+       }
+       optptr = p;
+       return c;
+}
+
+
+/*     $NetBSD: output.c,v 1.23 2001/01/07 23:39:07 lukem Exp $        */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Shell output routines.  We use our own output routines because:
+ *     When a builtin command is interrupted we have to discard
+ *             any pending output.
+ *     When a builtin command appears in back quotes, we want to
+ *             save the output of the command in a region obtained
+ *             via malloc, rather than doing a fork and reading the
+ *             output of the command via a pipe.
+ *     Our output routines may be smaller than the stdio routines.
+ */
+
+
+#define OUTBUFSIZ BUFSIZ
+#define MEM_OUT -3             /* output to dynamically allocated memory */
+
+
+#ifdef USE_GLIBC_STDIO
+struct output output = {NULL, NULL, 0, NULL, 0, 1, 0};
+struct output errout = {NULL, NULL, 0, NULL, 0, 2, 0};
+struct output memout = {NULL, NULL, 0, NULL, 0, MEM_OUT, 0};
+#else
+struct output output = {NULL, 0, NULL, OUTBUFSIZ, 1, 0};
+struct output errout = {NULL, 0, NULL, 0, 2, 0};
+struct output memout = {NULL, 0, NULL, 0, MEM_OUT, 0};
+#endif
+struct output *out1 = &output;
+struct output *out2 = &errout;
+
+
+#ifndef USE_GLIBC_STDIO
+static void __outstr __P((const char *, size_t, struct output*));
+#endif
+
+
+#ifdef mkinit
+
+INCLUDE "output.h"
+INCLUDE "memalloc.h"
+
+INIT {
+#ifdef USE_GLIBC_STDIO
+       initstreams();
+#endif
+}
+
+RESET {
+       out1 = &output;
+       out2 = &errout;
+#ifdef USE_GLIBC_STDIO
+       if (memout.stream != NULL)
+               __closememout();
+#endif
+       if (memout.buf != NULL) {
+               ckfree(memout.buf);
+               memout.buf = NULL;
+       }
+}
+
+#endif
+
+
+#ifndef USE_GLIBC_STDIO
+static void
+__outstr(const char *p, size_t len, struct output *dest) {
+       if (!dest->bufsize) {
+               dest->nleft = 0;
+       } else if (dest->buf == NULL) {
+               if (len > dest->bufsize && dest->fd == MEM_OUT) {
+                       dest->bufsize = len;
+               }
+               INTOFF;
+               dest->buf = ckmalloc(dest->bufsize);
+               dest->nextc = dest->buf;
+               dest->nleft = dest->bufsize;
+               INTON;
+       } else if (dest->fd == MEM_OUT) {
+               int offset;
+
+               offset = dest->bufsize;
+               INTOFF;
+               if (dest->bufsize >= len) {
+                       dest->bufsize <<= 1;
+               } else {
+                       dest->bufsize += len;
+               }
+               dest->buf = ckrealloc(dest->buf, dest->bufsize);
+               dest->nleft = dest->bufsize - offset;
+               dest->nextc = dest->buf + offset;
+               INTON;
+       } else {
+               flushout(dest);
+       }
+
+       if (len < dest->nleft) {
+               dest->nleft -= len;
+               memcpy(dest->nextc, p, len);
+               dest->nextc += len;
+               return;
+       }
+
+       if (xwrite(dest->fd, p, len) < len)
+               dest->flags |= OUTPUT_ERR;
+}
+#endif
+
+
+static void
+outstr(p, file)
+       const char *p;
+       struct output *file;
+       {
+#ifdef USE_GLIBC_STDIO
+       INTOFF;
+       fputs(p, file->stream);
+       INTON;
+#else
+       size_t len;
+
+       if (!*p) {
+               return;
+       }
+       len = strlen(p);
+       if ((file->nleft -= len) > 0) {
+               memcpy(file->nextc, p, len);
+               file->nextc += len;
+               return;
+       }
+       __outstr(p, len, file);
+#endif
+}
+
+
+#ifndef USE_GLIBC_STDIO
+
+
+static void
+outcslow(c, dest)
+       char c;
+       struct output *dest;
+       {
+       __outstr(&c, 1, dest);
+}
+#endif
+
+
+static void
+flushall() {
+       flushout(&output);
+#ifdef FLUSHERR
+       flushout(&errout);
+#endif
+}
+
+
+static void
+flushout(dest)
+       struct output *dest;
+       {
+#ifdef USE_GLIBC_STDIO
+       INTOFF;
+       fflush(dest->stream);
+       INTON;
+#else
+       size_t len;
+
+       len = dest->nextc - dest->buf;
+       if (dest->buf == NULL || !len || dest->fd < 0)
+               return;
+       dest->nextc = dest->buf;
+       dest->nleft = dest->bufsize;
+       if (xwrite(dest->fd, dest->buf, len) < len)
+               dest->flags |= OUTPUT_ERR;
+#endif
+}
+
+
+static void
+freestdout() {
+       if (output.buf) {
+               INTOFF;
+               ckfree(output.buf);
+               output.buf = NULL;
+               output.nleft = 0;
+               INTON;
+       }
+       output.flags = 0;
+}
+
+
+static void
+#ifdef __STDC__
+outfmt(struct output *file, const char *fmt, ...)
+#else
+static void
+outfmt(va_alist)
+       va_dcl
+#endif
+{
+       va_list ap;
+#ifndef __STDC__
+       struct output *file;
+       const char *fmt;
+
+       va_start(ap);
+       file = va_arg(ap, struct output *);
+       fmt = va_arg(ap, const char *);
+#else
+       va_start(ap, fmt);
+#endif
+       doformat(file, fmt, ap);
+       va_end(ap);
+}
+
+
+static void
+#ifdef __STDC__
+out1fmt(const char *fmt, ...)
+#else
+out1fmt(va_alist)
+       va_dcl
+#endif
+{
+       va_list ap;
+#ifndef __STDC__
+       const char *fmt;
+
+       va_start(ap);
+       fmt = va_arg(ap, const char *);
+#else
+       va_start(ap, fmt);
+#endif
+       doformat(out1, fmt, ap);
+       va_end(ap);
+}
+
+static void
+#ifdef __STDC__
+fmtstr(char *outbuf, size_t length, const char *fmt, ...)
+#else
+fmtstr(va_alist)
+       va_dcl
+#endif
+{
+       va_list ap;
+#ifndef __STDC__
+       char *outbuf;
+       size_t length;
+       const char *fmt;
+
+       va_start(ap);
+       outbuf = va_arg(ap, char *);
+       length = va_arg(ap, size_t);
+       fmt = va_arg(ap, const char *);
+#else
+       va_start(ap, fmt);
+#endif
+       INTOFF;
+       vsnprintf(outbuf, length, fmt, ap);
+       INTON;
+}
+
+#ifndef USE_GLIBC_STDIO
+/*
+ * Formatted output.  This routine handles a subset of the printf formats:
+ * - Formats supported: d, u, o, p, X, s, and c.
+ * - The x format is also accepted but is treated like X.
+ * - The l, ll and q modifiers are accepted.
+ * - The - and # flags are accepted; # only works with the o format.
+ * - Width and precision may be specified with any format except c.
+ * - An * may be given for the width or precision.
+ * - The obsolete practice of preceding the width with a zero to get
+ *   zero padding is not supported; use the precision field.
+ * - A % may be printed by writing %% in the format string.
+ */
+
+#define TEMPSIZE 24
+
+#ifdef BSD4_4
+#define HAVE_VASPRINTF 1
+#endif
+
+#if    !HAVE_VASPRINTF
+static const char digit[] = "0123456789ABCDEF";
+#endif
+
+
+static void
+doformat(dest, f, ap)
+       struct output *dest;
+       const char *f;          /* format string */
+       va_list ap;
+{
+#if    HAVE_VASPRINTF
+       char *s, *t;
+       int len;
+
+       INTOFF;
+       len = vasprintf(&t, f, ap);
+       if (len < 0) {
+               return;
+       }
+       s = stalloc(++len);
+       memcpy(s, t, len);
+       free(t);
+       INTON;
+       outstr(s, dest);
+       stunalloc(s);
+#else  /* !HAVE_VASPRINTF */
+       char c;
+       char temp[TEMPSIZE];
+       int flushleft;
+       int sharp;
+       int width;
+       int prec;
+       int islong;
+       int isquad;
+       char *p;
+       int sign;
+#ifdef BSD4_4
+       quad_t l;
+       u_quad_t num;
+#else
+       long l;
+       u_long num;
+#endif
+       unsigned base;
+       int len;
+       int size;
+       int pad;
+
+       while ((c = *f++) != '\0') {
+               if (c != '%') {
+                       outc(c, dest);
+                       continue;
+               }
+               flushleft = 0;
+               sharp = 0;
+               width = 0;
+               prec = -1;
+               islong = 0;
+               isquad = 0;
+               for (;;) {
+                       if (*f == '-')
+                               flushleft++;
+                       else if (*f == '#')
+                               sharp++;
+                       else
+                               break;
+                       f++;
+               }
+               if (*f == '*') {
+                       width = va_arg(ap, int);
+                       f++;
+               } else {
+                       while (is_digit(*f)) {
+                               width = 10 * width + digit_val(*f++);
+                       }
+               }
+               if (*f == '.') {
+                       if (*++f == '*') {
+                               prec = va_arg(ap, int);
+                               f++;
+                       } else {
+                               prec = 0;
+                               while (is_digit(*f)) {
+                                       prec = 10 * prec + digit_val(*f++);
+                               }
+                       }
+               }
+               if (*f == 'l') {
+                       f++;
+                       if (*f == 'l') {
+                               isquad++;
+                               f++;
+                       } else
+                               islong++;
+               } else if (*f == 'q') {
+                       isquad++;
+                       f++;
+               }
+               switch (*f) {
+               case 'd':
+#ifdef BSD4_4
+                       if (isquad)
+                               l = va_arg(ap, quad_t);
+                       else
+#endif
+                       if (islong)
+                               l = va_arg(ap, long);
+                       else
+                               l = va_arg(ap, int);
+                       sign = 0;
+                       num = l;
+                       if (l < 0) {
+                               num = -l;
+                               sign = 1;
+                       }
+                       base = 10;
+                       goto number;
+               case 'u':
+                       base = 10;
+                       goto uns_number;
+               case 'o':
+                       base = 8;
+                       goto uns_number;
+               case 'p':
+                       outc('0', dest);
+                       outc('x', dest);
+                       /*FALLTHROUGH*/
+               case 'x':
+                       /* we don't implement 'x'; treat like 'X' */
+               case 'X':
+                       base = 16;
+uns_number:      /* an unsigned number */
+                       sign = 0;
+#ifdef BSD4_4
+                       if (isquad)
+                               num = va_arg(ap, u_quad_t);
+                       else
+#endif
+                       if (islong)
+                               num = va_arg(ap, unsigned long);
+                       else
+                               num = va_arg(ap, unsigned int);
+number:                  /* process a number */
+                       p = temp + TEMPSIZE - 1;
+                       *p = '\0';
+                       while (num) {
+                               *--p = digit[num % base];
+                               num /= base;
+                       }
+                       len = (temp + TEMPSIZE - 1) - p;
+                       if (prec < 0)
+                               prec = 1;
+                       if (sharp && *f == 'o' && prec <= len)
+                               prec = len + 1;
+                       pad = 0;
+                       if (width) {
+                               size = len;
+                               if (size < prec)
+                                       size = prec;
+                               size += sign;
+                               pad = width - size;
+                               if (flushleft == 0) {
+                                       while (--pad >= 0)
+                                               outc(' ', dest);
+                               }
+                       }
+                       if (sign)
+                               outc('-', dest);
+                       prec -= len;
+                       while (--prec >= 0)
+                               outc('0', dest);
+                       while (*p)
+                               outc(*p++, dest);
+                       while (--pad >= 0)
+                               outc(' ', dest);
+                       break;
+               case 's':
+                       p = va_arg(ap, char *);
+                       pad = 0;
+                       if (width) {
+                               len = strlen(p);
+                               if (prec >= 0 && len > prec)
+                                       len = prec;
+                               pad = width - len;
+                               if (flushleft == 0) {
+                                       while (--pad >= 0)
+                                               outc(' ', dest);
+                               }
+                       }
+                       prec++;
+                       while (--prec != 0 && *p)
+                               outc(*p++, dest);
+                       while (--pad >= 0)
+                               outc(' ', dest);
+                       break;
+               case 'c':
+                       c = va_arg(ap, int);
+                       outc(c, dest);
+                       break;
+               default:
+                       outc(*f, dest);
+                       break;
+               }
+               f++;
+       }
+#endif /* !HAVE_VASPRINTF */
+}
+#endif
+
+
+
+/*
+ * Version of write which resumes after a signal is caught.
+ */
+
+static int
+xwrite(fd, buf, nbytes)
+       int fd;
+       const char *buf;
+       int nbytes;
+       {
+       int ntry;
+       int i;
+       int n;
+
+       n = nbytes;
+       ntry = 0;
+       for (;;) {
+               i = write(fd, buf, n);
+               if (i > 0) {
+                       if ((n -= i) <= 0)
+                               return nbytes;
+                       buf += i;
+                       ntry = 0;
+               } else if (i == 0) {
+                       if (++ntry > 10)
+                               return nbytes - n;
+               } else if (errno != EINTR) {
+                       return -1;
+               }
+       }
+}
+
+
+#ifdef notdef
+/*
+ * Version of ioctl that retries after a signal is caught.
+ * XXX unused function
+ */
+
+static int
+xioctl(fd, request, arg)
+       int fd;
+       unsigned long request;
+       char * arg;
+{
+       int i;
+
+       while ((i = ioctl(fd, request, arg)) == -1 && errno == EINTR);
+       return i;
+}
+#endif
+
+
+#ifdef USE_GLIBC_STDIO
+static void initstreams() {
+       output.stream = stdout;
+       errout.stream = stderr;
+}
+
+
+static void
+openmemout() {
+       INTOFF;
+       memout.stream = open_memstream(&memout.buf, &memout.bufsize);
+       INTON;
+}
+
+
+static int
+__closememout() {
+       int error;
+       error = fclose(memout.stream);
+       memout.stream = NULL;
+       return error;
+}
+#endif
+/*     $NetBSD: parser.c,v 1.46 2001/02/04 19:52:06 christos Exp $     */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+
+/*
+ * Shell command parser.
+ */
+
+#define EOFMARKLEN 79
+
+
+
+struct heredoc {
+       struct heredoc *next;   /* next here document in list */
+       union node *here;               /* redirection node */
+       char *eofmark;          /* string indicating end of input */
+       int striptabs;          /* if set, strip leading tabs */
+};
+
+
+
+struct heredoc *heredoclist;   /* list of here documents to read */
+static int parsebackquote;             /* nonzero if we are inside backquotes */
+static int doprompt;                   /* if set, prompt the user */
+static int needprompt;                 /* true if interactive and at start of line */
+static int lasttoken;                  /* last token read */
+static int tokpushback;                /* last token pushed back */
+static char *wordtext;                 /* text of last word returned by readtoken */
+static int checkkwd;            /* 1 == check for kwds, 2 == also eat newlines */
+/* 1 == check for aliases, 2 == also check for assignments */
+static int checkalias;
+struct nodelist *backquotelist;
+union node *redirnode;
+struct heredoc *heredoc;
+static int quoteflag;                  /* set if (part of) last token was quoted */
+static int startlinno;                 /* line # where last token started */
+
+
+static union node *list __P((int));
+static union node *andor __P((void));
+static union node *pipeline __P((void));
+static union node *command __P((void));
+static union node *simplecmd __P((void));
+static union node *makename __P((void));
+static void parsefname __P((void));
+static void parseheredoc __P((void));
+static int peektoken __P((void));
+static int readtoken __P((void));
+static int xxreadtoken __P((void));
+static int readtoken1 __P((int, char const *, char *, int));
+static int noexpand __P((char *));
+static void synexpect __P((int)) __attribute__((noreturn));
+static void synerror __P((const char *)) __attribute__((noreturn));
+static void setprompt __P((int));
+
+
+/*
+ * Read and parse a command.  Returns NEOF on end of file.  (NULL is a
+ * valid parse tree indicating a blank line.)
+ */
+
+union node *
+parsecmd(int interact)
+{
+       int t;
+
+       tokpushback = 0;
+       doprompt = interact;
+       if (doprompt)
+               setprompt(1);
+       else
+               setprompt(0);
+       needprompt = 0;
+       t = readtoken();
+       if (t == TEOF)
+               return NEOF;
+       if (t == TNL)
+               return NULL;
+       tokpushback++;
+       return list(1);
+}
+
+
+static union node *
+list(nlflag)
+       int nlflag;
+{
+       union node *n1, *n2, *n3;
+       int tok;
+
+       checkkwd = 2;
+       if (nlflag == 0 && tokendlist[peektoken()])
+               return NULL;
+       n1 = NULL;
+       for (;;) {
+               n2 = andor();
+               tok = readtoken();
+               if (tok == TBACKGND) {
+                       if (n2->type == NCMD || n2->type == NPIPE) {
+                               n2->ncmd.backgnd = 1;
+                       } else if (n2->type == NREDIR) {
+                               n2->type = NBACKGND;
+                       } else {
+                               n3 = (union node *)stalloc(sizeof (struct nredir));
+                               n3->type = NBACKGND;
+                               n3->nredir.n = n2;
+                               n3->nredir.redirect = NULL;
+                               n2 = n3;
+                       }
+               }
+               if (n1 == NULL) {
+                       n1 = n2;
+               }
+               else {
+                       n3 = (union node *)stalloc(sizeof (struct nbinary));
+                       n3->type = NSEMI;
+                       n3->nbinary.ch1 = n1;
+                       n3->nbinary.ch2 = n2;
+                       n1 = n3;
+               }
+               switch (tok) {
+               case TBACKGND:
+               case TSEMI:
+                       tok = readtoken();
+                       /* fall through */
+               case TNL:
+                       if (tok == TNL) {
+                               parseheredoc();
+                               if (nlflag)
+                                       return n1;
+                       } else {
+                               tokpushback++;
+                       }
+                       checkkwd = 2;
+                       if (tokendlist[peektoken()])
+                               return n1;
+                       break;
+               case TEOF:
+                       if (heredoclist)
+                               parseheredoc();
+                       else
+                               pungetc();              /* push back EOF on input */
+                       return n1;
+               default:
+                       if (nlflag)
+                               synexpect(-1);
+                       tokpushback++;
+                       return n1;
+               }
+       }
+}
+
+
+
+static union node *
+andor() {
+       union node *n1, *n2, *n3;
+       int t;
+
+       checkkwd = 1;
+       n1 = pipeline();
+       for (;;) {
+               if ((t = readtoken()) == TAND) {
+                       t = NAND;
+               } else if (t == TOR) {
+                       t = NOR;
+               } else {
+                       tokpushback++;
+                       return n1;
+               }
+               checkkwd = 2;
+               n2 = pipeline();
+               n3 = (union node *)stalloc(sizeof (struct nbinary));
+               n3->type = t;
+               n3->nbinary.ch1 = n1;
+               n3->nbinary.ch2 = n2;
+               n1 = n3;
+       }
+}
+
+
+
+static union node *
+pipeline() {
+       union node *n1, *n2, *pipenode;
+       struct nodelist *lp, *prev;
+       int negate;
+
+       negate = 0;
+       TRACE(("pipeline: entered\n"));
+       if (readtoken() == TNOT) {
+               negate = !negate;
+               checkkwd = 1;
+       } else
+               tokpushback++;
+       n1 = command();
+       if (readtoken() == TPIPE) {
+               pipenode = (union node *)stalloc(sizeof (struct npipe));
+               pipenode->type = NPIPE;
+               pipenode->npipe.backgnd = 0;
+               lp = (struct nodelist *)stalloc(sizeof (struct nodelist));
+               pipenode->npipe.cmdlist = lp;
+               lp->n = n1;
+               do {
+                       prev = lp;
+                       lp = (struct nodelist *)stalloc(sizeof (struct nodelist));
+                       checkkwd = 2;
+                       lp->n = command();
+                       prev->next = lp;
+               } while (readtoken() == TPIPE);
+               lp->next = NULL;
+               n1 = pipenode;
+       }
+       tokpushback++;
+       if (negate) {
+               n2 = (union node *)stalloc(sizeof (struct nnot));
+               n2->type = NNOT;
+               n2->nnot.com = n1;
+               return n2;
+       } else
+               return n1;
+}
+
+
+
+static union node *
+command() {
+       union node *n1, *n2;
+       union node *ap, **app;
+       union node *cp, **cpp;
+       union node *redir, **rpp;
+       int t;
+
+       redir = NULL;
+       n1 = NULL;
+       rpp = &redir;
+
+       switch (readtoken()) {
+       case TIF:
+               n1 = (union node *)stalloc(sizeof (struct nif));
+               n1->type = NIF;
+               n1->nif.test = list(0);
+               if (readtoken() != TTHEN)
+                       synexpect(TTHEN);
+               n1->nif.ifpart = list(0);
+               n2 = n1;
+               while (readtoken() == TELIF) {
+                       n2->nif.elsepart = (union node *)stalloc(sizeof (struct nif));
+                       n2 = n2->nif.elsepart;
+                       n2->type = NIF;
+                       n2->nif.test = list(0);
+                       if (readtoken() != TTHEN)
+                               synexpect(TTHEN);
+                       n2->nif.ifpart = list(0);
+               }
+               if (lasttoken == TELSE)
+                       n2->nif.elsepart = list(0);
+               else {
+                       n2->nif.elsepart = NULL;
+                       tokpushback++;
+               }
+               if (readtoken() != TFI)
+                       synexpect(TFI);
+               checkkwd = 1;
+               break;
+       case TWHILE:
+       case TUNTIL: {
+               int got;
+               n1 = (union node *)stalloc(sizeof (struct nbinary));
+               n1->type = (lasttoken == TWHILE)? NWHILE : NUNTIL;
+               n1->nbinary.ch1 = list(0);
+               if ((got=readtoken()) != TDO) {
+TRACE(("expecting DO got %s %s\n", tokname[got], got == TWORD ? wordtext : ""));
+                       synexpect(TDO);
+               }
+               n1->nbinary.ch2 = list(0);
+               if (readtoken() != TDONE)
+                       synexpect(TDONE);
+               checkkwd = 1;
+               break;
+       }
+       case TFOR:
+               if (readtoken() != TWORD || quoteflag || ! goodname(wordtext))
+                       synerror("Bad for loop variable");
+               n1 = (union node *)stalloc(sizeof (struct nfor));
+               n1->type = NFOR;
+               n1->nfor.var = wordtext;
+               checkkwd = 1;
+               if (readtoken() == TIN) {
+                       app = &ap;
+                       while (readtoken() == TWORD) {
+                               n2 = (union node *)stalloc(sizeof (struct narg));
+                               n2->type = NARG;
+                               n2->narg.text = wordtext;
+                               n2->narg.backquote = backquotelist;
+                               *app = n2;
+                               app = &n2->narg.next;
+                       }
+                       *app = NULL;
+                       n1->nfor.args = ap;
+                       if (lasttoken != TNL && lasttoken != TSEMI)
+                               synexpect(-1);
+               } else {
+                       static char argvars[5] = {CTLVAR, VSNORMAL|VSQUOTE,
+                                                                  '@', '=', '\0'};
+                       n2 = (union node *)stalloc(sizeof (struct narg));
+                       n2->type = NARG;
+                       n2->narg.text = argvars;
+                       n2->narg.backquote = NULL;
+                       n2->narg.next = NULL;
+                       n1->nfor.args = n2;
+                       /*
+                        * Newline or semicolon here is optional (but note
+                        * that the original Bourne shell only allowed NL).
+                        */
+                       if (lasttoken != TNL && lasttoken != TSEMI)
+                               tokpushback++;
+               }
+               checkkwd = 2;
+               if (readtoken() != TDO)
+                       synexpect(TDO);
+               n1->nfor.body = list(0);
+               if (readtoken() != TDONE)
+                       synexpect(TDONE);
+               checkkwd = 1;
+               break;
+       case TCASE:
+               n1 = (union node *)stalloc(sizeof (struct ncase));
+               n1->type = NCASE;
+               if (readtoken() != TWORD)
+                       synexpect(TWORD);
+               n1->ncase.expr = n2 = (union node *)stalloc(sizeof (struct narg));
+               n2->type = NARG;
+               n2->narg.text = wordtext;
+               n2->narg.backquote = backquotelist;
+               n2->narg.next = NULL;
+               do {
+                       checkkwd = 1;
+               } while (readtoken() == TNL);
+               if (lasttoken != TIN)
+                       synerror("expecting \"in\"");
+               cpp = &n1->ncase.cases;
+               checkkwd = 2, readtoken();
+               do {
+                       if (lasttoken == TLP)
+                               readtoken();
+                       *cpp = cp = (union node *)stalloc(sizeof (struct nclist));
+                       cp->type = NCLIST;
+                       app = &cp->nclist.pattern;
+                       for (;;) {
+                               *app = ap = (union node *)stalloc(sizeof (struct narg));
+                               ap->type = NARG;
+                               ap->narg.text = wordtext;
+                               ap->narg.backquote = backquotelist;
+                               if (checkkwd = 2, readtoken() != TPIPE)
+                                       break;
+                               app = &ap->narg.next;
+                               readtoken();
+                       }
+                       ap->narg.next = NULL;
+                       if (lasttoken != TRP)
+                               synexpect(TRP);
+                       cp->nclist.body = list(0);
+
+                       checkkwd = 2;
+                       if ((t = readtoken()) != TESAC) {
+                               if (t != TENDCASE)
+                                       synexpect(TENDCASE);
+                               else
+                                       checkkwd = 2, readtoken();
+                       }
+                       cpp = &cp->nclist.next;
+               } while(lasttoken != TESAC);
+               *cpp = NULL;
+               checkkwd = 1;
+               break;
+       case TLP:
+               n1 = (union node *)stalloc(sizeof (struct nredir));
+               n1->type = NSUBSHELL;
+               n1->nredir.n = list(0);
+               n1->nredir.redirect = NULL;
+               if (readtoken() != TRP)
+                       synexpect(TRP);
+               checkkwd = 1;
+               break;
+       case TBEGIN:
+               n1 = list(0);
+               if (readtoken() != TEND)
+                       synexpect(TEND);
+               checkkwd = 1;
+               break;
+       /* Handle an empty command like other simple commands.  */
+       case TSEMI:
+       case TAND:
+       case TOR:
+       case TNL:
+       case TEOF:
+       case TRP:
+       case TBACKGND:
+               /*
+                * An empty command before a ; doesn't make much sense, and
+                * should certainly be disallowed in the case of `if ;'.
+                */
+               if (!redir)
+                       synexpect(-1);
+       case TWORD:
+       case TREDIR:
+               tokpushback++;
+               n1 = simplecmd();
+               return n1;
+       default:
+               synexpect(-1);
+               /* NOTREACHED */
+       }
+
+       /* Now check for redirection which may follow command */
+       while (readtoken() == TREDIR) {
+               *rpp = n2 = redirnode;
+               rpp = &n2->nfile.next;
+               parsefname();
+       }
+       tokpushback++;
+       *rpp = NULL;
+       if (redir) {
+               if (n1->type != NSUBSHELL) {
+                       n2 = (union node *)stalloc(sizeof (struct nredir));
+                       n2->type = NREDIR;
+                       n2->nredir.n = n1;
+                       n1 = n2;
+               }
+               n1->nredir.redirect = redir;
+       }
+
+       return n1;
+}
+
+
+static union node *
+simplecmd() {
+       union node *args, **app;
+       union node *n = NULL;
+       union node *vars, **vpp;
+       union node **rpp, *redir;
+
+       args = NULL;
+       app = &args;
+       vars = NULL;
+       vpp = &vars;
+       redir = NULL;
+       rpp = &redir;
+
+       checkalias = 2;
+       for (;;) {
+               switch (readtoken()) {
+               case TWORD:
+               case TASSIGN:
+                       n = (union node *)stalloc(sizeof (struct narg));
+                       n->type = NARG;
+                       n->narg.text = wordtext;
+                       n->narg.backquote = backquotelist;
+                       if (lasttoken == TWORD) {
+                               *app = n;
+                               app = &n->narg.next;
+                       } else {
+                               *vpp = n;
+                               vpp = &n->narg.next;
+                       }
+                       break;
+               case TREDIR:
+                       *rpp = n = redirnode;
+                       rpp = &n->nfile.next;
+                       parsefname();   /* read name of redirection file */
+                       break;
+               case TLP:
+                       if (
+                               args && app == &args->narg.next &&
+                               !vars && !redir
+                       ) {
+                               /* We have a function */
+                               if (readtoken() != TRP)
+                                       synexpect(TRP);
+#ifdef notdef
+                               if (! goodname(n->narg.text))
+                                       synerror("Bad function name");
+#endif
+                               n->type = NDEFUN;
+                               checkkwd = 2;
+                               n->narg.next = command();
+                               return n;
+                       }
+                       /* fall through */
+               default:
+                       tokpushback++;
+                       goto out;
+               }
+       }
+out:
+       *app = NULL;
+       *vpp = NULL;
+       *rpp = NULL;
+       n = (union node *)stalloc(sizeof (struct ncmd));
+       n->type = NCMD;
+       n->ncmd.backgnd = 0;
+       n->ncmd.args = args;
+       n->ncmd.assign = vars;
+       n->ncmd.redirect = redir;
+       return n;
+}
+
+static union node *
+makename() {
+       union node *n;
+
+       n = (union node *)stalloc(sizeof (struct narg));
+       n->type = NARG;
+       n->narg.next = NULL;
+       n->narg.text = wordtext;
+       n->narg.backquote = backquotelist;
+       return n;
+}
+
+static void fixredir(union node *n, const char *text, int err)
+       {
+       TRACE(("Fix redir %s %d\n", text, err));
+       if (!err)
+               n->ndup.vname = NULL;
+
+       if (is_digit(text[0]) && text[1] == '\0')
+               n->ndup.dupfd = digit_val(text[0]);
+       else if (text[0] == '-' && text[1] == '\0')
+               n->ndup.dupfd = -1;
+       else {
+
+               if (err)
+                       synerror("Bad fd number");
+               else
+                       n->ndup.vname = makename();
+       }
+}
+
+
+static void
+parsefname() {
+       union node *n = redirnode;
+
+       if (readtoken() != TWORD)
+               synexpect(-1);
+       if (n->type == NHERE) {
+               struct heredoc *here = heredoc;
+               struct heredoc *p;
+               int i;
+
+               if (quoteflag == 0)
+                       n->type = NXHERE;
+               TRACE(("Here document %d\n", n->type));
+               if (here->striptabs) {
+                       while (*wordtext == '\t')
+                               wordtext++;
+               }
+               if (! noexpand(wordtext) || (i = strlen(wordtext)) == 0 || i > EOFMARKLEN)
+                       synerror("Illegal eof marker for << redirection");
+               rmescapes(wordtext);
+               here->eofmark = wordtext;
+               here->next = NULL;
+               if (heredoclist == NULL)
+                       heredoclist = here;
+               else {
+                       for (p = heredoclist ; p->next ; p = p->next);
+                       p->next = here;
+               }
+       } else if (n->type == NTOFD || n->type == NFROMFD) {
+               fixredir(n, wordtext, 0);
+       } else {
+               n->nfile.fname = makename();
+       }
+}
+
+
+/*
+ * Input any here documents.
+ */
+
+static void
+parseheredoc() {
+       struct heredoc *here;
+       union node *n;
+
+       while (heredoclist) {
+               here = heredoclist;
+               heredoclist = here->next;
+               if (needprompt) {
+                       setprompt(2);
+                       needprompt = 0;
+               }
+               readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX,
+                               here->eofmark, here->striptabs);
+               n = (union node *)stalloc(sizeof (struct narg));
+               n->narg.type = NARG;
+               n->narg.next = NULL;
+               n->narg.text = wordtext;
+               n->narg.backquote = backquotelist;
+               here->here->nhere.doc = n;
+       }
+}
+
+static int
+peektoken() {
+       int t;
+
+       t = readtoken();
+       tokpushback++;
+       return (t);
+}
+
+static int
+readtoken() {
+       int t;
+       int savecheckkwd = checkkwd;
+       int savecheckalias = checkalias;
+       struct alias *ap;
+#ifdef DEBUG
+       int alreadyseen = tokpushback;
+#endif
+
+top:
+       t = xxreadtoken();
+       checkalias = savecheckalias;
+
+       if (checkkwd) {
+               /*
+                * eat newlines
+                */
+               if (checkkwd == 2) {
+                       checkkwd = 0;
+                       while (t == TNL) {
+                               parseheredoc();
+                               t = xxreadtoken();
+                       }
+               }
+               checkkwd = 0;
+               /*
+                * check for keywords
+                */
+               if (t == TWORD && !quoteflag)
+               {
+                       const char *const *pp;
+
+                       if ((pp = findkwd(wordtext))) {
+                               lasttoken = t = pp - parsekwd + KWDOFFSET;
+                               TRACE(("keyword %s recognized\n", tokname[t]));
+                               goto out;
+                       }
+               }
+       }
+
+       if (t != TWORD) {
+               if (t != TREDIR) {
+                       checkalias = 0;
+               }
+       } else if (checkalias == 2 && isassignment(wordtext)) {
+               lasttoken = t = TASSIGN;
+       } else if (checkalias) {
+               if (!quoteflag && (ap = lookupalias(wordtext, 1)) != NULL) {
+                       if (*ap->val) {
+                               pushstring(ap->val, strlen(ap->val), ap);
+                       }
+                       checkkwd = savecheckkwd;
+                       goto top;
+               }
+               checkalias = 0;
+       }
+out:
+#ifdef DEBUG
+       if (!alreadyseen)
+           TRACE(("token %s %s\n", tokname[t], t == TWORD || t == TASSIGN ? wordtext : ""));
+       else
+           TRACE(("reread token %s %s\n", tokname[t], t == TWORD || t == TASSIGN ? wordtext : ""));
+#endif
+       return (t);
+}
+
+
+/*
+ * Read the next input token.
+ * If the token is a word, we set backquotelist to the list of cmds in
+ *     backquotes.  We set quoteflag to true if any part of the word was
+ *     quoted.
+ * If the token is TREDIR, then we set redirnode to a structure containing
+ *     the redirection.
+ * In all cases, the variable startlinno is set to the number of the line
+ *     on which the token starts.
+ *
+ * [Change comment:  here documents and internal procedures]
+ * [Readtoken shouldn't have any arguments.  Perhaps we should make the
+ *  word parsing code into a separate routine.  In this case, readtoken
+ *  doesn't need to have any internal procedures, but parseword does.
+ *  We could also make parseoperator in essence the main routine, and
+ *  have parseword (readtoken1?) handle both words and redirection.]
+ */
+
+#define RETURN(token)  return lasttoken = token
+
+static int
+xxreadtoken() {
+       int c;
+
+       if (tokpushback) {
+               tokpushback = 0;
+               return lasttoken;
+       }
+       if (needprompt) {
+               setprompt(2);
+               needprompt = 0;
+       }
+       startlinno = plinno;
+       for (;;) {      /* until token or start of word found */
+               c = pgetc_macro();
+               switch (c) {
+               case ' ': case '\t':
+               case PEOA:
+                       continue;
+               case '#':
+                       while ((c = pgetc()) != '\n' && c != PEOF);
+                       pungetc();
+                       continue;
+               case '\\':
+                       if (pgetc() == '\n') {
+                               startlinno = ++plinno;
+                               if (doprompt)
+                                       setprompt(2);
+                               else
+                                       setprompt(0);
+                               continue;
+                       }
+                       pungetc();
+                       goto breakloop;
+               case '\n':
+                       plinno++;
+                       needprompt = doprompt;
+                       RETURN(TNL);
+               case PEOF:
+                       RETURN(TEOF);
+               case '&':
+                       if (pgetc() == '&')
+                               RETURN(TAND);
+                       pungetc();
+                       RETURN(TBACKGND);
+               case '|':
+                       if (pgetc() == '|')
+                               RETURN(TOR);
+                       pungetc();
+                       RETURN(TPIPE);
+               case ';':
+                       if (pgetc() == ';')
+                               RETURN(TENDCASE);
+                       pungetc();
+                       RETURN(TSEMI);
+               case '(':
+                       RETURN(TLP);
+               case ')':
+                       RETURN(TRP);
+               default:
+                       goto breakloop;
+               }
+       }
+breakloop:
+       return readtoken1(c, BASESYNTAX, (char *)NULL, 0);
+#undef RETURN
+}
+
+
+
+/*
+ * If eofmark is NULL, read a word or a redirection symbol.  If eofmark
+ * is not NULL, read a here document.  In the latter case, eofmark is the
+ * word which marks the end of the document and striptabs is true if
+ * leading tabs should be stripped from the document.  The argument firstc
+ * is the first character of the input token or document.
+ *
+ * Because C does not have internal subroutines, I have simulated them
+ * using goto's to implement the subroutine linkage.  The following macros
+ * will run code that appears at the end of readtoken1.
+ */
+
+#define CHECKEND()     {goto checkend; checkend_return:;}
+#define PARSEREDIR()   {goto parseredir; parseredir_return:;}
+#define PARSESUB()     {goto parsesub; parsesub_return:;}
+#define PARSEBACKQOLD()        {oldstyle = 1; goto parsebackq; parsebackq_oldreturn:;}
+#define PARSEBACKQNEW()        {oldstyle = 0; goto parsebackq; parsebackq_newreturn:;}
+#define        PARSEARITH()    {goto parsearith; parsearith_return:;}
+
+static int
+readtoken1(firstc, syntax, eofmark, striptabs)
+       int firstc;
+       char const *syntax;
+       char *eofmark;
+       int striptabs;
+       {
+       int c = firstc;
+       char *out;
+       int len;
+       char line[EOFMARKLEN + 1];
+       struct nodelist *bqlist;
+       int quotef;
+       int dblquote;
+       int varnest;    /* levels of variables expansion */
+       int arinest;    /* levels of arithmetic expansion */
+       int parenlevel; /* levels of parens in arithmetic */
+       int dqvarnest;  /* levels of variables expansion within double quotes */
+       int oldstyle;
+       char const *prevsyntax; /* syntax before arithmetic */
+#if __GNUC__
+       /* Avoid longjmp clobbering */
+       (void) &out;
+       (void) &quotef;
+       (void) &dblquote;
+       (void) &varnest;
+       (void) &arinest;
+       (void) &parenlevel;
+       (void) &dqvarnest;
+       (void) &oldstyle;
+       (void) &prevsyntax;
+       (void) &syntax;
+#endif
+
+       startlinno = plinno;
+       dblquote = 0;
+       if (syntax == DQSYNTAX)
+               dblquote = 1;
+       quotef = 0;
+       bqlist = NULL;
+       varnest = 0;
+       arinest = 0;
+       parenlevel = 0;
+       dqvarnest = 0;
+
+       STARTSTACKSTR(out);
+       loop: { /* for each line, until end of word */
+#if ATTY
+               if (c == '\034' && doprompt
+                && attyset() && ! equal(termval(), "emacs")) {
+                       attyline();
+                       if (syntax == BASESYNTAX)
+                               return readtoken();
+                       c = pgetc();
+                       goto loop;
+               }
+#endif
+               CHECKEND();     /* set c to PEOF if at end of here document */
+               for (;;) {      /* until end of line or end of word */
+                       CHECKSTRSPACE(3, out);  /* permit 3 calls to USTPUTC */
+                       switch(syntax[c]) {
+                       case CNL:       /* '\n' */
+                               if (syntax == BASESYNTAX)
+                                       goto endword;   /* exit outer loop */
+                               USTPUTC(c, out);
+                               plinno++;
+                               if (doprompt)
+                                       setprompt(2);
+                               else
+                                       setprompt(0);
+                               c = pgetc();
+                               goto loop;              /* continue outer loop */
+                       case CWORD:
+                               USTPUTC(c, out);
+                               break;
+                       case CCTL:
+                               if ((eofmark == NULL || dblquote) &&
+                                   dqvarnest == 0)
+                                       USTPUTC(CTLESC, out);
+                               USTPUTC(c, out);
+                               break;
+                       case CBACK:     /* backslash */
+                               c = pgetc2();
+                               if (c == PEOF) {
+                                       USTPUTC('\\', out);
+                                       pungetc();
+                               } else if (c == '\n') {
+                                       if (doprompt)
+                                               setprompt(2);
+                                       else
+                                               setprompt(0);
+                               } else {
+                                       if (dblquote && c != '\\' && c != '`' && c != '$'
+                                                        && (c != '"' || eofmark != NULL))
+                                               USTPUTC('\\', out);
+                                       if (SQSYNTAX[c] == CCTL)
+                                               USTPUTC(CTLESC, out);
+                                       else if (eofmark == NULL)
+                                               USTPUTC(CTLQUOTEMARK, out);
+                                       USTPUTC(c, out);
+                                       quotef++;
+                               }
+                               break;
+                       case CSQUOTE:
+                               if (eofmark == NULL)
+                                       USTPUTC(CTLQUOTEMARK, out);
+                               syntax = SQSYNTAX;
+                               break;
+                       case CDQUOTE:
+                               if (eofmark == NULL)
+                                       USTPUTC(CTLQUOTEMARK, out);
+                               syntax = DQSYNTAX;
+                               dblquote = 1;
+                               break;
+                       case CENDQUOTE:
+                               if (eofmark != NULL && arinest == 0 &&
+                                   varnest == 0) {
+                                       USTPUTC(c, out);
+                               } else {
+                                       if (arinest) {
+                                               syntax = ARISYNTAX;
+                                               dblquote = 0;
+                                       } else if (eofmark == NULL &&
+                                                  dqvarnest == 0) {
+                                               syntax = BASESYNTAX;
+                                               dblquote = 0;
+                                       }
+                                       quotef++;
+                               }
+                               break;
+                       case CVAR:      /* '$' */
+                               PARSESUB();             /* parse substitution */
+                               break;
+                       case CENDVAR:   /* '}' */
+                               if (varnest > 0) {
+                                       varnest--;
+                                       if (dqvarnest > 0) {
+                                               dqvarnest--;
+                                       }
+                                       USTPUTC(CTLENDVAR, out);
+                               } else {
+                                       USTPUTC(c, out);
+                               }
+                               break;
+#ifdef ASH_MATH_SUPPORT
+                       case CLP:       /* '(' in arithmetic */
+                               parenlevel++;
+                               USTPUTC(c, out);
+                               break;
+                       case CRP:       /* ')' in arithmetic */
+                               if (parenlevel > 0) {
+                                       USTPUTC(c, out);
+                                       --parenlevel;
+                               } else {
+                                       if (pgetc() == ')') {
+                                               if (--arinest == 0) {
+                                                       USTPUTC(CTLENDARI, out);
+                                                       syntax = prevsyntax;
+                                                       if (syntax == DQSYNTAX)
+                                                               dblquote = 1;
+                                                       else
+                                                               dblquote = 0;
+                                               } else
+                                                       USTPUTC(')', out);
+                                       } else {
+                                               /*
+                                                * unbalanced parens
+                                                *  (don't 2nd guess - no error)
+                                                */
+                                               pungetc();
+                                               USTPUTC(')', out);
+                                       }
+                               }
+                               break;
+#endif
+                       case CBQUOTE:   /* '`' */
+                               PARSEBACKQOLD();
+                               break;
+                       case CEOF:
+                               goto endword;           /* exit outer loop */
+                       case CIGN:
+                               break;
+                       default:
+                               if (varnest == 0)
+                                       goto endword;   /* exit outer loop */
+                               if (c != PEOA) {
+                                       USTPUTC(c, out);
+                               }
+                       }
+                       c = pgetc_macro();
+               }
+       }
+endword:
+       if (syntax == ARISYNTAX)
+               synerror("Missing '))'");
+       if (syntax != BASESYNTAX && ! parsebackquote && eofmark == NULL)
+               synerror("Unterminated quoted string");
+       if (varnest != 0) {
+               startlinno = plinno;
+               synerror("Missing '}'");
+       }
+       USTPUTC('\0', out);
+       len = out - stackblock();
+       out = stackblock();
+       if (eofmark == NULL) {
+               if ((c == '>' || c == '<')
+                && quotef == 0
+                && len <= 2
+                && (*out == '\0' || is_digit(*out))) {
+                       PARSEREDIR();
+                       return lasttoken = TREDIR;
+               } else {
+                       pungetc();
+               }
+       }
+       quoteflag = quotef;
+       backquotelist = bqlist;
+       grabstackblock(len);
+       wordtext = out;
+       return lasttoken = TWORD;
+/* end of readtoken routine */
+
+
+
+/*
+ * Check to see whether we are at the end of the here document.  When this
+ * is called, c is set to the first character of the next input line.  If
+ * we are at the end of the here document, this routine sets the c to PEOF.
+ */
+
+checkend: {
+       if (eofmark) {
+               if (c == PEOA) {
+                       c = pgetc2();
+               }
+               if (striptabs) {
+                       while (c == '\t') {
+                               c = pgetc2();
+                       }
+               }
+               if (c == *eofmark) {
+                       if (pfgets(line, sizeof line) != NULL) {
+                               char *p, *q;
+
+                               p = line;
+                               for (q = eofmark + 1 ; *q && *p == *q ; p++, q++);
+                               if (*p == '\n' && *q == '\0') {
+                                       c = PEOF;
+                                       plinno++;
+                                       needprompt = doprompt;
+                               } else {
+                                       pushstring(line, strlen(line), NULL);
+                               }
+                       }
+               }
+       }
+       goto checkend_return;
+}
+
+
+/*
+ * Parse a redirection operator.  The variable "out" points to a string
+ * specifying the fd to be redirected.  The variable "c" contains the
+ * first character of the redirection operator.
+ */
+
+parseredir: {
+       char fd = *out;
+       union node *np;
+
+       np = (union node *)stalloc(sizeof (struct nfile));
+       if (c == '>') {
+               np->nfile.fd = 1;
+               c = pgetc();
+               if (c == '>')
+                       np->type = NAPPEND;
+               else if (c == '&')
+                       np->type = NTOFD;
+               else if (c == '|')
+                       np->type = NTOOV;
+               else {
+                       np->type = NTO;
+                       pungetc();
+               }
+       } else {        /* c == '<' */
+               np->nfile.fd = 0;
+               switch (c = pgetc()) {
+               case '<':
+                       if (sizeof (struct nfile) != sizeof (struct nhere)) {
+                               np = (union node *)stalloc(sizeof (struct nhere));
+                               np->nfile.fd = 0;
+                       }
+                       np->type = NHERE;
+                       heredoc = (struct heredoc *)stalloc(sizeof (struct heredoc));
+                       heredoc->here = np;
+                       if ((c = pgetc()) == '-') {
+                               heredoc->striptabs = 1;
+                       } else {
+                               heredoc->striptabs = 0;
+                               pungetc();
+                       }
+                       break;
+
+               case '&':
+                       np->type = NFROMFD;
+                       break;
+
+               case '>':
+                       np->type = NFROMTO;
+                       break;
+
+               default:
+                       np->type = NFROM;
+                       pungetc();
+                       break;
+               }
+       }
+       if (fd != '\0')
+               np->nfile.fd = digit_val(fd);
+       redirnode = np;
+       goto parseredir_return;
+}
+
+
+/*
+ * Parse a substitution.  At this point, we have read the dollar sign
+ * and nothing else.
+ */
+
+parsesub: {
+       int subtype;
+       int typeloc;
+       int flags;
+       char *p;
+       static const char types[] = "}-+?=";
+
+       c = pgetc();
+       if (
+               c <= PEOA  ||
+               (c != '(' && c != '{' && !is_name(c) && !is_special(c))
+       ) {
+               USTPUTC('$', out);
+               pungetc();
+       } else if (c == '(') {  /* $(command) or $((arith)) */
+               if (pgetc() == '(') {
+                       PARSEARITH();
+               } else {
+                       pungetc();
+                       PARSEBACKQNEW();
+               }
+       } else {
+               USTPUTC(CTLVAR, out);
+               typeloc = out - stackblock();
+               USTPUTC(VSNORMAL, out);
+               subtype = VSNORMAL;
+               if (c == '{') {
+                       c = pgetc();
+                       if (c == '#') {
+                               if ((c = pgetc()) == '}')
+                                       c = '#';
+                               else
+                                       subtype = VSLENGTH;
+                       }
+                       else
+                               subtype = 0;
+               }
+               if (c > PEOA && is_name(c)) {
+                       do {
+                               STPUTC(c, out);
+                               c = pgetc();
+                       } while (c > PEOA && is_in_name(c));
+               } else if (is_digit(c)) {
+                       do {
+                               USTPUTC(c, out);
+                               c = pgetc();
+                       } while (is_digit(c));
+               }
+               else if (is_special(c)) {
+                       USTPUTC(c, out);
+                       c = pgetc();
+               }
+               else
+badsub:                        synerror("Bad substitution");
+
+               STPUTC('=', out);
+               flags = 0;
+               if (subtype == 0) {
+                       switch (c) {
+                       case ':':
+                               flags = VSNUL;
+                               c = pgetc();
+                               /*FALLTHROUGH*/
+                       default:
+                               p = strchr(types, c);
+                               if (p == NULL)
+                                       goto badsub;
+                               subtype = p - types + VSNORMAL;
+                               break;
+                       case '%':
+                       case '#':
+                               {
+                                       int cc = c;
+                                       subtype = c == '#' ? VSTRIMLEFT :
+                                                            VSTRIMRIGHT;
+                                       c = pgetc();
+                                       if (c == cc)
+                                               subtype++;
+                                       else
+                                               pungetc();
+                                       break;
+                               }
+                       }
+               } else {
+                       pungetc();
+               }
+               if (dblquote || arinest)
+                       flags |= VSQUOTE;
+               *(stackblock() + typeloc) = subtype | flags;
+               if (subtype != VSNORMAL) {
+                       varnest++;
+                       if (dblquote) {
+                               dqvarnest++;
+                       }
+               }
+       }
+       goto parsesub_return;
+}
+
+
+/*
+ * Called to parse command substitutions.  Newstyle is set if the command
+ * is enclosed inside $(...); nlpp is a pointer to the head of the linked
+ * list of commands (passed by reference), and savelen is the number of
+ * characters on the top of the stack which must be preserved.
+ */
+
+parsebackq: {
+       struct nodelist **nlpp;
+       int savepbq;
+       union node *n;
+       char *volatile str;
+       struct jmploc jmploc;
+       struct jmploc *volatile savehandler;
+       int savelen;
+       int saveprompt;
+#ifdef __GNUC__
+       (void) &saveprompt;
+#endif
+
+       savepbq = parsebackquote;
+       if (setjmp(jmploc.loc)) {
+               if (str)
+                       ckfree(str);
+               parsebackquote = 0;
+               handler = savehandler;
+               longjmp(handler->loc, 1);
+       }
+       INTOFF;
+       str = NULL;
+       savelen = out - stackblock();
+       if (savelen > 0) {
+               str = ckmalloc(savelen);
+               memcpy(str, stackblock(), savelen);
+       }
+       savehandler = handler;
+       handler = &jmploc;
+       INTON;
+        if (oldstyle) {
+                /* We must read until the closing backquote, giving special
+                   treatment to some slashes, and then push the string and
+                   reread it as input, interpreting it normally.  */
+                char *pout;
+                int pc;
+                int psavelen;
+                char *pstr;
+
+
+                STARTSTACKSTR(pout);
+               for (;;) {
+                       if (needprompt) {
+                               setprompt(2);
+                               needprompt = 0;
+                       }
+                       switch (pc = pgetc()) {
+                       case '`':
+                               goto done;
+
+                       case '\\':
+                                if ((pc = pgetc()) == '\n') {
+                                       plinno++;
+                                       if (doprompt)
+                                               setprompt(2);
+                                       else
+                                               setprompt(0);
+                                       /*
+                                        * If eating a newline, avoid putting
+                                        * the newline into the new character
+                                        * stream (via the STPUTC after the
+                                        * switch).
+                                        */
+                                       continue;
+                               }
+                                if (pc != '\\' && pc != '`' && pc != '$'
+                                    && (!dblquote || pc != '"'))
+                                        STPUTC('\\', pout);
+                               if (pc > PEOA) {
+                                       break;
+                               }
+                               /* fall through */
+
+                       case PEOF:
+                       case PEOA:
+                               startlinno = plinno;
+                               synerror("EOF in backquote substitution");
+
+                       case '\n':
+                               plinno++;
+                               needprompt = doprompt;
+                               break;
+
+                       default:
+                               break;
+                       }
+                       STPUTC(pc, pout);
+                }
+done:
+                STPUTC('\0', pout);
+                psavelen = pout - stackblock();
+                if (psavelen > 0) {
+                       pstr = grabstackstr(pout);
+                       setinputstring(pstr);
+                }
+        }
+       nlpp = &bqlist;
+       while (*nlpp)
+               nlpp = &(*nlpp)->next;
+       *nlpp = (struct nodelist *)stalloc(sizeof (struct nodelist));
+       (*nlpp)->next = NULL;
+       parsebackquote = oldstyle;
+
+       if (oldstyle) {
+               saveprompt = doprompt;
+               doprompt = 0;
+       }
+
+       n = list(0);
+
+       if (oldstyle)
+               doprompt = saveprompt;
+       else {
+               if (readtoken() != TRP)
+                       synexpect(TRP);
+       }
+
+       (*nlpp)->n = n;
+        if (oldstyle) {
+               /*
+                * Start reading from old file again, ignoring any pushed back
+                * tokens left from the backquote parsing
+                */
+                popfile();
+               tokpushback = 0;
+       }
+       while (stackblocksize() <= savelen)
+               growstackblock();
+       STARTSTACKSTR(out);
+       if (str) {
+               memcpy(out, str, savelen);
+               STADJUST(savelen, out);
+               INTOFF;
+               ckfree(str);
+               str = NULL;
+               INTON;
+       }
+       parsebackquote = savepbq;
+       handler = savehandler;
+       if (arinest || dblquote)
+               USTPUTC(CTLBACKQ | CTLQUOTE, out);
+       else
+               USTPUTC(CTLBACKQ, out);
+       if (oldstyle)
+               goto parsebackq_oldreturn;
+       else
+               goto parsebackq_newreturn;
+}
+
+/*
+ * Parse an arithmetic expansion (indicate start of one and set state)
+ */
+parsearith: {
+
+       if (++arinest == 1) {
+               prevsyntax = syntax;
+               syntax = ARISYNTAX;
+               USTPUTC(CTLARI, out);
+               if (dblquote)
+                       USTPUTC('"',out);
+               else
+                       USTPUTC(' ',out);
+       } else {
+               /*
+                * we collapse embedded arithmetic expansion to
+                * parenthesis, which should be equivalent
+                */
+               USTPUTC('(', out);
+       }
+       goto parsearith_return;
+}
+
+} /* end of readtoken */
+
+
+
+#ifdef mkinit
+INCLUDE "parser.h"
+RESET {
+       tokpushback = 0;
+       checkkwd = 0;
+       checkalias = 0;
+}
+#endif
+
+/*
+ * Returns true if the text contains nothing to expand (no dollar signs
+ * or backquotes).
+ */
+
+static int
+noexpand(text)
+       char *text;
+       {
+       char *p;
+       char c;
+
+       p = text;
+       while ((c = *p++) != '\0') {
+               if (c == CTLQUOTEMARK)
+                       continue;
+               if (c == CTLESC)
+                       p++;
+               else if (BASESYNTAX[(int)c] == CCTL)
+                       return 0;
+       }
+       return 1;
+}
+
+
+/*
+ * Return true if the argument is a legal variable name (a letter or
+ * underscore followed by zero or more letters, underscores, and digits).
+ */
+
+static int
+goodname(char *name)
+       {
+       char *p;
+
+       p = name;
+       if (! is_name(*p))
+               return 0;
+       while (*++p) {
+               if (! is_in_name(*p))
+                       return 0;
+       }
+       return 1;
+}
+
+
+/*
+ * Called when an unexpected token is read during the parse.  The argument
+ * is the token that is expected, or -1 if more than one type of token can
+ * occur at this point.
+ */
+
+static void
+synexpect(token)
+       int token;
+{
+       char msg[64];
+
+       if (token >= 0) {
+               fmtstr(msg, 64, "%s unexpected (expecting %s)",
+                       tokname[lasttoken], tokname[token]);
+       } else {
+               fmtstr(msg, 64, "%s unexpected", tokname[lasttoken]);
+       }
+       synerror(msg);
+       /* NOTREACHED */
+}
+
+
+static void
+synerror(msg)
+       const char *msg;
+       {
+       if (commandname)
+               outfmt(&errout, "%s: %d: ", commandname, startlinno);
+       outfmt(&errout, "Syntax error: %s\n", msg);
+       error((char *)NULL);
+       /* NOTREACHED */
+}
+
+static void
+setprompt(int which)
+{
+    whichprompt = which;
+    putprompt(getprompt(NULL));
+}
+
+/*
+ * called by editline -- any expansions to the prompt
+ *    should be added here.
+ */
+static const char *
+getprompt(void *unused)
+       {
+       switch (whichprompt) {
+       case 0:
+               return "";
+       case 1:
+               return ps1val();
+       case 2:
+               return ps2val();
+       default:
+               return "<internal prompt error>";
+       }
+}
+
+static int
+isassignment(const char *word) {
+       if (!is_name(*word)) {
+               return 0;
+       }
+       do {
+               word++;
+       } while (is_in_name(*word));
+       return *word == '=';
+}
+
+static const char *const *
+findkwd(const char *s) {
+       return findstring(
+               s, parsekwd, sizeof(parsekwd) / sizeof(const char *)
+       );
+}
+/*     $NetBSD: redir.c,v 1.22 2000/05/22 10:18:47 elric Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Code for dealing with input/output redirection.
+ */
+
+#define EMPTY -2               /* marks an unused slot in redirtab */
+#ifndef PIPE_BUF
+# define PIPESIZE 4096         /* amount of buffering in a pipe */
+#else
+# define PIPESIZE PIPE_BUF
+#endif
+
+
+struct redirtab *redirlist;
+
+/*
+ * We keep track of whether or not fd0 has been redirected.  This is for
+ * background commands, where we want to redirect fd0 to /dev/null only
+ * if it hasn't already been redirected.
+*/
+static int fd0_redirected = 0;
+
+/*
+ * We also keep track of where fileno2 goes.
+ */
+static int fileno2 = 2;
+
+static int openredirect __P((union node *));
+static void dupredirect __P((union node *, int, char[10 ]));
+static int openhere __P((union node *));
+static int noclobberopen __P((const char *));
+
+
+/*
+ * 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(redir, flags)
+       union node *redir;
+       int flags;
+       {
+       union node *n;
+       struct redirtab *sv = NULL;
+       int i;
+       int fd;
+       int newfd;
+       int try;
+       char memory[10];        /* file descriptors to write to memory */
+
+       for (i = 10 ; --i >= 0 ; )
+               memory[i] = 0;
+       memory[1] = flags & REDIR_BACKQ;
+       if (flags & REDIR_PUSH) {
+               sv = ckmalloc(sizeof (struct redirtab));
+               for (i = 0 ; i < 10 ; i++)
+                       sv->renamed[i] = EMPTY;
+               sv->next = redirlist;
+               redirlist = sv;
+       }
+       for (n = redir ; n ; n = n->nfile.next) {
+               fd = n->nfile.fd;
+               try = 0;
+               if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) &&
+                   n->ndup.dupfd == fd)
+                       continue; /* redirect from/to same file descriptor */
+
+               INTOFF;
+               newfd = openredirect(n);
+               if (((flags & REDIR_PUSH) && sv->renamed[fd] == EMPTY) ||
+                   (fd == fileno2)) {
+                       if (newfd == fd) {
+                               try++;
+                       } else if ((i = fcntl(fd, F_DUPFD, 10)) == -1) {
+                               switch (errno) {
+                               case EBADF:
+                                       if (!try) {
+                                               dupredirect(n, newfd, memory);
+                                               try++;
+                                               break;
+                                       }
+                                       /* FALLTHROUGH*/
+                               default:
+                                       if (newfd >= 0) {
+                                               close(newfd);
+                                       }
+                                       INTON;
+                                       error("%d: %s", fd, strerror(errno));
+                                       /* NOTREACHED */
+                               }
+                       }
+                       if (!try) {
+                               close(fd);
+                               if (flags & REDIR_PUSH) {
+                                       sv->renamed[fd] = i;
+                               }
+                               if (fd == fileno2) {
+                                       fileno2 = i;
+                               }
+                       }
+               } else if (fd != newfd) {
+                       close(fd);
+               }
+                if (fd == 0)
+                        fd0_redirected++;
+               if (!try)
+                       dupredirect(n, newfd, memory);
+               INTON;
+       }
+       if (memory[1])
+               out1 = &memout;
+       if (memory[2])
+               out2 = &memout;
+}
+
+
+static int
+openredirect(redir)
+       union node *redir;
+       {
+       char *fname;
+       int f;
+
+       switch (redir->nfile.type) {
+       case NFROM:
+               fname = redir->nfile.expfname;
+               if ((f = open(fname, O_RDONLY)) < 0)
+                       goto eopen;
+               break;
+       case NFROMTO:
+               fname = redir->nfile.expfname;
+               if ((f = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0666)) < 0)
+                       goto ecreate;
+               break;
+       case NTO:
+               /* Take care of noclobber mode. */
+               if (Cflag) {
+                       fname = redir->nfile.expfname;
+                       if ((f = noclobberopen(fname)) < 0)
+                               goto ecreate;
+                       break;
+               }
+       case NTOOV:
+               fname = redir->nfile.expfname;
+#ifdef O_CREAT
+               if ((f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0)
+                       goto ecreate;
+#else
+               if ((f = creat(fname, 0666)) < 0)
+                       goto ecreate;
+#endif
+               break;
+       case NAPPEND:
+               fname = redir->nfile.expfname;
+#ifdef O_APPEND
+               if ((f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666)) < 0)
+                       goto ecreate;
+#else
+               if ((f = open(fname, O_WRONLY)) < 0
+                && (f = creat(fname, 0666)) < 0)
+                       goto ecreate;
+               lseek(f, (off_t)0, 2);
+#endif
+               break;
+       default:
+#ifdef 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:
+       error("cannot create %s: %s", fname, errmsg(errno, E_CREAT));
+eopen:
+       error("cannot open %s: %s", fname, errmsg(errno, E_OPEN));
+}
+
+
+static void
+dupredirect(redir, f, memory)
+       union node *redir;
+       int f;
+       char memory[10];
+       {
+       int fd = redir->nfile.fd;
+
+       memory[fd] = 0;
+       if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) {
+               if (redir->ndup.dupfd >= 0) {   /* if not ">&-" */
+                       if (memory[redir->ndup.dupfd])
+                               memory[fd] = 1;
+                       else
+                               dup_as_newfd(redir->ndup.dupfd, fd);
+               }
+               return;
+       }
+
+       if (f != fd) {
+               dup_as_newfd(f, fd);
+               close(f);
+       }
+       return;
+}
+
+
+/*
+ * Handle here documents.  Normally we fork off a process to write the
+ * data to a pipe.  If the document is short, we can stuff the data in
+ * the pipe without forking.
+ */
+
+static int
+openhere(redir)
+       union node *redir;
+       {
+       int pip[2];
+       int len = 0;
+
+       if (pipe(pip) < 0)
+               error("Pipe call failed");
+       if (redir->type == NHERE) {
+               len = strlen(redir->nhere.doc->narg.text);
+               if (len <= PIPESIZE) {
+                       xwrite(pip[1], redir->nhere.doc->narg.text, len);
+                       goto out;
+               }
+       }
+       if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) {
+               close(pip[0]);
+               signal(SIGINT, SIG_IGN);
+               signal(SIGQUIT, SIG_IGN);
+               signal(SIGHUP, SIG_IGN);
+#ifdef SIGTSTP
+               signal(SIGTSTP, SIG_IGN);
+#endif
+               signal(SIGPIPE, SIG_DFL);
+               if (redir->type == NHERE)
+                       xwrite(pip[1], redir->nhere.doc->narg.text, len);
+               else
+                       expandhere(redir->nhere.doc, pip[1]);
+               _exit(0);
+       }
+out:
+       close(pip[1]);
+       return pip[0];
+}
+
+
+
+/*
+ * Undo the effects of the last redirection.
+ */
+
+static void
+popredir() {
+       struct redirtab *rp = redirlist;
+       int i;
+
+       INTOFF;
+       for (i = 0 ; i < 10 ; i++) {
+               if (rp->renamed[i] != EMPTY) {
+                        if (i == 0)
+                                fd0_redirected--;
+                       close(i);
+                       if (rp->renamed[i] >= 0) {
+                               dup_as_newfd(rp->renamed[i], i);
+                               close(rp->renamed[i]);
+                       }
+                       if (rp->renamed[i] == fileno2) {
+                               fileno2 = i;
+                       }
+               }
+       }
+       redirlist = rp->next;
+       ckfree(rp);
+       INTON;
+}
+
+/*
+ * Undo all redirections.  Called on error or interrupt.
+ */
+
+#ifdef mkinit
+
+INCLUDE "redir.h"
+
+RESET {
+       while (redirlist)
+               popredir();
+}
+
+SHELLPROC {
+       clearredir();
+}
+
+#endif
+
+/* Return true if fd 0 has already been redirected at least once.  */
+static int
+fd0_redirected_p () {
+        return fd0_redirected != 0;
+}
+
+/*
+ * Discard all saved file descriptors.
+ */
+
+static void
+clearredir() {
+       struct redirtab *rp;
+       int i;
+
+       for (rp = redirlist ; rp ; rp = rp->next) {
+               for (i = 0 ; i < 10 ; i++) {
+                       if (rp->renamed[i] >= 0) {
+                               close(rp->renamed[i]);
+                               if (rp->renamed[i] == fileno2) {
+                                       fileno2 = -1;
+                               }
+                       }
+                       rp->renamed[i] = EMPTY;
+               }
+       }
+       if (fileno2 != 2 && fileno2 >= 0) {
+               close(fileno2);
+               fileno2 = -1;
+       }
+}
+
+
+
+/*
+ * 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
+dup_as_newfd(from, to)
+       int from;
+       int to;
+{
+       int newfd;
+
+       newfd = fcntl(from, F_DUPFD, to);
+       if (newfd < 0) {
+               if (errno == EMFILE)
+                       return EMPTY;
+               else
+                       error("%d: %s", from, strerror(errno));
+       }
+       return newfd;
+}
+
+/*
+ * Open a file in noclobber mode.
+ * The code was copied from bash.
+ */
+static int
+noclobberopen(fname)
+       const char *fname;
+{
+       int r, fd;
+       struct stat finfo, finfo2;
+
+       /*
+        * If the file exists and is a regular file, return an error
+        * immediately.
+        */
+       r = stat(fname, &finfo);
+       if (r == 0 && S_ISREG(finfo.st_mode)) {
+               errno = EEXIST;
+               return -1;
+       }
+
+       /*
+        * If the file was not present (r != 0), make sure we open it
+        * exclusively so that if it is created before we open it, our open
+        * will fail.  Make sure that we do not truncate an existing file.
+        * Note that we don't turn on O_EXCL unless the stat failed -- if the
+        * file was not a regular file, we leave O_EXCL off.
+        */
+       if (r != 0)
+               return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666);
+       fd = open(fname, O_WRONLY|O_CREAT, 0666);
+
+       /* If the open failed, return the file descriptor right away. */
+       if (fd < 0)
+               return fd;
+
+       /*
+        * OK, the open succeeded, but the file may have been changed from a
+        * non-regular file to a regular file between the stat and the open.
+        * We are assuming that the O_EXCL open handles the case where FILENAME
+        * did not exist and is symlinked to an existing file between the stat
+        * and open.
+        */
+
+       /*
+        * If we can open it and fstat the file descriptor, and neither check
+        * revealed that it was a regular file, and the file has not been
+        * replaced, return the file descriptor.
+        */
+        if (fstat(fd, &finfo2) == 0 && !S_ISREG(finfo2.st_mode) &&
+            finfo.st_dev == finfo2.st_dev && finfo.st_ino == finfo2.st_ino)
+               return fd;
+
+       /* The file has been replaced.  badness. */
+       close(fd);
+       errno = EEXIST;
+       return -1;
+}
+/*     $NetBSD: setmode.c,v 1.28 2000/01/25 15:43:43 enami Exp $       */
+
+/*
+ * Copyright (c) 1989, 1993, 1994
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Dave Borman at Cray Research, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef __weak_alias
+__weak_alias(getmode,_getmode)
+__weak_alias(setmode,_setmode)
+#endif
+
+#ifdef __GLIBC__
+#define S_ISTXT __S_ISVTX
+#endif
+
+#define        SET_LEN 6               /* initial # of bitcmd struct to malloc */
+#define        SET_LEN_INCR 4          /* # of bitcmd structs to add as needed */
+
+typedef struct bitcmd {
+       char    cmd;
+       char    cmd2;
+       mode_t  bits;
+} BITCMD;
+
+#define        CMD2_CLR        0x01
+#define        CMD2_SET        0x02
+#define        CMD2_GBITS      0x04
+#define        CMD2_OBITS      0x08
+#define        CMD2_UBITS      0x10
+
+static BITCMD  *addcmd __P((BITCMD *, int, int, int, u_int));
+static void     compress_mode __P((BITCMD *));
+#ifdef SETMODE_DEBUG
+static void     dumpmode __P((BITCMD *));
+#endif
+
+/*
+ * Given the old mode and an array of bitcmd structures, apply the operations
+ * described in the bitcmd structures to the old mode, and return the new mode.
+ * Note that there is no '=' command; a strict assignment is just a '-' (clear
+ * bits) followed by a '+' (set bits).
+ */
+mode_t
+getmode(bbox, omode)
+       const void *bbox;
+       mode_t omode;
+{
+       const BITCMD *set;
+       mode_t clrval, newmode, value;
+
+       _DIAGASSERT(bbox != NULL);
+
+       set = (const BITCMD *)bbox;
+       newmode = omode;
+       for (value = 0;; set++)
+               switch(set->cmd) {
+               /*
+                * When copying the user, group or other bits around, we "know"
+                * where the bits are in the mode so that we can do shifts to
+                * copy them around.  If we don't use shifts, it gets real
+                * grundgy with lots of single bit checks and bit sets.
+                */
+               case 'u':
+                       value = (newmode & S_IRWXU) >> 6;
+                       goto common;
+
+               case 'g':
+                       value = (newmode & S_IRWXG) >> 3;
+                       goto common;
+
+               case 'o':
+                       value = newmode & S_IRWXO;
+common:                        if (set->cmd2 & CMD2_CLR) {
+                               clrval =
+                                   (set->cmd2 & CMD2_SET) ?  S_IRWXO : value;
+                               if (set->cmd2 & CMD2_UBITS)
+                                       newmode &= ~((clrval<<6) & set->bits);
+                               if (set->cmd2 & CMD2_GBITS)
+                                       newmode &= ~((clrval<<3) & set->bits);
+                               if (set->cmd2 & CMD2_OBITS)
+                                       newmode &= ~(clrval & set->bits);
+                       }
+                       if (set->cmd2 & CMD2_SET) {
+                               if (set->cmd2 & CMD2_UBITS)
+                                       newmode |= (value<<6) & set->bits;
+                               if (set->cmd2 & CMD2_GBITS)
+                                       newmode |= (value<<3) & set->bits;
+                               if (set->cmd2 & CMD2_OBITS)
+                                       newmode |= value & set->bits;
+                       }
+                       break;
+
+               case '+':
+                       newmode |= set->bits;
+                       break;
+
+               case '-':
+                       newmode &= ~set->bits;
+                       break;
+
+               case 'X':
+                       if (omode & (S_IFDIR|S_IXUSR|S_IXGRP|S_IXOTH))
+                               newmode |= set->bits;
+                       break;
+
+               case '\0':
+               default:
+#ifdef SETMODE_DEBUG
+                       (void)printf("getmode:%04o -> %04o\n", omode, newmode);
+#endif
+                       return (newmode);
+               }
+}
+
+#define        ADDCMD(a, b, c, d) do {                                         \
+       if (set >= endset) {                                            \
+               BITCMD *newset;                                         \
+               setlen += SET_LEN_INCR;                                 \
+               newset = realloc(saveset, sizeof(BITCMD) * setlen);     \
+               if (newset == NULL) {                                   \
+                       free(saveset);                                  \
+                       return (NULL);                                  \
+               }                                                       \
+               set = newset + (set - saveset);                         \
+               saveset = newset;                                       \
+               endset = newset + (setlen - 2);                         \
+       }                                                               \
+       set = addcmd(set, (a), (b), (c), (d));                          \
+} while (/*CONSTCOND*/0)
+
+#define        STANDARD_BITS   (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO)
+
+static void *
+setmode(p)
+       const char *p;
+{
+       int perm, who;
+       char op, *ep;
+       BITCMD *set, *saveset, *endset;
+       sigset_t mysigset, sigoset;
+       mode_t mask;
+       int equalopdone = 0;    /* pacify gcc */
+       int permXbits, setlen;
+
+       if (!*p)
+               return (NULL);
+
+       /*
+        * Get a copy of the mask for the permissions that are mask relative.
+        * Flip the bits, we want what's not set.  Since it's possible that
+        * the caller is opening files inside a signal handler, protect them
+        * as best we can.
+        */
+       sigfillset(&mysigset);
+       (void)sigprocmask(SIG_BLOCK, &mysigset, &sigoset);
+       (void)umask(mask = umask(0));
+       mask = ~mask;
+       (void)sigprocmask(SIG_SETMASK, &sigoset, NULL);
+
+       setlen = SET_LEN + 2;
+       
+       if ((set = malloc((u_int)(sizeof(BITCMD) * setlen))) == NULL)
+               return (NULL);
+       saveset = set;
+       endset = set + (setlen - 2);
+
+       /*
+        * If an absolute number, get it and return; disallow non-octal digits
+        * or illegal bits.
+        */
+       if (isdigit((unsigned char)*p)) {
+               perm = (mode_t)strtol(p, &ep, 8);
+               if (*ep || perm & ~(STANDARD_BITS|S_ISTXT)) {
+                       free(saveset);
+                       return (NULL);
+               }
+               ADDCMD('=', (STANDARD_BITS|S_ISTXT), perm, mask);
+               set->cmd = 0;
+               return (saveset);
+       }
+
+       /*
+        * Build list of structures to set/clear/copy bits as described by
+        * each clause of the symbolic mode.
+        */
+       for (;;) {
+               /* First, find out which bits might be modified. */
+               for (who = 0;; ++p) {
+                       switch (*p) {
+                       case 'a':
+                               who |= STANDARD_BITS;
+                               break;
+                       case 'u':
+                               who |= S_ISUID|S_IRWXU;
+                               break;
+                       case 'g':
+                               who |= S_ISGID|S_IRWXG;
+                               break;
+                       case 'o':
+                               who |= S_IRWXO;
+                               break;
+                       default:
+                               goto getop;
+                       }
+               }
+
+getop:         if ((op = *p++) != '+' && op != '-' && op != '=') {
+                       free(saveset);
+                       return (NULL);
+               }
+               if (op == '=')
+                       equalopdone = 0;
+
+               who &= ~S_ISTXT;
+               for (perm = 0, permXbits = 0;; ++p) {
+                       switch (*p) {
+                       case 'r':
+                               perm |= S_IRUSR|S_IRGRP|S_IROTH;
+                               break;
+                       case 's':
+                               /*
+                                * If specific bits where requested and 
+                                * only "other" bits ignore set-id. 
+                                */
+                               if (who == 0 || (who & ~S_IRWXO))
+                                       perm |= S_ISUID|S_ISGID;
+                               break;
+                       case 't':
+                               /*
+                                * If specific bits where requested and 
+                                * only "other" bits ignore set-id. 
+                                */
+                               if (who == 0 || (who & ~S_IRWXO)) {
+                                       who |= S_ISTXT;
+                                       perm |= S_ISTXT;
+                               }
+                               break;
+                       case 'w':
+                               perm |= S_IWUSR|S_IWGRP|S_IWOTH;
+                               break;
+                       case 'X':
+                               permXbits = S_IXUSR|S_IXGRP|S_IXOTH;
+                               break;
+                       case 'x':
+                               perm |= S_IXUSR|S_IXGRP|S_IXOTH;
+                               break;
+                       case 'u':
+                       case 'g':
+                       case 'o':
+                               /*
+                                * When ever we hit 'u', 'g', or 'o', we have
+                                * to flush out any partial mode that we have,
+                                * and then do the copying of the mode bits.
+                                */
+                               if (perm) {
+                                       ADDCMD(op, who, perm, mask);
+                                       perm = 0;
+                               }
+                               if (op == '=')
+                                       equalopdone = 1;
+                               if (op == '+' && permXbits) {
+                                       ADDCMD('X', who, permXbits, mask);
+                                       permXbits = 0;
+                               }
+                               ADDCMD(*p, who, op, mask);
+                               break;
+
+                       default:
+                               /*
+                                * Add any permissions that we haven't already
+                                * done.
+                                */
+                               if (perm || (op == '=' && !equalopdone)) {
+                                       if (op == '=')
+                                               equalopdone = 1;
+                                       ADDCMD(op, who, perm, mask);
+                                       perm = 0;
+                               }
+                               if (permXbits) {
+                                       ADDCMD('X', who, permXbits, mask);
+                                       permXbits = 0;
+                               }
+                               goto apply;
+                       }
+               }
+
+apply:         if (!*p)
+                       break;
+               if (*p != ',')
+                       goto getop;
+               ++p;
+       }
+       set->cmd = 0;
+#ifdef SETMODE_DEBUG
+       (void)printf("Before compress_mode()\n");
+       dumpmode(saveset);
+#endif
+       compress_mode(saveset);
+#ifdef SETMODE_DEBUG
+       (void)printf("After compress_mode()\n");
+       dumpmode(saveset);
+#endif
+       return (saveset);
+}
+
+static BITCMD *
+addcmd(set, op, who, oparg, mask)
+       BITCMD *set;
+       int oparg, who;
+       int op;
+       u_int mask;
+{
+
+       _DIAGASSERT(set != NULL);
+
+       switch (op) {
+       case '=':
+               set->cmd = '-';
+               set->bits = who ? who : STANDARD_BITS;
+               set++;
+
+               op = '+';
+               /* FALLTHROUGH */
+       case '+':
+       case '-':
+       case 'X':
+               set->cmd = op;
+               set->bits = (who ? who : mask) & oparg;
+               break;
+
+       case 'u':
+       case 'g':
+       case 'o':
+               set->cmd = op;
+               if (who) {
+                       set->cmd2 = ((who & S_IRUSR) ? CMD2_UBITS : 0) |
+                                   ((who & S_IRGRP) ? CMD2_GBITS : 0) |
+                                   ((who & S_IROTH) ? CMD2_OBITS : 0);
+                       set->bits = (mode_t)~0;
+               } else {
+                       set->cmd2 = CMD2_UBITS | CMD2_GBITS | CMD2_OBITS;
+                       set->bits = mask;
+               }
+       
+               if (oparg == '+')
+                       set->cmd2 |= CMD2_SET;
+               else if (oparg == '-')
+                       set->cmd2 |= CMD2_CLR;
+               else if (oparg == '=')
+                       set->cmd2 |= CMD2_SET|CMD2_CLR;
+               break;
+       }
+       return (set + 1);
+}
+
+#ifdef SETMODE_DEBUG
+static void
+dumpmode(set)
+       BITCMD *set;
+{
+
+       _DIAGASSERT(set != NULL);
+
+       for (; set->cmd; ++set)
+               (void)printf("cmd: '%c' bits %04o%s%s%s%s%s%s\n",
+                   set->cmd, set->bits, set->cmd2 ? " cmd2:" : "",
+                   set->cmd2 & CMD2_CLR ? " CLR" : "",
+                   set->cmd2 & CMD2_SET ? " SET" : "",
+                   set->cmd2 & CMD2_UBITS ? " UBITS" : "",
+                   set->cmd2 & CMD2_GBITS ? " GBITS" : "",
+                   set->cmd2 & CMD2_OBITS ? " OBITS" : "");
+}
+#endif
+
+/*
+ * Given an array of bitcmd structures, compress by compacting consecutive
+ * '+', '-' and 'X' commands into at most 3 commands, one of each.  The 'u',
+ * 'g' and 'o' commands continue to be separate.  They could probably be 
+ * compacted, but it's not worth the effort.
+ */
+static void
+compress_mode(set)
+       BITCMD *set;
+{
+       BITCMD *nset;
+       int setbits, clrbits, Xbits, op;
+
+       _DIAGASSERT(set != NULL);
+
+       for (nset = set;;) {
+               /* Copy over any 'u', 'g' and 'o' commands. */
+               while ((op = nset->cmd) != '+' && op != '-' && op != 'X') {
+                       *set++ = *nset++;
+                       if (!op)
+                               return;
+               }
+
+               for (setbits = clrbits = Xbits = 0;; nset++) {
+                       if ((op = nset->cmd) == '-') {
+                               clrbits |= nset->bits;
+                               setbits &= ~nset->bits;
+                               Xbits &= ~nset->bits;
+                       } else if (op == '+') {
+                               setbits |= nset->bits;
+                               clrbits &= ~nset->bits;
+                               Xbits &= ~nset->bits;
+                       } else if (op == 'X')
+                               Xbits |= nset->bits & ~setbits;
+                       else
+                               break;
+               }
+               if (clrbits) {
+                       set->cmd = '-';
+                       set->cmd2 = 0;
+                       set->bits = clrbits;
+                       set++;
+               }
+               if (setbits) {
+                       set->cmd = '+';
+                       set->cmd2 = 0;
+                       set->bits = setbits;
+                       set++;
+               }
+               if (Xbits) {
+                       set->cmd = 'X';
+                       set->cmd2 = 0;
+                       set->bits = Xbits;
+                       set++;
+               }
+       }
+}
+/*     $NetBSD: show.c,v 1.18 1999/10/08 21:10:44 pk Exp $     */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+
+#ifdef DEBUG
+static void shtree __P((union node *, int, char *, FILE*));
+static void shcmd __P((union node *, FILE *));
+static void sharg __P((union node *, FILE *));
+static void indent __P((int, char *, FILE *));
+static void trstring __P((char *));
+
+
+static void
+showtree(n)
+       union node *n;
+{
+       trputs("showtree called\n");
+       shtree(n, 1, NULL, stdout);
+}
+
+
+static void
+shtree(n, ind, pfx, fp)
+       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(cmd, fp)
+       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 NAPPEND:   s = ">>"; dftfd = 1; break;
+                       case NTOFD:     s = ">&"; dftfd = 1; break;
+                       case NTOOV:     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(arg, fp)
+       union node *arg;
+       FILE *fp;
+       {
+       char *p;
+       struct nodelist *bqlist;
+       int subtype;
+
+       if (arg->type != NARG) {
+               printf("<node type %d>\n", arg->type);
+               fflush(stdout);
+               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:
+                               printf("<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;
+               }
+       }
+}
+
+
+static void
+indent(amount, pfx, fp)
+       int amount;
+       char *pfx;
+       FILE *fp;
+{
+       int i;
+
+       for (i = 0 ; i < amount ; i++) {
+               if (pfx && i == amount - 1)
+                       fputs(pfx, fp);
+               putc('\t', fp);
+       }
+}
+#endif
+
+
+
+/*
+ * Debugging stuff.
+ */
+
+
+#ifdef DEBUG
+FILE *tracefile;
+
+#if DEBUG == 2
+static int debug = 1;
+#else
+static int debug = 0;
+#endif
+
+
+static void
+trputc(c)
+       int c;
+{
+       if (tracefile == NULL)
+               return;
+       putc(c, tracefile);
+       if (c == '\n')
+               fflush(tracefile);
+}
+
+static void
+trace(const char *fmt, ...)
+{
+       va_list va;
+#ifdef __STDC__
+       va_start(va, fmt);
+#else
+       char *fmt;
+       va_start(va);
+       fmt = va_arg(va, char *);
+#endif
+       if (tracefile != NULL) {
+               (void) vfprintf(tracefile, fmt, va);
+               if (strchr(fmt, '\n'))
+                       (void) fflush(tracefile);
+       }
+       va_end(va);
+}
+
+
+static void
+trputs(s)
+       const char *s;
+{
+       if (tracefile == NULL)
+               return;
+       fputs(s, tracefile);
+       if (strchr(s, '\n'))
+               fflush(tracefile);
+}
+
+
+static void
+trstring(s)
+       char *s;
+{
+       char *p;
+       char c;
+
+       if (tracefile == NULL)
+               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;
+               }
+       }
+       putc('"', tracefile);
+}
+
+
+static void
+trargs(ap)
+       char **ap;
+{
+       if (tracefile == NULL)
+               return;
+       while (*ap) {
+               trstring(*ap++);
+               if (*ap)
+                       putc(' ', tracefile);
+               else
+                       putc('\n', tracefile);
+       }
+       fflush(tracefile);
+}
+
+
+static void
+opentrace() {
+       char s[100];
+#ifdef O_APPEND
+       int flags;
+#endif
+
+       if (!debug)
+               return;
+#ifdef not_this_way
+       {
+               char *p;
+               if ((p = getenv("HOME")) == NULL) {
+                       if (geteuid() == 0)
+                               p = "/";
+                       else
+                               p = "/tmp";
+               }
+               scopy(p, s);
+               strcat(s, "/trace");
+       }
+#else
+       scopy("./trace", s);
+#endif /* not_this_way */
+       if ((tracefile = fopen(s, "a")) == NULL) {
+               fprintf(stderr, "Can't open %s\n", s);
+               return;
+       }
+#ifdef O_APPEND
+       if ((flags = fcntl(fileno(tracefile), F_GETFL, 0)) >= 0)
+               fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND);
+#endif
+       fputs("\nTracing started.\n", tracefile);
+       fflush(tracefile);
+}
+#endif /* DEBUG */
+
+
+/*
+ * This file was generated by the mksyntax program.
+ */
+
+/* syntax table used when not in quotes */
+static const char basesyntax[257] = {
+      CEOF,    CSPCL,   CWORD,   CCTL,
+      CCTL,    CCTL,    CCTL,    CCTL,
+      CCTL,    CCTL,    CCTL,    CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CSPCL,
+      CNL,     CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CSPCL,   CWORD,
+      CDQUOTE, CWORD,   CVAR,    CWORD,
+      CSPCL,   CSQUOTE, CSPCL,   CSPCL,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CSPCL,   CSPCL,   CWORD,
+      CSPCL,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CBACK,   CWORD,
+      CWORD,   CWORD,   CBQUOTE, CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CSPCL,   CENDVAR,
+      CWORD
+};
+
+/* syntax table used when in double quotes */
+static const char dqsyntax[257] = {
+      CEOF,    CIGN,    CWORD,   CCTL,
+      CCTL,    CCTL,    CCTL,    CCTL,
+      CCTL,    CCTL,    CCTL,    CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CNL,     CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CCTL,
+      CENDQUOTE,CWORD,  CVAR,    CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CCTL,    CWORD,   CWORD,   CCTL,
+      CWORD,   CCTL,    CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CCTL,    CWORD,   CWORD,   CCTL,
+      CWORD,   CCTL,    CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CCTL,    CBACK,   CCTL,
+      CWORD,   CWORD,   CBQUOTE, CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CENDVAR,
+      CCTL
+};
+
+/* syntax table used when in single quotes */
+static const char sqsyntax[257] = {
+      CEOF,    CIGN,    CWORD,   CCTL,
+      CCTL,    CCTL,    CCTL,    CCTL,
+      CCTL,    CCTL,    CCTL,    CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CNL,     CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CCTL,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CENDQUOTE,CWORD,  CWORD,
+      CCTL,    CWORD,   CWORD,   CCTL,
+      CWORD,   CCTL,    CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CCTL,    CWORD,   CWORD,   CCTL,
+      CWORD,   CCTL,    CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CCTL,    CCTL,    CCTL,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CCTL
+};
+
+/* syntax table used when in arithmetic */
+static const char arisyntax[257] = {
+      CEOF,    CIGN,    CWORD,   CCTL,
+      CCTL,    CCTL,    CCTL,    CCTL,
+      CCTL,    CCTL,    CCTL,    CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CNL,     CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CDQUOTE, CWORD,   CVAR,    CWORD,
+      CWORD,   CSQUOTE, CLP,     CRP,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CBACK,   CWORD,
+      CWORD,   CWORD,   CBQUOTE, CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CWORD,
+      CWORD,   CWORD,   CWORD,   CENDVAR,
+      CWORD
+};
+
+/* character classification table */
+static const char is_type[257] = {
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       0,
+      0,       0,       0,       ISSPECL,
+      0,       ISSPECL, ISSPECL, 0,
+      0,       0,       0,       0,
+      ISSPECL, 0,       0,       ISSPECL,
+      0,       0,       ISDIGIT, ISDIGIT,
+      ISDIGIT, ISDIGIT, ISDIGIT, ISDIGIT,
+      ISDIGIT, ISDIGIT, ISDIGIT, ISDIGIT,
+      0,       0,       0,       0,
+      0,       ISSPECL, ISSPECL, ISUPPER,
+      ISUPPER, ISUPPER, ISUPPER, ISUPPER,
+      ISUPPER, ISUPPER, ISUPPER, ISUPPER,
+      ISUPPER, ISUPPER, ISUPPER, ISUPPER,
+      ISUPPER, ISUPPER, ISUPPER, ISUPPER,
+      ISUPPER, ISUPPER, ISUPPER, ISUPPER,
+      ISUPPER, ISUPPER, ISUPPER, ISUPPER,
+      ISUPPER, 0,       0,       0,
+      0,       ISUNDER, 0,       ISLOWER,
+      ISLOWER, ISLOWER, ISLOWER, ISLOWER,
+      ISLOWER, ISLOWER, ISLOWER, ISLOWER,
+      ISLOWER, ISLOWER, ISLOWER, ISLOWER,
+      ISLOWER, ISLOWER, ISLOWER, ISLOWER,
+      ISLOWER, ISLOWER, ISLOWER, ISLOWER,
+      ISLOWER, ISLOWER, ISLOWER, ISLOWER,
+      ISLOWER, 0,       0,       0,
+      0
+};
+/*     $NetBSD: trap.c,v 1.25 2001/02/04 19:52:07 christos Exp $       */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Sigmode records the current value of the signal handlers for the various
+ * modes.  A value of zero means that the current handler is not known.
+ * S_HARD_IGN indicates that the signal was ignored on entry to the shell,
+ */
+
+/*
+ * The trap builtin.
+ */
+
+static int
+trapcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       char *action;
+       char **ap;
+       int signo;
+
+       if (argc <= 1) {
+               for (signo = 0 ; signo < NSIG ; signo++) {
+                       if (trap[signo] != NULL) {
+                               char *p;
+
+                               p = single_quote(trap[signo]);
+                               out1fmt("trap -- %s %s\n", p,
+                                       signal_names[signo] + (signo ? 3 : 0)
+                               );
+                               stunalloc(p);
+                       }
+               }
+               return 0;
+       }
+       ap = argv + 1;
+       if (argc == 2)
+               action = NULL;
+       else
+               action = *ap++;
+       while (*ap) {
+               if ((signo = decode_signal(*ap, 0)) < 0)
+                       error("%s: bad trap", *ap);
+               INTOFF;
+               if (action) {
+                       if (action[0] == '-' && action[1] == '\0')
+                               action = NULL;
+                       else
+                               action = savestr(action);
+               }
+               if (trap[signo])
+                       ckfree(trap[signo]);
+               trap[signo] = action;
+               if (signo != 0)
+                       setsignal(signo);
+               INTON;
+               ap++;
+       }
+       return 0;
+}
+
+
+
+/*
+ * Clear traps on a fork.
+ */
+
+static void
+clear_traps() {
+       char **tp;
+
+       for (tp = trap ; tp < &trap[NSIG] ; tp++) {
+               if (*tp && **tp) {      /* trap not NULL or SIG_IGN */
+                       INTOFF;
+                       ckfree(*tp);
+                       *tp = NULL;
+                       if (tp != &trap[0])
+                               setsignal(tp - trap);
+                       INTON;
+               }
+       }
+}
+
+
+
+/*
+ * Set the signal handler for the specified signal.  The routine figures
+ * out what it should be set to.
+ */
+
+static void
+setsignal(signo)
+       int signo;
+{
+       int action;
+       char *t;
+       struct sigaction act;
+
+       if ((t = trap[signo]) == NULL)
+               action = S_DFL;
+       else if (*t != '\0')
+               action = S_CATCH;
+       else
+               action = S_IGN;
+       if (rootshell && action == S_DFL) {
+               switch (signo) {
+               case SIGINT:
+                       if (iflag || minusc || sflag == 0)
+                               action = S_CATCH;
+                       break;
+               case SIGQUIT:
+#ifdef DEBUG
+                       {
+                       extern int debug;
+
+                       if (debug)
+                               break;
+                       }
+#endif
+                       /* FALLTHROUGH */
+               case SIGTERM:
+                       if (iflag)
+                               action = S_IGN;
+                       break;
+#if JOBS
+               case SIGTSTP:
+               case SIGTTOU:
+                       if (mflag)
+                               action = S_IGN;
+                       break;
+#endif
+               }
+       }
+
+       t = &sigmode[signo - 1];
+       if (*t == 0) {
+               /*
+                * current setting unknown
+                */
+               if (sigaction(signo, 0, &act) == -1) {
+                       /*
+                        * Pretend it worked; maybe we should give a warning
+                        * here, but other shells don't. We don't alter
+                        * sigmode, so that we retry every time.
+                        */
+                       return;
+               }
+               if (act.sa_handler == SIG_IGN) {
+                       if (mflag && (signo == SIGTSTP ||
+                            signo == SIGTTIN || signo == SIGTTOU)) {
+                               *t = S_IGN;     /* don't hard ignore these */
+                       } else
+                               *t = S_HARD_IGN;
+               } else {
+                       *t = S_RESET;   /* force to be set */
+               }
+       }
+       if (*t == S_HARD_IGN || *t == action)
+               return;
+       switch (action) {
+       case S_CATCH:
+               act.sa_handler = onsig;
+               break;
+       case S_IGN:
+               act.sa_handler = SIG_IGN;
+               break;
+       default:
+               act.sa_handler = SIG_DFL;
+       }
+       *t = action;
+       act.sa_flags = 0;
+       sigemptyset(&act.sa_mask);
+       sigaction(signo, &act, 0);
+}
+
+/*
+ * Ignore a signal.
+ */
+
+static void
+ignoresig(signo)
+       int signo;
+{
+       if (sigmode[signo - 1] != S_IGN && sigmode[signo - 1] != S_HARD_IGN) {
+               signal(signo, SIG_IGN);
+       }
+       sigmode[signo - 1] = S_HARD_IGN;
+}
+
+
+#ifdef mkinit
+INCLUDE <signal.h>
+INCLUDE "trap.h"
+
+SHELLPROC {
+       char *sm;
+
+       clear_traps();
+       for (sm = sigmode ; sm < sigmode + NSIG - 1; sm++) {
+               if (*sm == S_IGN)
+                       *sm = S_HARD_IGN;
+       }
+}
+#endif
+
+
+
+/*
+ * Signal handler.
+ */
+
+static void
+onsig(signo)
+       int signo;
+{
+       if (signo == SIGINT && trap[SIGINT] == NULL) {
+               onint();
+               return;
+       }
+       gotsig[signo - 1] = 1;
+       pendingsigs++;
+}
+
+
+
+/*
+ * Called to execute a trap.  Perhaps we should avoid entering new trap
+ * handlers while we are executing a trap handler.
+ */
+
+static void
+dotrap() {
+       int i;
+       int savestatus;
+
+       for (;;) {
+               for (i = 1 ; ; i++) {
+                       if (gotsig[i - 1])
+                               break;
+                       if (i >= NSIG - 1)
+                               goto done;
+               }
+               gotsig[i - 1] = 0;
+               savestatus=exitstatus;
+               evalstring(trap[i], 0);
+               exitstatus=savestatus;
+       }
+done:
+       pendingsigs = 0;
+}
+
+
+
+/*
+ * Controls whether the shell is interactive or not.
+ */
+
+
+static void
+setinteractive(on)
+       int on;
+{
+       static int is_interactive;
+
+       if (on == is_interactive)
+               return;
+       setsignal(SIGINT);
+       setsignal(SIGQUIT);
+       setsignal(SIGTERM);
+       chkmail(1);
+       is_interactive = on;
+}
+
+
+
+/*
+ * Called to exit the shell.
+ */
+
+static void
+exitshell(status)
+       int status;
+{
+       struct jmploc loc1, loc2;
+       char *p;
+
+       TRACE(("exitshell(%d) pid=%d\n", status, getpid()));
+       if (setjmp(loc1.loc)) {
+               goto l1;
+       }
+       if (setjmp(loc2.loc)) {
+               goto l2;
+       }
+       handler = &loc1;
+       if ((p = trap[0]) != NULL && *p != '\0') {
+               trap[0] = NULL;
+               evalstring(p, 0);
+       }
+l1:   handler = &loc2;                 /* probably unnecessary */
+       flushall();
+#if JOBS
+       setjobctl(0);
+#endif
+l2:   _exit(status);
+       /* NOTREACHED */
+}
+
+static int decode_signal(const char *string, int minsig)
+{
+       int signo;
+
+       if (is_number(string)) {
+               signo = atoi(string);
+               if (signo >= NSIG) {
+                       return -1;
+               }
+               return signo;
+       }
+
+       signo = minsig;
+       if (!signo) {
+               goto zero;
+       }
+       for (; signo < NSIG; signo++) {
+               if (!strcasecmp(string, &(signal_names[signo])[3])) {
+                       return signo;
+               }
+zero:
+               if (!strcasecmp(string, signal_names[signo])) {
+                       return signo;
+               }
+       }
+
+       return -1;
+}
+/*     $NetBSD: var.c,v 1.27 2001/02/04 19:52:07 christos Exp $        */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+
+#define VTABSIZE 39
+
+
+struct varinit {
+       struct var *var;
+       int flags;
+       const char *text;
+       void (*func) __P((const char *));
+};
+
+struct localvar *localvars;
+
+#if ATTY
+struct var vatty;
+#endif
+struct var vifs;
+struct var vmail;
+struct var vmpath;
+struct var vpath;
+struct var vps1;
+struct var vps2;
+struct var vvers;
+struct var voptind;
+
+static const char defpathvar[] =
+       "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
+#ifdef IFS_BROKEN
+static const char defifsvar[] = "IFS= \t\n";
+#else
+static const char defifs[] = " \t\n";
+#endif
+
+static const struct varinit varinit[] = {
+#if ATTY
+       { &vatty,       VSTRFIXED|VTEXTFIXED|VUNSET,    "ATTY=",
+         NULL },
+#endif
+#ifdef IFS_BROKEN
+       { &vifs,        VSTRFIXED|VTEXTFIXED,           defifsvar,
+#else
+       { &vifs,        VSTRFIXED|VTEXTFIXED|VUNSET,    "IFS=",
+#endif
+         NULL },
+       { &vmail,       VSTRFIXED|VTEXTFIXED|VUNSET,    "MAIL=",
+         NULL },
+       { &vmpath,      VSTRFIXED|VTEXTFIXED|VUNSET,    "MAILPATH=",
+         NULL },
+       { &vpath,       VSTRFIXED|VTEXTFIXED,           defpathvar,
+         changepath },
+       /*
+        * vps1 depends on uid
+        */
+       { &vps2,        VSTRFIXED|VTEXTFIXED,           "PS2=> ",
+         NULL },
+       { &voptind,     VSTRFIXED|VTEXTFIXED,           "OPTIND=1",
+         getoptsreset },
+       { NULL, 0,                              NULL,
+         NULL }
+};
+
+struct var *vartab[VTABSIZE];
+
+static struct var **hashvar __P((const char *));
+static void showvars __P((const char *, int, int));
+static struct var **findvar __P((struct var **, const char *));
+
+/*
+ * Initialize the varable symbol tables and import the environment
+ */
+
+#ifdef mkinit
+INCLUDE <unistd.h>
+INCLUDE "output.h"
+INCLUDE "var.h"
+static char **environ;
+INIT {
+       char **envp;
+       char ppid[32];
+
+       initvar();
+       for (envp = environ ; *envp ; envp++) {
+               if (strchr(*envp, '=')) {
+                       setvareq(*envp, VEXPORT|VTEXTFIXED);
+               }
+       }
+
+       fmtstr(ppid, sizeof(ppid), "%d", (int) getppid());
+       setvar("PPID", ppid, 0);
+}
+#endif
+
+
+/*
+ * This routine initializes the builtin variables.  It is called when the
+ * shell is initialized and again when a shell procedure is spawned.
+ */
+
+static void
+initvar() {
+       const struct varinit *ip;
+       struct var *vp;
+       struct var **vpp;
+
+       for (ip = varinit ; (vp = ip->var) != NULL ; ip++) {
+               if ((vp->flags & VEXPORT) == 0) {
+                       vpp = hashvar(ip->text);
+                       vp->next = *vpp;
+                       *vpp = vp;
+                       vp->text = strdup(ip->text);
+                       vp->flags = ip->flags;
+                       vp->func = ip->func;
+               }
+       }
+       /*
+        * PS1 depends on uid
+        */
+       if ((vps1.flags & VEXPORT) == 0) {
+               vpp = hashvar("PS1=");
+               vps1.next = *vpp;
+               *vpp = &vps1;
+               vps1.text = strdup(geteuid() ? "PS1=$ " : "PS1=# ");
+               vps1.flags = VSTRFIXED|VTEXTFIXED;
+       }
+}
+
+/*
+ * 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(name, val, flags)
+       const char *name, *val;
+       int flags;
+{
+       const char *p;
+       int len;
+       int namelen;
+       char *nameeq;
+       int isbad;
+       int vallen = 0;
+
+       isbad = 0;
+       p = name;
+       if (! is_name(*p))
+               isbad = 1;
+       p++;
+       for (;;) {
+               if (! is_in_name(*p)) {
+                       if (*p == '\0' || *p == '=')
+                               break;
+                       isbad = 1;
+               }
+               p++;
+       }
+       namelen = p - name;
+       if (isbad)
+               error("%.*s: bad variable name", namelen, name);
+       len = namelen + 2;              /* 2 is space for '=' and '\0' */
+       if (val == NULL) {
+               flags |= VUNSET;
+       } else {
+               len += vallen = strlen(val);
+       }
+       INTOFF;
+       nameeq = ckmalloc(len);
+       memcpy(nameeq, name, namelen);
+       nameeq[namelen] = '=';
+       if (val) {
+               memcpy(nameeq + namelen + 1, val, vallen + 1);
+       } else {
+               nameeq[namelen + 1] = '\0';
+       }
+       setvareq(nameeq, flags);
+       INTON;
+}
+
+
+
+/*
+ * 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.
+ */
+
+static void
+setvareq(s, flags)
+       char *s;
+       int flags;
+{
+       struct var *vp, **vpp;
+
+       vpp = hashvar(s);
+       flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1));
+       if ((vp = *findvar(vpp, s))) {
+               if (vp->flags & VREADONLY) {
+                       size_t len = strchr(s, '=') - s;
+                       error("%.*s: is read only", len, s);
+               }
+               INTOFF;
+
+               if (vp->func && (flags & VNOFUNC) == 0)
+                       (*vp->func)(strchr(s, '=') + 1);
+
+               if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
+                       ckfree(vp->text);
+
+               vp->flags &= ~(VTEXTFIXED|VSTACK|VUNSET);
+               vp->flags |= flags;
+               vp->text = s;
+
+               /*
+                * We could roll this to a function, to handle it as
+                * a regular variable function callback, but why bother?
+                */
+               if (iflag && (vp == &vmpath || (vp == &vmail && !mpathset())))
+                       chkmail(1);
+               INTON;
+               return;
+       }
+       /* not found */
+       vp = ckmalloc(sizeof (*vp));
+       vp->flags = flags;
+       vp->text = s;
+       vp->next = *vpp;
+       vp->func = NULL;
+       *vpp = vp;
+}
+
+
+
+/*
+ * Process a linked list of variable assignments.
+ */
+
+static void
+listsetvar(mylist)
+       struct strlist *mylist;
+       {
+       struct strlist *lp;
+
+       INTOFF;
+       for (lp = mylist ; lp ; lp = lp->next) {
+               setvareq(savestr(lp->text), 0);
+       }
+       INTON;
+}
+
+
+
+/*
+ * Find the value of a variable.  Returns NULL if not set.
+ */
+
+static char *
+lookupvar(name)
+       const char *name;
+       {
+       struct var *v;
+
+       if ((v = *findvar(hashvar(name), name)) && !(v->flags & VUNSET)) {
+               return strchr(v->text, '=') + 1;
+       }
+       return NULL;
+}
+
+
+
+/*
+ * Search the environment of a builtin command.
+ */
+
+static char *
+bltinlookup(name)
+       const char *name;
+{
+       struct strlist *sp;
+
+       for (sp = cmdenviron ; sp ; sp = sp->next) {
+               if (varequal(sp->text, name))
+                       return strchr(sp->text, '=') + 1;
+       }
+       return lookupvar(name);
+}
+
+
+
+/*
+ * Generate a list of exported variables.  This routine is used to construct
+ * the third argument to execve when executing a program.
+ */
+
+static char **
+environment() {
+       int nenv;
+       struct var **vpp;
+       struct var *vp;
+       char **env;
+       char **ep;
+
+       nenv = 0;
+       for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
+               for (vp = *vpp ; vp ; vp = vp->next)
+                       if (vp->flags & VEXPORT)
+                               nenv++;
+       }
+       ep = env = stalloc((nenv + 1) * sizeof *env);
+       for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
+               for (vp = *vpp ; vp ; vp = vp->next)
+                       if (vp->flags & VEXPORT)
+                               *ep++ = vp->text;
+       }
+       *ep = NULL;
+       return env;
+}
+
+
+/*
+ * Called when a shell procedure is invoked to clear out nonexported
+ * variables.  It is also necessary to reallocate variables of with
+ * VSTACK set since these are currently allocated on the stack.
+ */
+
+#ifdef mkinit
+static void shprocvar __P((void));
+
+SHELLPROC {
+       shprocvar();
+}
+#endif
+
+static void
+shprocvar() {
+       struct var **vpp;
+       struct var *vp, **prev;
+
+       for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
+               for (prev = vpp ; (vp = *prev) != NULL ; ) {
+                       if ((vp->flags & VEXPORT) == 0) {
+                               *prev = vp->next;
+                               if ((vp->flags & VTEXTFIXED) == 0)
+                                       ckfree(vp->text);
+                               if ((vp->flags & VSTRFIXED) == 0)
+                                       ckfree(vp);
+                       } else {
+                               if (vp->flags & VSTACK) {
+                                       vp->text = savestr(vp->text);
+                                       vp->flags &=~ VSTACK;
+                               }
+                               prev = &vp->next;
+                       }
+               }
+       }
+       initvar();
+}
+
+
+
+/*
+ * Command to list all variables which are set.  Currently this command
+ * is invoked from the set command when the set command is called without
+ * any variables.
+ */
+
+static int
+showvarscmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       showvars(nullstr, VUNSET, VUNSET);
+       return 0;
+}
+
+
+
+/*
+ * The export and readonly commands.
+ */
+
+static int
+exportcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       struct var *vp;
+       char *name;
+       const char *p;
+       int flag = argv[0][0] == 'r'? VREADONLY : VEXPORT;
+       int pflag;
+
+       listsetvar(cmdenviron);
+       pflag = (nextopt("p") == 'p');
+       if (argc > 1 && !pflag) {
+               while ((name = *argptr++) != NULL) {
+                       if ((p = strchr(name, '=')) != NULL) {
+                               p++;
+                       } else {
+                               if ((vp = *findvar(hashvar(name), name))) {
+                                       vp->flags |= flag;
+                                       goto found;
+                               }
+                       }
+                       setvar(name, p, flag);
+found:;
+               }
+       } else {
+               showvars(argv[0], flag, 0);
+       }
+       return 0;
+}
+
+
+/*
+ * The "local" command.
+ */
+
+static int
+localcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       char *name;
+
+       if (! in_function())
+               error("Not in a function");
+       while ((name = *argptr++) != NULL) {
+               mklocal(name);
+       }
+       return 0;
+}
+
+
+/*
+ * Make a variable a local variable.  When a variable is made local, it's
+ * value and flags are saved in a localvar structure.  The saved values
+ * will be restored when the shell function returns.  We handle the name
+ * "-" as a special case.
+ */
+
+static void
+mklocal(name)
+       char *name;
+       {
+       struct localvar *lvp;
+       struct var **vpp;
+       struct var *vp;
+
+       INTOFF;
+       lvp = ckmalloc(sizeof (struct localvar));
+       if (name[0] == '-' && name[1] == '\0') {
+               char *p;
+               p = ckmalloc(sizeof optlist);
+               lvp->text = memcpy(p, optlist, sizeof optlist);
+               vp = NULL;
+       } else {
+               vpp = hashvar(name);
+               vp = *findvar(vpp, name);
+               if (vp == NULL) {
+                       if (strchr(name, '='))
+                               setvareq(savestr(name), VSTRFIXED);
+                       else
+                               setvar(name, NULL, VSTRFIXED);
+                       vp = *vpp;      /* the new variable */
+                       lvp->text = NULL;
+                       lvp->flags = VUNSET;
+               } else {
+                       lvp->text = vp->text;
+                       lvp->flags = vp->flags;
+                       vp->flags |= VSTRFIXED|VTEXTFIXED;
+                       if (strchr(name, '='))
+                               setvareq(savestr(name), 0);
+               }
+       }
+       lvp->vp = vp;
+       lvp->next = localvars;
+       localvars = lvp;
+       INTON;
+}
+
+
+/*
+ * Called after a function returns.
+ */
+
+static void
+poplocalvars() {
+       struct localvar *lvp;
+       struct var *vp;
+
+       while ((lvp = localvars) != NULL) {
+               localvars = lvp->next;
+               vp = lvp->vp;
+               if (vp == NULL) {       /* $- saved */
+                       memcpy(optlist, lvp->text, sizeof optlist);
+                       ckfree(lvp->text);
+               } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) {
+                       (void)unsetvar(vp->text);
+               } else {
+                       if ((vp->flags & VTEXTFIXED) == 0)
+                               ckfree(vp->text);
+                       vp->flags = lvp->flags;
+                       vp->text = lvp->text;
+               }
+               ckfree(lvp);
+       }
+}
+
+
+static int
+setvarcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       if (argc <= 2)
+               return unsetcmd(argc, argv);
+       else if (argc == 3)
+               setvar(argv[1], argv[2], 0);
+       else
+               error("List assignment not implemented");
+       return 0;
+}
+
+
+/*
+ * The unset builtin command.  We unset the function before we unset the
+ * variable to allow a function to be unset when there is a readonly variable
+ * with the same name.
+ */
+
+static int
+unsetcmd(argc, argv)
+       int argc;
+       char **argv;
+{
+       char **ap;
+       int i;
+       int flg_func = 0;
+       int flg_var = 0;
+       int ret = 0;
+
+       while ((i = nextopt("vf")) != '\0') {
+               if (i == 'f')
+                       flg_func = 1;
+               else
+                       flg_var = 1;
+       }
+       if (flg_func == 0 && flg_var == 0)
+               flg_var = 1;
+
+       for (ap = argptr; *ap ; ap++) {
+               if (flg_func)
+                       unsetfunc(*ap);
+               if (flg_var)
+                       ret |= unsetvar(*ap);
+       }
+       return ret;
+}
+
+
+/*
+ * Unset the specified variable.
+ */
+
+static int
+unsetvar(s)
+       const char *s;
+       {
+       struct var **vpp;
+       struct var *vp;
+
+       vpp = findvar(hashvar(s), s);
+       vp = *vpp;
+       if (vp) {
+               if (vp->flags & VREADONLY)
+                       return (1);
+               INTOFF;
+               if (*(strchr(vp->text, '=') + 1) != '\0')
+                       setvar(s, nullstr, 0);
+               vp->flags &= ~VEXPORT;
+               vp->flags |= VUNSET;
+               if ((vp->flags & VSTRFIXED) == 0) {
+                       if ((vp->flags & VTEXTFIXED) == 0)
+                               ckfree(vp->text);
+                       *vpp = vp->next;
+                       ckfree(vp);
+               }
+               INTON;
+               return (0);
+       }
+
+       return (0);
+}
+
+
+
+/*
+ * Find the appropriate entry in the hash table from the name.
+ */
+
+static struct var **
+hashvar(p)
+       const char *p;
+       {
+       unsigned int hashval;
+
+       hashval = ((unsigned char) *p) << 4;
+       while (*p && *p != '=')
+               hashval += (unsigned char) *p++;
+       return &vartab[hashval % VTABSIZE];
+}
+
+
+
+/*
+ * Returns true if the two strings specify the same varable.  The first
+ * variable name is terminated by '='; the second may be terminated by
+ * either '=' or '\0'.
+ */
+
+static int
+varequal(p, q)
+       const char *p, *q;
+       {
+       while (*p == *q++) {
+               if (*p++ == '=')
+                       return 1;
+       }
+       if (*p == '=' && *(q - 1) == '\0')
+               return 1;
+       return 0;
+}
+
+static void
+showvars(const char *myprefix, int mask, int xor)
+{
+       struct var **vpp;
+       struct var *vp;
+       const char *sep = myprefix == nullstr ? myprefix : spcstr;
+
+       for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
+               for (vp = *vpp ; vp ; vp = vp->next) {
+                       if ((vp->flags & mask) ^ xor) {
+                               char *p;
+                               int len;
+
+                               p = strchr(vp->text, '=') + 1;
+                               len = p - vp->text;
+                               p = single_quote(p);
+
+                               out1fmt(
+                                       "%s%s%.*s%s\n", myprefix, sep, len,
+                                       vp->text, p
+                               );
+                               stunalloc(p);
+                       }
+               }
+       }
+}
+
+static struct var **
+findvar(struct var **vpp, const char *name)
+{
+       for (; *vpp; vpp = &(*vpp)->next) {
+               if (varequal((*vpp)->text, name)) {
+                       break;
+               }
+       }
+       return vpp;
+}
+
+/*
+ * Copyright (c) 1999 Herbert Xu <herbert@debian.org>
+ * This file contains code for the times builtin.
+ * $Id: ash.c,v 1.1 2001/06/28 07:25:15 andersen Exp $
+ */
+static int timescmd (int argc, char **argv)
+{
+       struct tms buf;
+       long int clk_tck = sysconf(_SC_CLK_TCK);
+
+       times(&buf);
+       printf("%dm%fs %dm%fs\n%dm%fs %dm%fs\n",
+              (int) (buf.tms_utime / clk_tck / 60),
+              ((double) buf.tms_utime) / clk_tck,
+              (int) (buf.tms_stime / clk_tck / 60),
+              ((double) buf.tms_stime) / clk_tck,
+              (int) (buf.tms_cutime / clk_tck / 60),
+              ((double) buf.tms_cutime) / clk_tck,
+              (int) (buf.tms_cstime / clk_tck / 60),
+              ((double) buf.tms_cstime) / clk_tck);
+       return 0;
+}
+