]> Zhao Yanbai Git Server - minix.git/commitdiff
Importing bin/ksh
authorThomas Cort <tcort@minix3.org>
Wed, 13 Mar 2013 03:41:18 +0000 (03:41 +0000)
committerLionel Sambuc <lionel@minix3.org>
Thu, 14 Mar 2013 10:33:10 +0000 (11:33 +0100)
58 files changed:
bin/Makefile
bin/ksh/Makefile [new file with mode: 0644]
bin/ksh/alloc.c [new file with mode: 0644]
bin/ksh/c_ksh.c [new file with mode: 0644]
bin/ksh/c_sh.c [new file with mode: 0644]
bin/ksh/c_test.c [new file with mode: 0644]
bin/ksh/c_test.h [new file with mode: 0644]
bin/ksh/c_ulimit.c [new file with mode: 0644]
bin/ksh/conf-end.h [new file with mode: 0644]
bin/ksh/config.h [new file with mode: 0644]
bin/ksh/edit.c [new file with mode: 0644]
bin/ksh/edit.h [new file with mode: 0644]
bin/ksh/emacs-gen.sh [new file with mode: 0755]
bin/ksh/emacs.c [new file with mode: 0644]
bin/ksh/eval.c [new file with mode: 0644]
bin/ksh/exec.c [new file with mode: 0644]
bin/ksh/expand.h [new file with mode: 0644]
bin/ksh/expr.c [new file with mode: 0644]
bin/ksh/history.c [new file with mode: 0644]
bin/ksh/io.c [new file with mode: 0644]
bin/ksh/jobs.c [new file with mode: 0644]
bin/ksh/ksh.Man [new file with mode: 0644]
bin/ksh/ksh_dir.h [new file with mode: 0644]
bin/ksh/ksh_limval.h [new file with mode: 0644]
bin/ksh/ksh_stat.h [new file with mode: 0644]
bin/ksh/ksh_time.h [new file with mode: 0644]
bin/ksh/ksh_times.h [new file with mode: 0644]
bin/ksh/ksh_wait.h [new file with mode: 0644]
bin/ksh/lex.c [new file with mode: 0644]
bin/ksh/lex.h [new file with mode: 0644]
bin/ksh/mail.c [new file with mode: 0644]
bin/ksh/main.c [new file with mode: 0644]
bin/ksh/misc.c [new file with mode: 0644]
bin/ksh/mkman [new file with mode: 0755]
bin/ksh/path.c [new file with mode: 0644]
bin/ksh/proto.h [new file with mode: 0644]
bin/ksh/sh.h [new file with mode: 0644]
bin/ksh/shf.c [new file with mode: 0644]
bin/ksh/shf.h [new file with mode: 0644]
bin/ksh/sigact.c [new file with mode: 0644]
bin/ksh/sigact.h [new file with mode: 0644]
bin/ksh/siglist.in [new file with mode: 0644]
bin/ksh/siglist.sh [new file with mode: 0755]
bin/ksh/syn.c [new file with mode: 0644]
bin/ksh/table.c [new file with mode: 0644]
bin/ksh/table.h [new file with mode: 0644]
bin/ksh/trap.c [new file with mode: 0644]
bin/ksh/tree.c [new file with mode: 0644]
bin/ksh/tree.h [new file with mode: 0644]
bin/ksh/tty.c [new file with mode: 0644]
bin/ksh/tty.h [new file with mode: 0644]
bin/ksh/var.c [new file with mode: 0644]
bin/ksh/version.c [new file with mode: 0644]
bin/ksh/vi.c [new file with mode: 0644]
distrib/sets/lists/minix/mi
docs/UPDATING
etc/shells
releasetools/nbsd_ports

index cf014658b542414e71142ada79aa16b71ee47d12..cfbbfbb1fd63200c5df8a4351d0ebd09409201d4 100644 (file)
@@ -1,7 +1,7 @@
 #      $NetBSD: Makefile,v 1.22 2007/12/31 15:31:24 ad Exp $
 #      @(#)Makefile    8.1 (Berkeley) 5/31/93
 
-SUBDIR=        cat date echo ed expr kill ln ls \
+SUBDIR=        cat date echo ed expr kill ksh ln ls \
        mkdir pax pwd rm rmdir sync test
 
 .include <bsd.subdir.mk>
diff --git a/bin/ksh/Makefile b/bin/ksh/Makefile
new file mode 100644 (file)
index 0000000..b91e23e
--- /dev/null
@@ -0,0 +1,50 @@
+#      $NetBSD: Makefile,v 1.30 2011/10/16 17:12:11 joerg Exp $
+
+WARNS=3
+
+.include <bsd.own.mk>
+
+CPPFLAGS+=     -I.
+
+PROG=  ksh
+SRCS=  alloc.c c_ksh.c c_sh.c c_test.c c_ulimit.c edit.c emacs.c \
+       eval.c exec.c expr.c history.c io.c jobs.c lex.c mail.c \
+       main.c misc.c path.c shf.c sigact.c syn.c table.c trap.c \
+       tree.c tty.c var.c version.c vi.c
+DPSRCS=        emacs.out siglist.out
+.if (${MKMAN} != "no")
+DPSRCS+=ksh.1
+.endif
+
+# needs tbl for the man page.
+USETBL= 
+
+# Environment for scripts executed during build.
+SCRIPT_ENV= \
+       AWK=${TOOL_AWK:Q} \
+       SED=${TOOL_SED:Q}
+
+CLEANFILES+=   siglist.out siglist.out.tmp
+# two steps to prevent the creation of a bogus siglist.out
+siglist.out: config.h sh.h siglist.in siglist.sh
+       ${_MKTARGET_CREATE}
+       ${SCRIPT_ENV} \
+       ${HOST_SH} $(.CURDIR)/siglist.sh "$(CC) -E $(CPPFLAGS) $(DEFS) -I. -I$(.CURDIR)" < $(.CURDIR)/siglist.in > siglist.out.tmp \
+           && mv siglist.out.tmp siglist.out
+
+# two steps to prevent the creation of a bogus emacs.out
+CLEANFILES+=   emacs.out emacs.out.tmp
+emacs.out: emacs.c
+       ${_MKTARGET_CREATE}
+       ${SCRIPT_ENV} \
+       ${HOST_SH} $(.CURDIR)/emacs-gen.sh $(.CURDIR)/emacs.c > emacs.out.tmp \
+           && mv emacs.out.tmp emacs.out
+
+CLEANFILES+=   ksh.1 ksh.1.tmp
+ksh.1: ksh.Man mkman
+       ${_MKTARGET_CREATE}
+       ${SCRIPT_ENV} \
+       ${HOST_SH} $(.CURDIR)/mkman ksh $(.CURDIR)/ksh.Man >ksh.1.tmp \
+           && mv ksh.1.tmp ksh.1
+
+.include <bsd.prog.mk>
diff --git a/bin/ksh/alloc.c b/bin/ksh/alloc.c
new file mode 100644 (file)
index 0000000..31f2330
--- /dev/null
@@ -0,0 +1,127 @@
+/*     $NetBSD: alloc.c,v 1.10 2007/12/12 22:55:42 lukem Exp $ */
+
+/*
+ * Copyright (c) 2002 Marc Espie.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT 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 OPENBSD
+ * PROJECT 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.
+ */
+
+/*
+ * area-based allocation built on malloc/free
+ */
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: alloc.c,v 1.10 2007/12/12 22:55:42 lukem Exp $");
+
+#include "sh.h"
+
+struct link {
+       struct link *prev;
+       struct link *next;
+};
+
+Area *
+ainit(Area *ap)
+{
+       ap->freelist = NULL;
+       return ap;
+}
+
+void
+afreeall(Area *ap)
+{
+       struct link *l, *l2;
+
+       for (l = ap->freelist; l != NULL; l = l2) {
+               l2 = l->next;
+               free(l);
+       }
+       ap->freelist = NULL;
+}
+
+#define L2P(l) ( (void *)(((char *)(l)) + sizeof(struct link)) )
+#define P2L(p) ( (struct link *)(((char *)(p)) - sizeof(struct link)) )
+
+/* coverity[+alloc] */
+void *
+alloc(size_t size, Area *ap)
+{
+       struct link *l;
+
+       l = malloc(sizeof(struct link) + size);
+       if (l == NULL)
+               internal_errorf(1, "unable to allocate memory");
+       l->next = ap->freelist;
+       l->prev = NULL;
+       if (ap->freelist)
+               ap->freelist->prev = l;
+       ap->freelist = l;
+
+       return L2P(l);
+}
+
+/* coverity[+alloc] */
+/* coverity[+free : arg-0] */
+void *
+aresize(void *ptr, size_t size, Area *ap)
+{
+       struct link *l, *l2, *lprev, *lnext;
+
+       if (ptr == NULL)
+               return alloc(size, ap);
+
+       l = P2L(ptr);
+       lprev = l->prev;
+       lnext = l->next;
+
+       l2 = realloc(l, sizeof(struct link) + size);
+       if (l2 == NULL)
+               internal_errorf(1, "unable to allocate memory");
+       if (lprev)
+               lprev->next = l2;
+       else
+               ap->freelist = l2;
+       if (lnext)
+               lnext->prev = l2;
+
+       return L2P(l2);
+}
+
+/* coverity[+free : arg-0] */
+void
+afree(void *ptr, Area *ap)
+{
+       struct link *l;
+
+       if (!ptr)
+               return;
+
+       l = P2L(ptr);
+
+       if (l->prev)
+               l->prev->next = l->next;
+       else
+               ap->freelist = l->next;
+       if (l->next)
+               l->next->prev = l->prev;
+
+       free(l);
+}
diff --git a/bin/ksh/c_ksh.c b/bin/ksh/c_ksh.c
new file mode 100644 (file)
index 0000000..5fff393
--- /dev/null
@@ -0,0 +1,1490 @@
+/*     $NetBSD: c_ksh.c,v 1.18 2011/10/16 17:12:11 joerg Exp $ */
+
+/*
+ * built-in Korn commands: c_*
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: c_ksh.c,v 1.18 2011/10/16 17:12:11 joerg Exp $");
+#endif
+
+#include "sh.h"
+#include "ksh_stat.h"
+#include <ctype.h>
+
+#ifdef __CYGWIN__
+#include <sys/cygwin.h>
+#endif /* __CYGWIN__ */
+
+int
+c_cd(wp)
+       char    **wp;
+{
+       int optc;
+       int physical = Flag(FPHYSICAL);
+       int cdnode;                     /* was a node from cdpath added in? */
+       int printpath = 0;              /* print where we cd'd? */
+       int rval;
+       struct tbl *pwd_s, *oldpwd_s;
+       XString xs;
+       char *xp;
+       char *dir, *try, *pwd;
+       int phys_path;
+       char *cdpath;
+       char *fdir = NULL;
+
+       while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != EOF)
+               switch (optc) {
+               case 'L':
+                       physical = 0;
+                       break;
+               case 'P':
+                       physical = 1;
+                       break;
+               case '?':
+                       return 1;
+               }
+       wp += builtin_opt.optind;
+
+       if (Flag(FRESTRICTED)) {
+               bi_errorf("restricted shell - can't cd");
+               return 1;
+       }
+
+       pwd_s = global("PWD");
+       oldpwd_s = global("OLDPWD");
+
+       if (!wp[0]) {
+               /* No arguments - go home */
+               if ((dir = str_val(global("HOME"))) == null) {
+                       bi_errorf("no home directory (HOME not set)");
+                       return 1;
+               }
+       } else if (!wp[1]) {
+               /* One argument: - or dir */
+               dir = wp[0];
+               if (strcmp(dir, "-") == 0) {
+                       dir = str_val(oldpwd_s);
+                       if (dir == null) {
+                               bi_errorf("no OLDPWD");
+                               return 1;
+                       }
+                       printpath++;
+               }
+       } else if (!wp[2]) {
+               /* Two arguments - substitute arg1 in PWD for arg2 */
+               int ilen, olen, nlen, elen;
+               char *cp;
+
+               if (!current_wd[0]) {
+                       bi_errorf("don't know current directory");
+                       return 1;
+               }
+               /* substitute arg1 for arg2 in current path.
+                * if the first substitution fails because the cd fails
+                * we could try to find another substitution. For now
+                * we don't
+                */
+               if ((cp = strstr(current_wd, wp[0])) == (char *) 0) {
+                       bi_errorf("bad substitution");
+                       return 1;
+               }
+               ilen = cp - current_wd;
+               olen = strlen(wp[0]);
+               nlen = strlen(wp[1]);
+               elen = strlen(current_wd + ilen + olen) + 1;
+               fdir = dir = alloc(ilen + nlen + elen, ATEMP);
+               memcpy(dir, current_wd, ilen);
+               memcpy(dir + ilen, wp[1], nlen);
+               memcpy(dir + ilen + nlen, current_wd + ilen + olen, elen);
+               printpath++;
+       } else {
+               bi_errorf("too many arguments");
+               return 1;
+       }
+
+       Xinit(xs, xp, PATH, ATEMP);
+       /* xp will have a bogus value after make_path() - set it to 0
+        * so that if it's used, it will cause a dump
+        */
+       xp = (char *) 0;
+
+       cdpath = str_val(global("CDPATH"));
+       do {
+               cdnode = make_path(current_wd, dir, &cdpath, &xs, &phys_path);
+#ifdef S_ISLNK
+               if (physical)
+                       rval = chdir(try = Xstring(xs, xp) + phys_path);
+               else
+#endif /* S_ISLNK */
+               {
+                       simplify_path(Xstring(xs, xp));
+                       rval = chdir(try = Xstring(xs, xp));
+               }
+       } while (rval < 0 && cdpath != (char *) 0);
+
+       if (rval < 0) {
+               if (cdnode)
+                       bi_errorf("%s: bad directory", dir);
+               else
+                       bi_errorf("%s - %s", try, strerror(errno));
+               if (fdir)
+                       afree(fdir, ATEMP);
+               return 1;
+       }
+
+       /* Clear out tracked aliases with relative paths */
+       flushcom(0);
+
+       /* Set OLDPWD (note: unsetting OLDPWD does not disable this
+        * setting in at&t ksh)
+        */
+       if (current_wd[0])
+               /* Ignore failure (happens if readonly or integer) */
+               setstr(oldpwd_s, current_wd, KSH_RETURN_ERROR);
+
+       if (!ISABSPATH(Xstring(xs, xp))) {
+#ifdef OS2
+               /* simplify_path() doesn't know about os/2's drive contexts,
+                * so it can't set current_wd when changing to a:foo.
+                * Handle this by calling getcwd()...
+                */
+               pwd = ksh_get_wd((char *) 0, 0);
+#else /* OS2 */
+               pwd = (char *) 0;
+#endif /* OS2 */
+       } else
+#ifdef S_ISLNK
+       if (!physical || !(pwd = get_phys_path(Xstring(xs, xp))))
+#endif /* S_ISLNK */
+               pwd = Xstring(xs, xp);
+
+       /* Set PWD */
+       if (pwd) {
+#ifdef __CYGWIN__
+               char ptmp[PATH];  /* larger than MAX_PATH */
+               cygwin_conv_to_full_posix_path(pwd, ptmp);
+#else /* __CYGWIN__ */
+               char *ptmp = pwd;
+#endif /* __CYGWIN__ */
+               set_current_wd(ptmp);
+               /* Ignore failure (happens if readonly or integer) */
+               setstr(pwd_s, ptmp, KSH_RETURN_ERROR);
+       } else {
+               set_current_wd(null);
+               pwd = Xstring(xs, xp);
+               /* XXX unset $PWD? */
+       }
+       if (printpath || cdnode)
+               shprintf("%s\n", pwd);
+
+       if (fdir)
+               afree(fdir, ATEMP);
+
+       return 0;
+}
+
+int
+c_pwd(wp)
+       char    **wp;
+{
+       int optc;
+       int physical = Flag(FPHYSICAL);
+       char *p, *freep = NULL;
+
+       while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != EOF)
+               switch (optc) {
+               case 'L':
+                       physical = 0;
+                       break;
+               case 'P':
+                       physical = 1;
+                       break;
+               case '?':
+                       return 1;
+               }
+       wp += builtin_opt.optind;
+
+       if (wp[0]) {
+               bi_errorf("too many arguments");
+               return 1;
+       }
+#ifdef S_ISLNK
+       p = current_wd[0] ? (physical ? get_phys_path(current_wd) : current_wd)
+                         : (char *) 0;
+#else /* S_ISLNK */
+       p = current_wd[0] ? current_wd : (char *) 0;
+#endif /* S_ISLNK */
+       if (p && eaccess(p, R_OK) < 0)
+               p = (char *) 0;
+       if (!p) {
+               freep = p = ksh_get_wd((char *) 0, 0);
+               if (!p) {
+                       bi_errorf("can't get current directory - %s",
+                               strerror(errno));
+                       return 1;
+               }
+       }
+       shprintf("%s\n", p);
+       if (freep)
+               afree(freep, ATEMP);
+       return 0;
+}
+
+int
+c_print(wp)
+       char **wp;
+{
+#define PO_NL          BIT(0)  /* print newline */
+#define PO_EXPAND      BIT(1)  /* expand backslash sequences */
+#define PO_PMINUSMINUS BIT(2)  /* print a -- argument */
+#define PO_HIST                BIT(3)  /* print to history instead of stdout */
+#define PO_COPROC      BIT(4)  /* printing to coprocess: block SIGPIPE */
+#define PO_FSLASH      BIT(5)  /* swap slash for backslash (for os2 ) */
+       int fd = 1;
+       int flags = PO_EXPAND|PO_NL;
+       char *s;
+       const char *emsg;
+       XString xs;
+       char *xp;
+
+       if (wp[0][0] == 'e') {  /* echo command */
+               int nflags = flags;
+
+               /* A compromise between sysV and BSD echo commands:
+                * escape sequences are enabled by default, and
+                * -n, -e and -E are recognized if they appear
+                * in arguments with no illegal options (ie, echo -nq
+                * will print -nq).
+                * Different from sysV echo since options are recognized,
+                * different from BSD echo since escape sequences are enabled
+                * by default.
+                */
+               wp += 1;
+               while ((s = *wp) && *s == '-' && s[1]) {
+                       while (*++s)
+                               if (*s == 'n')
+                                       nflags &= ~PO_NL;
+                               else if (*s == 'e')
+                                       nflags |= PO_EXPAND;
+                               else if (*s == 'E')
+                                       nflags &= ~PO_EXPAND;
+                               else
+                                       /* bad option: don't use nflags, print
+                                        * argument
+                                        */
+                                       break;
+                       if (*s)
+                               break;
+                       wp++;
+                       flags = nflags;
+               }
+       } else {
+               int optc;
+#if OS2
+               const char *options = "Rnpfrsu,"; /* added f flag */
+#else
+               const char *options = "Rnprsu,";
+#endif
+               while ((optc = ksh_getopt(wp, &builtin_opt, options)) != EOF)
+                       switch (optc) {
+                         case 'R': /* fake BSD echo command */
+                               flags |= PO_PMINUSMINUS;
+                               flags &= ~PO_EXPAND;
+                               options = "ne";
+                               break;
+                         case 'e':
+                               flags |= PO_EXPAND;
+                               break;
+#ifdef OS2
+                         case 'f':
+                               flags |= PO_FSLASH;
+                               break;
+#endif
+                         case 'n':
+                               flags &= ~PO_NL;
+                               break;
+#ifdef KSH
+                         case 'p':
+                               if ((fd = coproc_getfd(W_OK, &emsg)) < 0) {
+                                       bi_errorf("-p: %s", emsg);
+                                       return 1;
+                               }
+                               break;
+#endif /* KSH */
+                         case 'r':
+                               flags &= ~PO_EXPAND;
+                               break;
+                         case 's':
+                               flags |= PO_HIST;
+                               break;
+                         case 'u':
+                               if (!*(s = builtin_opt.optarg))
+                                       fd = 0;
+                               else if ((fd = check_fd(s, W_OK, &emsg)) < 0) {
+                                       bi_errorf("-u: %s: %s", s, emsg);
+                                       return 1;
+                               }
+                               break;
+                         case '?':
+                               return 1;
+                       }
+               if (!(builtin_opt.info & GI_MINUSMINUS)) {
+                       /* treat a lone - like -- */
+                       if (wp[builtin_opt.optind]
+                           && strcmp(wp[builtin_opt.optind], "-") == 0)
+                               builtin_opt.optind++;
+               } else if (flags & PO_PMINUSMINUS)
+                       builtin_opt.optind--;
+               wp += builtin_opt.optind;
+       }
+
+       Xinit(xs, xp, 128, ATEMP);
+
+       while (*wp != NULL) {
+               register int c;
+               s = *wp;
+               while ((c = *s++) != '\0') {
+                       Xcheck(xs, xp);
+#ifdef OS2
+                       if ((flags & PO_FSLASH) && c == '\\')
+                               if (*s == '\\')
+                                       *s++;
+                               else
+                                       c = '/';
+#endif /* OS2 */
+                       if ((flags & PO_EXPAND) && c == '\\') {
+                               int i;
+
+                               switch ((c = *s++)) {
+                               /* Oddly enough, \007 seems more portable than
+                                * \a (due to HP-UX cc, Ultrix cc, old pcc's,
+                                * etc.).
+                                */
+                               case 'a': c = '\007'; break;
+                               case 'b': c = '\b'; break;
+                               case 'c': flags &= ~PO_NL;
+                                         continue; /* AT&T brain damage */
+                               case 'f': c = '\f'; break;
+                               case 'n': c = '\n'; break;
+                               case 'r': c = '\r'; break;
+                               case 't': c = '\t'; break;
+                               case 'v': c = 0x0B; break;
+                               case '0':
+                                       /* Look for an octal number: can have
+                                        * three digits (not counting the
+                                        * leading 0).  Truly burnt.
+                                        */
+                                       c = 0;
+                                       for (i = 0; i < 3; i++) {
+                                               if (*s >= '0' && *s <= '7')
+                                                       c = c*8 + *s++ - '0';
+                                               else
+                                                       break;
+                                       }
+                                       break;
+                               case '\0': s--; c = '\\'; break;
+                               case '\\': break;
+                               default:
+                                       Xput(xs, xp, '\\');
+                               }
+                       }
+                       Xput(xs, xp, c);
+               }
+               if (*++wp != NULL)
+                       Xput(xs, xp, ' ');
+       }
+       if (flags & PO_NL)
+               Xput(xs, xp, '\n');
+
+       if (flags & PO_HIST) {
+               Xput(xs, xp, '\0');
+               source->line++;
+               histsave(source->line, Xstring(xs, xp), 1);
+               Xfree(xs, xp);
+       } else {
+               int n, len = Xlength(xs, xp);
+               int UNINITIALIZED(opipe);
+#ifdef KSH
+
+               /* Ensure we aren't killed by a SIGPIPE while writing to
+                * a coprocess.  at&t ksh doesn't seem to do this (seems
+                * to just check that the co-process is alive, which is
+                * not enough).
+                */
+               if (coproc.write >= 0 && coproc.write == fd) {
+                       flags |= PO_COPROC;
+                       opipe = block_pipe();
+               }
+#endif /* KSH */
+               for (s = Xstring(xs, xp); len > 0; ) {
+                       n = write(fd, s, len);
+                       if (n < 0) {
+#ifdef KSH
+                               if (flags & PO_COPROC)
+                                       restore_pipe(opipe);
+#endif /* KSH */
+                               if (errno == EINTR) {
+                                       /* allow user to ^C out */
+                                       intrcheck();
+#ifdef KSH
+                                       if (flags & PO_COPROC)
+                                               opipe = block_pipe();
+#endif /* KSH */
+                                       continue;
+                               }
+#ifdef KSH
+                               /* This doesn't really make sense - could
+                                * break scripts (print -p generates
+                                * error message).
+                               *if (errno == EPIPE)
+                               *       coproc_write_close(fd);
+                                */
+#endif /* KSH */
+                               return 1;
+                       }
+                       s += n;
+                       len -= n;
+               }
+#ifdef KSH
+               if (flags & PO_COPROC)
+                       restore_pipe(opipe);
+#endif /* KSH */
+       }
+
+       return 0;
+}
+
+int
+c_whence(wp)
+       char **wp;
+{
+       struct tbl *tp;
+       char *id;
+       int pflag = 0, vflag = 0, Vflag = 0;
+       int ret = 0;
+       int optc;
+       int iam_whence = wp[0][0] == 'w';
+       int fcflags;
+       const char *options = iam_whence ? "pv" : "pvV";
+
+       while ((optc = ksh_getopt(wp, &builtin_opt, options)) != EOF)
+               switch (optc) {
+               case 'p':
+                       pflag = 1;
+                       break;
+               case 'v':
+                       vflag = 1;
+                       break;
+               case 'V':
+                       Vflag = 1;
+                       break;
+               case '?':
+                       return 1;
+               }
+       wp += builtin_opt.optind;
+
+
+       fcflags = FC_BI | FC_PATH | FC_FUNC;
+       if (!iam_whence) {
+               /* Note that -p on its own is deal with in comexec() */
+               if (pflag)
+                       fcflags |= FC_DEFPATH;
+               /* Convert command options to whence options - note that
+                * command -pV uses a different path search than whence -v
+                * or whence -pv.  This should be considered a feature.
+                */
+               vflag = Vflag;
+       }
+       if (pflag)
+               fcflags &= ~(FC_BI | FC_FUNC);
+
+       while ((vflag || ret == 0) && (id = *wp++) != NULL) {
+               tp = NULL;
+               if ((iam_whence || vflag) && !pflag)
+                       tp = tsearch(&keywords, id, hash(id));
+               if (!tp && !pflag) {
+                       tp = tsearch(&aliases, id, hash(id));
+                       if (tp && !(tp->flag & ISSET))
+                               tp = NULL;
+               }
+               if (!tp)
+                       tp = findcom(id, fcflags);
+               if (vflag || (tp->type != CALIAS && tp->type != CEXEC
+                             && tp->type != CTALIAS))
+                       shprintf("%s", id);
+               switch (tp->type) {
+                 case CKEYWD:
+                       if (vflag)
+                               shprintf(" is a reserved word");
+                       break;
+                 case CALIAS:
+                       if (vflag)
+                               shprintf(" is an %salias for ",
+                                       (tp->flag & EXPORT) ? "exported "
+                                                           : null);
+                       if (!iam_whence && !vflag)
+                               shprintf("alias %s=", id);
+                       print_value_quoted(tp->val.s);
+                       break;
+                 case CFUNC:
+                       if (vflag) {
+                               shprintf(" is a");
+                               if (tp->flag & EXPORT)
+                                       shprintf("n exported");
+                               if (tp->flag & TRACE)
+                                       shprintf(" traced");
+                               if (!(tp->flag & ISSET)) {
+                                       shprintf(" undefined");
+                                       if (tp->u.fpath)
+                                               shprintf(" (autoload from %s)",
+                                                       tp->u.fpath);
+                               }
+                               shprintf(" function");
+                       }
+                       break;
+                 case CSHELL:
+                       if (vflag)
+                               shprintf(" is a%s shell builtin",
+                                   (tp->flag & SPEC_BI) ? " special" : null);
+                       break;
+                 case CTALIAS:
+                 case CEXEC:
+                       if (tp->flag & ISSET) {
+                               if (vflag) {
+                                       shprintf(" is ");
+                                       if (tp->type == CTALIAS)
+                                               shprintf(
+                                                   "a tracked %salias for ",
+                                                       (tp->flag & EXPORT) ?
+                                                               "exported "
+                                                             : null);
+                               }
+                               shprintf("%s", tp->val.s);
+                       } else {
+                               if (vflag)
+                                       shprintf(" not found");
+                               ret = 1;
+                       }
+                       break;
+                 default:
+                       shprintf("%s is *GOK*", id);
+                       break;
+               }
+               if (vflag || !ret)
+                       shprintf("%s", newline);
+       }
+       return ret;
+}
+
+/* Deal with command -vV - command -p dealt with in comexec() */
+int
+c_command(wp)
+       char **wp;
+{
+       /* Let c_whence do the work.  Note that c_command() must be
+        * a distinct function from c_whence() (tested in comexec()).
+        */
+       return c_whence(wp);
+}
+
+/* typeset, export, and readonly */
+int
+c_typeset(wp)
+       char **wp;
+{
+       struct block *l = e->loc;
+       struct tbl *vp, **p;
+       Tflag fset = 0, fclr = 0;
+       int thing = 0, func = 0, localv = 0;
+       const char *options = "L#R#UZ#fi#lprtux";       /* see comment below */
+       char *fieldstr, *basestr;
+       int field, base;
+       int optc;
+       Tflag flag;
+       int pflag = 0;
+
+       switch (**wp) {
+         case 'e':             /* export */
+               fset |= EXPORT;
+               options = "p";
+               break;
+         case 'r':             /* readonly */
+               fset |= RDONLY;
+               options = "p";
+               break;
+         case 's':             /* set */
+               /* called with 'typeset -' */
+               break;
+         case 't':             /* typeset */
+               localv = 1;
+               break;
+       }
+
+       fieldstr = basestr = (char *) 0;
+       builtin_opt.flags |= GF_PLUSOPT;
+       /* at&t ksh seems to have 0-9 as options, which are multiplied
+        * to get a number that is used with -L, -R, -Z or -i (eg, -1R2
+        * sets right justify in a field of 12).  This allows options
+        * to be grouped in an order (eg, -Lu12), but disallows -i8 -L3 and
+        * does not allow the number to be specified as a separate argument
+        * Here, the number must follow the RLZi option, but is optional
+        * (see the # kludge in ksh_getopt()).
+        */
+       while ((optc = ksh_getopt(wp, &builtin_opt, options)) != EOF) {
+               flag = 0;
+               switch (optc) {
+                 case 'L':
+                       flag = LJUST;
+                       fieldstr = builtin_opt.optarg;
+                       break;
+                 case 'R':
+                       flag = RJUST;
+                       fieldstr = builtin_opt.optarg;
+                       break;
+                 case 'U':
+                       /* at&t ksh uses u, but this conflicts with
+                        * upper/lower case.  If this option is changed,
+                        * need to change the -U below as well
+                        */
+                       flag = INT_U;
+                       break;
+                 case 'Z':
+                       flag = ZEROFIL;
+                       fieldstr = builtin_opt.optarg;
+                       break;
+                 case 'f':
+                       func = 1;
+                       break;
+                 case 'i':
+                       flag = INTEGER;
+                       basestr = builtin_opt.optarg;
+                       break;
+                 case 'l':
+                       flag = LCASEV;
+                       break;
+                 case 'p': /* posix export/readonly -p flag.
+                            * typeset -p is the same as typeset (in pdksh);
+                            * here for compatibility with ksh93.
+                            */
+                       pflag = 1;
+                       break;
+                 case 'r':
+                       flag = RDONLY;
+                       break;
+                 case 't':
+                       flag = TRACE;
+                       break;
+                 case 'u':
+                       flag = UCASEV_AL;       /* upper case / autoload */
+                       break;
+                 case 'x':
+                       flag = EXPORT;
+                       break;
+                 case '?':
+                       return 1;
+               }
+               if (builtin_opt.info & GI_PLUS) {
+                       fclr |= flag;
+                       fset &= ~flag;
+                       thing = '+';
+               } else {
+                       fset |= flag;
+                       fclr &= ~flag;
+                       thing = '-';
+               }
+       }
+
+       field = 0;
+       if (fieldstr && !bi_getn(fieldstr, &field))
+               return 1;
+       base = 0;
+       if (basestr && !bi_getn(basestr, &base))
+               return 1;
+
+       if (!(builtin_opt.info & GI_MINUSMINUS) && wp[builtin_opt.optind]
+           && (wp[builtin_opt.optind][0] == '-'
+               || wp[builtin_opt.optind][0] == '+')
+           && wp[builtin_opt.optind][1] == '\0')
+       {
+               thing = wp[builtin_opt.optind][0];
+               builtin_opt.optind++;
+       }
+
+       if (func && ((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT))) {
+               bi_errorf("only -t, -u and -x options may be used with -f");
+               return 1;
+       }
+       if (wp[builtin_opt.optind]) {
+               /* Take care of exclusions.
+                * At this point, flags in fset are cleared in fclr and vise
+                * versa.  This property should be preserved.
+                */
+               if (fset & LCASEV)      /* LCASEV has priority over UCASEV_AL */
+                       fset &= ~UCASEV_AL;
+               if (fset & LJUST)       /* LJUST has priority over RJUST */
+                       fset &= ~RJUST;
+               if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) { /* -Z implies -ZR */
+                       fset |= RJUST;
+                       fclr &= ~RJUST;
+               }
+               /* Setting these attributes clears the others, unless they
+                * are also set in this command
+                */
+               if (fset & (LJUST|RJUST|ZEROFIL|UCASEV_AL|LCASEV|INTEGER
+                           |INT_U|INT_L))
+                       fclr |= ~fset &
+                               (LJUST|RJUST|ZEROFIL|UCASEV_AL|LCASEV|INTEGER
+                                |INT_U|INT_L);
+       }
+
+       /* set variables and attributes */
+       if (wp[builtin_opt.optind]) {
+               int i;
+               int rval = 0;
+               struct tbl *f;
+
+               if (localv && !func)
+                       fset |= LOCAL;
+               for (i = builtin_opt.optind; wp[i]; i++) {
+                       if (func) {
+                               f = findfunc(wp[i], hash(wp[i]),
+                                            (fset&UCASEV_AL) ? TRUE : FALSE);
+                               if (!f) {
+                                       /* at&t ksh does ++rval: bogus */
+                                       rval = 1;
+                                       continue;
+                               }
+                               if (fset | fclr) {
+                                       f->flag |= fset;
+                                       f->flag &= ~fclr;
+                               } else
+                                       fptreef(shl_stdout, 0,
+                                               f->flag & FKSH ?
+                                                   "function %s %T\n"
+                                                   : "%s() %T\n"
+                                               ,
+                                               wp[i], f->val.t);
+                       } else if (!typeset(wp[i], fset, fclr, field, base)) {
+                               bi_errorf("%s: not identifier", wp[i]);
+                               return 1;
+                       }
+               }
+               return rval;
+       }
+
+       /* list variables and attributes */
+       flag = fset | fclr; /* no difference at this point.. */
+       if (func) {
+           for (l = e->loc; l; l = l->next) {
+               for (p = tsort(&l->funs); (vp = *p++); ) {
+                   if (flag && (vp->flag & flag) == 0)
+                           continue;
+                   if (thing == '-')
+                       fptreef(shl_stdout, 0, vp->flag & FKSH ?
+                                                   "function %s %T\n"
+                                                   : "%s() %T\n",
+                               vp->name, vp->val.t);
+                   else
+                       shprintf("%s\n", vp->name);
+               }
+           }
+       } else {
+           for (l = e->loc; l; l = l->next) {
+               for (p = tsort(&l->vars); (vp = *p++); ) {
+                   struct tbl *tvp;
+                   int any_set = 0;
+                   /*
+                    * See if the parameter is set (for arrays, if any
+                    * element is set).
+                    */
+                   for (tvp = vp; tvp; tvp = tvp->u.array)
+                       if (tvp->flag & ISSET) {
+                           any_set = 1;
+                           break;
+                       }
+                   /*
+                    * Check attributes - note that all array elements
+                    * have (should have?) the same attributes, so checking
+                    * the first is sufficient.
+                    *
+                    * Report an unset param only if the user has
+                    * explicitly given it some attribute (like export);
+                    * otherwise, after "echo $FOO", we would report FOO...
+                    */
+                   if (!any_set && !(vp->flag & USERATTRIB))
+                       continue;
+                   if (flag && (vp->flag & flag) == 0)
+                       continue;
+                   for (; vp; vp = vp->u.array) {
+                       /* Ignore array elements that aren't set unless there
+                        * are no set elements, in which case the first is
+                        * reported on
+                        */
+                       if ((vp->flag&ARRAY) && any_set && !(vp->flag & ISSET))
+                           continue;
+                       /* no arguments */
+                       if (thing == 0 && flag == 0) {
+                           /* at&t ksh prints things like export, integer,
+                            * leftadj, zerofill, etc., but POSIX says must
+                            * be suitable for re-entry...
+                            */
+                           shprintf("typeset ");
+                           if ((vp->flag&INTEGER))
+                               shprintf("-i ");
+                           if ((vp->flag&EXPORT))
+                               shprintf("-x ");
+                           if ((vp->flag&RDONLY))
+                               shprintf("-r ");
+                           if ((vp->flag&TRACE))
+                               shprintf("-t ");
+                           if ((vp->flag&LJUST))
+                               shprintf("-L%d ", vp->u2.field);
+                           if ((vp->flag&RJUST))
+                               shprintf("-R%d ", vp->u2.field);
+                           if ((vp->flag&ZEROFIL))
+                               shprintf("-Z ");
+                           if ((vp->flag&LCASEV))
+                               shprintf("-l ");
+                           if ((vp->flag&UCASEV_AL))
+                               shprintf("-u ");
+                           if ((vp->flag&INT_U))
+                               shprintf("-U ");
+                           shprintf("%s\n", vp->name);
+                           if (vp->flag&ARRAY)
+                               break;
+                       } else {
+                           if (pflag)
+                               shprintf("%s ",
+                                   (flag & EXPORT) ?  "export" : "readonly");
+                           if ((vp->flag&ARRAY) && any_set)
+                               shprintf("%s[%d]", vp->name, vp->index);
+                           else
+                               shprintf("%s", vp->name);
+                           if (thing == '-' && (vp->flag&ISSET)) {
+                               char *s = str_val(vp);
+
+                               shprintf("=");
+                               /* at&t ksh can't have justified integers.. */
+                               if ((vp->flag & (INTEGER|LJUST|RJUST))
+                                                               == INTEGER)
+                                   shprintf("%s", s);
+                               else
+                                   print_value_quoted(s);
+                           }
+                           shprintf("%s", newline);
+                       }
+                       /* Only report first `element' of an array with
+                        * no set elements.
+                        */
+                       if (!any_set)
+                           break;
+                   }
+               }
+           }
+       }
+       return 0;
+}
+       
+int
+c_alias(wp)
+       char **wp;
+{
+       struct table *t = &aliases;
+       int rv = 0, rflag = 0, tflag, Uflag = 0, pflag = 0;
+       int prefix = 0;
+       Tflag xflag = 0;
+       int optc;
+
+       builtin_opt.flags |= GF_PLUSOPT;
+       while ((optc = ksh_getopt(wp, &builtin_opt, "dprtUx")) != EOF) {
+               prefix = builtin_opt.info & GI_PLUS ? '+' : '-';
+               switch (optc) {
+                 case 'd':
+                       t = &homedirs;
+                       break;
+                 case 'p':
+                       pflag = 1;
+                       break;
+                 case 'r':
+                       rflag = 1;
+                       break;
+                 case 't':
+                       t = &taliases;
+                       break;
+                 case 'U': /* kludge for tracked alias initialization
+                            * (don't do a path search, just make an entry)
+                            */
+                       Uflag = 1;
+                       break;
+                 case 'x':
+                       xflag = EXPORT;
+                       break;
+                 case '?':
+                       return 1;
+               }
+       }
+       wp += builtin_opt.optind;
+
+       if (!(builtin_opt.info & GI_MINUSMINUS) && *wp
+           && (wp[0][0] == '-' || wp[0][0] == '+') && wp[0][1] == '\0')
+       {
+               prefix = wp[0][0];
+               wp++;
+       }
+
+       tflag = t == &taliases;
+
+       /* "hash -r" means reset all the tracked aliases.. */
+       if (rflag) {
+               static const char *const args[] = {
+                           "unalias", "-ta", (const char *) 0
+                       };
+
+               if (!tflag || *wp) {
+                       shprintf(
+           "alias: -r flag can only be used with -t and without arguments\n");
+                       return 1;
+               }
+               ksh_getopt_reset(&builtin_opt, GF_ERROR);
+               return c_unalias((char **)__UNCONST(args));
+       }
+
+       
+       if (*wp == NULL) {
+               struct tbl *ap, **p;
+
+               for (p = tsort(t); (ap = *p++) != NULL; )
+                       if ((ap->flag & (ISSET|xflag)) == (ISSET|xflag)) {
+                               if (pflag)
+                                       shf_puts("alias ", shl_stdout);
+                               shf_puts(ap->name, shl_stdout);
+                               if (prefix != '+') {
+                                       shf_putc('=', shl_stdout);
+                                       print_value_quoted(ap->val.s);
+                               }
+                               shprintf("%s", newline);
+                       }
+       }
+
+       for (; *wp != NULL; wp++) {
+               char *alias = *wp;
+               char *val = strchr(alias, '=');
+               char *newval;
+               struct tbl *ap;
+               int h;
+
+               if (val)
+                       alias = str_nsave(alias, val++ - alias, ATEMP);
+               h = hash(alias);
+               if (val == NULL && !tflag && !xflag) {
+                       ap = tsearch(t, alias, h);
+                       if (ap != NULL && (ap->flag&ISSET)) {
+                               if (pflag)
+                                       shf_puts("alias ", shl_stdout);
+                               shf_puts(ap->name, shl_stdout);
+                               if (prefix != '+') {
+                                       shf_putc('=', shl_stdout);
+                                       print_value_quoted(ap->val.s);
+                               }
+                               shprintf("%s", newline);
+                       } else {
+                               shprintf("%s alias not found\n", alias);
+                               rv = 1;
+                       }
+                       continue;
+               }
+               ap = tenter(t, alias, h);
+               ap->type = tflag ? CTALIAS : CALIAS;
+               /* Are we setting the value or just some flags? */
+               if ((val && !tflag) || (!val && tflag && !Uflag)) {
+                       if (ap->flag&ALLOC) {
+                               ap->flag &= ~(ALLOC|ISSET);
+                               afree((void*)ap->val.s, APERM);
+                       }
+                       /* ignore values for -t (at&t ksh does this) */
+                       newval = tflag ? search(alias, path, X_OK, (int *) 0)
+                                       : val;
+                       if (newval) {
+                               ap->val.s = str_save(newval, APERM);
+                               ap->flag |= ALLOC|ISSET;
+                       } else
+                               ap->flag &= ~ISSET;
+               }
+               ap->flag |= DEFINED;
+               if (prefix == '+')
+                       ap->flag &= ~xflag;
+               else
+                       ap->flag |= xflag;
+               if (val)
+                       afree(alias, ATEMP);
+       }
+
+       return rv;
+}
+
+int
+c_unalias(wp)
+       char **wp;
+{
+       register struct table *t = &aliases;
+       register struct tbl *ap;
+       int rv = 0, all = 0;
+       int optc;
+
+       while ((optc = ksh_getopt(wp, &builtin_opt, "adt")) != EOF)
+               switch (optc) {
+                 case 'a':
+                       all = 1;
+                       break;
+                 case 'd':
+                       t = &homedirs;
+                       break;
+                 case 't':
+                       t = &taliases;
+                       break;
+                 case '?':
+                       return 1;
+               }
+       wp += builtin_opt.optind;
+
+       for (; *wp != NULL; wp++) {
+               ap = tsearch(t, *wp, hash(*wp));
+               if (ap == NULL) {
+                       rv = 1; /* POSIX */
+                       continue;
+               }
+               if (ap->flag&ALLOC) {
+                       ap->flag &= ~(ALLOC|ISSET);
+                       afree((void*)ap->val.s, APERM);
+               }
+               ap->flag &= ~(DEFINED|ISSET|EXPORT);
+       }
+
+       if (all) {
+               struct tstate ts;
+
+               for (twalk(&ts, t); (ap = tnext(&ts)); ) {
+                       if (ap->flag&ALLOC) {
+                               ap->flag &= ~(ALLOC|ISSET);
+                               afree((void*)ap->val.s, APERM);
+                       }
+                       ap->flag &= ~(DEFINED|ISSET|EXPORT);
+               }
+       }
+
+       return rv;
+}
+
+#ifdef KSH
+int
+c_let(wp)
+       char **wp;
+{
+       int rv = 1;
+       long val;
+
+       if (wp[1] == (char *) 0) /* at&t ksh does this */
+               bi_errorf("no arguments");
+       else
+               for (wp++; *wp; wp++)
+                       if (!evaluate(*wp, &val, KSH_RETURN_ERROR)) {
+                               rv = 2; /* distinguish error from zero result */
+                               break;
+                       } else
+                               rv = val == 0;
+       return rv;
+}
+#endif /* KSH */
+
+int
+c_jobs(wp)
+       char **wp;
+{
+       int optc;
+       int flag = 0;
+       int nflag = 0;
+       int rv = 0;
+
+       while ((optc = ksh_getopt(wp, &builtin_opt, "lpnz")) != EOF)
+               switch (optc) {
+                 case 'l':
+                       flag = 1;
+                       break;
+                 case 'p':
+                       flag = 2;
+                       break;
+                 case 'n':
+                       nflag = 1;
+                       break;
+                 case 'z':     /* debugging: print zombies */
+                       nflag = -1;
+                       break;
+                 case '?':
+                       return 1;
+               }
+       wp += builtin_opt.optind;
+       if (!*wp) {
+               if (j_jobs((char *) 0, flag, nflag))
+                       rv = 1;
+       } else {
+               for (; *wp; wp++)
+                       if (j_jobs(*wp, flag, nflag))
+                               rv = 1;
+       }
+       return rv;
+}
+
+#ifdef JOBS
+int
+c_fgbg(wp)
+       char **wp;
+{
+       int bg = strcmp(*wp, "bg") == 0;
+       int UNINITIALIZED(rv);
+
+       if (!Flag(FMONITOR)) {
+               bi_errorf("job control not enabled");
+               return 1;
+       }
+       if (ksh_getopt(wp, &builtin_opt, null) == '?')
+               return 1;
+       wp += builtin_opt.optind;
+       if (*wp)
+               for (; *wp; wp++)
+                       rv = j_resume(*wp, bg);
+       else
+               rv = j_resume("%%", bg);
+       /* POSIX says fg shall return 0 (unless an error occurs).
+        * at&t ksh returns the exit value of the job...
+        */
+       return (bg || Flag(FPOSIX)) ? 0 : rv;
+}
+#endif
+
+struct kill_info {
+       int num_width;
+       int name_width;
+};
+static char *kill_fmt_entry ARGS((void *arg, int i, char *buf, int buflen));
+
+/* format a single kill item */
+static char *
+kill_fmt_entry(arg, i, buf, buflen)
+       void *arg;
+       int i;
+       char *buf;
+       int buflen;
+{
+       struct kill_info *ki = (struct kill_info *) arg;
+
+       i++;
+       if (sigtraps[i].name)
+               shf_snprintf(buf, buflen, "%*d %*s %s",
+                       ki->num_width, i,
+                       ki->name_width, sigtraps[i].name,
+                       sigtraps[i].mess);
+       else
+               shf_snprintf(buf, buflen, "%*d %*d %s",
+                       ki->num_width, i,
+                       ki->name_width, sigtraps[i].signal,
+                       sigtraps[i].mess);
+       return buf;
+}
+
+
+int
+c_kill(wp)
+       char **wp;
+{
+       Trap *t = (Trap *) 0;
+       char *p;
+       int lflag = 0;
+       int i, n, rv, sig;
+
+       /* assume old style options if -digits or -UPPERCASE */
+       if ((p = wp[1]) && *p == '-'
+           && (digit(p[1]) || isupper((unsigned char)p[1]))) {
+               if (!(t = gettrap(p + 1, TRUE))) {
+                       bi_errorf("bad signal `%s'", p + 1);
+                       return 1;
+               }
+               i = (wp[2] && strcmp(wp[2], "--") == 0) ? 3 : 2;
+       } else {
+               int optc;
+
+               while ((optc = ksh_getopt(wp, &builtin_opt, "ls:")) != EOF)
+                       switch (optc) {
+                         case 'l':
+                               lflag = 1;
+                               break;
+                         case 's':
+                               if (!(t = gettrap(builtin_opt.optarg, TRUE))) {
+                                       bi_errorf("bad signal `%s'",
+                                               builtin_opt.optarg);
+                                       return 1;
+                               }
+                               break;
+                         case '?':
+                               return 1;
+                       }
+               i = builtin_opt.optind;
+       }
+       if ((lflag && t) || (!wp[i] && !lflag)) {
+               shf_fprintf(shl_out,
+"usage: kill [ -s signame | -signum | -signame ] {pid|job}...\n\
+       kill -l [exit_status]\n"
+                       );
+               bi_errorf("%s", null);
+               return 1;
+       }
+
+       if (lflag) {
+               if (wp[i]) {
+                       for (; wp[i]; i++) {
+                               if (!bi_getn(wp[i], &n))
+                                       return 1;
+                               if (n > 128 && n < 128 + SIGNALS)
+                                       n -= 128;
+                               if (n > 0 && n < SIGNALS && sigtraps[n].name)
+                                       shprintf("%s\n", sigtraps[n].name);
+                               else
+                                       shprintf("%d\n", n);
+                       }
+               } else if (Flag(FPOSIX)) {
+                       p = null;
+                       for (i = 1; i < SIGNALS; i++, p = space)
+                               if (sigtraps[i].name)
+                                       shprintf("%s%s", p, sigtraps[i].name);
+                       shprintf("%s", newline);
+               } else {
+                       int w, si;
+                       int mess_width;
+                       struct kill_info ki;
+
+                       for (si = SIGNALS, ki.num_width = 1; si >= 10; si /= 10)
+                               ki.num_width++;
+                       ki.name_width = mess_width = 0;
+                       for (si = 0; si < SIGNALS; si++) {
+                               w = sigtraps[si].name ?
+                                   (int)strlen(sigtraps[si].name) :
+                                   ki.num_width;
+                               if (w > ki.name_width)
+                                       ki.name_width = w;
+                               w = strlen(sigtraps[si].mess);
+                               if (w > mess_width)
+                                       mess_width = w;
+                       }
+
+                       print_columns(shl_stdout, SIGNALS - 1,
+                               kill_fmt_entry, (void *) &ki,
+                               ki.num_width + ki.name_width + mess_width + 3, 1);
+               }
+               return 0;
+       }
+       rv = 0;
+       sig = t ? t->signal : SIGTERM;
+       for (; (p = wp[i]); i++) {
+               if (*p == '%') {
+                       if (j_kill(p, sig))
+                               rv = 1;
+               } else if (!getn(p, &n)) {
+                       bi_errorf("%s: arguments must be jobs or process IDs",
+                               p);
+                       rv = 1;
+               } else {
+                       /* use killpg if < -1 since -1 does special things for
+                        * some non-killpg-endowed kills
+                        */
+                       if ((n < -1 ? killpg(-n, sig) : kill(n, sig)) < 0) {
+                               bi_errorf("%s: %s", p, strerror(errno));
+                               rv = 1;
+                       }
+               }
+       }
+       return rv;
+}
+
+void
+getopts_reset(val)
+       int val;
+{
+       if (val >= 1) {
+               ksh_getopt_reset(&user_opt,
+                       GF_NONAME | (Flag(FPOSIX) ? 0 : GF_PLUSOPT));
+               user_opt.optind = user_opt.uoptind = val;
+       }
+}
+
+int
+c_getopts(wp)
+       char **wp;
+{
+       int     argc;
+       const char *options;
+       const char *var;
+       int     optc;
+       int     ret;
+       char    buf[3];
+       struct tbl *vq, *voptarg;
+
+       if (ksh_getopt(wp, &builtin_opt, null) == '?')
+               return 1;
+       wp += builtin_opt.optind;
+
+       options = *wp++;
+       if (!options) {
+               bi_errorf("missing options argument");
+               return 1;
+       }
+
+       var = *wp++;
+       if (!var) {
+               bi_errorf("missing name argument");
+               return 1;
+       }
+       if (!*var || *skip_varname(var, TRUE)) {
+               bi_errorf("%s: is not an identifier", var);
+               return 1;
+       }
+
+       if (e->loc->next == (struct block *) 0) {
+               internal_errorf(0, "c_getopts: no argv");
+               return 1;
+       }
+       /* Which arguments are we parsing... */
+       if (*wp == (char *) 0)
+               wp = e->loc->next->argv;
+       else
+               *--wp = e->loc->next->argv[0];
+
+       /* Check that our saved state won't cause a core dump... */
+       for (argc = 0; wp[argc]; argc++)
+               ;
+       if (user_opt.optind > argc
+           || (user_opt.p != 0
+               && user_opt.p > strlen(wp[user_opt.optind - 1])))
+       {
+             bi_errorf("arguments changed since last call");
+             return 1;
+       }
+
+       user_opt.optarg = (char *) 0;
+       optc = ksh_getopt(wp, &user_opt, options);
+
+       if (optc >= 0 && optc != '?' && (user_opt.info & GI_PLUS)) {
+               buf[0] = '+';
+               buf[1] = optc;
+               buf[2] = '\0';
+       } else {
+               /* POSIX says var is set to ? at end-of-options, at&t ksh
+                * sets it to null - we go with POSIX...
+                */
+               buf[0] = optc < 0 ? '?' : optc;
+               buf[1] = '\0';
+       }
+
+       /* at&t ksh does not change OPTIND if it was an unknown option.
+        * Scripts counting on this are prone to break... (ie, don't count
+        * on this staying).
+        */
+       if (optc != '?') {
+               user_opt.uoptind = user_opt.optind;
+       }
+
+       voptarg = global("OPTARG");
+       voptarg->flag &= ~RDONLY;       /* at&t ksh clears ro and int */
+       /* Paranoia: ensure no bizarre results. */
+       if (voptarg->flag & INTEGER)
+           typeset("OPTARG", 0, INTEGER, 0, 0);
+       if (user_opt.optarg == (char *) 0)
+               unset(voptarg, 0);
+       else
+               /* This can't fail (have cleared readonly/integer) */
+               setstr(voptarg, user_opt.optarg, KSH_RETURN_ERROR);
+
+       ret = 0;
+
+       vq = global(var);
+       /* Error message already printed (integer, readonly) */
+       if (!setstr(vq, buf, KSH_RETURN_ERROR))
+           ret = 1;
+       if (Flag(FEXPORT))
+               typeset(var, EXPORT, 0, 0, 0);
+
+       return optc < 0 ? 1 : ret;
+}
+
+#ifdef EMACS
+int
+c_bind(wp)
+       char **wp;
+{
+       int rv = 0, macro = 0, list = 0;
+       register char *cp;
+       int optc;
+
+       while ((optc = ksh_getopt(wp, &builtin_opt, "lm")) != EOF)
+               switch (optc) {
+                 case 'l':
+                       list = 1;
+                       break;
+                 case 'm':
+                       macro = 1;
+                       break;
+                 case '?':
+                       return 1;
+               }
+       wp += builtin_opt.optind;
+
+       if (*wp == NULL)        /* list all */
+               rv = x_bind(NULL, NULL, 0, list);
+
+       for (; *wp != NULL; wp++) {
+               cp = strchr(*wp, '=');
+               if (cp != NULL)
+                       *cp++ = '\0';
+               if (x_bind(*wp, cp, macro, 0))
+                       rv = 1;
+       }
+
+       return rv;
+}
+#endif
+
+/* A leading = means assignments before command are kept;
+ * a leading * means a POSIX special builtin;
+ * a leading + means a POSIX regular builtin
+ * (* and + should not be combined).
+ */
+const struct builtin kshbuiltins [] = {
+       {"+alias", c_alias},    /* no =: at&t manual wrong */
+       {"+cd", c_cd},
+       {"+command", c_command},
+       {"echo", c_print},
+       {"*=export", c_typeset},
+#ifdef HISTORY
+       {"+fc", c_fc},
+#endif /* HISTORY */
+       {"+getopts", c_getopts},
+       {"+jobs", c_jobs},
+       {"+kill", c_kill},
+#ifdef KSH
+       {"let", c_let},
+#endif /* KSH */
+       {"print", c_print},
+       {"pwd", c_pwd},
+       {"*=readonly", c_typeset},
+       {"=typeset", c_typeset},
+       {"+unalias", c_unalias},
+       {"whence", c_whence},
+#ifdef JOBS
+       {"+bg", c_fgbg},
+       {"+fg", c_fgbg},
+#endif
+#ifdef EMACS
+       {"bind", c_bind},
+#endif
+       {NULL, NULL}
+};
diff --git a/bin/ksh/c_sh.c b/bin/ksh/c_sh.c
new file mode 100644 (file)
index 0000000..ba3ae2b
--- /dev/null
@@ -0,0 +1,917 @@
+/*     $NetBSD: c_sh.c,v 1.14 2011/08/31 16:24:54 plunky Exp $ */
+
+/*
+ * built-in Bourne commands
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: c_sh.c,v 1.14 2011/08/31 16:24:54 plunky Exp $");
+#endif
+
+
+#include "sh.h"
+#include "ksh_stat.h"  /* umask() */
+#include "ksh_time.h"
+#include "ksh_times.h"
+
+static char *clocktos ARGS((clock_t t));
+
+
+/* :, false and true */
+int
+c_label(wp)
+       char **wp;
+{
+       return wp[0][0] == 'f' ? 1 : 0;
+}
+
+int
+c_shift(wp)
+       char **wp;
+{
+       register struct block *l = e->loc;
+       register int n;
+       long val;
+       char *arg;
+
+       if (ksh_getopt(wp, &builtin_opt, null) == '?')
+               return 1;
+       arg = wp[builtin_opt.optind];
+
+       if (arg) {
+               evaluate(arg, &val, KSH_UNWIND_ERROR);
+               n = val;
+       } else
+               n = 1;
+       if (n < 0) {
+               bi_errorf("%s: bad number", arg);
+               return (1);
+       }
+       if (l->argc < n) {
+               bi_errorf("nothing to shift");
+               return (1);
+       }
+       l->argv[n] = l->argv[0];
+       l->argv += n;
+       l->argc -= n;
+       return 0;
+}
+
+int
+c_umask(wp)
+       char **wp;
+{
+       register int i;
+       register char *cp;
+       int symbolic = 0;
+       int old_umask;
+       int optc;
+
+       while ((optc = ksh_getopt(wp, &builtin_opt, "S")) != EOF)
+               switch (optc) {
+                 case 'S':
+                       symbolic = 1;
+                       break;
+                 case '?':
+                       return 1;
+               }
+       cp = wp[builtin_opt.optind];
+       if (cp == NULL) {
+               old_umask = umask(0);
+               umask(old_umask);
+               if (symbolic) {
+                       char buf[18];
+                       int j;
+
+                       old_umask = ~old_umask;
+                       cp = buf;
+                       for (i = 0; i < 3; i++) {
+                               *cp++ = "ugo"[i];
+                               *cp++ = '=';
+                               for (j = 0; j < 3; j++)
+                                       if (old_umask & (1 << (8 - (3*i + j))))
+                                               *cp++ = "rwx"[j];
+                               *cp++ = ',';
+                       }
+                       cp[-1] = '\0';
+                       shprintf("%s\n", buf);
+               } else
+                       shprintf("%#3.3o\n", old_umask);
+       } else {
+               int new_umask;
+
+               if (digit(*cp)) {
+                       for (new_umask = 0; *cp >= '0' && *cp <= '7'; cp++)
+                               new_umask = new_umask * 8 + (*cp - '0');
+                       if (*cp) {
+                               bi_errorf("bad number");
+                               return 1;
+                       }
+               } else {
+                       /* symbolic format */
+                       int positions, new_val;
+                       char op;
+
+                       old_umask = umask(0);
+                       umask(old_umask); /* in case of error */
+                       old_umask = ~old_umask;
+                       new_umask = old_umask;
+                       positions = 0;
+                       while (*cp) {
+                               while (*cp && strchr("augo", *cp))
+                                       switch (*cp++) {
+                                       case 'a': positions |= 0111; break;
+                                       case 'u': positions |= 0100; break;
+                                       case 'g': positions |= 0010; break;
+                                       case 'o': positions |= 0001; break;
+                                       }
+                               if (!positions)
+                                       positions = 0111; /* default is a */
+                               if (!strchr("=+-", op = *cp))
+                                       break;
+                               cp++;
+                               new_val = 0;
+                               while (*cp && strchr("rwxugoXs", *cp))
+                                       switch (*cp++) {
+                                       case 'r': new_val |= 04; break;
+                                       case 'w': new_val |= 02; break;
+                                       case 'x': new_val |= 01; break;
+                                       case 'u': new_val |= old_umask >> 6;
+                                                 break;
+                                       case 'g': new_val |= old_umask >> 3;
+                                                 break;
+                                       case 'o': new_val |= old_umask >> 0;
+                                                 break;
+                                       case 'X': if (old_umask & 0111)
+                                                       new_val |= 01;
+                                                 break;
+                                       case 's': /* ignored */
+                                                 break;
+                                       }
+                               new_val = (new_val & 07) * positions;
+                               switch (op) {
+                               case '-':
+                                       new_umask &= ~new_val;
+                                       break;
+                               case '=':
+                                       new_umask = new_val
+                                           | (new_umask & ~(positions * 07));
+                                       break;
+                               case '+':
+                                       new_umask |= new_val;
+                               }
+                               if (*cp == ',') {
+                                       positions = 0;
+                                       cp++;
+                               } else if (!strchr("=+-", *cp))
+                                       break;
+                       }
+                       if (*cp) {
+                               bi_errorf("bad mask");
+                               return 1;
+                       }
+                       new_umask = ~new_umask;
+               }
+               umask(new_umask);
+       }
+       return 0;
+}
+
+int
+c_dot(wp)
+       char **wp;
+{
+       char *file, *cp;
+       char **argv;
+       int argc;
+       int i;
+       int err;
+
+       if (ksh_getopt(wp, &builtin_opt, null) == '?')
+               return 1;
+
+       if ((cp = wp[builtin_opt.optind]) == NULL)
+               return 0;
+       file = search(cp, path, R_OK, &err);
+       if (file == NULL) {
+               bi_errorf("%s: %s", cp, err ? strerror(err) : "not found");
+               return 1;
+       }
+
+       /* Set positional parameters? */
+       if (wp[builtin_opt.optind + 1]) {
+               argv = wp + builtin_opt.optind;
+               argv[0] = e->loc->argv[0]; /* preserve $0 */
+               for (argc = 0; argv[argc + 1]; argc++)
+                       ;
+       } else {
+               argc = 0;
+               argv = (char **) 0;
+       }
+       i = include(file, argc, argv, 0);
+       if (i < 0) { /* should not happen */
+               bi_errorf("%s: %s", cp, strerror(errno));
+               return 1;
+       }
+       return i;
+}
+
+int
+c_wait(wp)
+       char **wp;
+{
+       int UNINITIALIZED(rv);
+       int sig;
+
+       if (ksh_getopt(wp, &builtin_opt, null) == '?')
+               return 1;
+       wp += builtin_opt.optind;
+       if (*wp == (char *) 0) {
+               while (waitfor((char *) 0, &sig) >= 0)
+                       ;
+               rv = sig;
+       } else {
+               for (; *wp; wp++)
+                       rv = waitfor(*wp, &sig);
+               if (rv < 0)
+                       rv = sig ? sig : 127; /* magic exit code: bad job-id */
+       }
+       return rv;
+}
+
+int
+c_read(wp)
+       char **wp;
+{
+       register int c = 0;
+       int expandv = 1, history = 0;
+       int expanding;
+       int ecode = 0;
+       register char *cp;
+       int fd = 0;
+       struct shf *shf;
+       int optc;
+       const char *emsg;
+       XString cs, xs;
+       struct tbl *vp;
+       char UNINITIALIZED(*xp);
+       static char REPLY[] = "REPLY";
+
+       while ((optc = ksh_getopt(wp, &builtin_opt, "prsu,")) != EOF)
+               switch (optc) {
+#ifdef KSH
+                 case 'p':
+                       if ((fd = coproc_getfd(R_OK, &emsg)) < 0) {
+                               bi_errorf("-p: %s", emsg);
+                               return 1;
+                       }
+                       break;
+#endif /* KSH */
+                 case 'r':
+                       expandv = 0;
+                       break;
+                 case 's':
+                       history = 1;
+                       break;
+                 case 'u':
+                       if (!*(cp = builtin_opt.optarg))
+                               fd = 0;
+                       else if ((fd = check_fd(cp, R_OK, &emsg)) < 0) {
+                               bi_errorf("-u: %s: %s", cp, emsg);
+                               return 1;
+                       }
+                       break;
+                 case '?':
+                       return 1;
+               }
+       wp += builtin_opt.optind;
+
+       if (*wp == NULL)
+               *--wp = REPLY;
+
+       /* Since we can't necessarily seek backwards on non-regular files,
+        * don't buffer them so we can't read too much.
+        */
+       shf = shf_reopen(fd, SHF_RD | SHF_INTERRUPT | can_seek(fd), shl_spare);
+
+       if ((cp = strchr(*wp, '?')) != NULL) {
+               *cp = 0;
+               if (isatty(fd)) {
+                       /* at&t ksh says it prints prompt on fd if it's open
+                        * for writing and is a tty, but it doesn't do it
+                        * (it also doesn't check the interactive flag,
+                        * as is indicated in the Kornshell book).
+                        */
+                       shellf("%s", cp+1);
+               }
+       }
+
+#ifdef KSH
+       /* If we are reading from the co-process for the first time,
+        * make sure the other side of the pipe is closed first.  This allows
+        * the detection of eof.
+        *
+        * This is not compatible with at&t ksh... the fd is kept so another
+        * coproc can be started with same output, however, this means eof
+        * can't be detected...  This is why it is closed here.
+        * If this call is removed, remove the eof check below, too.
+        * coproc_readw_close(fd);
+        */
+#endif /* KSH */
+
+       if (history)
+               Xinit(xs, xp, 128, ATEMP);
+       expanding = 0;
+       Xinit(cs, cp, 128, ATEMP);
+       for (; *wp != NULL; wp++) {
+               for (cp = Xstring(cs, cp); ; ) {
+                       if (c == '\n' || c == EOF)
+                               break;
+                       while (1) {
+                               c = shf_getc(shf);
+                               if (c == '\0'
+#ifdef OS2
+                                   || c == '\r'
+#endif /* OS2 */
+                                   )
+                                       continue;
+                               if (c == EOF && shf_error(shf)
+                                   && shf_errno(shf) == EINTR)
+                               {
+                                       /* Was the offending signal one that
+                                        * would normally kill a process?
+                                        * If so, pretend the read was killed.
+                                        */
+                                       ecode = fatal_trap_check();
+
+                                       /* non fatal (eg, CHLD), carry on */
+                                       if (!ecode) {
+                                               shf_clearerr(shf);
+                                               continue;
+                                       }
+                               }
+                               break;
+                       }
+                       if (history) {
+                               Xcheck(xs, xp);
+                               Xput(xs, xp, c);
+                       }
+                       Xcheck(cs, cp);
+                       if (expanding) {
+                               expanding = 0;
+                               if (c == '\n') {
+                                       c = 0;
+                                       if (Flag(FTALKING_I) && isatty(fd)) {
+                                               /* set prompt in case this is
+                                                * called from .profile or $ENV
+                                                */
+                                               set_prompt(PS2, (Source *) 0);
+                                               pprompt(prompt, 0);
+                                       }
+                               } else if (c != EOF)
+                                       Xput(cs, cp, c);
+                               continue;
+                       }
+                       if (expandv && c == '\\') {
+                               expanding = 1;
+                               continue;
+                       }
+                       if (c == '\n' || c == EOF)
+                               break;
+                       if (ctype(c, C_IFS)) {
+                               if (Xlength(cs, cp) == 0 && ctype(c, C_IFSWS))
+                                       continue;
+                               if (wp[1])
+                                       break;
+                       }
+                       Xput(cs, cp, c);
+               }
+               /* strip trailing IFS white space from last variable */
+               if (!wp[1])
+                       while (Xlength(cs, cp) && ctype(cp[-1], C_IFS)
+                              && ctype(cp[-1], C_IFSWS))
+                               cp--;
+               Xput(cs, cp, '\0');
+               vp = global(*wp);
+               /* Must be done before setting export. */
+               if (vp->flag & RDONLY) {
+                       shf_flush(shf);
+                       bi_errorf("%s is read only", *wp);
+                       return 1;
+               }
+               if (Flag(FEXPORT))
+                       typeset(*wp, EXPORT, 0, 0, 0);
+               if (!setstr(vp, Xstring(cs, cp), KSH_RETURN_ERROR)) {
+                   shf_flush(shf);
+                   return 1;
+               }
+       }
+
+       shf_flush(shf);
+       if (history) {
+               Xput(xs, xp, '\0');
+               source->line++;
+               histsave(source->line, Xstring(xs, xp), 1);
+               Xfree(xs, xp);
+       }
+#ifdef KSH
+       /* if this is the co-process fd, close the file descriptor
+        * (can get eof if and only if all processes are have died, ie,
+        * coproc.njobs is 0 and the pipe is closed).
+        */
+       if (c == EOF && !ecode)
+               coproc_read_close(fd);
+#endif /* KSH */
+
+       return ecode ? ecode : c == EOF;
+}
+
+int
+c_eval(wp)
+       char **wp;
+{
+       register struct source *s;
+       int rv;
+
+       if (ksh_getopt(wp, &builtin_opt, null) == '?')
+               return 1;
+       s = pushs(SWORDS, ATEMP);
+       s->u.strv = wp + builtin_opt.optind;
+       if (!Flag(FPOSIX)) {
+               /*
+                * Handle case where the command is empty due to failed
+                * command substitution, eg, eval "$(false)".
+                * In this case, shell() will not set/change exstat (because
+                * compiled tree is empty), so will use this value.
+                * subst_exstat is cleared in execute(), so should be 0 if
+                * there were no substitutions.
+                *
+                * A strict reading of POSIX says we don't do this (though
+                * it is traditionally done). [from 1003.2-1992]
+                *    3.9.1: Simple Commands
+                *      ... If there is a command name, execution shall
+                *      continue as described in 3.9.1.1.  If there
+                *      is no command name, but the command contained a command
+                *      substitution, the command shall complete with the exit
+                *      status of the last command substitution
+                *    3.9.1.1: Command Search and Execution
+                *      ...(1)...(a) If the command name matches the name of
+                *      a special built-in utility, that special built-in
+                *      utility shall be invoked.
+                * 3.14.5: Eval
+                *      ... If there are no arguments, or only null arguments,
+                *      eval shall return an exit status of zero.
+                */
+               exstat = subst_exstat;
+       }
+
+       rv = shell(s, FALSE);
+       afree(s, ATEMP);
+       return rv;
+}
+
+int
+c_trap(wp)
+       char **wp;
+{
+       int i;
+       char *s;
+       register Trap *p;
+
+       if (ksh_getopt(wp, &builtin_opt, null) == '?')
+               return 1;
+       wp += builtin_opt.optind;
+
+       if (*wp == NULL) {
+               int anydfl = 0;
+
+               for (p = sigtraps, i = SIGNALS+1; --i >= 0; p++) {
+                       if (p->trap == NULL)
+                               anydfl = 1;
+                       else {
+                               shprintf("trap -- ");
+                               print_value_quoted(p->trap);
+                               shprintf(" %s\n", p->name);
+                       }
+               }
+#if 0 /* this is ugly and not clear POSIX needs it */
+               /* POSIX may need this so output of trap can be saved and
+                * used to restore trap conditions
+                */
+               if (anydfl) {
+                       shprintf("trap -- -");
+                       for (p = sigtraps, i = SIGNALS+1; --i >= 0; p++)
+                               if (p->trap == NULL && p->name)
+                                       shprintf(" %s", p->name);
+                       shprintf(newline);
+               }
+#endif
+               return 0;
+       }
+
+       /*
+        * Use case sensitive lookup for first arg so the
+        * command 'exit' isn't confused with the pseudo-signal
+        * 'EXIT'.
+        */
+       s = (gettrap(*wp, FALSE) == NULL) ? *wp++ : NULL; /* get command */
+       if (s != NULL && s[0] == '-' && s[1] == '\0')
+               s = NULL;
+
+       /* set/clear traps */
+       while (*wp != NULL) {
+               p = gettrap(*wp++, TRUE);
+               if (p == NULL) {
+                       bi_errorf("bad signal %s", wp[-1]);
+                       return 1;
+               }
+               settrap(p, s);
+       }
+       return 0;
+}
+
+int
+c_exitreturn(wp)
+       char **wp;
+{
+       int how = LEXIT;
+       int n;
+       char *arg;
+
+       if (ksh_getopt(wp, &builtin_opt, null) == '?')
+               return 1;
+       arg = wp[builtin_opt.optind];
+
+       if (arg) {
+           if (!getn(arg, &n)) {
+                   exstat = 1;
+                   warningf(TRUE, "%s: bad number", arg);
+           } else
+                   exstat = n;
+       }
+       if (wp[0][0] == 'r') { /* return */
+               struct env *ep;
+
+               /* need to tell if this is exit or return so trap exit will
+                * work right (POSIX)
+                */
+               for (ep = e; ep; ep = ep->oenv)
+                       if (STOP_RETURN(ep->type)) {
+                               how = LRETURN;
+                               break;
+                       }
+       }
+
+       if (how == LEXIT && !really_exit && j_stopped_running()) {
+               really_exit = 1;
+               how = LSHELL;
+       }
+
+       quitenv();      /* get rid of any i/o redirections */
+       unwind(how);
+       /*NOTREACHED*/
+       return 0;
+}
+
+int
+c_brkcont(wp)
+       char **wp;
+{
+       int n, quit;
+       struct env *ep, *last_ep = (struct env *) 0;
+       char *arg;
+
+       if (ksh_getopt(wp, &builtin_opt, null) == '?')
+               return 1;
+       arg = wp[builtin_opt.optind];
+
+       if (!arg)
+               n = 1;
+       else if (!bi_getn(arg, &n))
+               return 1;
+       quit = n;
+       if (quit <= 0) {
+               /* at&t ksh does this for non-interactive shells only - weird */
+               bi_errorf("%s: bad value", arg);
+               return 1;
+       }
+
+       /* Stop at E_NONE, E_PARSE, E_FUNC, or E_INCL */
+       for (ep = e; ep && !STOP_BRKCONT(ep->type); ep = ep->oenv)
+               if (ep->type == E_LOOP) {
+                       if (--quit == 0)
+                               break;
+                       ep->flags |= EF_BRKCONT_PASS;
+                       last_ep = ep;
+               }
+
+       if (quit) {
+               /* at&t ksh doesn't print a message - just does what it
+                * can.  We print a message 'cause it helps in debugging
+                * scripts, but don't generate an error (ie, keep going).
+                */
+               if (n == quit) {
+                       warningf(TRUE, "%s: cannot %s", wp[0], wp[0]);
+                       return 0;
+               }
+               /* POSIX says if n is too big, the last enclosing loop
+                * shall be used.  Doesn't say to print an error but we
+                * do anyway 'cause the user messed up.
+                */
+               if (last_ep)
+                       last_ep->flags &= ~EF_BRKCONT_PASS;
+               warningf(TRUE, "%s: can only %s %d level(s)",
+                       wp[0], wp[0], n - quit);
+       }
+
+       unwind(*wp[0] == 'b' ? LBREAK : LCONTIN);
+       /*NOTREACHED*/
+}
+
+int
+c_set(wp)
+       char **wp;
+{
+       int argi, setargs;
+       struct block *l = e->loc;
+       register char **owp = wp;
+
+       if (wp[1] == NULL) {
+               static const char *const args [] = { "set", "-", NULL };
+               return c_typeset((char **)__UNCONST(args));
+       }
+
+       argi = parse_args(wp, OF_SET, &setargs);
+       if (argi < 0)
+               return 1;
+       /* set $# and $* */
+       if (setargs) {
+               owp = wp += argi - 1;
+               wp[0] = l->argv[0]; /* save $0 */
+               while (*++wp != NULL)
+                       *wp = str_save(*wp, &l->area);
+               l->argc = wp - owp - 1;
+               l->argv = (char **) alloc(sizeofN(char *, l->argc+2), &l->area);
+               for (wp = l->argv; (*wp++ = *owp++) != NULL; )
+                       ;
+       }
+       /* POSIX says set exit status is 0, but old scripts that use
+        * getopt(1), use the construct: set -- `getopt ab:c "$@"`
+        * which assumes the exit value set will be that of the ``
+        * (subst_exstat is cleared in execute() so that it will be 0
+        * if there are no command substitutions).
+        */
+       return Flag(FPOSIX) ? 0 : subst_exstat;
+}
+
+int
+c_unset(wp)
+       char **wp;
+{
+       register char *id;
+       int optc, unset_var = 1;
+       int ret = 0;
+
+       while ((optc = ksh_getopt(wp, &builtin_opt, "fv")) != EOF)
+               switch (optc) {
+                 case 'f':
+                       unset_var = 0;
+                       break;
+                 case 'v':
+                       unset_var = 1;
+                       break;
+                 case '?':
+                       return 1;
+               }
+       wp += builtin_opt.optind;
+       for (; (id = *wp) != NULL; wp++)
+               if (unset_var) {        /* unset variable */
+                       struct tbl *vp = global(id);
+
+                       if (!(vp->flag & ISSET))
+                           ret = 1;
+                       if ((vp->flag&RDONLY)) {
+                               bi_errorf("%s is read only", vp->name);
+                               return 1;
+                       }
+                       unset(vp, strchr(id, '[') ? 1 : 0);
+               } else {                /* unset function */
+                       if (define(id, NULL))
+                               ret = 1;
+               }
+       return ret;
+}
+
+int
+c_times(wp)
+       char **wp;
+{
+       struct tms all;
+
+       (void) ksh_times(&all);
+       shprintf("Shell: %8ss user ", clocktos(all.tms_utime));
+       shprintf("%8ss system\n", clocktos(all.tms_stime));
+       shprintf("Kids:  %8ss user ", clocktos(all.tms_cutime));
+       shprintf("%8ss system\n", clocktos(all.tms_cstime));
+
+       return 0;
+}
+
+/*
+ * time pipeline (really a statement, not a built-in command)
+ */
+int
+timex(t, f)
+       struct op *t;
+       int f;
+{
+#define TF_NOARGS      BIT(0)
+#define TF_NOREAL      BIT(1)          /* don't report real time */
+#define TF_POSIX       BIT(2)          /* report in posix format */
+       int rv = 0;
+       struct tms t0, t1, tms;
+       clock_t t0t, t1t = 0;
+       int tf = 0;
+       extern clock_t j_usrtime, j_systime; /* computed by j_wait */
+       char opts[1];
+
+       t0t = ksh_times(&t0);
+       if (t->left) {
+               /*
+                * Two ways of getting cpu usage of a command: just use t0
+                * and t1 (which will get cpu usage from other jobs that
+                * finish while we are executing t->left), or get the
+                * cpu usage of t->left. at&t ksh does the former, while
+                * pdksh tries to do the later (the j_usrtime hack doesn't
+                * really work as it only counts the last job).
+                */
+               j_usrtime = j_systime = 0;
+               if (t->left->type == TCOM)
+                       t->left->str = opts;
+               opts[0] = 0;
+               rv = execute(t->left, f | XTIME);
+               tf |= opts[0];
+               t1t = ksh_times(&t1);
+       } else
+               tf = TF_NOARGS;
+
+       if (tf & TF_NOARGS) { /* ksh93 - report shell times (shell+kids) */
+               tf |= TF_NOREAL;
+               tms.tms_utime = t0.tms_utime + t0.tms_cutime;
+               tms.tms_stime = t0.tms_stime + t0.tms_cstime;
+       } else {
+               tms.tms_utime = t1.tms_utime - t0.tms_utime + j_usrtime;
+               tms.tms_stime = t1.tms_stime - t0.tms_stime + j_systime;
+       }
+
+       if (!(tf & TF_NOREAL))
+               shf_fprintf(shl_out,
+                       tf & TF_POSIX ? "real %8s\n" : "%8ss real ",
+                       clocktos(t1t - t0t));
+       shf_fprintf(shl_out, tf & TF_POSIX ? "user %8s\n" : "%8ss user ",
+               clocktos(tms.tms_utime));
+       shf_fprintf(shl_out, tf & TF_POSIX ? "sys  %8s\n" : "%8ss system\n",
+               clocktos(tms.tms_stime));
+       shf_flush(shl_out);
+
+       return rv;
+}
+
+void
+timex_hook(t, app)
+       struct op *t;
+       char ** volatile *app;
+{
+       char **wp = *app;
+       int optc;
+       int i, j;
+       Getopt opt;
+
+       ksh_getopt_reset(&opt, 0);
+       opt.optind = 0; /* start at the start */
+       while ((optc = ksh_getopt(wp, &opt, ":p")) != EOF)
+               switch (optc) {
+                 case 'p':
+                       t->str[0] |= TF_POSIX;
+                       break;
+                 case '?':
+                       errorf("time: -%s unknown option", opt.optarg);
+                 case ':':
+                       errorf("time: -%s requires an argument",
+                               opt.optarg);
+               }
+       /* Copy command words down over options. */
+       if (opt.optind != 0) {
+               for (i = 0; i < opt.optind; i++)
+                       afree(wp[i], ATEMP);
+               for (i = 0, j = opt.optind; (wp[i] = wp[j]); i++, j++)
+                       ;
+       }
+       if (!wp[0])
+               t->str[0] |= TF_NOARGS;
+       *app = wp;
+}
+
+static char *
+clocktos(t)
+       clock_t t;
+{
+       static char temp[22]; /* enough for 64 bit clock_t */
+       register int i;
+       register char *cp = temp + sizeof(temp);
+
+       /* note: posix says must use max precision, ie, if clk_tck is
+        * 1000, must print 3 places after decimal (if non-zero, else 1).
+        */
+       if (CLK_TCK != 100)     /* convert to 1/100'ths */
+           t = (t < (clock_t)(1000000000/CLK_TCK)) ?
+                   (t * 100) / CLK_TCK : (t / CLK_TCK) * 100;
+
+       *--cp = '\0';
+       for (i = -2; i <= 0 || t > 0; i++) {
+               if (i == 0)
+                       *--cp = '.';
+               *--cp = '0' + (char)(t%10);
+               t /= 10;
+       }
+       return cp;
+}
+
+/* exec with no args - args case is taken care of in comexec() */
+int
+c_exec(wp)
+       char ** wp;
+{
+       int i;
+
+       /* make sure redirects stay in place */
+       if (e->savefd != NULL) {
+               for (i = 0; i < NUFILE; i++) {
+                       if (e->savefd[i] > 0)
+                               close(e->savefd[i]);
+                       /*
+                        * For ksh keep anything > 2 private,
+                        * for sh, let them be (POSIX says what
+                        * happens is unspecified and the bourne shell
+                        * keeps them open).
+                        */
+#ifdef KSH
+                       if (i > 2 && e->savefd[i])
+                               fd_clexec(i);
+#endif /* KSH */
+               }
+               e->savefd = NULL;
+       }
+       return 0;
+}
+
+/* dummy function, special case in comexec() */
+int
+c_builtin(wp)
+       char ** wp;
+{
+       return 0;
+}
+
+extern int c_test ARGS((char **wp));           /* in c_test.c */
+extern int c_ulimit ARGS((char **wp));         /* in c_ulimit.c */
+
+/* A leading = means assignments before command are kept;
+ * a leading * means a POSIX special builtin;
+ * a leading + means a POSIX regular builtin
+ * (* and + should not be combined).
+ */
+const struct builtin shbuiltins [] = {
+       {"*=.", c_dot},
+       {"*=:", c_label},
+       {"[", c_test},
+       {"*=break", c_brkcont},
+       {"=builtin", c_builtin},
+       {"*=continue", c_brkcont},
+       {"*=eval", c_eval},
+       {"*=exec", c_exec},
+       {"*=exit", c_exitreturn},
+       {"+false", c_label},
+       {"*=return", c_exitreturn},
+       {"*=set", c_set},
+       {"*=shift", c_shift},
+       {"=times", c_times},
+       {"*=trap", c_trap},
+       {"+=wait", c_wait},
+       {"+read", c_read},
+       {"test", c_test},
+       {"+true", c_label},
+       {"ulimit", c_ulimit},
+       {"+umask", c_umask},
+       {"*=unset", c_unset},
+#ifdef OS2
+       /* In OS2, the first line of a file can be "extproc name", which
+        * tells the command interpreter (cmd.exe) to use name to execute
+        * the file.  For this to be useful, ksh must ignore commands
+        * starting with extproc and this does the trick...
+        */
+       {"extproc", c_label},
+#endif /* OS2 */
+       {NULL, NULL}
+};
diff --git a/bin/ksh/c_test.c b/bin/ksh/c_test.c
new file mode 100644 (file)
index 0000000..41e10fe
--- /dev/null
@@ -0,0 +1,666 @@
+/*     $NetBSD: c_test.c,v 1.6 2005/06/26 19:09:00 christos Exp $      */
+
+/*
+ * test(1); version 7-like  --  author Erik Baalbergen
+ * modified by Eric Gisin to be used as built-in.
+ * modified by Arnold Robbins to add SVR3 compatibility
+ * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket).
+ * modified by Michael Rendell to add Korn's [[ .. ]] expressions.
+ * modified by J.T. Conklin to add POSIX compatibility.
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: c_test.c,v 1.6 2005/06/26 19:09:00 christos Exp $");
+#endif
+
+
+#include "sh.h"
+#include "ksh_stat.h"
+#include "c_test.h"
+
+/* test(1) accepts the following grammar:
+       oexpr   ::= aexpr | aexpr "-o" oexpr ;
+       aexpr   ::= nexpr | nexpr "-a" aexpr ;
+       nexpr   ::= primary | "!" nexpr ;
+       primary ::= unary-operator operand
+               | operand binary-operator operand
+               | operand
+               | "(" oexpr ")"
+               ;
+
+       unary-operator ::= "-a"|"-r"|"-w"|"-x"|"-e"|"-f"|"-d"|"-c"|"-b"|"-p"|
+                          "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|
+                          "-L"|"-h"|"-S"|"-H";
+
+       binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
+                           "-nt"|"-ot"|"-ef"|
+                           "<"|">"     # rules used for [[ .. ]] expressions
+                           ;
+       operand ::= <any thing>
+*/
+
+#define T_ERR_EXIT     2       /* POSIX says > 1 for errors */
+
+struct t_op {
+       char    op_text[4];
+       Test_op op_num;
+};
+static const struct t_op u_ops [] = {
+       {"-a",  TO_FILAXST },
+       {"-b",  TO_FILBDEV },
+       {"-c",  TO_FILCDEV },
+       {"-d",  TO_FILID },
+       {"-e",  TO_FILEXST },
+       {"-f",  TO_FILREG },
+       {"-G",  TO_FILGID },
+       {"-g",  TO_FILSETG },
+       {"-h",  TO_FILSYM },
+       {"-H",  TO_FILCDF },
+       {"-k",  TO_FILSTCK },
+       {"-L",  TO_FILSYM },
+       {"-n",  TO_STNZE },
+       {"-O",  TO_FILUID },
+       {"-o",  TO_OPTION },
+       {"-p",  TO_FILFIFO },
+       {"-r",  TO_FILRD },
+       {"-s",  TO_FILGZ },
+       {"-S",  TO_FILSOCK },
+       {"-t",  TO_FILTT },
+       {"-u",  TO_FILSETU },
+       {"-w",  TO_FILWR },
+       {"-x",  TO_FILEX },
+       {"-z",  TO_STZER },
+       {"",    TO_NONOP }
+    };
+static const struct t_op b_ops [] = {
+       {"=",   TO_STEQL },
+#ifdef KSH
+       {"==",  TO_STEQL },
+#endif /* KSH */
+       {"!=",  TO_STNEQ },
+       {"<",   TO_STLT },
+       {">",   TO_STGT },
+       {"-eq", TO_INTEQ },
+       {"-ne", TO_INTNE },
+       {"-gt", TO_INTGT },
+       {"-ge", TO_INTGE },
+       {"-lt", TO_INTLT },
+       {"-le", TO_INTLE },
+       {"-ef", TO_FILEQ },
+       {"-nt", TO_FILNT },
+       {"-ot", TO_FILOT },
+       {"",    TO_NONOP }
+    };
+
+static int     test_stat ARGS((const char *, struct stat *));
+static int     test_eaccess ARGS((const char *, int));
+static int     test_oexpr ARGS((Test_env *, int));
+static int     test_aexpr ARGS((Test_env *, int));
+static int     test_nexpr ARGS((Test_env *, int));
+static int     test_primary ARGS((Test_env *, int));
+static int     ptest_isa ARGS((Test_env *, Test_meta));
+static const char *ptest_getopnd ARGS((Test_env *, Test_op, int));
+static int     ptest_eval ARGS((Test_env *, Test_op, const char *,
+                               const char *, int));
+static void    ptest_error ARGS((Test_env *, int, const char *));
+
+int
+c_test(wp)
+       char **wp;
+{
+       int argc;
+       int res;
+       Test_env te;
+
+       te.flags = 0;
+       te.isa = ptest_isa;
+       te.getopnd = ptest_getopnd;
+       te.eval = ptest_eval;
+       te.error = ptest_error;
+
+       for (argc = 0; wp[argc]; argc++)
+               ;
+
+       if (strcmp(wp[0], "[") == 0) {
+               if (strcmp(wp[--argc], "]") != 0) {
+                       bi_errorf("missing ]");
+                       return T_ERR_EXIT;
+               }
+       }
+
+       te.pos.wp = wp + 1;
+       te.wp_end = wp + argc;
+
+       /*
+        * Handle the special cases from POSIX.2, section 4.62.4.
+        * Implementation of all the rules isn't necessary since
+        * our parser does the right thing for the omitted steps.
+        */
+       if (argc <= 5) {
+               char **owp = wp;
+               int invert = 0;
+               Test_op op;
+               const char *opnd1, *opnd2;
+
+               while (--argc >= 0) {
+                       if ((*te.isa)(&te, TM_END))
+                               return !0;
+                       if (argc == 3) {
+                               opnd1 = (*te.getopnd)(&te, TO_NONOP, 1);
+                               if ((op = (Test_op) (*te.isa)(&te, TM_BINOP))) {
+                                       opnd2 = (*te.getopnd)(&te, op, 1);
+                                       res = (*te.eval)(&te, op, opnd1, opnd2,
+                                                       1);
+                                       if (te.flags & TEF_ERROR)
+                                               return T_ERR_EXIT;
+                                       if (invert & 1)
+                                               res = !res;
+                                       return !res;
+                               }
+                               /* back up to opnd1 */
+                               te.pos.wp--;
+                       }
+                       if (argc == 1) {
+                               opnd1 = (*te.getopnd)(&te, TO_NONOP, 1);
+                               /* Historically, -t by itself test if fd 1
+                                * is a file descriptor, but POSIX says its
+                                * a string test...
+                                */
+                               if (!Flag(FPOSIX) && strcmp(opnd1, "-t") == 0)
+                                   break;
+                               res = (*te.eval)(&te, TO_STNZE, opnd1,
+                                               (char *) 0, 1);
+                               if (invert & 1)
+                                       res = !res;
+                               return !res;
+                       }
+                       if ((*te.isa)(&te, TM_NOT)) {
+                               invert++;
+                       } else
+                               break;
+               }
+               te.pos.wp = owp + 1;
+       }
+
+       return test_parse(&te);
+}
+
+/*
+ * Generic test routines.
+ */
+
+Test_op
+test_isop(te, meta, s)
+       Test_env *te;
+       Test_meta meta;
+       const char *s;
+{
+       char sc1;
+       const struct t_op *otab;
+
+       otab = meta == TM_UNOP ? u_ops : b_ops;
+       if (*s) {
+               sc1 = s[1];
+               for (; otab->op_text[0]; otab++)
+                       if (sc1 == otab->op_text[1]
+                           && strcmp(s, otab->op_text) == 0
+                           && ((te->flags & TEF_DBRACKET)
+                               || (otab->op_num != TO_STLT
+                                   && otab->op_num != TO_STGT)))
+                               return otab->op_num;
+       }
+       return TO_NONOP;
+}
+
+int
+test_eval(te, op, opnd1, opnd2, do_eval)
+       Test_env *te;
+       Test_op op;
+       const char *opnd1;
+       const char *opnd2;
+       int do_eval;
+{
+       int res;
+       int not;
+       struct stat b1, b2;
+
+       if (!do_eval)
+               return 0;
+
+       switch ((int) op) {
+       /*
+        * Unary Operators
+        */
+         case TO_STNZE: /* -n */
+               return *opnd1 != '\0';
+         case TO_STZER: /* -z */
+               return *opnd1 == '\0';
+         case TO_OPTION: /* -o */
+               if ((not = *opnd1 == '!'))
+                       opnd1++;
+               if ((res = option(opnd1)) < 0)
+                       res = 0;
+               else {
+                       res = Flag(res);
+                       if (not)
+                               res = !res;
+               }
+               return res;
+         case TO_FILRD: /* -r */
+               return test_eaccess(opnd1, R_OK) == 0;
+         case TO_FILWR: /* -w */
+               return test_eaccess(opnd1, W_OK) == 0;
+         case TO_FILEX: /* -x */
+               return test_eaccess(opnd1, X_OK) == 0;
+         case TO_FILAXST: /* -a */
+               return test_stat(opnd1, &b1) == 0;
+         case TO_FILEXST: /* -e */
+               /* at&t ksh does not appear to do the /dev/fd/ thing for
+                * this (unless the os itself handles it)
+                */
+               return stat(opnd1, &b1) == 0;
+         case TO_FILREG: /* -r */
+               return test_stat(opnd1, &b1) == 0 && S_ISREG(b1.st_mode);
+         case TO_FILID: /* -d */
+               return test_stat(opnd1, &b1) == 0 && S_ISDIR(b1.st_mode);
+         case TO_FILCDEV: /* -c */
+#ifdef S_ISCHR
+               return test_stat(opnd1, &b1) == 0 && S_ISCHR(b1.st_mode);
+#else
+               return 0;
+#endif
+         case TO_FILBDEV: /* -b */
+#ifdef S_ISBLK
+               return test_stat(opnd1, &b1) == 0 && S_ISBLK(b1.st_mode);
+#else
+               return 0;
+#endif
+         case TO_FILFIFO: /* -p */
+#ifdef S_ISFIFO
+               return test_stat(opnd1, &b1) == 0 && S_ISFIFO(b1.st_mode);
+#else
+               return 0;
+#endif
+         case TO_FILSYM: /* -h -L */
+#ifdef S_ISLNK
+               return lstat(opnd1, &b1) == 0 && S_ISLNK(b1.st_mode);
+#else
+               return 0;
+#endif
+         case TO_FILSOCK: /* -S */
+#ifdef S_ISSOCK
+               return test_stat(opnd1, &b1) == 0 && S_ISSOCK(b1.st_mode);
+#else
+               return 0;
+#endif
+         case TO_FILCDF:/* -H HP context dependent files (directories) */
+#ifdef S_ISCDF
+         {
+               /* Append a + to filename and check to see if result is a
+                * setuid directory.  CDF stuff in general is hookey, since
+                * it breaks for the following sequence: echo hi > foo+;
+                * mkdir foo; echo bye > foo/default; chmod u+s foo
+                * (foo+ refers to the file with hi in it, there is no way
+                * to get at the file with bye in it - please correct me if
+                * I'm wrong about this).
+                */
+               int len = strlen(opnd1);
+               char *p = str_nsave(opnd1, len + 1, ATEMP);
+
+               p[len++] = '+';
+               p[len] = '\0';
+               return stat(p, &b1) == 0 && S_ISCDF(b1.st_mode);
+         }
+#else
+               return 0;
+#endif
+         case TO_FILSETU: /* -u */
+#ifdef S_ISUID
+               return test_stat(opnd1, &b1) == 0
+                       && (b1.st_mode & S_ISUID) == S_ISUID;
+#else
+               return 0;
+#endif
+         case TO_FILSETG: /* -g */
+#ifdef S_ISGID
+               return test_stat(opnd1, &b1) == 0
+                       && (b1.st_mode & S_ISGID) == S_ISGID;
+#else
+               return 0;
+#endif
+         case TO_FILSTCK: /* -k */
+               return test_stat(opnd1, &b1) == 0
+                       && (b1.st_mode & S_ISVTX) == S_ISVTX;
+         case TO_FILGZ: /* -s */
+               return test_stat(opnd1, &b1) == 0 && b1.st_size > 0L;
+         case TO_FILTT: /* -t */
+               if (opnd1 && !bi_getn(opnd1, &res)) {
+                       te->flags |= TEF_ERROR;
+                       res = 0;
+               } else {
+                       /* generate error if in FPOSIX mode? */
+                       res = isatty(opnd1 ? res : 0);
+               }
+               return res;
+         case TO_FILUID: /* -O */
+               return test_stat(opnd1, &b1) == 0 && b1.st_uid == ksheuid;
+         case TO_FILGID: /* -G */
+               return test_stat(opnd1, &b1) == 0 && b1.st_gid == getegid();
+       /*
+        * Binary Operators
+        */
+         case TO_STEQL: /* = */
+               if (te->flags & TEF_DBRACKET)
+                       return gmatch(opnd1, opnd2, FALSE);
+               return strcmp(opnd1, opnd2) == 0;
+         case TO_STNEQ: /* != */
+               if (te->flags & TEF_DBRACKET)
+                       return !gmatch(opnd1, opnd2, FALSE);
+               return strcmp(opnd1, opnd2) != 0;
+         case TO_STLT: /* < */
+               return strcmp(opnd1, opnd2) < 0;
+         case TO_STGT: /* > */
+               return strcmp(opnd1, opnd2) > 0;
+         case TO_INTEQ: /* -eq */
+         case TO_INTNE: /* -ne */
+         case TO_INTGE: /* -ge */
+         case TO_INTGT: /* -gt */
+         case TO_INTLE: /* -le */
+         case TO_INTLT: /* -lt */
+               {
+                       long v1, v2;
+
+                       if (!evaluate(opnd1, &v1, KSH_RETURN_ERROR)
+                           || !evaluate(opnd2, &v2, KSH_RETURN_ERROR))
+                       {
+                               /* error already printed.. */
+                               te->flags |= TEF_ERROR;
+                               return 1;
+                       }
+                       switch ((int) op) {
+                         case TO_INTEQ:
+                               return v1 == v2;
+                         case TO_INTNE:
+                               return v1 != v2;
+                         case TO_INTGE:
+                               return v1 >= v2;
+                         case TO_INTGT:
+                               return v1 > v2;
+                         case TO_INTLE:
+                               return v1 <= v2;
+                         case TO_INTLT:
+                               return v1 < v2;
+                       }
+               }
+         case TO_FILNT: /* -nt */
+               {
+                       int s2;
+                       /* ksh88/ksh93 succeed if file2 can't be stated
+                        * (subtly different from `does not exist').
+                        */
+                       return stat(opnd1, &b1) == 0
+                               && (((s2 = stat(opnd2, &b2)) == 0
+                                     && b1.st_mtime > b2.st_mtime) || s2 < 0);
+               }
+         case TO_FILOT: /* -ot */
+               {
+                       int s1;
+                       /* ksh88/ksh93 succeed if file1 can't be stated
+                        * (subtly different from `does not exist').
+                        */
+                       return stat(opnd2, &b2) == 0
+                               && (((s1 = stat(opnd1, &b1)) == 0
+                                     && b1.st_mtime < b2.st_mtime) || s1 < 0);
+               }
+         case TO_FILEQ: /* -ef */
+               return stat (opnd1, &b1) == 0 && stat (opnd2, &b2) == 0
+                      && b1.st_dev == b2.st_dev
+                      && b1.st_ino == b2.st_ino;
+       }
+       (*te->error)(te, 0, "internal error: unknown op");
+       return 1;
+}
+
+/* Nasty kludge to handle Korn's bizarre /dev/fd hack */
+static int
+test_stat(pathx, statb)
+       const char *pathx;
+       struct stat *statb;
+{
+#if !defined(HAVE_DEV_FD)
+       int fd;
+
+       if (strncmp(pathx, "/dev/fd/", 8) == 0 && getn(pathx + 8, &fd))
+               return fstat(fd, statb);
+#endif /* !HAVE_DEV_FD */
+
+       return stat(pathx, statb);
+}
+
+/* Routine to handle Korn's /dev/fd hack, and to deal with X_OK on
+ * non-directories when running as root.
+ */
+static int
+test_eaccess(pathx, mode)
+       const char *pathx;
+       int mode;
+{
+       int res;
+
+#if !defined(HAVE_DEV_FD)
+       int fd;
+
+       /* Note: doesn't handle //dev/fd, etc.. (this is ok) */
+       if (strncmp(pathx, "/dev/fd/", 8) == 0 && getn(pathx + 8, &fd)) {
+               int flags;
+
+               if ((flags = fcntl(fd, F_GETFL, 0)) < 0
+                   || (mode & X_OK)
+                   || ((mode & W_OK) && (flags & O_ACCMODE) == O_RDONLY)
+                   || ((mode & R_OK) && (flags & O_ACCMODE) == O_WRONLY))
+                       return -1;
+               return 0;
+       }
+#endif /* !HAVE_DEV_FD */
+
+       res = eaccess(pathx, mode);
+       /*
+        * On most (all?) unixes, access() says everything is executable for
+        * root - avoid this on files by using stat().
+        */
+       if (res == 0 && ksheuid == 0 && (mode & X_OK)) {
+               struct stat statb;
+
+               if (stat(pathx, &statb) < 0)
+                       res = -1;
+               else if (S_ISDIR(statb.st_mode))
+                       res = 0;
+               else
+                       res = (statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))
+                               ? 0 : -1;
+       }
+
+       return res;
+}
+
+int
+test_parse(te)
+       Test_env *te;
+{
+       int res;
+
+       res = test_oexpr(te, 1);
+
+       if (!(te->flags & TEF_ERROR) && !(*te->isa)(te, TM_END))
+               (*te->error)(te, 0, "unexpected operator/operand");
+
+       return (te->flags & TEF_ERROR) ? T_ERR_EXIT : !res;
+}
+
+static int
+test_oexpr(te, do_eval)
+       Test_env *te;
+       int do_eval;
+{
+       int res;
+
+       res = test_aexpr(te, do_eval);
+       if (res)
+               do_eval = 0;
+       if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_OR))
+               return test_oexpr(te, do_eval) || res;
+       return res;
+}
+
+static int
+test_aexpr(te, do_eval)
+       Test_env *te;
+       int do_eval;
+{
+       int res;
+
+       res = test_nexpr(te, do_eval);
+       if (!res)
+               do_eval = 0;
+       if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_AND))
+               return test_aexpr(te, do_eval) && res;
+       return res;
+}
+
+static int
+test_nexpr(te, do_eval)
+       Test_env *te;
+       int do_eval;
+{
+       if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_NOT))
+               return !test_nexpr(te, do_eval);
+       return test_primary(te, do_eval);
+}
+
+static int
+test_primary(te, do_eval)
+       Test_env *te;
+       int do_eval;
+{
+       const char *opnd1, *opnd2;
+       int res;
+       Test_op op;
+
+       if (te->flags & TEF_ERROR)
+               return 0;
+       if ((*te->isa)(te, TM_OPAREN)) {
+               res = test_oexpr(te, do_eval);
+               if (te->flags & TEF_ERROR)
+                       return 0;
+               if (!(*te->isa)(te, TM_CPAREN)) {
+                       (*te->error)(te, 0, "missing closing paren");
+                       return 0;
+               }
+               return res;
+       }
+       if ((op = (Test_op) (*te->isa)(te, TM_UNOP))) {
+               /* unary expression */
+               opnd1 = (*te->getopnd)(te, op, do_eval);
+               if (!opnd1) {
+                       (*te->error)(te, -1, "missing argument");
+                       return 0;
+               }
+
+               return (*te->eval)(te, op, opnd1, (const char *) 0, do_eval);
+       }
+       opnd1 = (*te->getopnd)(te, TO_NONOP, do_eval);
+       if (!opnd1) {
+               (*te->error)(te, 0, "expression expected");
+               return 0;
+       }
+       if ((op = (Test_op) (*te->isa)(te, TM_BINOP))) {
+               /* binary expression */
+               opnd2 = (*te->getopnd)(te, op, do_eval);
+               if (!opnd2) {
+                       (*te->error)(te, -1, "missing second argument");
+                       return 0;
+               }
+
+               return (*te->eval)(te, op, opnd1, opnd2, do_eval);
+       }
+       if (te->flags & TEF_DBRACKET) {
+               (*te->error)(te, -1, "missing expression operator");
+               return 0;
+       }
+       return (*te->eval)(te, TO_STNZE, opnd1, (const char *) 0, do_eval);
+}
+
+/*
+ * Plain test (test and [ .. ]) specific routines.
+ */
+
+/* Test if the current token is a whatever.  Accepts the current token if
+ * it is.  Returns 0 if it is not, non-zero if it is (in the case of
+ * TM_UNOP and TM_BINOP, the returned value is a Test_op).
+ */
+static int
+ptest_isa(te, meta)
+       Test_env *te;
+       Test_meta meta;
+{
+       /* Order important - indexed by Test_meta values */
+       static const char *const tokens[] = {
+                               "-o", "-a", "!", "(", ")"
+                       };
+       int ret;
+
+       if (te->pos.wp >= te->wp_end)
+               return meta == TM_END;
+
+       if (meta == TM_UNOP || meta == TM_BINOP)
+               ret = (int) test_isop(te, meta, *te->pos.wp);
+       else if (meta == TM_END)
+               ret = 0;
+       else
+               ret = strcmp(*te->pos.wp, tokens[(int) meta]) == 0;
+
+       /* Accept the token? */
+       if (ret)
+               te->pos.wp++;
+
+       return ret;
+}
+
+static const char *
+ptest_getopnd(te, op, do_eval)
+       Test_env *te;
+       Test_op op;
+       int do_eval;
+{
+       if (te->pos.wp >= te->wp_end)
+               return op == TO_FILTT ? "1" : (const char *) 0;
+       return *te->pos.wp++;
+}
+
+static int
+ptest_eval(te, op, opnd1, opnd2, do_eval)
+       Test_env *te;
+       Test_op op;
+       const char *opnd1;
+       const char *opnd2;
+       int do_eval;
+{
+       return test_eval(te, op, opnd1, opnd2, do_eval);
+}
+
+static void
+ptest_error(te, offset, msg)
+       Test_env *te;
+       int offset;
+       const char *msg;
+{
+       const char *op = te->pos.wp + offset >= te->wp_end ?
+                               (const char *) 0 : te->pos.wp[offset];
+
+       te->flags |= TEF_ERROR;
+       if (op)
+               bi_errorf("%s: %s", op, msg);
+       else
+               bi_errorf("%s", msg);
+}
diff --git a/bin/ksh/c_test.h b/bin/ksh/c_test.h
new file mode 100644 (file)
index 0000000..f1182f3
--- /dev/null
@@ -0,0 +1,55 @@
+/*     $NetBSD: c_test.h,v 1.3 2004/07/07 19:20:09 mycroft Exp $       */
+
+/* Various types of operations.  Keeping things grouped nicely
+ * (unary,binary) makes switch() statements more efficient.
+ */
+enum Test_op {
+       TO_NONOP = 0,   /* non-operator */
+       /* unary operators */
+       TO_STNZE, TO_STZER, TO_OPTION,
+       TO_FILAXST,
+       TO_FILEXST,
+       TO_FILREG, TO_FILBDEV, TO_FILCDEV, TO_FILSYM, TO_FILFIFO, TO_FILSOCK,
+       TO_FILCDF, TO_FILID, TO_FILGID, TO_FILSETG, TO_FILSTCK, TO_FILUID,
+       TO_FILRD, TO_FILGZ, TO_FILTT, TO_FILSETU, TO_FILWR, TO_FILEX,
+       /* binary operators */
+       TO_STEQL, TO_STNEQ, TO_STLT, TO_STGT, TO_INTEQ, TO_INTNE, TO_INTGT,
+       TO_INTGE, TO_INTLT, TO_INTLE, TO_FILEQ, TO_FILNT, TO_FILOT
+};
+typedef enum Test_op Test_op;
+
+/* Used by Test_env.isa() (order important - used to index *_tokens[] arrays) */
+enum Test_meta {
+       TM_OR,          /* -o or || */
+       TM_AND,         /* -a or && */
+       TM_NOT,         /* ! */
+       TM_OPAREN,      /* ( */
+       TM_CPAREN,      /* ) */
+       TM_UNOP,        /* unary operator */
+       TM_BINOP,       /* binary operator */
+       TM_END          /* end of input */
+};
+typedef enum Test_meta Test_meta;
+
+#define TEF_ERROR      BIT(0)          /* set if we've hit an error */
+#define TEF_DBRACKET   BIT(1)          /* set if [[ .. ]] test */
+
+typedef struct test_env Test_env;
+struct test_env {
+       int     flags;          /* TEF_* */
+       union {
+               char    **wp;           /* used by ptest_* */
+               XPtrV   *av;            /* used by dbtestp_* */
+       } pos;
+       char **wp_end;                  /* used by ptest_* */
+       int     (*isa) ARGS((Test_env *te, Test_meta meta));
+       const char *(*getopnd) ARGS((Test_env *te, Test_op op, int do_eval));
+       int     (*eval) ARGS((Test_env *te, Test_op op, const char *opnd1,
+                                const char *opnd2, int do_eval));
+       void    (*error) ARGS((Test_env *te, int offset, const char *msg));
+};
+
+Test_op        test_isop ARGS((Test_env *te, Test_meta meta, const char *s));
+int     test_eval ARGS((Test_env *te, Test_op op, const char *opnd1,
+                       const char *opnd2, int do_eval));
+int    test_parse ARGS((Test_env *te));
diff --git a/bin/ksh/c_ulimit.c b/bin/ksh/c_ulimit.c
new file mode 100644 (file)
index 0000000..92c3fad
--- /dev/null
@@ -0,0 +1,285 @@
+/*     $NetBSD: c_ulimit.c,v 1.10 2012/06/09 02:51:50 christos Exp $   */
+
+/*
+       ulimit -- handle "ulimit" builtin
+
+       Reworked to use getrusage() and ulimit() at once (as needed on
+       some schizophrenic systems, eg, HP-UX 9.01), made argument parsing
+       conform to at&t ksh, added autoconf support.  Michael Rendell, May, '94
+
+       Eric Gisin, September 1988
+       Adapted to PD KornShell. Removed AT&T code.
+
+       last edit:      06-Jun-1987     D A Gwyn
+
+       This started out as the BRL UNIX System V system call emulation
+       for 4.nBSD, and was later extended by Doug Kingston to handle
+       the extended 4.nBSD resource limits.  It now includes the code
+       that was originally under case SYSULIMIT in source file "xec.c".
+*/
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: c_ulimit.c,v 1.10 2012/06/09 02:51:50 christos Exp $");
+#endif
+
+
+#include "sh.h"
+#include "ksh_time.h"
+#ifdef HAVE_SYS_RESOURCE_H
+# include <sys/resource.h>
+#endif /* HAVE_SYS_RESOURCE_H */
+#ifdef HAVE_ULIMIT_H
+# include <ulimit.h>
+#else /* HAVE_ULIMIT_H */
+# ifdef HAVE_ULIMIT
+extern long ulimit();
+# endif /* HAVE_ULIMIT */
+#endif /* HAVE_ULIMIT_H */
+
+#define SOFT   0x1
+#define HARD   0x2
+
+#ifdef RLIM_INFINITY
+# define KSH_RLIM_INFINITY RLIM_INFINITY
+#else
+# define KSH_RLIM_INFINITY ((rlim_t) 1 << (sizeof(rlim_t) * 8 - 1) - 1)
+#endif /* RLIM_INFINITY */
+
+int
+c_ulimit(wp)
+       char **wp;
+{
+       static const struct limits {
+               const char      *name;
+               enum { RLIMIT, ULIMIT } which;
+               int     gcmd;   /* get command */
+               int     scmd;   /* set command (or -1, if no set command) */
+               int     factor; /* multiply by to get rlim_{cur,max} values */
+               char    option;
+       } limits[] = {
+               /* Do not use options -H, -S or -a */
+#ifdef RLIMIT_CPU
+               { "time(cpu-seconds)", RLIMIT, RLIMIT_CPU, RLIMIT_CPU, 1, 't' },
+#endif
+#ifdef RLIMIT_FSIZE
+               { "file(blocks)", RLIMIT, RLIMIT_FSIZE, RLIMIT_FSIZE, 512, 'f' },
+#else /* RLIMIT_FSIZE */
+# ifdef UL_GETFSIZE /* x/open */
+               { "file(blocks)", ULIMIT, UL_GETFSIZE, UL_SETFSIZE, 1, 'f' },
+# else /* UL_GETFSIZE */
+#  ifdef UL_GFILLIM /* svr4/xenix */
+               { "file(blocks)", ULIMIT, UL_GFILLIM, UL_SFILLIM, 1, 'f' },
+#  else /* UL_GFILLIM */
+               { "file(blocks)", ULIMIT, 1, 2, 1, 'f' },
+#  endif /* UL_GFILLIM */
+# endif /* UL_GETFSIZE */
+#endif /* RLIMIT_FSIZE */
+#ifdef RLIMIT_CORE
+               { "coredump(blocks)", RLIMIT, RLIMIT_CORE, RLIMIT_CORE, 512, 'c' },
+#endif
+#ifdef RLIMIT_DATA
+               { "data(kbytes)", RLIMIT, RLIMIT_DATA, RLIMIT_DATA, 1024, 'd' },
+#endif
+#ifdef RLIMIT_STACK
+               { "stack(kbytes)", RLIMIT, RLIMIT_STACK, RLIMIT_STACK, 1024, 's' },
+#endif
+#ifdef RLIMIT_MEMLOCK
+               { "lockedmem(kbytes)", RLIMIT, RLIMIT_MEMLOCK, RLIMIT_MEMLOCK, 1024, 'l' },
+#endif
+#ifdef RLIMIT_RSS
+               { "memory(kbytes)", RLIMIT, RLIMIT_RSS, RLIMIT_RSS, 1024, 'm' },
+#endif
+#ifdef RLIMIT_NOFILE
+               { "nofiles(descriptors)", RLIMIT, RLIMIT_NOFILE, RLIMIT_NOFILE, 1, 'n' },
+#else /* RLIMIT_NOFILE */
+# ifdef UL_GDESLIM /* svr4/xenix */
+               { "nofiles(descriptors)", ULIMIT, UL_GDESLIM, -1, 1, 'n' },
+# endif /* UL_GDESLIM */
+#endif /* RLIMIT_NOFILE */
+#ifdef RLIMIT_NPROC
+               { "processes", RLIMIT, RLIMIT_NPROC, RLIMIT_NPROC, 1, 'p' },
+#endif
+#ifdef RLIMIT_NTHR
+               { "threads", RLIMIT, RLIMIT_NTHR, RLIMIT_NTHR, 1, 'r' },
+#endif
+#ifdef RLIMIT_VMEM
+               { "vmemory(kbytes)", RLIMIT, RLIMIT_VMEM, RLIMIT_VMEM, 1024, 'v' },
+#else /* RLIMIT_VMEM */
+  /* These are not quite right - really should subtract etext or something */
+# ifdef UL_GMEMLIM /* svr4/xenix */
+               { "vmemory(maxaddr)", ULIMIT, UL_GMEMLIM, -1, 1, 'v' },
+# else /* UL_GMEMLIM */
+#  ifdef UL_GETBREAK /* osf/1 */
+               { "vmemory(maxaddr)", ULIMIT, UL_GETBREAK, -1, 1, 'v' },
+#  else /* UL_GETBREAK */
+#   ifdef UL_GETMAXBRK /* hpux */
+               { "vmemory(maxaddr)", ULIMIT, UL_GETMAXBRK, -1, 1, 'v' },
+#   endif /* UL_GETMAXBRK */
+#  endif /* UL_GETBREAK */
+# endif /* UL_GMEMLIM */
+#endif /* RLIMIT_VMEM */
+#ifdef RLIMIT_SWAP
+               { "swap(kbytes)", RLIMIT, RLIMIT_SWAP, RLIMIT_SWAP, 1024, 'w' },
+#endif
+#ifdef RLIMIT_SBSIZE
+               { "sbsize(bytes)", RLIMIT, RLIMIT_SBSIZE, RLIMIT_SBSIZE, 1, 'b' },
+#endif
+               { .name = NULL }
+           };
+       static char     options[3 + NELEM(limits)];
+       rlim_t          UNINITIALIZED(val);
+       int             how = SOFT | HARD;
+       const struct limits     *l;
+       int             set, all = 0;
+       int             optc, what;
+#ifdef HAVE_SETRLIMIT
+       struct rlimit   limit;
+#endif /* HAVE_SETRLIMIT */
+
+       if (!options[0]) {
+               /* build options string on first call - yuck */
+               char *p = options;
+
+               *p++ = 'H'; *p++ = 'S'; *p++ = 'a';
+               for (l = limits; l->name; l++)
+                       *p++ = l->option;
+               *p = '\0';
+       }
+       what = 'f';
+       while ((optc = ksh_getopt(wp, &builtin_opt, options)) != EOF)
+               switch (optc) {
+                 case 'H':
+                       how = HARD;
+                       break;
+                 case 'S':
+                       how = SOFT;
+                       break;
+                 case 'a':
+                       all = 1;
+                       break;
+                 case '?':
+                       return 1;
+                 default:
+                       what = optc;
+               }
+
+       for (l = limits; l->name && l->option != what; l++)
+               ;
+       if (!l->name) {
+               internal_errorf(0, "ulimit: %c", what);
+               return 1;
+       }
+
+       wp += builtin_opt.optind;
+       set = *wp ? 1 : 0;
+       if (set) {
+               if (all || wp[1]) {
+                       bi_errorf("too many arguments");
+                       return 1;
+               }
+               if (strcmp(wp[0], "unlimited") == 0)
+                       val = KSH_RLIM_INFINITY;
+               else {
+                       long rval;
+
+                       if (!evaluate(wp[0], &rval, KSH_RETURN_ERROR))
+                               return 1;
+                       /* Avoid problems caused by typos that
+                        * evaluate misses due to evaluating unset
+                        * parameters to 0...
+                        * If this causes problems, will have to
+                        * add parameter to evaluate() to control
+                        * if unset params are 0 or an error.
+                        */
+                       if (!rval && !digit(wp[0][0])) {
+                           bi_errorf("invalid limit: %s", wp[0]);
+                           return 1;
+                       }
+                       val = (u_long)rval * l->factor;
+               }
+       }
+       if (all) {
+               for (l = limits; l->name; l++) {
+#ifdef HAVE_SETRLIMIT
+                       if (l->which == RLIMIT) {
+                               getrlimit(l->gcmd, &limit);
+                               if (how & SOFT)
+                                       val = limit.rlim_cur;
+                               else if (how & HARD)
+                                       val = limit.rlim_max;
+                       } else
+#endif /* HAVE_SETRLIMIT */
+#ifdef HAVE_ULIMIT
+                       {
+                               val = ulimit(l->gcmd, (rlim_t) 0);
+                       }
+#else /* HAVE_ULIMIT */
+                               ;
+#endif /* HAVE_ULIMIT */
+                       shprintf("%-20s ", l->name);
+#ifdef RLIM_INFINITY
+                       if (val == RLIM_INFINITY)
+                               shprintf("unlimited\n");
+                       else
+#endif /* RLIM_INFINITY */
+                       {
+                               val /= l->factor;
+                               shprintf("%ld\n", (long) val);
+                       }
+               }
+               return 0;
+       }
+#ifdef HAVE_SETRLIMIT
+       if (l->which == RLIMIT) {
+               getrlimit(l->gcmd, &limit);
+               if (set) {
+                       if (how & SOFT)
+                               limit.rlim_cur = val;
+                       if (how & HARD)
+                               limit.rlim_max = val;
+                       if (setrlimit(l->scmd, &limit) < 0) {
+                               if (errno == EPERM)
+                                       bi_errorf("exceeds allowable limit");
+                               else
+                                       bi_errorf("bad limit: %s",
+                                               strerror(errno));
+                               return 1;
+                       }
+               } else {
+                       if (how & SOFT)
+                               val = limit.rlim_cur;
+                       else if (how & HARD)
+                               val = limit.rlim_max;
+               }
+       } else
+#endif /* HAVE_SETRLIMIT */
+#ifdef HAVE_ULIMIT
+       {
+               if (set) {
+                       if (l->scmd == -1) {
+                               bi_errorf("can't change limit");
+                               return 1;
+                       } else if (ulimit(l->scmd, val) < 0) {
+                               bi_errorf("bad limit: %s", strerror(errno));
+                               return 1;
+                       }
+               } else
+                       val = ulimit(l->gcmd, (rlim_t) 0);
+       }
+#else /* HAVE_ULIMIT */
+               ;
+#endif /* HAVE_ULIMIT */
+       if (!set) {
+#ifdef RLIM_INFINITY
+               if (val == RLIM_INFINITY)
+                       shprintf("unlimited\n");
+               else
+#endif /* RLIM_INFINITY */
+               {
+                       val /= l->factor;
+                       shprintf("%ld\n", (long) val);
+               }
+       }
+       return 0;
+}
diff --git a/bin/ksh/conf-end.h b/bin/ksh/conf-end.h
new file mode 100644 (file)
index 0000000..67451f1
--- /dev/null
@@ -0,0 +1,57 @@
+/*     $NetBSD: conf-end.h,v 1.2 1997/01/12 19:11:43 tls Exp $ */
+
+/*
+ * End of configuration stuff for PD ksh.
+ *
+ * RCSid: $NetBSD: conf-end.h,v 1.2 1997/01/12 19:11:43 tls Exp $
+ */
+
+#if defined(EMACS) || defined(VI)
+# define       EDIT
+#else
+# undef                EDIT
+#endif
+
+/* Editing implies history */
+#if defined(EDIT) && !defined(HISTORY)
+# define HISTORY
+#endif /* EDIT */
+
+/*
+ * if you don't have mmap() you can't use Peter Collinson's history
+ * mechanism.  If that is the case, then define EASY_HISTORY
+ */
+#if defined(HISTORY) && (!defined(COMPLEX_HISTORY) || !defined(HAVE_MMAP) || !defined(HAVE_FLOCK))
+# undef COMPLEX_HISTORY
+# define EASY_HISTORY                  /* sjg's trivial history file */
+#endif
+
+/* Can we safely catch sigchld and wait for processes? */
+#if (defined(HAVE_WAITPID) || defined(HAVE_WAIT3)) \
+    && (defined(POSIX_SIGNALS) || defined(BSD42_SIGNALS))
+# define JOB_SIGS
+#endif
+
+#if !defined(JOB_SIGS) || !(defined(POSIX_PGRP) || defined(BSD_PGRP))
+# undef JOBS /* if no JOB_SIGS, no job control support */
+#endif
+
+/* pdksh assumes system calls return EINTR if a signal happened (this so
+ * the signal handler doesn't have to longjmp()).  I don't know if this
+ * happens (or can be made to happen) with sigset() et. al. (the bsd41 signal
+ * routines), so, the autoconf stuff checks what they do and defines
+ * SIGNALS_DONT_INTERRUPT if signals don't interrupt read().
+ * If SIGNALS_DONT_INTERRUPT isn't defined and your compiler chokes on this,
+ * delete the hash in front of the error (and file a bug report).
+ */
+#ifdef SIGNALS_DONT_INTERRUPT
+  # error pdksh needs interruptable system calls.
+#endif /* SIGNALS_DONT_INTERRUPT */
+
+#ifdef HAVE_GCC_FUNC_ATTR
+# define GCC_FUNC_ATTR(x)      __attribute__((x))
+# define GCC_FUNC_ATTR2(x,y)   __attribute__((x,y))
+#else
+# define GCC_FUNC_ATTR(x)
+# define GCC_FUNC_ATTR2(x,y)
+#endif /* HAVE_GCC_FUNC_ATTR */
diff --git a/bin/ksh/config.h b/bin/ksh/config.h
new file mode 100644 (file)
index 0000000..e8ae8e6
--- /dev/null
@@ -0,0 +1,366 @@
+/* config.h.  Generated automatically by configure.  */
+/* config.h.in.  Generated automatically from configure.in by autoheader.  */
+/*
+ * This file, acconfig.h, which is a part of pdksh (the public domain ksh),
+ * is placed in the public domain.  It comes with no licence, warranty
+ * or guarantee of any kind (i.e., at your own risk).
+ */
+
+#ifndef CONFIG_H
+#define CONFIG_H
+
+
+/* Define if on AIX 3.
+   System headers sometimes define this.
+   We just want to avoid a redefinition error message.  */
+#ifndef _ALL_SOURCE
+/* #undef _ALL_SOURCE */
+#endif
+
+/* Define if the closedir function returns void instead of int.  */
+/* #undef CLOSEDIR_VOID */
+
+/* Define to empty if the keyword does not work.  */
+/* #undef const */
+
+
+/* Define to `int' if <sys/types.h> doesn't define.  */
+/* #undef gid_t */
+
+/* Define if you have a working `mmap' system call.  */
+/* #undef HAVE_MMAP */
+
+/* Define if your struct stat has st_rdev.  */
+#define HAVE_ST_RDEV 1
+
+/* Define if you have <sys/wait.h> that is POSIX.1 compatible.  */
+#define HAVE_SYS_WAIT_H 1
+
+/* Define if you have <unistd.h>.  */
+#define HAVE_UNISTD_H 1
+
+/* Define if on MINIX.  */
+#define _MINIX 1
+
+/* Define to `int' if <sys/types.h> doesn't define.  */
+/* #undef mode_t */
+
+/* Define to `long' if <sys/types.h> doesn't define.  */
+/* #undef off_t */
+
+/* Define to `int' if <sys/types.h> doesn't define.  */
+/* #undef pid_t */
+
+/* Define if the system does not provide POSIX.1 features except
+   with this defined.  */
+#define _POSIX_1_SOURCE 2
+
+/* Define if you need to in order for stat and other things to work.  */
+#define _POSIX_SOURCE 1
+
+/* Define as the return type of signal handlers (int or void).  */
+#define RETSIGTYPE void
+
+/* Define if the `S_IS*' macros in <sys/stat.h> do not work properly.  */
+/* #undef STAT_MACROS_BROKEN */
+
+/* Define if `sys_siglist' is declared by <signal.h>.  */
+#define SYS_SIGLIST_DECLARED 1
+
+/* Define if you can safely include both <sys/time.h> and <time.h>.  */
+#define TIME_WITH_SYS_TIME 1
+
+/* Define to `int' if <sys/types.h> doesn't define.  */
+/* #undef uid_t */
+
+/* Define if the closedir function returns void instead of int.  */
+/* #undef VOID_CLOSEDIR */
+
+/* Define if your kernal doesn't handle scripts starting with #! */
+/* #undef SHARPBANG */
+
+/* Define if dup2() preserves the close-on-exec flag (ultrix does this) */
+/* #undef DUP2_BROKEN */
+
+/* Define as the return value of signal handlers (0 or ).  */
+#define RETSIGVAL 
+
+/* Define if you have posix signal routines (sigaction(), et. al.) */
+#define POSIX_SIGNALS 1
+
+/* Define if you have BSD4.2 signal routines (sigsetmask(), et. al.) */
+/* #undef BSD42_SIGNALS */
+
+/* Define if you have BSD4.1 signal routines (sigset(), et. al.) */
+/* #undef BSD41_SIGNALS */
+
+/* Define if you have v7 signal routines (signal(), signal reset on delivery) */
+/* #undef V7_SIGNALS */
+
+/* Define to use the fake posix signal routines (sigact.[ch]) */
+/* #undef USE_FAKE_SIGACT */
+
+/* Define if signals don't interrupt read() */
+/* #undef SIGNALS_DONT_INTERRUPT */
+
+/* Define if you have bsd versions of the setpgrp() and getpgrp() routines */
+/* #undef BSD_PGRP */
+
+/* Define if you have POSIX versions of the setpgid() and getpgrp() routines */
+/* #undef POSIX_PGRP */
+
+/* Define if you have sysV versions of the setpgrp() and getpgrp() routines */
+/* #undef SYSV_PGRP */
+
+/* Define if you don't have setpgrp(), setpgid() or getpgrp() routines */
+#define NO_PGRP 1
+
+/* Define to char if your compiler doesn't like the void keyword */
+/* #undef void */
+
+/* Define to nothing if compiler doesn't like the volatile keyword */
+/* #undef volatile */
+
+/* Define if C compiler groks function prototypes */
+#define HAVE_PROTOTYPES 1
+
+/* Define if C compiler groks __attribute__((...)) (const, noreturn, format) */
+#define HAVE_GCC_FUNC_ATTR 1
+
+/* Define to 32-bit signed integer type if <sys/types.h> doesn't define */
+/* #undef clock_t */
+
+/* Define to the type of struct rlimit fields if the rlim_t type is missing */
+/* #undef rlim_t */
+
+/* Define if time() is declared in <time.h> */
+#define TIME_DECLARED 1
+
+/* Define to `unsigned' if <signal.h> doesn't define */
+/* #undef sigset_t */
+
+/* Define if sys_errlist[] and sys_nerr are in the C library */
+#define HAVE_SYS_ERRLIST 1
+
+/* Define if sys_errlist[] and sys_nerr are defined in <errno.h> */
+#define SYS_ERRLIST_DECLARED 1
+
+/* Define if sys_siglist[] is in the C library */
+/* #undef HAVE_SYS_SIGLIST */
+
+/* Define if you have a sane <termios.h> header file */
+#define HAVE_TERMIOS_H 1
+
+/* Define if you have a memset() function in your C library */
+#define HAVE_MEMSET 1
+
+/* Define if you have a memmove() function in your C library */
+#define HAVE_MEMMOVE 1
+
+/* Define if you have a bcopy() function in your C library */
+/* #undef HAVE_BCOPY */
+
+/* Define if you have a lstat() function in your C library */
+#define HAVE_LSTAT 1
+
+/* Define if you have a sane <termio.h> header file */
+/* #undef HAVE_TERMIO_H */
+
+/* Define if you don't have times() or if it always returns 0 */
+/* #undef TIMES_BROKEN */
+
+/* Define if opendir() will open non-directory files */
+/* #undef OPENDIR_DOES_NONDIR */
+
+/* Define if the pgrp of setpgrp() can't be the pid of a zombie process */
+/* #undef NEED_PGRP_SYNC */
+
+/* Define if you arg running SCO unix */
+/* #undef OS_SCO */
+
+/* Define if you arg running ISC unix */
+/* #undef OS_ISC */
+
+/* Define if you arg running OS2 with the EMX library */
+/* #undef OS2 */
+
+/* Define if you have a POSIX.1 compatiable <sys/wait.h> */
+#define POSIX_SYS_WAIT 1
+
+/* Define if your OS maps references to /dev/fd/n to file descriptor n */
+/* #undef HAVE_DEV_FD */
+
+/* Define if your C library's getwd/getcwd function dumps core in unreadable
+ * directories.  */
+/* #undef HPUX_GETWD_BUG */
+
+/* Default PATH (see comments in configure.in for more details) */
+#define DEFAULT_PATH "/bin:/usr/bin:/usr/ucb"
+
+/* Include ksh features? (see comments in configure.in for more details) */
+#define KSH 1
+
+/* Include emacs editing? (see comments in configure.in for more details) */
+#define EMACS 1
+
+/* Include vi editing? (see comments in configure.in for more details) */
+#define VI 1
+
+/* Include job control? (see comments in configure.in for more details) */
+#define JOBS 1
+
+/* Include brace-expansion? (see comments in configure.in for more details) */
+#define BRACE_EXPAND 1
+
+/* Include any history? (see comments in configure.in for more details) */
+#define HISTORY 1
+
+/* Include complex history? (see comments in configure.in for more details) */
+/* #undef COMPLEX_HISTORY */
+
+/* Strict POSIX behaviour? (see comments in configure.in for more details) */
+/* #undef POSIXLY_CORRECT */
+
+/* Specify default $ENV? (see comments in configure.in for more details) */
+/* #undef DEFAULT_ENV */
+
+/* Include shl(1) support? (see comments in configure.in for more details) */
+/* #undef SWTCH */
+
+/* Include game-of-life? (see comments in configure.in for more details) */
+/* #undef SILLY */
+
+/* The number of bytes in a int.  */
+#define SIZEOF_INT 4
+
+/* The number of bytes in a long.  */
+#define SIZEOF_LONG 4
+
+/* Define if you have the _setjmp function.  */
+/* #undef HAVE__SETJMP */
+
+/* Define if you have the confstr function.  */
+/* #undef HAVE_CONFSTR */
+
+/* Define if you have the dup2 function.  */
+#define HAVE_DUP2 1
+
+/* Define if you have the flock function.  */
+#define HAVE_FLOCK 1
+
+/* Define if you have the getcwd function.  */
+#define HAVE_GETCWD 1
+
+/* Define if you have the getgroups function.  */
+/* #undef HAVE_GETGROUPS */
+
+/* Define if you have the getpagesize function.  */
+#define HAVE_GETPAGESIZE 1
+
+/* Define if you have the getrusage function.  */
+/* #undef HAVE_GETRUSAGE */
+
+/* Define if you have the getwd function.  */
+/* #undef HAVE_GETWD */
+
+/* Define if you have the killpg function.  */
+#define HAVE_KILLPG 1
+
+/* Define if you have the nice function.  */
+#define HAVE_NICE 1
+
+/* Define if you have the setrlimit function.  */
+/* #undef HAVE_SETRLIMIT */
+
+/* Define if you have the sigsetjmp function.  */
+#define HAVE_SIGSETJMP 1
+
+/* Define if you have the strcasecmp function.  */
+#define HAVE_STRCASECMP 1
+
+/* Define if you have the strerror function.  */
+#define HAVE_STRERROR 1
+
+/* Define if you have the strlcat function.  */
+#define HAVE_STRLCAT 1
+/* Define if you have the strlcpy function.  */
+#define HAVE_STRLCPY 1
+
+/* Define if you have the strstr function.  */
+#define HAVE_STRSTR 1
+
+/* Define if you have the sysconf function.  */
+#define HAVE_SYSCONF 1
+
+/* Define if you have the tcsetpgrp function.  */
+#define HAVE_TCSETPGRP 1
+
+/* Define if you have the ulimit function.  */
+#define HAVE_ULIMIT 1
+
+/* Define if you have the valloc function.  */
+#define HAVE_VALLOC 1
+
+/* Define if you have the wait3 function.  */
+/* #undef HAVE_WAIT3 */
+
+/* Define if you have the waitpid function.  */
+#define HAVE_WAITPID 1
+
+/* Define if you have the <dirent.h> header file.  */
+#define HAVE_DIRENT_H 1
+
+/* Define if you have the <fcntl.h> header file.  */
+#define HAVE_FCNTL_H 1
+
+/* Define if you have the <limits.h> header file.  */
+#define HAVE_LIMITS_H 1
+
+/* Define if you have the <memory.h> header file.  */
+#define HAVE_MEMORY_H 1
+
+/* Define if you have the <ndir.h> header file.  */
+/* #undef HAVE_NDIR_H */
+
+/* Define if you have the <paths.h> header file.  */
+#define HAVE_PATHS_H 1
+
+/* Define if you have the <stddef.h> header file.  */
+#define HAVE_STDDEF_H 1
+
+/* Define if you have the <stdlib.h> header file.  */
+#define HAVE_STDLIB_H 1
+
+/* Define if you have the <string.h> header file.  */
+#define HAVE_STRING_H 1
+
+/* Define if you have the <sys/dir.h> header file.  */
+/* #undef HAVE_SYS_DIR_H */
+
+/* Define if you have the <sys/ndir.h> header file.  */
+/* #undef HAVE_SYS_NDIR_H */
+
+/* Define if you have the <sys/param.h> header file.  */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define if you have the <sys/resource.h> header file.  */
+#define HAVE_SYS_RESOURCE_H 1
+
+/* Define if you have the <sys/time.h> header file.  */
+#define HAVE_SYS_TIME_H 1
+
+/* Define if you have the <sys/wait.h> header file.  */
+#define HAVE_SYS_WAIT_H 1
+
+/* Define if you have the <ulimit.h> header file.  */
+#define HAVE_ULIMIT_H 1
+
+/* Define if you have the <values.h> header file.  */
+/* #undef HAVE_VALUES_H */
+
+/* Need to use a separate file to keep the configure script from commenting
+ * out the undefs....
+ */
+#include "conf-end.h"
+
+#endif /* CONFIG_H */
diff --git a/bin/ksh/edit.c b/bin/ksh/edit.c
new file mode 100644 (file)
index 0000000..07a2f90
--- /dev/null
@@ -0,0 +1,1095 @@
+/*     $NetBSD: edit.c,v 1.25 2010/06/05 03:02:37 sjg Exp $    */
+
+/*
+ * Command line editing - common code
+ *
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: edit.c,v 1.25 2010/06/05 03:02:37 sjg Exp $");
+#endif
+
+
+#include "config.h"
+#ifdef EDIT
+
+#include "sh.h"
+#include "tty.h"
+#define EXTERN
+#include "edit.h"
+#undef EXTERN
+#ifdef OS_SCO  /* SCO Unix 3.2v4.1 */
+# include <sys/stream.h>       /* needed for <sys/ptem.h> */
+# include <sys/ptem.h>         /* needed for struct winsize */
+#endif /* OS_SCO */
+#include <sys/ioctl.h>
+#include <ctype.h>
+#include "ksh_stat.h"
+
+
+#if defined(TIOCGWINSZ)
+static RETSIGTYPE x_sigwinch ARGS((int sig));
+static int got_sigwinch;
+static void check_sigwinch ARGS((void));
+#endif /* TIOCGWINSZ */
+
+static int     x_file_glob ARGS((int flags, const char *str, int slen,
+                                 char ***wordsp));
+static int     x_command_glob ARGS((int flags, const char *str, int slen,
+                                    char ***wordsp));
+static int     x_locate_word ARGS((const char *buf, int buflen, int pos,
+                                   int *startp, int *is_command));
+
+static char vdisable_c;
+
+
+/* Called from main */
+void
+x_init()
+{
+       /* set to -2 to force initial binding */
+       edchars.erase = edchars.kill = edchars.intr = edchars.quit
+               = edchars.eof = -2;
+       /* default value for deficient systems */
+       edchars.werase = 027;   /* ^W */
+
+#ifdef TIOCGWINSZ
+# ifdef SIGWINCH
+       if (setsig(&sigtraps[SIGWINCH], x_sigwinch, SS_RESTORE_ORIG|SS_SHTRAP))
+               sigtraps[SIGWINCH].flags |= TF_SHELL_USES;
+# endif /* SIGWINCH */
+       got_sigwinch = 1; /* force initial check */
+       check_sigwinch();
+#endif /* TIOCGWINSZ */
+
+#ifdef EMACS
+       x_init_emacs();
+#endif /* EMACS */
+
+       /* Bizarreness to figure out how to disable
+        * a struct termios.c_cc[] char
+        */
+#ifdef _POSIX_VDISABLE
+       if (_POSIX_VDISABLE >= 0)
+               vdisable_c = (char) _POSIX_VDISABLE;
+       else
+               /* `feature not available' */
+               vdisable_c = (char) 0377;
+#else
+# if defined(HAVE_PATHCONF) && defined(_PC_VDISABLE)
+       vdisable_c = fpathconf(tty_fd, _PC_VDISABLE);
+# else
+       vdisable_c = (char) 0377;       /* default to old BSD value */
+# endif
+#endif /* _POSIX_VDISABLE */
+}
+
+#if defined(TIOCGWINSZ)
+static RETSIGTYPE
+x_sigwinch(sig)
+       int sig;
+{
+       got_sigwinch = 1;
+       return RETSIGVAL;
+}
+
+static void
+check_sigwinch ARGS((void))
+{
+       if (got_sigwinch) {
+               struct winsize ws;
+
+               got_sigwinch = 0;
+               if (procpid == kshpid && ioctl(tty_fd, TIOCGWINSZ, &ws) >= 0) {
+                       struct tbl *vp;
+
+                       /* Do NOT export COLUMNS/LINES.  Many applications
+                        * check COLUMNS/LINES before checking ws.ws_col/row,
+                        * so if the app is started with C/L in the environ
+                        * and the window is then resized, the app won't
+                        * see the change cause the environ doesn't change.
+                        */
+                       if (ws.ws_col) {
+                               x_cols = ws.ws_col < MIN_COLS ? MIN_COLS
+                                               : ws.ws_col;
+                               
+                               if ((vp = typeset("COLUMNS", 0, 0, 0, 0)))
+                                       setint(vp, (long) ws.ws_col);
+                       }
+                       if (ws.ws_row
+                           && (vp = typeset("LINES", 0, 0, 0, 0)))
+                               setint(vp, (long) ws.ws_row);
+               }
+       }
+}
+#endif /* TIOCGWINSZ */
+
+/*
+ * read an edited command line
+ */
+int
+x_read(buf, len)
+       char *buf;
+       size_t len;
+{
+       int     i;
+
+       x_mode(TRUE);
+#ifdef EMACS
+       if (Flag(FEMACS) || Flag(FGMACS))
+               i = x_emacs(buf, len);
+       else
+#endif
+#ifdef VI
+       if (Flag(FVI))
+               i = x_vi(buf, len);
+       else
+#endif
+               i = -1;         /* internal error */
+       x_mode(FALSE);
+#if defined(TIOCGWINSZ)
+       if (got_sigwinch)
+               check_sigwinch();
+#endif /* TIOCGWINSZ */
+
+       return i;
+}
+
+/* tty I/O */
+
+int
+x_getc()
+{
+#ifdef OS2
+       unsigned char c = _read_kbd(0, 1, 0);
+       return c == 0 ? 0xE0 : c;
+#else /* OS2 */
+       char c;
+       int n;
+
+       while ((n = blocking_read(0, &c, 1)) < 0 && errno == EINTR)
+               if (trap) {
+                       x_mode(FALSE);
+                       runtraps(0);
+                       x_mode(TRUE);
+               }
+       if (n != 1)
+               return -1;
+       return (int) (unsigned char) c;
+#endif /* OS2 */
+}
+
+void
+x_flush()
+{
+       shf_flush(shl_out);
+}
+
+void
+x_putc(c)
+       int c;
+{
+       shf_putc(c, shl_out);
+}
+
+void
+x_puts(s)
+       const char *s;
+{
+       while (*s != 0)
+               shf_putc(*s++, shl_out);
+}
+
+bool_t
+x_mode(onoff)
+       bool_t  onoff;
+{
+       static bool_t   x_cur_mode;
+       bool_t          prev;
+
+       if (x_cur_mode == onoff)
+               return x_cur_mode;
+       prev = x_cur_mode;
+       x_cur_mode = onoff;
+
+       if (onoff) {
+               TTY_state       cb;
+               X_chars         oldchars;
+               
+               oldchars = edchars;
+               cb = tty_state;
+
+#if defined(HAVE_TERMIOS_H) || defined(HAVE_TERMIO_H)
+               edchars.erase = cb.c_cc[VERASE];
+               edchars.kill = cb.c_cc[VKILL];
+               edchars.intr = cb.c_cc[VINTR];
+               edchars.quit = cb.c_cc[VQUIT];
+               edchars.eof = cb.c_cc[VEOF];
+# ifdef VWERASE
+               edchars.werase = cb.c_cc[VWERASE];
+# endif
+# ifdef _CRAY2         /* brain-damaged terminal handler */
+               cb.c_lflag &= ~(ICANON|ECHO);
+               /* rely on print routine to map '\n' to CR,LF */
+# else
+               cb.c_iflag &= ~(INLCR|ICRNL);
+#  ifdef _BSD_SYSV     /* need to force CBREAK instead of RAW (need CRMOD on output) */
+               cb.c_lflag &= ~(ICANON|ECHO);
+#  else
+#   ifdef SWTCH        /* need CBREAK to handle swtch char */
+               cb.c_lflag &= ~(ICANON|ECHO);
+               cb.c_lflag |= ISIG;
+               cb.c_cc[VINTR] = vdisable_c;
+               cb.c_cc[VQUIT] = vdisable_c;
+#   else
+               cb.c_lflag &= ~(ISIG|ICANON|ECHO);
+#   endif
+#  endif
+#  ifdef VLNEXT
+               /* osf/1 processes lnext when ~icanon */
+               cb.c_cc[VLNEXT] = vdisable_c;
+#  endif /* VLNEXT */
+#  ifdef VDISCARD
+               /* sunos 4.1.x & osf/1 processes discard(flush) when ~icanon */
+               cb.c_cc[VDISCARD] = vdisable_c;
+#  endif /* VDISCARD */
+               cb.c_cc[VTIME] = 0;
+               cb.c_cc[VMIN] = 1;
+# endif        /* _CRAY2 */
+#else
+       /* Assume BSD tty stuff. */
+               edchars.erase = cb.sgttyb.sg_erase;
+               edchars.kill = cb.sgttyb.sg_kill;
+               cb.sgttyb.sg_flags &= ~ECHO;
+               cb.sgttyb.sg_flags |= CBREAK;
+#  ifdef TIOCGATC
+               edchars.intr = cb.lchars.tc_intrc;
+               edchars.quit = cb.lchars.tc_quitc;
+               edchars.eof = cb.lchars.tc_eofc;
+               edchars.werase = cb.lchars.tc_werasc;
+               cb.lchars.tc_suspc = -1;
+               cb.lchars.tc_dsuspc = -1;
+               cb.lchars.tc_lnextc = -1;
+               cb.lchars.tc_statc = -1;
+               cb.lchars.tc_intrc = -1;
+               cb.lchars.tc_quitc = -1;
+               cb.lchars.tc_rprntc = -1;
+#  else
+               edchars.intr = cb.tchars.t_intrc;
+               edchars.quit = cb.tchars.t_quitc;
+               edchars.eof = cb.tchars.t_eofc;
+               cb.tchars.t_intrc = -1;
+               cb.tchars.t_quitc = -1;
+#   ifdef TIOCGLTC
+               edchars.werase = cb.ltchars.t_werasc;
+               cb.ltchars.t_suspc = -1;
+               cb.ltchars.t_dsuspc = -1;
+               cb.ltchars.t_lnextc = -1;
+               cb.ltchars.t_rprntc = -1;
+#   endif
+#  endif /* TIOCGATC */
+#endif /* HAVE_TERMIOS_H || HAVE_TERMIO_H */
+
+               set_tty(tty_fd, &cb, TF_WAIT);
+
+#ifdef __CYGWIN__
+               if (edchars.eof == '\0')
+                       edchars.eof = '\4';
+#endif /* __CYGWIN__ */
+
+               /* Convert unset values to internal `unset' value */
+               if (edchars.erase == vdisable_c)
+                       edchars.erase = -1;
+               if (edchars.kill == vdisable_c)
+                       edchars.kill = -1;
+               if (edchars.intr == vdisable_c)
+                       edchars.intr = -1;
+               if (edchars.quit == vdisable_c)
+                       edchars.quit = -1;
+               if (edchars.eof == vdisable_c)
+                       edchars.eof = -1;
+               if (edchars.werase == vdisable_c)
+                       edchars.werase = -1;
+               if (memcmp(&edchars, &oldchars, sizeof(edchars)) != 0) {
+#ifdef EMACS
+                       x_emacs_keys(&edchars);
+#endif
+               }
+       } else {
+               /* TF_WAIT doesn't seem to be necessary when leaving xmode */
+               set_tty(tty_fd, &tty_state, TF_NONE);
+       }
+
+       return prev;
+}
+
+/* NAME:
+ *      promptlen - calculate the length of PS1 etc.
+ *
+ * DESCRIPTION:
+ *      This function is based on a fix from guy@demon.co.uk
+ *      It fixes a bug in that if PS1 contains '!', the length
+ *      given by strlen() is probably wrong.
+ *
+ * RETURN VALUE:
+ *      length
+ */
+int
+promptlen(cp, spp)
+    const char  *cp;
+    const char **spp;
+{
+    int count = 0;
+    const char *sp = cp;
+    char delimiter = 0;
+    int indelimit = 0;
+
+    /* Undocumented AT&T ksh feature:
+     * If the second char in the prompt string is \r then the first char
+     * is taken to be a non-printing delimiter and any chars between two
+     * instances of the delimiter are not considered to be part of the
+     * prompt length
+     */
+    if (*cp && cp[1] == '\r') {
+       delimiter = *cp;
+       cp += 2;
+    }
+
+    for (; *cp; cp++) {
+       if (indelimit && *cp != delimiter)
+           ;
+       else if (*cp == '\n' || *cp == '\r') {
+           count = 0;
+           sp = cp + 1;
+       } else if (*cp == '\t') {
+           count = (count | 7) + 1;
+       } else if (*cp == '\b') {
+           if (count > 0)
+               count--;
+       } else if (*cp == delimiter)
+           indelimit = !indelimit;
+       else
+           count++;
+    }
+    if (spp)
+       *spp = sp;
+    return count;
+}
+
+void
+set_editmode(ed)
+       const char *ed;
+{
+       static const enum sh_flag edit_flags[] = {
+#ifdef EMACS
+                       FEMACS, FGMACS,
+#endif
+#ifdef VI
+                       FVI,
+#endif
+                   };
+       char *rcp;
+       size_t i;
+
+       if ((rcp = ksh_strrchr_dirsep(ed)))
+               ed = ++rcp;
+       for (i = 0; i < NELEM(edit_flags); i++)
+               if (strstr(ed, goptions[(int) edit_flags[i]].name)) {
+                       change_flag(edit_flags[i], OF_SPECIAL, 1);
+                       return;
+               }
+}
+
+/* ------------------------------------------------------------------------- */
+/*           Misc common code for vi/emacs                                  */
+
+/* Handle the commenting/uncommenting of a line.
+ * Returns:
+ *     1 if a carriage return is indicated (comment added)
+ *     0 if no return (comment removed)
+ *     -1 if there is an error (not enough room for comment chars)
+ * If successful, *lenp contains the new length.  Note: cursor should be
+ * moved to the start of the line after (un)commenting.
+ */
+int
+x_do_comment(buf, bsize, lenp)
+       char *buf;
+       int bsize;
+       int *lenp;
+{
+       int i, j;
+       int len = *lenp;
+
+       if (len == 0)
+               return 1; /* somewhat arbitrary - it's what at&t ksh does */
+
+       /* Already commented? */
+       if (buf[0] == '#') {
+               int saw_nl = 0;
+
+               for (j = 0, i = 1; i < len; i++) {
+                       if (!saw_nl || buf[i] != '#')
+                               buf[j++] = buf[i];
+                       saw_nl = buf[i] == '\n';
+               }
+               *lenp = j;
+               return 0;
+       } else {
+               int n = 1;
+
+               /* See if there's room for the #'s - 1 per \n */
+               for (i = 0; i < len; i++)
+                       if (buf[i] == '\n')
+                               n++;
+               if (len + n >= bsize)
+                       return -1;
+               /* Now add them... */
+               for (i = len, j = len + n; --i >= 0; ) {
+                       if (buf[i] == '\n')
+                               buf[--j] = '#';
+                       buf[--j] = buf[i];
+               }
+               buf[0] = '#';
+               *lenp += n;
+               return 1;
+       }
+}
+
+/* ------------------------------------------------------------------------- */
+/*           Common file/command completion code for vi/emacs               */
+
+
+static char    *add_glob ARGS((const char *, int));
+static void    glob_table ARGS((const char *, XPtrV *, struct table *));
+static void    glob_path ARGS((int, const char *, XPtrV *, const char *));
+
+#if 0 /* not used... */
+int    x_complete_word ARGS((const char *, int, int, int *, char **));
+int
+x_complete_word(str, slen, is_command, nwordsp, ret)
+       const char *str;
+       int slen;
+       int is_command;
+       int *nwordsp;
+       char **ret;
+{
+       int nwords;
+       int prefix_len;
+       char **words;
+
+       nwords = (is_command ? x_command_glob : x_file_glob)(XCF_FULLPATH,
+                               str, slen, &words);
+       *nwordsp = nwords;
+       if (nwords == 0) {
+               *ret = (char *) 0;
+               return -1;
+       }
+
+       prefix_len = x_longest_prefix(nwords, words);
+       *ret = str_nsave(words[0], prefix_len, ATEMP);
+       x_free_words(nwords, words);
+       return prefix_len;
+}
+#endif /* 0 */
+
+void
+x_print_expansions(nwords, words, is_command)
+       int nwords;
+       char *const *words;
+       int is_command;
+{
+       int use_copy = 0;
+       int prefix_len;
+       XPtrV l;
+
+       l.beg = NULL;
+
+       /* Check if all matches are in the same directory (in this
+        * case, we want to omit the directory name)
+        */
+       if (!is_command
+           && (prefix_len = x_longest_prefix(nwords, words)) > 0)
+       {
+               int i;
+
+               /* Special case for 1 match (prefix is whole word) */
+               if (nwords == 1)
+                       prefix_len = x_basename(words[0], (char *) 0);
+               /* Any (non-trailing) slashes in non-common word suffixes? */
+               for (i = 0; i < nwords; i++)
+                       if (x_basename(words[i] + prefix_len, (char *) 0)
+                                                       > prefix_len)
+                               break;
+               /* All in same directory? */
+               if (i == nwords) {
+                       while (prefix_len > 0
+                              && !ISDIRSEP(words[0][prefix_len - 1]))
+                               prefix_len--;
+                       use_copy = 1;
+                       XPinit(l, nwords + 1);
+                       for (i = 0; i < nwords; i++)
+                               XPput(l, words[i] + prefix_len);
+                       XPput(l, (char *) 0);
+               }
+       }
+
+       /*
+        * Enumerate expansions
+        */
+       x_putc('\r');
+       x_putc('\n');
+       pr_list(use_copy ? (char **) XPptrv(l) : words);
+
+       if (use_copy)
+               XPfree(l); /* not x_free_words() */
+}
+
+/*
+ *  Do file globbing:
+ *     - appends * to (copy of) str if no globbing chars found
+ *     - does expansion, checks for no match, etc.
+ *     - sets *wordsp to array of matching strings
+ *     - returns number of matching strings
+ */
+static int
+x_file_glob(flags, str, slen, wordsp)
+       int flags;
+       const char *str;
+       int slen;
+       char ***wordsp;
+{
+       char *toglob;
+       char **words;
+       int nwords, i, idx, escaping;
+       XPtrV w;
+       struct source *s, *sold;
+
+       if (slen < 0)
+               return 0;
+
+       toglob = add_glob(str, slen);
+
+       /* remove all escaping backward slashes */
+       escaping = 0;
+       for(i = 0, idx = 0; toglob[i]; i++) {
+               if (toglob[i] == '\\' && !escaping) {
+                       escaping = 1;
+                       continue;
+               }
+
+               toglob[idx] = toglob[i];
+               idx++;
+               if (escaping) escaping = 0;
+       }
+       toglob[idx] = '\0';
+
+       /*
+        * Convert "foo*" (toglob) to an array of strings (words)
+        */
+       sold = source;
+       s = pushs(SWSTR, ATEMP);
+       s->start = s->str = toglob;
+       source = s;
+       if (yylex(ONEWORD) != LWORD) {
+               source = sold;
+               internal_errorf(0, "fileglob: substitute error");
+               return 0;
+       }
+       source = sold;
+       XPinit(w, 32);
+       expand(yylval.cp, &w, DOGLOB|DOTILDE|DOMARKDIRS);
+       XPput(w, NULL);
+       words = (char **) XPclose(w);
+
+       for (nwords = 0; words[nwords]; nwords++)
+               ;
+       if (nwords == 1) {
+               struct stat statb;
+
+               /* Check if globbing failed (returned glob pattern),
+                * but be careful (E.g. toglob == "ab*" when the file
+                * "ab*" exists is not an error).
+                * Also, check for empty result - happens if we tried
+                * to glob something which evaluated to an empty
+                * string (e.g., "$FOO" when there is no FOO, etc).
+                */
+               if ((strcmp(words[0], toglob) == 0
+                    && stat(words[0], &statb) < 0)
+                   || words[0][0] == '\0')
+               {
+                       x_free_words(nwords, words);
+                       words = NULL;
+                       nwords = 0;
+               }
+       }
+       afree(toglob, ATEMP);
+
+       if (nwords) {
+               *wordsp = words;
+       } else if (words) {
+               x_free_words(nwords, words);
+               *wordsp = NULL;
+       }
+       return nwords;
+}
+
+/* Data structure used in x_command_glob() */
+struct path_order_info {
+       char *word;
+       int base;
+       int path_order;
+};
+
+static int path_order_cmp(const void *aa, const void *bb);
+
+/* Compare routine used in x_command_glob() */
+static int
+path_order_cmp(aa, bb)
+       const void *aa;
+       const void *bb;
+{
+       const struct path_order_info *a = (const struct path_order_info *) aa;
+       const struct path_order_info *b = (const struct path_order_info *) bb;
+       int t;
+
+       t = FILECMP(a->word + a->base, b->word + b->base);
+       return t ? t : a->path_order - b->path_order;
+}
+
+static int
+x_command_glob(flags, str, slen, wordsp)
+       int flags;
+       const char *str;
+       int slen;
+       char ***wordsp;
+{
+       char *toglob;
+       char *pat;
+       char *fpath;
+       int nwords;
+       XPtrV w;
+       struct block *l;
+
+       if (slen < 0)
+               return 0;
+
+       toglob = add_glob(str, slen);
+
+       /* Convert "foo*" (toglob) to a pattern for future use */
+       pat = evalstr(toglob, DOPAT|DOTILDE);
+       afree(toglob, ATEMP);
+
+       XPinit(w, 32);
+
+       glob_table(pat, &w, &keywords);
+       glob_table(pat, &w, &aliases);
+       glob_table(pat, &w, &builtins);
+       for (l = e->loc; l; l = l->next)
+               glob_table(pat, &w, &l->funs);
+
+       glob_path(flags, pat, &w, path);
+       if ((fpath = str_val(global("FPATH"))) != null)
+               glob_path(flags, pat, &w, fpath);
+
+       nwords = XPsize(w);
+
+       if (!nwords) {
+               *wordsp = (char **) 0;
+               XPfree(w);
+               return 0;
+       }
+
+       /* Sort entries */
+       if (flags & XCF_FULLPATH) {
+               /* Sort by basename, then path order */
+               struct path_order_info *info;
+               struct path_order_info *last_info = 0;
+               char **words = (char **) XPptrv(w);
+               int path_order = 0;
+               int i;
+
+               info = (struct path_order_info *)
+                       alloc(sizeof(struct path_order_info) * nwords, ATEMP);
+               for (i = 0; i < nwords; i++) {
+                       info[i].word = words[i];
+                       info[i].base = x_basename(words[i], (char *) 0);
+                       if (!last_info || info[i].base != last_info->base
+                           || FILENCMP(words[i],
+                                       last_info->word, info[i].base) != 0)
+                       {
+                               last_info = &info[i];
+                               path_order++;
+                       }
+                       info[i].path_order = path_order;
+               }
+               qsort(info, nwords, sizeof(struct path_order_info),
+                       path_order_cmp);
+               for (i = 0; i < nwords; i++)
+                       words[i] = info[i].word;
+               afree((void *) info, ATEMP);
+       } else {
+               /* Sort and remove duplicate entries */
+               char **words = (char **) XPptrv(w);
+               int i, j;
+
+               qsortp(XPptrv(w), (size_t) nwords, xstrcmp);
+
+               for (i = j = 0; i < nwords - 1; i++) {
+                       if (strcmp(words[i], words[i + 1]))
+                               words[j++] = words[i];
+                       else
+                               afree(words[i], ATEMP);
+               }
+               words[j++] = words[i];
+               nwords = j;
+               w.cur = (void **) &words[j];
+       }
+
+       XPput(w, NULL);
+       *wordsp = (char **) XPclose(w);
+
+       return nwords;
+}
+
+#define IS_WORDC(c)    !( ctype(c, C_LEX1) || (c) == '\'' || (c) == '"'  \
+                           || (c) == '`' || (c) == '=' || (c) == ':' )
+
+static int
+x_locate_word(buf, buflen, pos, startp, is_commandp)
+       const char *buf;
+       int buflen;
+       int pos;
+       int *startp;
+       int *is_commandp;
+{
+       int p;
+       int start, end;
+
+       /* Bad call?  Probably should report error */
+       if (pos < 0 || pos > buflen) {
+               *startp = pos;
+               *is_commandp = 0;
+               return 0;
+       }
+       /* The case where pos == buflen happens to take care of itself... */
+
+       start = pos;
+       /* Keep going backwards to start of word (has effect of allowing
+        * one blank after the end of a word)
+        */
+       for (; (start > 0 && IS_WORDC(buf[start - 1]))
+               || (start > 1 && buf[start-2] == '\\'); start--)
+               ;
+       /* Go forwards to end of word */
+       for (end = start; end < buflen && IS_WORDC(buf[end]); end++) {
+               if (buf[end] == '\\' && (end+1) < buflen)
+                       end++;
+       }
+
+       if (is_commandp) {
+               int iscmd;
+
+               /* Figure out if this is a command */
+               for (p = start - 1; p >= 0 && isspace((unsigned char)buf[p]); p--)
+                       ;
+               iscmd = p < 0 || strchr(";|&()`", buf[p]);
+               if (iscmd) {
+                       /* If command has a /, path, etc. is not searched;
+                        * only current directory is searched, which is just
+                        * like file globbing.
+                        */
+                       for (p = start; p < end; p++)
+                               if (ISDIRSEP(buf[p]))
+                                       break;
+                       iscmd = p == end;
+               }
+               *is_commandp = iscmd;
+       }
+
+       *startp = start;
+
+       return end - start;
+}
+
+int
+x_cf_glob(flags, buf, buflen, pos, startp, endp, wordsp, is_commandp)
+       int flags;
+       const char *buf;
+       int buflen;
+       int pos;
+       int *startp;
+       int *endp;
+       char ***wordsp;
+       int *is_commandp;
+{
+       int len;
+       int nwords;
+       char **words;
+       int is_command;
+
+       len = x_locate_word(buf, buflen, pos, startp, &is_command);
+       if (!(flags & XCF_COMMAND))
+               is_command = 0;
+       /* Don't do command globing on zero length strings - it takes too
+        * long and isn't very useful.  File globs are more likely to be
+        * useful, so allow these.
+        */
+       if (len == 0 && is_command)
+               return 0;
+
+       nwords = (is_command ? x_command_glob : x_file_glob)(flags,
+                                   buf + *startp, len, &words);
+       if (nwords == 0) {
+               *wordsp = (char **) 0;
+               return 0;
+       }
+
+       if (is_commandp)
+               *is_commandp = is_command;
+       *wordsp = words;
+       *endp = *startp + len;
+
+       return nwords;
+}
+
+/* Given a string, copy it and possibly add a '*' to the end.  The
+ * new string is returned.
+ */
+static char *
+add_glob(str, slen)
+       const char *str;
+       int slen;
+{
+       char *toglob;
+       char *s;
+       bool_t saw_slash = FALSE;
+
+       if (slen < 0)
+               return (char *) 0;
+
+       toglob = str_nsave(str, slen + 1, ATEMP); /* + 1 for "*" */
+       toglob[slen] = '\0';
+
+       /*
+        * If the pathname contains a wildcard (an unquoted '*',
+        * '?', or '['), or a ~username
+        * with no trailing slash, then it is globbed based on that
+        * value (i.e., without the appended '*').
+        */
+       for (s = toglob; *s; s++) {
+               if (*s == '\\' && s[1])
+                       s++;
+               else if (*s == '*' || *s == '[' || *s == '?'
+                        || (s[1] == '(' /*)*/ && strchr("*+?@!", *s)))
+                       break;
+               else if (ISDIRSEP(*s))
+                       saw_slash = TRUE;
+       }
+       if (!*s && (*toglob != '~' || saw_slash)) {
+               toglob[slen] = '*';
+               toglob[slen + 1] = '\0';
+       }
+
+       return toglob;
+}
+
+/*
+ * Find longest common prefix
+ */
+int
+x_longest_prefix(nwords, words)
+       int nwords;
+       char *const *words;
+{
+       int i, j;
+       int prefix_len;
+       char *p;
+
+       if (nwords <= 0)
+               return 0;
+
+       prefix_len = strlen(words[0]);
+       for (i = 1; i < nwords; i++)
+               for (j = 0, p = words[i]; j < prefix_len; j++)
+                       if (FILECHCONV((unsigned char)p[j])
+                           != FILECHCONV((unsigned char)words[0][j])) {
+                               prefix_len = j;
+                               break;
+                       }
+       return prefix_len;
+}
+
+void
+x_free_words(nwords, words)
+       int nwords;
+       char **words;
+{
+       int i;
+
+       for (i = 0; i < nwords; i++)
+               if (words[i])
+                       afree(words[i], ATEMP);
+       afree(words, ATEMP);
+}
+
+/* Return the offset of the basename of string s (which ends at se - need not
+ * be null terminated).  Trailing slashes are ignored.  If s is just a slash,
+ * then the offset is 0 (actually, length - 1).
+ *     s               Return
+ *     /etc            1
+ *     /etc/           1
+ *     /etc//          1
+ *     /etc/fo         5
+ *     foo             0
+ *     ///             2
+ *                     0
+ */
+int
+x_basename(s, se)
+       const char *s;
+       const char *se;
+{
+       const char *p;
+
+       if (se == (char *) 0)
+               se = s + strlen(s);
+       if (s == se)
+               return 0;
+
+       /* Skip trailing slashes */
+       for (p = se - 1; p > s && ISDIRSEP(*p); p--)
+               ;
+       for (; p > s && !ISDIRSEP(*p); p--)
+               ;
+       if (ISDIRSEP(*p) && p + 1 < se)
+               p++;
+
+       return p - s;
+}
+
+/*
+ *  Apply pattern matching to a table: all table entries that match a pattern
+ * are added to wp.
+ */
+static void
+glob_table(pat, wp, tp)
+       const char *pat;
+       XPtrV *wp;
+       struct table *tp;
+{
+       struct tstate ts;
+       struct tbl *te;
+
+       for (twalk(&ts, tp); (te = tnext(&ts)); ) {
+               if (gmatch(te->name, pat, FALSE))
+                       XPput(*wp, str_save(te->name, ATEMP));
+       }
+}
+
+static void
+glob_path(flags, pat, wp, xpath)
+       int flags;
+       const char *pat;
+       XPtrV *wp;
+       const char *xpath;
+{
+       const char *sp, *p;
+       char *xp;
+       int staterr;
+       int pathlen;
+       int patlen;
+       int oldsize, newsize, i, j;
+       char **words;
+       XString xs;
+
+       patlen = strlen(pat) + 1;
+       sp = xpath;
+       Xinit(xs, xp, patlen + 128, ATEMP);
+       while (sp) {
+               xp = Xstring(xs, xp);
+               if (!(p = strchr(sp, PATHSEP)))
+                       p = sp + strlen(sp);
+               pathlen = p - sp;
+               if (pathlen) {
+                       /* Copy sp into xp, stuffing any MAGIC characters
+                        * on the way
+                        */
+                       const char *s = sp;
+
+                       XcheckN(xs, xp, pathlen * 2);
+                       while (s < p) {
+                               if (ISMAGIC(*s))
+                                       *xp++ = MAGIC;
+                               *xp++ = *s++;
+                       }
+                       *xp++ = DIRSEP;
+                       pathlen++;
+               }
+               sp = p;
+               XcheckN(xs, xp, patlen);
+               memcpy(xp, pat, patlen);
+
+               oldsize = XPsize(*wp);
+               glob_str(Xstring(xs, xp), wp, 1); /* mark dirs */
+               newsize = XPsize(*wp);
+
+               /* Check that each match is executable... */
+               words = (char **) XPptrv(*wp);
+               for (i = j = oldsize; i < newsize; i++) {
+                       staterr = 0;
+                       if ((search_access(words[i], X_OK, &staterr) >= 0)
+                           || (staterr == EISDIR)) {
+                               words[j] = words[i];
+                               if (!(flags & XCF_FULLPATH))
+                                       memmove(words[j], words[j] + pathlen,
+                                               strlen(words[j] + pathlen) + 1);
+                               j++;
+                       } else
+                               afree(words[i], ATEMP);
+               }
+               wp->cur = (void **) &words[j];
+
+               if (!*sp++)
+                       break;
+       }
+       Xfree(xs, xp);
+}
+
+/*
+ * if argument string contains any special characters, they will
+ * be escaped and the result will be put into edit buffer by
+ * keybinding-specific function
+ */
+int
+x_escape(s, len, putbuf_func)
+       const char *s;
+       size_t len;
+       int (*putbuf_func) ARGS((const char *, size_t));
+{
+       size_t add, wlen;
+       const char *ifs = str_val(local("IFS", 0));
+       int rval=0;
+
+       for (add = 0, wlen = len; wlen - add > 0; add++) {
+               if (strchr("\\$(){}[]?*&;#|<>\"'`", s[add]) || strchr(ifs, s[add])) {
+                       if (putbuf_func(s, add) != 0) {
+                               rval = -1;
+                               break;
+                       }
+
+                       putbuf_func("\\", 1);
+                       putbuf_func(&s[add], 1);
+
+                       add++;
+                       wlen -= add;
+                       s += add;
+                       add = -1; /* after the increment it will go to 0 */
+               }
+       }
+       if (wlen > 0 && rval == 0)
+               rval = putbuf_func(s, wlen);
+
+       return (rval);
+}
+#endif /* EDIT */
diff --git a/bin/ksh/edit.h b/bin/ksh/edit.h
new file mode 100644 (file)
index 0000000..c0bc8c5
--- /dev/null
@@ -0,0 +1,87 @@
+/*     $NetBSD: edit.h,v 1.3 1999/11/02 22:06:45 jdolecek Exp $        */
+
+/* NAME:
+ *      edit.h - globals for edit modes
+ *
+ * DESCRIPTION:
+ *      This header defines various global edit objects.
+ *
+ * SEE ALSO:
+ *      
+ *
+ * RCSid:
+ *      $NetBSD: edit.h,v 1.3 1999/11/02 22:06:45 jdolecek Exp $
+ *
+ */
+
+/* some useful #defines */
+#ifdef EXTERN
+# define I__(i) = i
+#else
+# define I__(i)
+# define EXTERN extern
+# define EXTERN_DEFINED
+#endif
+
+#define        BEL             0x07
+
+/* tty driver characters we are interested in */
+typedef struct {
+       int erase;
+       int kill;
+       int werase;
+       int intr;
+       int quit;
+       int eof;
+} X_chars;
+
+EXTERN X_chars edchars;
+
+/* x_fc_glob() flags */
+#define XCF_COMMAND    BIT(0)  /* Do command completion */
+#define XCF_FILE       BIT(1)  /* Do file completion */
+#define XCF_FULLPATH   BIT(2)  /* command completion: store full path */
+#define XCF_COMMAND_FILE (XCF_COMMAND|XCF_FILE)
+
+/* edit.c */
+int    x_getc          ARGS((void));
+void   x_flush         ARGS((void));
+void   x_putc          ARGS((int c));
+void   x_puts          ARGS((const char *s));
+bool_t         x_mode          ARGS((bool_t onoff));
+int    promptlen       ARGS((const char *cp, const char **spp));
+int    x_do_comment    ARGS((char *buf, int bsize, int *lenp));
+void   x_print_expansions ARGS((int nwords, char *const *words, int is_command));
+int    x_cf_glob ARGS((int flags, const char *buf, int buflen, int pos, int *startp,
+                         int *endp, char ***wordsp, int *is_commandp));
+int    x_longest_prefix ARGS((int nwords, char *const *words));
+int    x_basename ARGS((const char *s, const char *se));
+void   x_free_words ARGS((int nwords, char **words));
+int    x_escape ARGS((const char *, size_t, int (*)(const char *s, size_t len)));
+/* emacs.c */
+int    x_emacs         ARGS((char *buf, size_t len));
+void   x_init_emacs    ARGS((void));
+void   x_emacs_keys    ARGS((X_chars *ec));
+/* vi.c */
+int    x_vi            ARGS((char *buf, size_t len));
+
+
+#ifdef DEBUG
+# define D__(x) x
+#else
+# define D__(x)
+#endif
+
+/* This lot goes at the END */
+/* be sure not to interfere with anyone else's idea about EXTERN */
+#ifdef EXTERN_DEFINED
+# undef EXTERN_DEFINED
+# undef EXTERN
+#endif
+#undef I__
+/*
+ * Local Variables:
+ * version-control:t
+ * comment-column:40
+ * End:
+ */
diff --git a/bin/ksh/emacs-gen.sh b/bin/ksh/emacs-gen.sh
new file mode 100755 (executable)
index 0000000..3a2de8a
--- /dev/null
@@ -0,0 +1,47 @@
+#!/bin/sh
+#      $NetBSD: emacs-gen.sh,v 1.4 2008/10/25 22:18:15 apb Exp $
+
+: ${AWK:=awk}
+: ${SED:=sed}
+
+case $# in
+1)     file=$1;;
+*)
+       echo "$0: Usage: $0 path-to-emacs.c" 1>&2
+       exit 1
+esac;
+
+if [ ! -r "$file" ] ;then
+       echo "$0: can't read $file" 1>&2
+       exit 1
+fi
+
+cat << E_O_F || exit 1
+/*
+ * NOTE: THIS FILE WAS GENERATED AUTOMATICALLY FROM $file
+ *
+ * DO NOT BOTHER EDITING THIS FILE
+ */
+E_O_F
+
+# Pass 1: print out lines before @START-FUNC-TAB@
+#        and generate defines and function declarations,
+${SED} -e '1,/@START-FUNC-TAB@/d' -e '/@END-FUNC-TAB@/,$d' < $file |
+       ${AWK} 'BEGIN { nfunc = 0; }
+           /^[  ]*#/ {
+                           print $0;
+                           next;
+                   }
+           {
+               fname = $2;
+               c = substr(fname, length(fname), 1);
+               if (c == ",")
+                       fname = substr(fname, 1, length(fname) - 1);
+               if (fname != "0") {
+                       printf "#define XFUNC_%s %d\n", substr(fname, 3, length(fname) - 2), nfunc;
+                       printf "static int %s ARGS((int c));\n", fname;
+                       nfunc++;
+               }
+           }' || exit 1
+
+exit 0
diff --git a/bin/ksh/emacs.c b/bin/ksh/emacs.c
new file mode 100644 (file)
index 0000000..2177da3
--- /dev/null
@@ -0,0 +1,2221 @@
+/*     $NetBSD: emacs.c,v 1.32 2009/04/25 05:11:37 lukem Exp $ */
+
+/*
+ *  Emacs-like command line editing and history
+ *
+ *  created by Ron Natalie at BRL
+ *  modified by Doug Kingston, Doug Gwyn, and Lou Salkind
+ *  adapted to PD ksh by Eric Gisin
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: emacs.c,v 1.32 2009/04/25 05:11:37 lukem Exp $");
+#endif
+
+
+#include "config.h"
+#ifdef EMACS
+
+#include "sh.h"
+#include "ksh_stat.h"
+#include "ksh_dir.h"
+#include <ctype.h>
+#include <locale.h>
+#include "edit.h"
+
+static Area    aedit;
+#define        AEDIT   &aedit          /* area for kill ring and macro defns */
+
+#undef CTRL                    /* _BSD brain damage */
+#define        CTRL(x)         ((x) == '?' ? 0x7F : (x) & 0x1F)        /* ASCII */
+#define        UNCTRL(x)       ((x) == 0x7F ? '?' : (x) | 0x40)        /* ASCII */
+#define        META(x)         ((x) & 0x7f)
+#define        ISMETA(x)       (Flag(FEMACSUSEMETA) && ((x) & 0x80))
+
+
+/* values returned by keyboard functions */
+#define        KSTD    0
+#define        KEOL    1               /* ^M, ^J */
+#define        KINTR   2               /* ^G, ^C */
+
+struct x_ftab  {
+       int             (*xf_func) ARGS((int c));
+       const char      *xf_name;
+       short           xf_flags;
+};
+
+/* index into struct x_ftab x_ftab[] - small is good */
+typedef unsigned char Findex;
+
+struct x_defbindings {
+       Findex          xdb_func;       /* XFUNC_* */
+       unsigned char   xdb_tab;
+       unsigned char   xdb_char;
+};
+
+#define XF_ARG         1       /* command takes number prefix */
+#define        XF_NOBIND       2       /* not allowed to bind to function */
+#define        XF_PREFIX       4       /* function sets prefix */
+
+/* Separator for completion */
+#define        is_cfs(c)       (c == ' ' || c == '\t' || c == '"' || c == '\'')
+#define        is_mfs(c)       (!(isalnum((unsigned char)c) || c == '_' || c == '$'))  /* Separator for motion */
+
+#ifdef OS2
+  /* Deal with 8 bit chars & an extra prefix for function key (these two
+   * changes increase memory usage from 9,216 bytes to 24,416 bytes...)
+   */
+# define CHARMASK      0xFF            /* 8-bit ASCII character mask */
+# define X_NTABS       4               /* normal, meta1, meta2, meta3 */
+static int     x_prefix3 = 0xE0;
+#else /* OS2 */
+# define CHARMASK      0xFF            /* 8-bit character mask */
+# define X_NTABS       3               /* normal, meta1, meta2 */
+#endif /* OS2 */
+#define X_TABSZ                (CHARMASK+1)    /* size of keydef tables etc */
+
+/* Arguments for do_complete()
+ * 0 = enumerate  M-= complete as much as possible and then list
+ * 1 = complete   M-Esc
+ * 2 = list       M-?
+ */
+typedef enum { CT_LIST,        /* list the possible completions */
+                CT_COMPLETE,   /* complete to longest prefix */
+                CT_COMPLIST    /* complete and then list (if non-exact) */
+       } Comp_type;
+
+/* { from 4.9 edit.h */
+/*
+ * The following are used for my horizontal scrolling stuff
+ */
+static char   *xbuf;           /* beg input buffer */
+static char   *xend;           /* end input buffer */
+static char    *xcp;           /* current position */
+static char    *xep;           /* current end */
+static char    *xbp;           /* start of visible portion of input buffer */
+static char    *xlp;           /* last char visible on screen */
+static int     x_adj_ok;
+/*
+ * we use x_adj_done so that functions can tell
+ * whether x_adjust() has been called while they are active.
+ */
+static int     x_adj_done;
+
+static int     xx_cols;
+static int     x_col;
+static int     x_displen;
+static int     x_arg;          /* general purpose arg */
+static int     x_arg_defaulted;/* x_arg not explicitly set; defaulted to 1 */
+
+static int     xlp_valid;
+/* end from 4.9 edit.h } */
+
+static int     x_prefix1 = CTRL('['), x_prefix2 = CTRL('X');
+static char   **x_histp;       /* history position */
+static int     x_nextcmd;      /* for newline-and-next */
+static char    *xmp;           /* mark pointer */
+static Findex   x_last_command;
+static Findex (*x_tab)[X_TABSZ];       /* key definition */
+static char    *(*x_atab)[X_TABSZ];    /* macro definitions */
+static unsigned char   x_bound[(X_TABSZ * X_NTABS + 7) / 8];
+#define        KILLSIZE        20
+static char    *killstack[KILLSIZE];
+static int     killsp, killtp;
+static int     x_curprefix;
+static char    *macroptr;
+static int     prompt_trunc;
+static int     prompt_skip;
+
+static int      x_ins       ARGS((char *cp));
+static void     x_delete    ARGS((int nc, int push));
+static int     x_bword     ARGS((void));
+static int     x_fword     ARGS((void));
+static void     x_goto      ARGS((char *cp));
+static void     x_bs        ARGS((int c));
+static int      x_size_str  ARGS((char *cp));
+static int      x_size      ARGS((int c));
+static void     x_zots      ARGS((char *str));
+static void     x_zotc      ARGS((int c));
+static void     x_load_hist ARGS((char **hp));
+static int      x_search    ARGS((char *pat, int sameline, int offset));
+static int      x_match     ARGS((char *str, char *pat));
+static void    x_redraw    ARGS((int limit));
+static void     x_push      ARGS((int nchars));
+static char *   x_mapin     ARGS((const char *cp, Area *area));
+static char *   x_mapout    ARGS((int c));
+static void     x_print     ARGS((int prefix, int key));
+static void    x_adjust    ARGS((void));
+static void    x_e_ungetc  ARGS((int c));
+static int     x_e_getc    ARGS((void));
+static void    x_e_putc    ARGS((int c));
+static void    x_e_puts    ARGS((const char *s));
+static int     x_comment   ARGS((int c));
+static int     x_fold_case ARGS((int c));
+static char    *x_lastcp ARGS((void));
+static void    do_complete ARGS((int flags, Comp_type type));
+static int     x_emacs_putbuf  ARGS((const char *s, size_t len));
+
+
+/* The lines between START-FUNC-TAB .. END-FUNC-TAB are run through a
+ * script (emacs-gen.sh) that generates emacs.out which contains:
+ *     - function declarations for x_* functions
+ *     - defines of the form XFUNC_<name> where <name> is function
+ *       name, sans leading x_.
+ * Note that the script treats #ifdef and { 0, 0, 0} specially - use with
+ * caution.
+ */
+#include "emacs.out"
+static const struct x_ftab x_ftab[] = {
+/* @START-FUNC-TAB@ */
+       { x_abort,              "abort",                        0 },
+       { x_beg_hist,           "beginning-of-history",         0 },
+       { x_comp_comm,          "complete-command",             0 },
+       { x_comp_file,          "complete-file",                0 },
+       { x_complete,           "complete",                     0 },
+       { x_del_back,           "delete-char-backward",         XF_ARG },
+       { x_del_bword,          "delete-word-backward",         XF_ARG },
+       { x_del_char,           "delete-char-forward",          XF_ARG },
+       { x_del_fword,          "delete-word-forward",          XF_ARG },
+       { x_del_line,           "kill-line",                    0 },
+       { x_draw_line,          "redraw",                       0 },
+       { x_end_hist,           "end-of-history",               0 },
+       { x_end_of_text,        "eot",                          0 },
+       { x_enumerate,          "list",                         0 },
+       { x_eot_del,            "eot-or-delete",                XF_ARG },
+       { x_error,              "error",                        0 },
+       { x_goto_hist,          "goto-history",                 XF_ARG },
+       { x_ins_string,         "macro-string",                 XF_NOBIND },
+       { x_insert,             "auto-insert",                  XF_ARG },
+       { x_kill,               "kill-to-eol",                  XF_ARG },
+       { x_kill_region,        "kill-region",                  0 },
+       { x_list_comm,          "list-command",                 0 },
+       { x_list_file,          "list-file",                    0 },
+       { x_literal,            "quote",                        0 },
+       { x_meta1,              "prefix-1",                     XF_PREFIX },
+       { x_meta2,              "prefix-2",                     XF_PREFIX },
+       { x_meta_yank,          "yank-pop",                     0 },
+       { x_mv_back,            "backward-char",                XF_ARG },
+       { x_mv_begin,           "beginning-of-line",            0 },
+       { x_mv_bword,           "backward-word",                XF_ARG },
+       { x_mv_end,             "end-of-line",                  0 },
+       { x_mv_forw,            "forward-char",                 XF_ARG },
+       { x_mv_fword,           "forward-word",                 XF_ARG },
+       { x_newline,            "newline",                      0 },
+       { x_next_com,           "down-history",                 XF_ARG },
+       { x_nl_next_com,        "newline-and-next",             0 },
+       { x_noop,               "no-op",                        0 },
+       { x_prev_com,           "up-history",                   XF_ARG },
+       { x_prev_histword,      "prev-hist-word",               XF_ARG },
+       { x_search_char_forw,   "search-character-forward",     XF_ARG },
+       { x_search_char_back,   "search-character-backward",    XF_ARG },
+       { x_search_hist,        "search-history",               0 },
+       { x_set_mark,           "set-mark-command",             0 },
+       { x_stuff,              "stuff",                        0 },
+       { x_stuffreset,         "stuff-reset",                  0 },
+       { x_transpose,          "transpose-chars",              0 },
+       { x_version,            "version",                      0 },
+       { x_xchg_point_mark,    "exchange-point-and-mark",      0 },
+       { x_yank,               "yank",                         0 },
+        { x_comp_list,         "complete-list",                0 },
+        { x_expand,            "expand-file",                  0 },
+        { x_fold_capitalize,   "capitalize-word",              XF_ARG },
+        { x_fold_lower,                "downcase-word",                XF_ARG },
+        { x_fold_upper,                "upcase-word",                  XF_ARG },
+        { x_set_arg,           "set-arg",                      XF_NOBIND },
+        { x_comment,           "comment",                      0 },
+#ifdef SILLY
+       { x_game_of_life,       "play-game-of-life",            0 },
+#else
+       { 0, 0, 0 },
+#endif
+#ifdef DEBUG
+        { x_debug_info,                "debug-info",                   0 },
+#else
+       { 0, 0, 0 },
+#endif
+#ifdef OS2
+       { x_meta3,              "prefix-3",                     XF_PREFIX },
+#else
+       { 0, 0, 0 },
+#endif
+/* @END-FUNC-TAB@ */
+    };
+
+static struct x_defbindings const x_defbindings[] = {
+       { XFUNC_del_back,               0, CTRL('?') },
+       { XFUNC_del_bword,              1, CTRL('?') },
+       { XFUNC_eot_del,                0, CTRL('D') },
+       { XFUNC_del_back,               0, CTRL('H') },
+       { XFUNC_del_bword,              1, CTRL('H') },
+       { XFUNC_del_bword,              1,      'h'  },
+       { XFUNC_mv_bword,               1,      'b'  },
+       { XFUNC_mv_fword,               1,      'f'  },
+       { XFUNC_del_fword,              1,      'd'  },
+       { XFUNC_mv_back,                0, CTRL('B') },
+       { XFUNC_mv_forw,                0, CTRL('F') },
+       { XFUNC_search_char_forw,       0, CTRL(']') },
+       { XFUNC_search_char_back,       1, CTRL(']') },
+       { XFUNC_newline,                0, CTRL('M') },
+       { XFUNC_newline,                0, CTRL('J') },
+       { XFUNC_end_of_text,            0, CTRL('_') },
+       { XFUNC_abort,                  0, CTRL('G') },
+       { XFUNC_prev_com,               0, CTRL('P') },
+       { XFUNC_next_com,               0, CTRL('N') },
+       { XFUNC_nl_next_com,            0, CTRL('O') },
+       { XFUNC_search_hist,            0, CTRL('R') },
+       { XFUNC_beg_hist,               1,      '<'  },
+       { XFUNC_end_hist,               1,      '>'  },
+       { XFUNC_goto_hist,              1,      'g'  },
+       { XFUNC_mv_end,                 0, CTRL('E') },
+       { XFUNC_mv_begin,               0, CTRL('A') },
+       { XFUNC_draw_line,              0, CTRL('L') },
+       { XFUNC_meta1,                  0, CTRL('[') },
+       { XFUNC_meta2,                  0, CTRL('X') },
+       { XFUNC_kill,                   0, CTRL('K') },
+       { XFUNC_yank,                   0, CTRL('Y') },
+       { XFUNC_meta_yank,              1,      'y'  },
+       { XFUNC_literal,                0, CTRL('^') },
+        { XFUNC_comment,               1,      '#'  },
+#if defined(BRL) && defined(TIOCSTI)
+       { XFUNC_stuff,                  0, CTRL('T') },
+#else
+       { XFUNC_transpose,              0, CTRL('T') },
+#endif
+       { XFUNC_complete,               1, CTRL('[') },
+       { XFUNC_comp_list,              0, CTRL('I') },
+        { XFUNC_comp_list,             1,      '='  },
+       { XFUNC_enumerate,              1,      '?'  },
+        { XFUNC_expand,                        1,      '*'  },
+       { XFUNC_comp_file,              1, CTRL('X') },
+       { XFUNC_comp_comm,              2, CTRL('[') },
+       { XFUNC_list_comm,              2,      '?'  },
+       { XFUNC_list_file,              2, CTRL('Y') },
+       { XFUNC_set_mark,               1,      ' '  },
+       { XFUNC_kill_region,            0, CTRL('W') },
+       { XFUNC_xchg_point_mark,        2, CTRL('X') },
+       { XFUNC_version,                0, CTRL('V') },
+#ifdef DEBUG
+        { XFUNC_debug_info,            1, CTRL('H') },
+#endif
+       { XFUNC_prev_histword,          1,      '.'  },
+       { XFUNC_prev_histword,          1,      '_'  },
+        { XFUNC_set_arg,               1,      '0'  },
+        { XFUNC_set_arg,               1,      '1'  },
+        { XFUNC_set_arg,               1,      '2'  },
+        { XFUNC_set_arg,               1,      '3'  },
+        { XFUNC_set_arg,               1,      '4'  },
+        { XFUNC_set_arg,               1,      '5'  },
+        { XFUNC_set_arg,               1,      '6'  },
+        { XFUNC_set_arg,               1,      '7'  },
+        { XFUNC_set_arg,               1,      '8'  },
+        { XFUNC_set_arg,               1,      '9'  },
+        { XFUNC_fold_upper,            1,      'U'  },
+        { XFUNC_fold_upper,            1,      'u'  },
+        { XFUNC_fold_lower,            1,      'L'  },
+        { XFUNC_fold_lower,            1,      'l'  },
+        { XFUNC_fold_capitalize,       1,      'C'  },
+        { XFUNC_fold_capitalize,       1,      'c'  },
+#ifdef OS2
+       { XFUNC_meta3,                  0,      0xE0 },
+       { XFUNC_mv_back,                3,      'K'  },
+       { XFUNC_mv_forw,                3,      'M'  },
+       { XFUNC_next_com,               3,      'P'  },
+       { XFUNC_prev_com,               3,      'H'  },
+#endif /* OS2 */
+       /* These for ansi arrow keys: arguablely shouldn't be here by
+        * default, but its simpler/faster/smaller than using termcap
+        * entries.
+        */
+        { XFUNC_meta2,                 1,      '['  },
+        { XFUNC_meta2,                 1,      'O'  },
+       { XFUNC_prev_com,               2,      'A'  },
+       { XFUNC_next_com,               2,      'B'  },
+       { XFUNC_mv_forw,                2,      'C'  },
+       { XFUNC_mv_back,                2,      'D'  },
+};
+
+int
+x_emacs(buf, len)
+       char *buf;
+       size_t len;
+{
+       int     c;
+       const char *p;
+       int     i;
+       Findex  f;
+
+       xbp = xbuf = buf; xend = buf + len;
+       xlp = xcp = xep = buf;
+       *xcp = 0;
+       xlp_valid = TRUE;
+       xmp = NULL;
+       x_curprefix = 0;
+       macroptr = (char *) 0;
+       x_histp = histptr + 1;
+       x_last_command = XFUNC_error;
+
+       xx_cols = x_cols;
+       x_col = promptlen(prompt, &p);
+       prompt_skip = p - prompt;
+       prompt_trunc = x_col - (x_cols - 3 - MIN_EDIT_SPACE);
+       if (prompt_trunc > 0)
+               x_col -= prompt_trunc;
+       else
+               prompt_trunc = 0;
+       x_adj_ok = 1;
+       x_displen = xx_cols - 2 - x_col;
+       x_adj_done = 0;
+
+       pprompt(prompt, prompt_trunc);
+
+       if (x_nextcmd >= 0) {
+               int off = source->line - x_nextcmd;
+               if (histptr - histlist >= off)
+                       x_load_hist(histptr - off);
+               x_nextcmd = -1;
+       }
+
+       while (1) {
+               x_flush();
+               if ((c = x_e_getc()) < 0)
+                       return 0;
+
+               if (ISMETA(c)) {
+                       c = META(c);
+                       x_curprefix = 1;
+               }
+
+               f = x_curprefix == -1 ? XFUNC_insert
+                       : x_tab[x_curprefix][c&CHARMASK];
+
+               if (!(x_ftab[f].xf_flags & XF_PREFIX)
+                   && x_last_command != XFUNC_set_arg)
+               {
+                       x_arg = 1;
+                       x_arg_defaulted = 1;
+               }
+               i = c | (x_curprefix << 8);
+               x_curprefix = 0;
+               switch (i = (*x_ftab[f].xf_func)(i))  {
+                 case KSTD:
+                       if (!(x_ftab[f].xf_flags & XF_PREFIX))
+                               x_last_command = f;
+                       break;
+                 case KEOL:
+                       i = xep - xbuf;
+                       return i;
+                 case KINTR:   /* special case for interrupt */
+                       trapsig(SIGINT);
+                       x_mode(FALSE);
+                       unwind(LSHELL);
+               }
+       }
+}
+
+static int
+x_insert(c)
+       int c;
+{
+       char    str[2];
+
+       /*
+        *  Should allow tab and control chars.
+        */
+       if (c == 0)  {
+               x_e_putc(BEL);
+               return KSTD;
+       }
+       str[0] = c;
+       str[1] = '\0';
+       while (x_arg--)
+               x_ins(str);
+       return KSTD;
+}
+
+static int
+x_ins_string(c)
+       int c;
+{
+       if (macroptr)   {
+               x_e_putc(BEL);
+               return KSTD;
+       }
+       macroptr = x_atab[c>>8][c & CHARMASK];
+       if (macroptr && !*macroptr) {
+               /* XXX bell? */
+               macroptr = (char *) 0;
+       }
+       return KSTD;
+}
+
+static int x_do_ins(const char *cp, int len);
+
+static int
+x_do_ins(cp, len)
+       const char *cp;
+       int len;
+{
+       if (xep+len >= xend) {
+               x_e_putc(BEL);
+               return -1;
+       }
+
+       memmove(xcp+len, xcp, xep - xcp + 1);
+       memmove(xcp, cp, len);
+       xcp += len;
+       xep += len;
+       return 0;
+}
+
+static int
+x_ins(s)
+       char    *s;
+{
+       char *cp = xcp;
+       register int    adj = x_adj_done;
+
+       if (x_do_ins(s, strlen(s)) < 0)
+               return -1;
+       /*
+        * x_zots() may result in a call to x_adjust()
+        * we want xcp to reflect the new position.
+        */
+       xlp_valid = FALSE;
+       x_lastcp();
+       x_adj_ok = (xcp >= xlp);
+       x_zots(cp);
+       if (adj == x_adj_done)  /* has x_adjust() been called? */
+       {
+         /* no */
+         for (cp = xlp; cp > xcp; )
+           x_bs(*--cp);
+       }
+
+       x_adj_ok = 1;
+       return 0;
+}
+
+/*
+ * this is used for x_escape() in do_complete()
+ */
+static int
+x_emacs_putbuf(s, len)
+       const char *s;
+       size_t len;
+{
+       int rval;
+
+       if ((rval = x_do_ins(s, len)) != 0)
+               return (rval);
+       return (rval);
+}
+
+static int
+x_del_back(c)
+       int c;
+{
+       int col = xcp - xbuf;
+
+       if (col == 0)  {
+               x_e_putc(BEL);
+               return KSTD;
+       }
+       if (x_arg > col)
+               x_arg = col;
+       x_goto(xcp - x_arg);
+       x_delete(x_arg, FALSE);
+       return KSTD;
+}
+
+static int
+x_del_char(c)
+       int c;
+{
+       int nleft = xep - xcp;
+
+       if (!nleft) {
+               x_e_putc(BEL);
+               return KSTD;
+       }
+       if (x_arg > nleft)
+               x_arg = nleft;
+       x_delete(x_arg, FALSE);
+       return KSTD;
+}
+
+/* Delete nc chars to the right of the cursor (including cursor position) */
+static void
+x_delete(nc, push)
+       int nc;
+       int push;
+{
+       int     i,j;
+       char    *cp;
+       
+       if (nc == 0)
+               return;
+       if (xmp != NULL && xmp > xcp) {
+               if (xcp + nc > xmp)
+                       xmp = xcp;
+               else
+                       xmp -= nc;
+       }
+
+       /*
+        * This lets us yank a word we have deleted.
+        */
+       if (push)
+               x_push(nc);
+
+       xep -= nc;
+       cp = xcp;
+       j = 0;
+       i = nc;
+       while (i--)  {
+               j += x_size(*cp++);
+       }
+       memmove(xcp, xcp+nc, xep - xcp + 1);    /* Copies the null */
+       x_adj_ok = 0;                   /* don't redraw */
+       x_zots(xcp);
+       /*
+        * if we are already filling the line,
+        * there is no need to ' ','\b'.
+        * But if we must, make sure we do the minimum.
+        */
+       if ((i = x_displen) > 0)
+       {
+         j = (j < i) ? j : i;
+         i = j;
+         while (i--)
+           x_e_putc(' ');
+         i = j;
+         while (i--)
+           x_e_putc('\b');
+       }
+       /*x_goto(xcp);*/
+       x_adj_ok = 1;
+       xlp_valid = FALSE;
+       for (cp = x_lastcp(); cp > xcp; )
+               x_bs(*--cp);
+
+       return; 
+}
+
+static int
+x_del_bword(c)
+       int c;
+{
+       x_delete(x_bword(), TRUE);
+       return KSTD;
+}
+
+static int
+x_mv_bword(c)
+       int c;
+{
+       (void)x_bword();
+       return KSTD;
+}
+
+static int
+x_mv_fword(c)
+       int c;
+{
+       x_goto(xcp + x_fword());
+       return KSTD;
+}
+
+static int
+x_del_fword(c)
+       int c;
+{
+       x_delete(x_fword(), TRUE);
+       return KSTD;
+}
+
+static int
+x_bword()
+{
+       int     nc = 0;
+       register char *cp = xcp;
+
+       if (cp == xbuf)  {
+               x_e_putc(BEL);
+               return 0;
+       }
+       while (x_arg--)
+       {
+         while (cp != xbuf && is_mfs(cp[-1]))
+         {
+           cp--;
+           nc++;
+         }
+         while (cp != xbuf && !is_mfs(cp[-1]))
+         {
+           cp--;
+           nc++;
+         }
+       }
+       x_goto(cp);
+       return nc;
+}
+
+static int
+x_fword()
+{
+       int     nc = 0;
+       register char   *cp = xcp;
+
+       if (cp == xep)  {
+               x_e_putc(BEL);
+               return 0;
+       }
+       while (x_arg--)
+       {
+         while (cp != xep && is_mfs(*cp))
+         {
+           cp++;
+           nc++;
+         }
+         while (cp != xep && !is_mfs(*cp))
+         {
+           cp++;
+           nc++;
+         }
+       }
+       return nc;
+}
+
+static void
+x_goto(cp)
+       register char *cp;
+{
+  if (cp < xbp || cp >= (xbp + x_displen))
+  {
+    /* we are heading off screen */
+    xcp = cp;
+    x_adjust();
+  }
+  else
+  {
+    if (cp < xcp)              /* move back */
+    {
+      while (cp < xcp)
+       x_bs(*--xcp);
+    }
+    else
+    {
+      if (cp > xcp)            /* move forward */
+      {
+       while (cp > xcp)
+         x_zotc(*xcp++);
+      }
+    }
+  }
+}
+
+static void
+x_bs(c)
+       int c;
+{
+       register int i;
+       i = x_size(c);
+       while (i--)
+               x_e_putc('\b');
+}
+
+static int
+x_size_str(cp)
+       register char *cp;
+{
+       register int size = 0;
+       while (*cp)
+               size += x_size(*cp++);
+       return size;
+}
+
+static int
+x_size(c)
+       int c;
+{
+       if (c=='\t')
+               return 4;       /* Kludge, tabs are always four spaces. */
+       if (iscntrl((unsigned char)c))          /* control char */
+               return 2;
+       return 1;
+}
+
+static void
+x_zots(str)
+       register char *str;
+{
+  register int adj = x_adj_done;
+
+  x_lastcp();
+  while (*str && str < xlp && adj == x_adj_done)
+    x_zotc(*str++);
+}
+
+static void
+x_zotc(c)
+       int c;
+{
+       if (c == '\t')  {
+               /*  Kludge, tabs are always four spaces.  */
+               x_e_puts("    ");
+       } else if (iscntrl((unsigned char)c))  {
+               x_e_putc('^');
+               x_e_putc(UNCTRL(c));
+       } else
+               x_e_putc(c);
+}
+
+static int
+x_mv_back(c)
+       int c;
+{
+       int col = xcp - xbuf;
+
+       if (col == 0)  {
+               x_e_putc(BEL);
+               return KSTD;
+       }
+       if (x_arg > col)
+               x_arg = col;
+       x_goto(xcp - x_arg);
+       return KSTD;
+}
+
+static int
+x_mv_forw(c)
+       int c;
+{
+       int nleft = xep - xcp;
+
+       if (!nleft) {
+               x_e_putc(BEL);
+               return KSTD;
+       }
+       if (x_arg > nleft)
+               x_arg = nleft;
+       x_goto(xcp + x_arg);
+       return KSTD;
+}
+
+static int
+x_search_char_forw(c)
+       int c;
+{
+       char *cp = xcp;
+
+       *xep = '\0';
+       c = x_e_getc();
+       while (x_arg--) {
+           if (c < 0
+              || ((cp = (cp == xep) ? NULL : strchr(cp + 1, c)) == NULL
+                  && (cp = strchr(xbuf, c)) == NULL))
+           {
+                   x_e_putc(BEL);
+                   return KSTD;
+           }
+       }
+       x_goto(cp);
+       return KSTD;
+}
+
+static int
+x_search_char_back(c)
+       int c;
+{
+       char *cp = xcp, *p;
+
+       c = x_e_getc();
+       for (; x_arg--; cp = p)
+               for (p = cp; ; ) {
+                       if (p-- == xbuf)
+                               p = xep;
+                       if (c < 0 || p == cp) {
+                               x_e_putc(BEL);
+                               return KSTD;
+                       }
+                       if (*p == c)
+                               break;
+               }
+       x_goto(cp);
+       return KSTD;
+}
+
+static int
+x_newline(c)
+       int c;
+{
+       x_e_putc('\r');
+       x_e_putc('\n');
+       x_flush();
+       *xep++ = '\n';
+       return KEOL;
+}
+
+static int
+x_end_of_text(c)
+       int c;
+{
+       return KEOL;
+}
+
+static int x_beg_hist(c) int c; { x_load_hist(histlist); return KSTD;}
+
+static int x_end_hist(c) int c; { x_load_hist(histptr); return KSTD;}
+
+static int x_prev_com(c) int c; { x_load_hist(x_histp - x_arg); return KSTD;}
+
+static int x_next_com(c) int c; { x_load_hist(x_histp + x_arg); return KSTD;}
+/* Goto a particular history number obtained from argument.
+ * If no argument is given history 1 is probably not what you
+ * want so we'll simply go to the oldest one.
+ */
+static int
+x_goto_hist(c)
+       int c;
+{
+       if (x_arg_defaulted)
+               x_load_hist(histlist);
+       else
+               x_load_hist(histptr + x_arg - source->line);
+       return KSTD;
+}
+
+static void
+x_load_hist(hp)
+       register char **hp;
+{
+       int     oldsize;
+
+       if (hp < histlist || hp > histptr) {
+               x_e_putc(BEL);
+               return;
+       }
+       x_histp = hp;
+       oldsize = x_size_str(xbuf);
+       strlcpy(xbuf, *hp, xend - xbuf);
+       xbp = xbuf;
+       xep = xcp = xbuf + strlen(xbuf);
+       xlp_valid = FALSE;
+       if (xep > x_lastcp())
+         x_goto(xep);
+       else
+         x_redraw(oldsize);
+}
+
+static int
+x_nl_next_com(c)
+       int     c;
+{
+       x_nextcmd = source->line - (histptr - x_histp) + 1;
+       return (x_newline(c));
+}
+
+static int
+x_eot_del(c)
+       int     c;
+{
+       if (xep == xbuf && x_arg_defaulted)
+               return (x_end_of_text(c));
+       else
+               return (x_del_char(c));
+}
+
+/* reverse incremental history search */
+static int
+x_search_hist(c)
+       int c;
+{
+       int offset = -1;        /* offset of match in xbuf, else -1 */
+       char pat [256+1];       /* pattern buffer */
+       register char *p = pat;
+       Findex f;
+
+       *p = '\0';
+       while (1) {
+               if (offset < 0) {
+                       x_e_puts("\nI-search: ");
+                       x_e_puts(pat);
+               }
+               x_flush();
+               if ((c = x_e_getc()) < 0)
+                       return KSTD;
+               f = x_tab[0][c&CHARMASK];
+               if (c == CTRL('['))
+                       break;
+               else if (f == XFUNC_search_hist)
+                       offset = x_search(pat, 0, offset);
+               else if (f == XFUNC_del_back) {
+                       if (p == pat) {
+                               offset = -1;
+                               break;
+                       }
+                       if (p > pat)
+                               *--p = '\0';
+                       if (p == pat)
+                               offset = -1;
+                       else
+                               offset = x_search(pat, 1, offset);
+                       continue;
+               } else if (f == XFUNC_insert) {
+                       /* add char to pattern */
+                       /* overflow check... */
+                       if (p >= &pat[sizeof(pat) - 1]) {
+                               x_e_putc(BEL);
+                               continue;
+                       }
+                       *p++ = c, *p = '\0';
+                       if (offset >= 0) {
+                               /* already have partial match */
+                               offset = x_match(xbuf, pat);
+                               if (offset >= 0) {
+                                       x_goto(xbuf + offset + (p - pat) - (*pat == '^'));
+                                       continue;
+                               }
+                       }
+                       offset = x_search(pat, 0, offset);
+               } else { /* other command */
+                       x_e_ungetc(c);
+                       break;
+               }
+       }
+       if (offset < 0)
+               x_redraw(-1);
+       return KSTD;
+}
+
+/* search backward from current line */
+static int
+x_search(pat, sameline, offset)
+       char *pat;
+       int sameline;
+       int offset;
+{
+       register char **hp;
+       int i;
+
+       for (hp = x_histp - (sameline ? 0 : 1) ; hp >= histlist; --hp) {
+               i = x_match(*hp, pat);
+               if (i >= 0) {
+                       if (offset < 0)
+                               x_e_putc('\n');
+                       x_load_hist(hp);
+                       x_goto(xbuf + i + strlen(pat) - (*pat == '^'));
+                       return i;
+               }
+       }
+       x_e_putc(BEL);
+       x_histp = histptr;
+       return -1;
+}
+
+/* return position of first match of pattern in string, else -1 */
+static int
+x_match(str, pat)
+       char *str, *pat;
+{
+       if (*pat == '^') {
+               return (strncmp(str, pat+1, strlen(pat+1)) == 0) ? 0 : -1;
+       } else {
+               char *q = strstr(str, pat);
+               return (q == NULL) ? -1 : q - str;
+       }
+}
+
+static int
+x_del_line(c)
+       int c;
+{
+       int     i, j;
+
+       *xep = 0;
+       i = xep - xbuf;
+       j = x_size_str(xbuf);
+       xcp = xbuf;
+       x_push(i);
+       xlp = xbp = xep = xbuf;
+       xlp_valid = TRUE;
+       *xcp = 0;
+       xmp = NULL;
+       x_redraw(j);
+       return KSTD;
+}
+
+static int
+x_mv_end(c)
+       int c;
+{
+       x_goto(xep);
+       return KSTD;
+}
+
+static int
+x_mv_begin(c)
+       int c;
+{
+       x_goto(xbuf);
+       return KSTD;
+}
+
+static int
+x_draw_line(c)
+       int c;
+{
+       x_redraw(-1);
+       return KSTD;
+
+}
+
+/* Redraw (part of) the line.  If limit is < 0, the everything is redrawn
+ * on a NEW line, otherwise limit is the screen column up to which needs
+ * redrawing.
+ */
+static void
+x_redraw(limit)
+  int limit;
+{
+       int     i, j;
+       char    *cp;
+       
+       x_adj_ok = 0;
+       if (limit == -1)
+               x_e_putc('\n');
+       else
+               x_e_putc('\r');
+       x_flush();
+       if (xbp == xbuf)
+       {
+         pprompt(prompt + prompt_skip, 0);
+         x_col = promptlen(prompt, (const char **) 0);
+       }
+       x_displen = xx_cols - 2 - x_col;
+       xlp_valid = FALSE;
+       cp = x_lastcp();
+       x_zots(xbp);
+       if (xbp != xbuf || xep > xlp)
+         limit = xx_cols;
+       if (limit >= 0)
+       {
+         if (xep > xlp)
+           i = 0;                      /* we fill the line */
+         else
+           i = limit - (xlp - xbp);
+
+         for (j = 0; j < i && x_col < (xx_cols - 2); j++)
+           x_e_putc(' ');
+         i = ' ';
+         if (xep > xlp)                /* more off screen */
+         {
+           if (xbp > xbuf)
+             i = '*';
+           else
+             i = '>';
+         }
+         else
+           if (xbp > xbuf)
+             i = '<';
+         x_e_putc(i);
+         j++;
+         while (j--)
+           x_e_putc('\b');
+       }
+       for (cp = xlp; cp > xcp; )
+         x_bs(*--cp);
+       x_adj_ok = 1;
+       D__(x_flush();)
+       return;
+}
+
+static int
+x_transpose(c)
+       int c;
+{
+       char    tmp;
+
+       /* What transpose is meant to do seems to be up for debate. This
+        * is a general summary of the options; the text is abcd with the
+        * upper case character or underscore indicating the cursor position:
+        *     Who                      Before  After  Before   After
+        *     at&t ksh in emacs mode:  abCd    abdC   abcd_    (bell)
+        *     at&t ksh in gmacs mode:  abCd    baCd   abcd_    abdc_
+        *     gnu emacs:               abCd    acbD   abcd_    abdc_
+        * Pdksh currently goes with GNU behavior since I believe this is the
+        * most common version of emacs, unless in gmacs mode, in which case
+        * it does the at&t ksh gmacs mode.
+        * This should really be broken up into 3 functions so users can bind
+        * to the one they want.
+        */
+       if (xcp == xbuf) {
+               x_e_putc(BEL);
+               return KSTD;
+       } else if (xcp == xep || Flag(FGMACS)) {
+               if (xcp - xbuf == 1) {
+                       x_e_putc(BEL);
+                       return KSTD;
+               }
+               /* Gosling/Unipress emacs style: Swap two characters before the
+                * cursor, do not change cursor position
+                */
+               x_bs(xcp[-1]);
+               x_bs(xcp[-2]);
+               x_zotc(xcp[-1]);
+               x_zotc(xcp[-2]);
+               tmp = xcp[-1];
+               xcp[-1] = xcp[-2];
+               xcp[-2] = tmp;
+       } else {
+               /* GNU emacs style: Swap the characters before and under the
+                * cursor, move cursor position along one.
+                */
+               x_bs(xcp[-1]);
+               x_zotc(xcp[0]);
+               x_zotc(xcp[-1]);
+               tmp = xcp[-1];
+               xcp[-1] = xcp[0];
+               xcp[0] = tmp;
+               x_bs(xcp[0]);
+               x_goto(xcp + 1);
+       }
+       return KSTD;
+}
+
+static int
+x_literal(c)
+       int c;
+{
+       x_curprefix = -1;
+       return KSTD;
+}
+
+static int
+x_meta1(c)
+       int c;
+{
+       x_curprefix = 1;
+       return KSTD;
+}
+
+static int
+x_meta2(c)
+       int c;
+{
+       x_curprefix = 2;
+       return KSTD;
+}
+
+#ifdef OS2
+static int
+x_meta3(c)
+       int c;
+{
+       x_curprefix = 3;
+       return KSTD;
+}
+#endif /* OS2 */
+
+static int
+x_kill(c)
+       int c;
+{
+       int col = xcp - xbuf;
+       int lastcol = xep - xbuf;
+       int ndel;
+
+       if (x_arg_defaulted)
+               x_arg = lastcol;
+       else if (x_arg > lastcol)
+               x_arg = lastcol;
+       ndel = x_arg - col;
+       if (ndel < 0) {
+               x_goto(xbuf + x_arg);
+               ndel = -ndel;
+       }
+       x_delete(ndel, TRUE);
+       return KSTD;
+}
+
+static void
+x_push(nchars)
+       int nchars;
+{
+       char    *cp = str_nsave(xcp, nchars, AEDIT);
+       if (killstack[killsp])
+               afree((void *)killstack[killsp], AEDIT);
+       killstack[killsp] = cp;
+       killsp = (killsp + 1) % KILLSIZE;
+}
+
+static int
+x_yank(c)
+       int c;
+{
+       if (killsp == 0)
+               killtp = KILLSIZE;
+       else
+               killtp = killsp;
+       killtp--;
+       if (killstack[killtp] == 0)  {
+               x_e_puts("\nnothing to yank");
+               x_redraw(-1);
+               return KSTD;
+       }
+       xmp = xcp;
+       x_ins(killstack[killtp]);
+       return KSTD;
+}
+
+static int
+x_meta_yank(c)
+       int c;
+{
+       int     len;
+       if ((x_last_command != XFUNC_yank && x_last_command != XFUNC_meta_yank)
+           || killstack[killtp] == 0) {
+               killtp = killsp;
+               x_e_puts("\nyank something first");
+               x_redraw(-1);
+               return KSTD;
+       }
+       len = strlen(killstack[killtp]);
+       x_goto(xcp - len);
+       x_delete(len, FALSE);
+       do {
+               if (killtp == 0)
+                       killtp = KILLSIZE - 1;
+               else
+                       killtp--;
+       } while (killstack[killtp] == 0);
+       x_ins(killstack[killtp]);
+       return KSTD;
+}
+
+static int
+x_abort(c)
+       int c;
+{
+       /* x_zotc(c); */
+       xlp = xep = xcp = xbp = xbuf;
+       xlp_valid = TRUE;
+       *xcp = 0;
+       return KINTR;
+}
+
+static int
+x_error(c)
+       int c;
+{
+       x_e_putc(BEL);
+       return KSTD;
+}
+
+static int
+x_stuffreset(c)
+       int c;
+{
+#ifdef TIOCSTI
+       (void)x_stuff(c);
+       return KINTR;
+#else
+       x_zotc(c);
+       xlp = xcp = xep = xbp = xbuf;
+       xlp_valid = TRUE;
+       *xcp = 0;
+       x_redraw(-1);
+       return KSTD;
+#endif
+}
+
+static int
+x_stuff(c)
+       int c;
+{
+#if 0 || defined TIOCSTI
+       char    ch = c;
+       bool_t  savmode = x_mode(FALSE);
+
+       (void)ioctl(TTY, TIOCSTI, &ch);
+       (void)x_mode(savmode);
+       x_redraw(-1);
+#endif
+       return KSTD;
+}
+
+static char *
+x_mapin(cp, area)
+       const char *cp;
+       Area *area;
+{
+       char *new, *op;
+
+       op = new = str_save(cp, area);
+       while (*cp)  {
+               /* XXX -- should handle \^ escape? */
+               if (*cp == '^')  {
+                       cp++;
+#ifdef OS2
+                       if (*cp == '0') /* To define function keys */
+                               *op++ = 0xE0;
+                       else
+#endif /* OS2 */
+                       if (*cp >= '?') /* includes '?'; ASCII */
+                               *op++ = CTRL(*cp);
+                       else  {
+                               *op++ = '^';
+                               cp--;
+                       }
+               } else
+                       *op++ = *cp;
+               cp++;
+       }
+       *op = '\0';
+
+       return new;
+}
+
+static char *
+x_mapout(c)
+       int c;
+{
+       static char buf[8];
+       register char *p = buf;
+
+#ifdef OS2
+       if (c == 0xE0) {
+               *p++ = '^';
+               *p++ = '0';
+       } else
+#endif /* OS2 */
+       if (iscntrl((unsigned char)c))  {
+               *p++ = '^';
+               *p++ = UNCTRL(c);
+       } else
+               *p++ = c;
+       *p = 0;
+       return buf;
+}
+
+static void
+x_print(prefix, key)
+       int prefix, key;
+{
+       if (prefix == 1)
+               shprintf("%s", x_mapout(x_prefix1));
+       if (prefix == 2)
+               shprintf("%s", x_mapout(x_prefix2));
+#ifdef OS2
+       if (prefix == 3)
+               shprintf("%s", x_mapout(x_prefix3));
+#endif /* OS2 */
+       shprintf("%s = ", x_mapout(key));
+       if (x_tab[prefix][key] != XFUNC_ins_string)
+               shprintf("%s\n", x_ftab[x_tab[prefix][key]].xf_name);
+       else
+               shprintf("'%s'\n", x_atab[prefix][key]);
+}
+
+int
+x_bind(a1, a2, macro, list)
+       const char *a1, *a2;
+       int macro;              /* bind -m */
+       int list;               /* bind -l */
+{
+       Findex f;
+       int prefix, key;
+       char *sp = NULL;
+       char *m1, *m2;
+
+       if (x_tab == NULL) {
+               bi_errorf("cannot bind, not a tty");
+               return 1;
+       }
+
+       /* List function names */
+       if (list) {
+               for (f = 0; f < NELEM(x_ftab); f++)
+                       if (x_ftab[f].xf_name
+                           && !(x_ftab[f].xf_flags & XF_NOBIND))
+                               shprintf("%s\n", x_ftab[f].xf_name);
+               return 0;
+       }
+
+       if (a1 == NULL) {
+               for (prefix = 0; prefix < X_NTABS; prefix++)
+                       for (key = 0; key < X_TABSZ; key++) {
+                               f = x_tab[prefix][key];
+                               if (f == XFUNC_insert || f == XFUNC_error
+                                   || (macro && f != XFUNC_ins_string))
+                                       continue;
+                               x_print(prefix, key);
+                       }
+               return 0;
+       }
+
+       m2 = m1 = x_mapin(a1, ATEMP);
+       prefix = key = 0;
+       for (;; m1++) {
+               key = *m1 & CHARMASK;
+               if (x_tab[prefix][key] == XFUNC_meta1)
+                       prefix = 1;
+               else if (x_tab[prefix][key] == XFUNC_meta2)
+                       prefix = 2;
+#ifdef OS2
+               else if (x_tab[prefix][key] == XFUNC_meta3)
+                       prefix = 3;
+#endif /* OS2 */
+               else
+                       break;
+       }
+       afree(m2, ATEMP);
+
+       if (a2 == NULL) {
+               x_print(prefix, key);
+               return 0;
+       }
+
+       if (*a2 == 0)
+               f = XFUNC_insert;
+       else if (!macro) {
+               for (f = 0; f < NELEM(x_ftab); f++)
+                       if (x_ftab[f].xf_name
+                           && strcmp(x_ftab[f].xf_name, a2) == 0)
+                               break;
+               if (f == NELEM(x_ftab) || x_ftab[f].xf_flags & XF_NOBIND) {
+                       bi_errorf("%s: no such function", a2);
+                       return 1;
+               }
+#if 0          /* This breaks the bind commands that map arrow keys */
+               if (f == XFUNC_meta1)
+                       x_prefix1 = key;
+               if (f == XFUNC_meta2)
+                       x_prefix2 = key;
+#endif /* 0 */
+       } else {
+               f = XFUNC_ins_string;
+               sp = x_mapin(a2, AEDIT);
+       }
+
+       if (x_tab[prefix][key] == XFUNC_ins_string && x_atab[prefix][key])
+               afree((void *)x_atab[prefix][key], AEDIT);
+       x_tab[prefix][key] = f;
+       x_atab[prefix][key] = sp;
+
+       /* Track what the user has bound so x_emacs_keys() won't toast things */
+       if (f == XFUNC_insert)
+               x_bound[(prefix * X_TABSZ + key) / 8] &=
+                       ~(1 << ((prefix * X_TABSZ + key) % 8));
+       else
+               x_bound[(prefix * X_TABSZ + key) / 8] |=
+                       (1 << ((prefix * X_TABSZ + key) % 8));
+
+       return 0;
+}
+
+void
+x_init_emacs()
+{
+       size_t i;
+       register int j;
+       char *locale;
+
+       ainit(AEDIT);
+       x_nextcmd = -1;
+
+       x_tab = (Findex (*)[X_TABSZ]) alloc(sizeofN(*x_tab, X_NTABS), AEDIT);
+       for (j = 0; j < X_TABSZ; j++)
+               x_tab[0][j] = XFUNC_insert;
+       for (i = 1; i < X_NTABS; i++)
+               for (j = 0; j < X_TABSZ; j++)
+                       x_tab[i][j] = XFUNC_error;
+       for (i = 0; i < NELEM(x_defbindings); i++)
+               x_tab[(unsigned char)x_defbindings[i].xdb_tab][x_defbindings[i].xdb_char]
+                       = x_defbindings[i].xdb_func;
+
+       x_atab = (char *(*)[X_TABSZ]) alloc(sizeofN(*x_atab, X_NTABS), AEDIT);
+       for (i = 1; i < X_NTABS; i++)
+               for (j = 0; j < X_TABSZ; j++)
+                       x_atab[i][j] = NULL;
+
+       /* Determine if we can translate meta key or use 8-bit AscII 
+        * XXX - It would be nice if there was a locale attribute to
+        * determine if the locale is 7-bit or not.
+        */
+       locale = setlocale(LC_CTYPE, NULL);
+       if (locale == NULL || !strcmp(locale, "C") || !strcmp(locale, "POSIX"))
+               Flag(FEMACSUSEMETA) = 0;
+}
+
+static void bind_if_not_bound(int p, int k, int func);
+
+static void
+bind_if_not_bound(p, k, func)
+       int p, k;
+       int func;
+{
+       /* Has user already bound this key?  If so, don't override it */
+       if (x_bound[((p) * X_TABSZ + (k)) / 8]
+           & (1 << (((p) * X_TABSZ + (k)) % 8)))
+               return;
+
+       x_tab[p][k] = func;
+}
+
+void
+x_emacs_keys(ec)
+       X_chars *ec;
+{
+       if (ec->erase >= 0) {
+               bind_if_not_bound(0, ec->erase, XFUNC_del_back);
+               bind_if_not_bound(1, ec->erase, XFUNC_del_bword);
+       }
+       if (ec->kill >= 0)
+               bind_if_not_bound(0, ec->kill, XFUNC_del_line);
+       if (ec->werase >= 0)
+               bind_if_not_bound(0, ec->werase, XFUNC_del_bword);
+       if (ec->intr >= 0)
+               bind_if_not_bound(0, ec->intr, XFUNC_abort);
+       if (ec->quit >= 0)
+               bind_if_not_bound(0, ec->quit, XFUNC_noop);
+}
+
+static int
+x_set_mark(c)
+       int c;
+{
+       xmp = xcp;
+       return KSTD;
+}
+
+static int
+x_kill_region(c)
+       int c;
+{
+       int     rsize;
+       char    *xr;
+
+       if (xmp == NULL) {
+               x_e_putc(BEL);
+               return KSTD;
+       }
+       if (xmp > xcp) {
+               rsize = xmp - xcp;
+               xr = xcp;
+       } else {
+               rsize = xcp - xmp;
+               xr = xmp;
+       }
+       x_goto(xr);
+       x_delete(rsize, TRUE);
+       xmp = xr;
+       return KSTD;
+}
+
+static int
+x_xchg_point_mark(c)
+       int c;
+{
+       char    *tmp;
+
+       if (xmp == NULL) {
+               x_e_putc(BEL);
+               return KSTD;
+       }
+       tmp = xmp;
+       xmp = xcp;
+       x_goto( tmp );
+       return KSTD;
+}
+
+static int
+x_version(c)
+       int c;
+{
+       char *o_xbuf = xbuf, *o_xend = xend;
+       char *o_xbp = xbp, *o_xep = xep, *o_xcp = xcp;
+       int lim = x_lastcp() - xbp;
+
+       xbuf = xbp = xcp = ksh_version + 4;
+       xend = xep = ksh_version + 4 + strlen(ksh_version + 4);
+       x_redraw(lim);
+       x_flush();
+
+       c = x_e_getc();
+       xbuf = o_xbuf;
+       xend = o_xend;
+       xbp = o_xbp;
+       xep = o_xep;
+       xcp = o_xcp;
+       x_redraw(strlen(ksh_version));
+
+       if (c < 0)
+               return KSTD;
+       /* This is what at&t ksh seems to do...  Very bizarre */
+       if (c != ' ')
+               x_e_ungetc(c);
+
+       return KSTD;
+}
+
+static int
+x_noop(c)
+       int c;
+{
+       return KSTD;
+}
+
+#ifdef SILLY
+static int
+x_game_of_life(c)
+       int c;
+{
+       char    newbuf [256+1];
+       register char *ip, *op;
+       int     i, len;
+
+       i = xep - xbuf;
+       *xep = 0;
+       len = x_size_str(xbuf);
+       xcp = xbp = xbuf;
+       memmove(newbuf+1, xbuf, i);
+       newbuf[0] = 'A';
+       newbuf[i] = 'A';
+       for (ip = newbuf+1, op = xbuf; --i >= 0; ip++, op++)  {
+               /*  Empty space  */
+               if (*ip < '@' || *ip == '_' || *ip == 0x7F)  {
+                       /*  Two adults, make whoopee */
+                       if (ip[-1] < '_' && ip[1] < '_')  {
+                               /*  Make kid look like parents.  */
+                               *op = '`' + ((ip[-1] + ip[1])/2)%32;
+                               if (*op == 0x7F) /* Birth defect */
+                                       *op = '`';
+                       }
+                       else
+                               *op = ' ';      /* nothing happens */
+                       continue;
+               }
+               /*  Child */
+               if (*ip > '`')  {
+                       /*  All alone, dies  */
+                       if (ip[-1] == ' ' && ip[1] == ' ')
+                               *op = ' ';
+                       else    /*  Gets older */
+                               *op = *ip-'`'+'@';
+                       continue;
+               }
+               /*  Adult  */
+               /*  Overcrowded, dies */
+               if (ip[-1] >= '@' && ip[1] >= '@')  {
+                       *op = ' ';
+                       continue;
+               }
+               *op = *ip;
+       }
+       *op = 0;
+       x_redraw(len);
+       return KSTD;
+}
+#endif
+
+/*
+ *     File/command name completion routines
+ */
+
+
+static int
+x_comp_comm(c)
+       int c;
+{
+       do_complete(XCF_COMMAND, CT_COMPLETE);
+       return KSTD;
+}
+static int
+x_list_comm(c)
+       int c;
+{
+       do_complete(XCF_COMMAND, CT_LIST);
+       return KSTD;
+}
+static int
+x_complete(c)
+       int c;
+{
+       do_complete(XCF_COMMAND_FILE, CT_COMPLETE);
+       return KSTD;
+}
+static int
+x_enumerate(c)
+       int c;
+{
+       do_complete(XCF_COMMAND_FILE, CT_LIST);
+       return KSTD;
+}
+static int
+x_comp_file(c)
+       int c;
+{
+       do_complete(XCF_FILE, CT_COMPLETE);
+       return KSTD;
+}
+static int
+x_list_file(c)
+       int c;
+{
+       do_complete(XCF_FILE, CT_LIST);
+       return KSTD;
+}
+static int
+x_comp_list(c)
+       int c;
+{
+       do_complete(XCF_COMMAND_FILE, CT_COMPLIST);
+       return KSTD;
+}
+static int
+x_expand(c)
+       int c;
+{
+       char **words;
+       int nwords = 0;
+       int start, end;
+       int is_command;
+       int i;
+
+       nwords = x_cf_glob(XCF_FILE,
+               xbuf, xep - xbuf, xcp - xbuf,
+               &start, &end, &words, &is_command);
+
+       if (nwords == 0) {
+               x_e_putc(BEL);
+               return KSTD;
+       }
+
+       x_goto(xbuf + start);
+       x_delete(end - start, FALSE);
+       for (i = 0; i < nwords;) {
+               if (x_escape(words[i], strlen(words[i]), x_emacs_putbuf) < 0 ||
+                   (++i < nwords && x_ins(space) < 0))
+               {
+                       x_e_putc(BEL);
+                       return KSTD;
+               }
+       }
+       x_adjust();
+
+       return KSTD;
+}
+
+/* type == 0 for list, 1 for complete and 2 for complete-list */
+static void
+do_complete(flags, type)
+       int flags;      /* XCF_{COMMAND,FILE,COMMAND_FILE} */
+       Comp_type type;
+{
+       char **words;
+       int nwords;
+       int start, end, nlen, olen;
+       int is_command;
+       int completed = 0;
+
+       nwords = x_cf_glob(flags, xbuf, xep - xbuf, xcp - xbuf,
+                           &start, &end, &words, &is_command);
+       /* no match */
+       if (nwords == 0) {
+               x_e_putc(BEL);
+               return;
+       }
+
+       if (type == CT_LIST) {
+               x_print_expansions(nwords, words, is_command);
+               x_redraw(0);
+               x_free_words(nwords, words);
+               return;
+       }
+
+       olen = end - start;
+       nlen = x_longest_prefix(nwords, words);
+       /* complete */
+       if (nwords == 1 || nlen > olen) {
+               x_goto(xbuf + start);
+               x_delete(olen, FALSE);
+               x_escape(words[0], nlen, x_emacs_putbuf);
+               x_adjust();
+               completed = 1;
+       }
+       /* add space if single non-dir match */
+       if ((nwords == 1) && (!ISDIRSEP(words[0][nlen - 1]))) {
+               x_ins(space);
+               completed = 1;
+       }
+
+       if (type == CT_COMPLIST && !completed) {
+               x_print_expansions(nwords, words, is_command);
+               completed = 1;
+       }
+
+       if (completed)  
+               x_redraw(0);    
+
+       x_free_words(nwords, words);
+}
+
+/* NAME:
+ *      x_adjust - redraw the line adjusting starting point etc.
+ *
+ * DESCRIPTION:
+ *      This function is called when we have exceeded the bounds
+ *      of the edit window.  It increments x_adj_done so that
+ *      functions like x_ins and x_delete know that we have been
+ *      called and can skip the x_bs() stuff which has already
+ *      been done by x_redraw.
+ *
+ * RETURN VALUE:
+ *      None
+ */
+
+static void
+x_adjust()
+{
+  x_adj_done++;                        /* flag the fact that we were called. */
+  /*
+   * we had a problem if the prompt length > xx_cols / 2
+   */
+  if ((xbp = xcp - (x_displen / 2)) < xbuf)
+    xbp = xbuf;
+  xlp_valid = FALSE;
+  x_redraw(xx_cols);
+  x_flush();
+}
+
+static int unget_char = -1;
+
+static void
+x_e_ungetc(c)
+       int c;
+{
+       unget_char = c;
+}
+
+static int
+x_e_getc()
+{
+       int c;
+       
+       if (unget_char >= 0) {
+               c = unget_char;
+               unget_char = -1;
+       } else {
+               if (macroptr)  {
+                       c = (unsigned char) *macroptr++;
+                       if (!*macroptr)
+                               macroptr = (char *) 0;
+               } else
+                       c = x_getc();
+       }
+
+       return c <= CHARMASK ? c : (c & CHARMASK);
+}
+
+static void
+x_e_putc(c)
+       int c;
+{
+  if (c == '\r' || c == '\n')
+    x_col = 0;
+  if (x_col < xx_cols)
+  {
+    x_putc(c);
+    switch(c)
+    {
+    case BEL:
+      break;
+    case '\r':
+    case '\n':
+    break;
+    case '\b':
+      x_col--;
+      break;
+    default:
+      x_col++;
+      break;
+    }
+  }
+  if (x_adj_ok && (x_col < 0 || x_col >= (xx_cols - 2)))
+  {
+    x_adjust();
+  }
+}
+
+#ifdef DEBUG
+static int
+x_debug_info(c)
+       int c;
+{
+       x_flush();
+       shellf("\nksh debug:\n");
+       shellf("\tx_col == %d,\t\tx_cols == %d,\tx_displen == %d\n",
+                x_col, xx_cols, x_displen);
+       shellf("\txcp == 0x%lx,\txep == 0x%lx\n", (long) xcp, (long) xep);
+       shellf("\txbp == 0x%lx,\txbuf == 0x%lx\n", (long) xbp, (long) xbuf);
+       shellf("\txlp == 0x%lx\n", (long) xlp);
+       shellf("\txlp == 0x%lx\n", (long) x_lastcp());
+       shellf(newline);
+       x_redraw(-1);
+       return 0;
+}
+#endif
+
+static void
+x_e_puts(s)
+       const char *s;
+{
+  register int adj = x_adj_done;
+
+  while (*s && adj == x_adj_done)
+    x_e_putc(*s++);
+}
+
+/* NAME:
+ *      x_set_arg - set an arg value for next function
+ *
+ * DESCRIPTION:
+ *      This is a simple implementation of M-[0-9].
+ *
+ * RETURN VALUE:
+ *      KSTD
+ */
+
+static int
+x_set_arg(c)
+       int c;
+{
+       int n = 0;
+       int first = 1;
+
+       c &= CHARMASK;  /* strip command prefix */
+       for (; c >= 0 && isdigit(c); c = x_e_getc(), first = 0)
+               n = n * 10 + (c - '0');
+       if (c < 0 || first) {
+               x_e_putc(BEL);
+               x_arg = 1;
+               x_arg_defaulted = 1;
+       } else {
+               x_e_ungetc(c);
+               x_arg = n;
+               x_arg_defaulted = 0;
+       }
+       return KSTD;
+}
+
+
+/* Comment or uncomment the current line. */
+static int
+x_comment(c)
+       int c;
+{
+       int oldsize = x_size_str(xbuf);
+       int len = xep - xbuf;
+       int ret = x_do_comment(xbuf, xend - xbuf, &len);
+
+       if (ret < 0)
+               x_e_putc(BEL);
+       else {
+               xep = xbuf + len;
+               *xep = '\0';
+               xcp = xbp = xbuf;
+               x_redraw(oldsize);
+               if (ret > 0)
+                       return x_newline('\n');
+       }
+       return KSTD;
+}
+
+
+/* NAME:
+ *      x_prev_histword - recover word from prev command
+ *
+ * DESCRIPTION:
+ *      This function recovers the last word from the previous
+ *      command and inserts it into the current edit line.  If a
+ *      numeric arg is supplied then the n'th word from the
+ *      start of the previous command is used.
+ *
+ *      Bound to M-.
+ *
+ * RETURN VALUE:
+ *      KSTD
+ */
+
+static int
+x_prev_histword(c)
+       int c;
+{
+  register char *rcp;
+  char *cp;
+
+  cp = *histptr;
+  if (!cp)
+    x_e_putc(BEL);
+  else if (x_arg_defaulted) {
+    rcp = &cp[strlen(cp) - 1];
+    /*
+     * ignore white-space after the last word
+     */
+    while (rcp > cp && is_cfs(*rcp))
+      rcp--;
+    while (rcp > cp && !is_cfs(*rcp))
+      rcp--;
+    if (is_cfs(*rcp))
+      rcp++;
+    x_ins(rcp);
+  } else {
+    int i;
+
+    rcp = cp;
+    /*
+     * ignore white-space at start of line
+     */
+    while (*rcp && is_cfs(*rcp))
+      rcp++;
+    while (x_arg-- > 1)
+    {
+      while (*rcp && !is_cfs(*rcp))
+       rcp++;
+      while (*rcp && is_cfs(*rcp))
+       rcp++;
+    }
+    cp = rcp;
+    while (*rcp && !is_cfs(*rcp))
+      rcp++;
+    i = *rcp;
+    *rcp = '\0';
+    x_ins(cp);
+    *rcp = i;
+  }
+  return KSTD;
+}
+
+/* Uppercase N(1) words */
+static int
+x_fold_upper(c)
+  int c;
+{
+       return x_fold_case('U');
+}
+
+/* Lowercase N(1) words */
+static int
+x_fold_lower(c)
+  int c;
+{
+       return x_fold_case('L');
+}
+
+/* Lowercase N(1) words */
+static int
+x_fold_capitalize(c)
+  int c;
+{
+       return x_fold_case('C');
+}
+
+/* NAME:
+ *      x_fold_case - convert word to UPPER/lower/Capital case
+ *
+ * DESCRIPTION:
+ *      This function is used to implement M-U,M-u,M-L,M-l,M-C and M-c
+ *      to UPPER case, lower case or Capitalize words.
+ *
+ * RETURN VALUE:
+ *      None
+ */
+
+static int
+x_fold_case(c)
+       int c;
+{
+       char *cp = xcp;
+       
+       if (cp == xep) {
+               x_e_putc(BEL);
+               return KSTD;
+       }
+       while (x_arg--) {
+               /*
+                * first skip over any white-space
+                */
+               while (cp != xep && is_mfs(*cp))
+                       cp++;
+               /*
+                * do the first char on its own since it may be
+                * a different action than for the rest.
+                */
+               if (cp != xep) {
+                       if (c == 'L') {         /* lowercase */
+                               if (isupper((unsigned char)*cp))
+                                       *cp = tolower((unsigned char)*cp);
+                       } else {                /* uppercase, capitialize */
+                               if (islower((unsigned char)*cp))
+                                       *cp = toupper((unsigned char)*cp);
+                       }
+                       cp++;
+               }
+               /*
+                * now for the rest of the word
+                */
+               while (cp != xep && !is_mfs((unsigned char)*cp)) {
+                       if (c == 'U') {         /* uppercase */
+                               if (islower((unsigned char)*cp))
+                                       *cp = toupper((unsigned char)*cp);
+                       } else {                /* lowercase, capitialize */
+                               if (isupper((unsigned char)*cp))
+                                       *cp = tolower((unsigned char)*cp);
+                       }
+                       cp++;
+               }
+       }
+       x_goto(cp);
+       return KSTD;
+}
+
+/* NAME:
+ *      x_lastcp - last visible char
+ *
+ * SYNOPSIS:
+ *      x_lastcp()
+ *
+ * DESCRIPTION:
+ *      This function returns a pointer to that  char in the
+ *      edit buffer that will be the last displayed on the
+ *      screen.  The sequence:
+ *
+ *      for (cp = x_lastcp(); cp > xcp; cp)
+ *        x_bs(*--cp);
+ *
+ *      Will position the cursor correctly on the screen.
+ *
+ * RETURN VALUE:
+ *      cp or NULL
+ */
+
+static char *
+x_lastcp()
+{
+  register char *rcp;
+  register int i;
+
+  if (!xlp_valid)
+  {
+    for (i = 0, rcp = xbp; rcp < xep && i < x_displen; rcp++)
+      i += x_size(*rcp);
+    xlp = rcp;
+  }
+  xlp_valid = TRUE;
+  return (xlp);
+}
+
+#endif /* EDIT */
diff --git a/bin/ksh/eval.c b/bin/ksh/eval.c
new file mode 100644 (file)
index 0000000..3ce2026
--- /dev/null
@@ -0,0 +1,1405 @@
+/*     $NetBSD: eval.c,v 1.14 2011/08/21 21:24:34 dholland Exp $       */
+
+/*
+ * Expansion - quoting, separation, substitution, globbing
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: eval.c,v 1.14 2011/08/21 21:24:34 dholland Exp $");
+#endif
+
+#include <stdint.h>
+#include <pwd.h>
+
+#include "sh.h"
+#include "ksh_dir.h"
+#include "ksh_stat.h"
+
+/*
+ * string expansion
+ *
+ * first pass: quoting, IFS separation, ~, ${}, $() and $(()) substitution.
+ * second pass: alternation ({,}), filename expansion (*?[]).
+ */
+
+/* expansion generator state */
+typedef struct Expand {
+       /* int  type; */        /* see expand() */
+       const char *str;        /* string */
+       union {
+               const char **strv;/* string[] */
+               struct shf *shf;/* file */
+       } u;                    /* source */
+       struct tbl *var;        /* variable in ${var..} */
+       short   split;          /* split "$@" / call waitlast $() */
+} Expand;
+
+#define        XBASE           0       /* scanning original */
+#define        XSUB            1       /* expanding ${} string */
+#define        XARGSEP         2       /* ifs0 between "$*" */
+#define        XARG            3       /* expanding $*, $@ */
+#define        XCOM            4       /* expanding $() */
+#define XNULLSUB       5       /* "$@" when $# is 0 (don't generate word) */
+
+/* States used for field splitting */
+#define IFS_WORD       0       /* word has chars (or quotes) */
+#define IFS_WS         1       /* have seen IFS white-space */
+#define IFS_NWS                2       /* have seen IFS non-white-space */
+
+static int     varsub ARGS((Expand *xp, char *sp, char *word, int *stypep, int *slenp));
+static int     comsub ARGS((Expand *xp, char *cp));
+static char   *trimsub ARGS((char *str, char *pat, int how));
+static void    glob ARGS((char *cp, XPtrV *wp, int markdirs));
+static void    globit ARGS((XString *xs, char **xpp, char *sp, XPtrV *wp,
+                            int check));
+static char    *maybe_expand_tilde ARGS((char *p, XString *dsp, char **dpp,
+                                         int isassign));
+static char   *tilde ARGS((char *acp));
+static char   *homedir ARGS((char *name));
+#ifdef BRACE_EXPAND
+static void    alt_expand ARGS((XPtrV *wp, char *start, char *exp_start,
+                                char *end, int fdo));
+#endif
+
+/* compile and expand word */
+char *
+substitute(cp, f)
+       const char *cp;
+       int f;
+{
+       struct source *s, *sold;
+
+       sold = source;
+       s = pushs(SWSTR, ATEMP);
+       s->start = s->str = cp;
+       source = s;
+       if (yylex(ONEWORD) != LWORD)
+               internal_errorf(1, "substitute");
+       source = sold;
+       afree(s, ATEMP);
+       return evalstr(yylval.cp, f);
+}
+
+/*
+ * expand arg-list
+ */
+char **
+eval(ap, f)
+       register char **ap;
+       int f;
+{
+       XPtrV w;
+
+       if (*ap == NULL)
+               return ap;
+       XPinit(w, 32);
+       XPput(w, NULL);         /* space for shell name */
+#ifdef SHARPBANG
+       XPput(w, NULL);         /* and space for one arg */
+#endif
+       while (*ap != NULL)
+               expand(*ap++, &w, f);
+       XPput(w, NULL);
+#ifdef SHARPBANG
+       return (char **) XPclose(w) + 2;
+#else
+       return (char **) XPclose(w) + 1;
+#endif
+}
+
+/*
+ * expand string
+ */
+char *
+evalstr(cp, f)
+       char *cp;
+       int f;
+{
+       XPtrV w;
+
+       XPinit(w, 1);
+       expand(cp, &w, f);
+       cp = (XPsize(w) == 0) ? null : (char*) *XPptrv(w);
+       XPfree(w);
+       return cp;
+}
+
+/*
+ * expand string - return only one component
+ * used from iosetup to expand redirection files
+ */
+char *
+evalonestr(cp, f)
+       register char *cp;
+       int f;
+{
+       XPtrV w;
+
+       XPinit(w, 1);
+       expand(cp, &w, f);
+       switch (XPsize(w)) {
+       case 0:
+               cp = null;
+               break;
+       case 1:
+               cp = (char*) *XPptrv(w);
+               break;
+       default:
+               cp = evalstr(cp, f&~DOGLOB);
+               break;
+       }
+       XPfree(w);
+       return cp;
+}
+
+/* for nested substitution: ${var:=$var2} */
+typedef struct SubType {
+       short   stype;          /* [=+-?%#] action after expanded word */
+       short   base;           /* begin position of expanded word */
+       short   f;              /* saved value of f (DOPAT, etc) */
+       struct tbl *var;        /* variable for ${var..} */
+       short   quote;          /* saved value of quote (for ${..[%#]..}) */
+       struct SubType *prev;   /* old type */
+       struct SubType *next;   /* poped type (to avoid re-allocating) */
+} SubType;
+
+void
+expand(cp, wp, f)
+       char *cp;               /* input word */
+       register XPtrV *wp;     /* output words */
+       int f;                  /* DO* flags */
+{
+       register int UNINITIALIZED(c);
+       register int type;      /* expansion type */
+       register int quote = 0; /* quoted */
+       XString ds;             /* destination string */
+       register char *dp, *sp; /* dest., source */
+       int fdo, word;          /* second pass flags; have word */
+       int doblank;            /* field splitting of parameter/command subst */
+       Expand x;               /* expansion variables */
+       SubType st_head, *st;
+       int UNINITIALIZED(newlines); /* For trailing newlines in COMSUB */
+       int saw_eq, tilde_ok;
+       int make_magic;
+       size_t len;
+
+       x.split = 0;    /* XXX gcc */
+       x.str = NULL;   /* XXX gcc */
+       x.u.strv = NULL;/* XXX gcc */
+       if (cp == NULL)
+               internal_errorf(1, "expand(NULL)");
+       /* for alias, readonly, set, typeset commands */
+       if ((f & DOVACHECK) && is_wdvarassign(cp)) {
+               f &= ~(DOVACHECK|DOBLANK|DOGLOB|DOTILDE);
+               f |= DOASNTILDE;
+       }
+       if (Flag(FNOGLOB))
+               f &= ~DOGLOB;
+       if (Flag(FMARKDIRS))
+               f |= DOMARKDIRS;
+#ifdef BRACE_EXPAND
+       if (Flag(FBRACEEXPAND) && (f & DOGLOB))
+               f |= DOBRACE_;
+#endif /* BRACE_EXPAND */
+
+       Xinit(ds, dp, 128, ATEMP);      /* init dest. string */
+       type = XBASE;
+       sp = cp;
+       fdo = 0;
+       saw_eq = 0;
+       tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0; /* must be 1/0 */
+       doblank = 0;
+       make_magic = 0;
+       word = (f&DOBLANK) ? IFS_WS : IFS_WORD;
+       st_head.next = (SubType *) 0;
+       st = &st_head;
+
+       while (1) {
+               Xcheck(ds, dp);
+
+               switch (type) {
+                 case XBASE:   /* original prefixed string */
+                       c = *sp++;
+                       switch (c) {
+                         case EOS:
+                               c = 0;
+                               break;
+                         case CHAR:
+                               c = *sp++;
+                               break;
+                         case QCHAR:
+                               quote |= 2; /* temporary quote */
+                               c = *sp++;
+                               break;
+                         case OQUOTE:
+                               word = IFS_WORD;
+                               tilde_ok = 0;
+                               quote = 1;
+                               continue;
+                         case CQUOTE:
+                               quote = 0;
+                               continue;
+                         case COMSUB:
+                               tilde_ok = 0;
+                               if (f & DONTRUNCOMMAND) {
+                                       word = IFS_WORD;
+                                       *dp++ = '$'; *dp++ = '(';
+                                       while (*sp != '\0') {
+                                               Xcheck(ds, dp);
+                                               *dp++ = *sp++;
+                                       }
+                                       *dp++ = ')';
+                               } else {
+                                       type = comsub(&x, sp);
+                                       if (type == XCOM && (f&DOBLANK))
+                                               doblank++;
+                                       sp = strchr(sp, 0) + 1;
+                                       newlines = 0;
+                               }
+                               continue;
+                         case EXPRSUB:
+                               word = IFS_WORD;
+                               tilde_ok = 0;
+                               if (f & DONTRUNCOMMAND) {
+                                       *dp++ = '$'; *dp++ = '('; *dp++ = '(';
+                                       while (*sp != '\0') {
+                                               Xcheck(ds, dp);
+                                               *dp++ = *sp++;
+                                       }
+                                       *dp++ = ')'; *dp++ = ')';
+                               } else {
+                                       struct tbl v;
+                                       char *p;
+
+                                       v.flag = DEFINED|ISSET|INTEGER;
+                                       v.type = 10; /* not default */
+                                       v.name[0] = '\0';
+                                       v_evaluate(&v, substitute(sp, 0),
+                                               KSH_UNWIND_ERROR);
+                                       sp = strchr(sp, 0) + 1;
+                                       for (p = str_val(&v); *p; ) {
+                                               Xcheck(ds, dp);
+                                               *dp++ = *p++;
+                                       }
+                               }
+                               continue;
+                         case OSUBST: /* ${{#}var{:}[=+-?#%]word} */
+                         /* format is:
+                          *   OSUBST [{x] plain-variable-part \0
+                          *     compiled-word-part CSUBST [}x]
+                          * This is were all syntax checking gets done...
+                          */
+                         {
+                               char *varname = ++sp; /* skip the { or x (}) */
+                               int stype;
+                               int slen;
+
+                               slen = -1;      /* XXX gcc */
+                               sp = strchr(sp, '\0') + 1; /* skip variable */
+                               type = varsub(&x, varname, sp, &stype, &slen);
+                               if (type < 0) {
+                                       char endc;
+                                       char *str, *end;
+
+                                       end = (char *) wdscan(sp, CSUBST);
+                                       /* ({) the } or x is already skipped */
+                                       endc = *end;
+                                       *end = EOS;
+                                       str = snptreef((char *) 0, 64, "%S",
+                                                       varname - 1);
+                                       *end = endc;
+                                       errorf("%s: bad substitution", str);
+                               }
+                               if (f&DOBLANK)
+                                       doblank++;
+                               tilde_ok = 0;
+                               if (type == XBASE) {    /* expand? */
+                                       if (!st->next) {
+                                               SubType *newst;
+
+                                               newst = (SubType *) alloc(
+                                                       sizeof(SubType), ATEMP);
+                                               newst->next = (SubType *) 0;
+                                               newst->prev = st;
+                                               st->next = newst;
+                                       }
+                                       st = st->next;
+                                       st->stype = stype;
+                                       st->base = Xsavepos(ds, dp);
+                                       st->f = f;
+                                       st->var = x.var;
+                                       st->quote = quote;
+                                       /* skip qualifier(s) */
+                                       if (stype)
+                                               sp += slen;
+                                       switch (stype & 0x7f) {
+                                         case '#':
+                                         case '%':
+                                               /* ! DOBLANK,DOBRACE_,DOTILDE */
+                                               f = DOPAT | (f&DONTRUNCOMMAND)
+                                                   | DOTEMP_;
+                                               quote = 0;
+                                               /* Prepend open pattern (so |
+                                                * in a trim will work as
+                                                * expected)
+                                                */
+                                               *dp++ = MAGIC;
+                                               *dp++ = '@' + 0x80;
+                                               break;
+                                         case '=':
+                                               /* Enabling tilde expansion
+                                                * after :'s here is
+                                                * non-standard ksh, but is
+                                                * consistent with rules for
+                                                * other assignments.  Not
+                                                * sure what POSIX thinks of
+                                                * this.
+                                                * Not doing tilde expansion
+                                                * for integer variables is a
+                                                * non-POSIX thing - makes
+                                                * sense though, since ~ is
+                                                * a arithmetic operator.
+                                                */
+                                               if (!(x.var->flag & INTEGER))
+                                                       f |= DOASNTILDE|DOTILDE;
+                                               f |= DOTEMP_;
+                                               /* These will be done after the
+                                                * value has been assigned.
+                                                */
+                                               f &= ~(DOBLANK|DOGLOB|DOBRACE_);
+                                               tilde_ok = 1;
+                                               break;
+                                         case '?':
+                                               f &= ~DOBLANK;
+                                               f |= DOTEMP_;
+                                               /* fall through */
+                                         default:
+                                               /* Enable tilde expansion */
+                                               tilde_ok = 1;
+                                               f |= DOTILDE;
+                                       }
+                               } else
+                                       /* skip word */
+                                       sp = (char *) wdscan(sp, CSUBST);
+                               continue;
+                         }
+                         case CSUBST: /* only get here if expanding word */
+                               sp++; /* ({) skip the } or x */
+                               tilde_ok = 0;   /* in case of ${unset:-} */
+                               *dp = '\0';
+                               quote = st->quote;
+                               f = st->f;
+                               if (f&DOBLANK)
+                                       doblank--;
+                               switch (st->stype&0x7f) {
+                                 case '#':
+                                 case '%':
+                                       /* Append end-pattern */
+                                       *dp++ = MAGIC; *dp++ = ')'; *dp = '\0';
+                                       dp = Xrestpos(ds, dp, st->base);
+                                       /* Must use st->var since calling
+                                        * global would break things
+                                        * like x[i+=1].
+                                        */
+                                       x.str = trimsub(str_val(st->var),
+                                               dp, st->stype);
+                                       type = XSUB;
+                                       if (f&DOBLANK)
+                                               doblank++;
+                                       st = st->prev;
+                                       continue;
+                                 case '=':
+                                       /* Restore our position and substitute
+                                        * the value of st->var (may not be
+                                        * the assigned value in the presence
+                                        * of integer/right-adj/etc attributes).
+                                        */
+                                       dp = Xrestpos(ds, dp, st->base);
+                                       /* Must use st->var since calling
+                                        * global would cause with things
+                                        * like x[i+=1] to be evaluated twice.
+                                        */
+                                       /* Note: not exported by FEXPORT
+                                        * in at&t ksh.
+                                        */
+                                       /* XXX POSIX says readonly is only
+                                        * fatal for special builtins (setstr
+                                        * does readonly check).
+                                        */
+                                       len = strlen(dp) + 1;
+                                       setstr(st->var,
+                                           debunk((char *) alloc(len, ATEMP),
+                                               dp, len),
+                                           KSH_UNWIND_ERROR);
+                                       x.str = str_val(st->var);
+                                       type = XSUB;
+                                       if (f&DOBLANK)
+                                               doblank++;
+                                       st = st->prev;
+                                       continue;
+                                 case '?':
+                                   {
+                                       char *s = Xrestpos(ds, dp, st->base);
+
+                                       errorf("%s: %s", st->var->name,
+                                           dp == s ?
+                                             "parameter null or not set"
+                                           : (debunk(s, s, strlen(s) + 1), s));
+                                   }
+                               }
+                               st = st->prev;
+                               type = XBASE;
+                               continue;
+
+                         case OPAT: /* open pattern: *(foo|bar) */
+                               /* Next char is the type of pattern */
+                               make_magic = 1;
+                               c = *sp++ + 0x80;
+                               break;
+
+                         case SPAT: /* pattern separator (|) */
+                               make_magic = 1;
+                               c = '|';
+                               break;
+
+                         case CPAT: /* close pattern */
+                               make_magic = 1;
+                               c = /*(*/ ')';
+                               break;
+                       }
+                       break;
+
+                 case XNULLSUB:
+                       /* Special case for "$@" (and "${foo[@]}") - no
+                        * word is generated if $# is 0 (unless there is
+                        * other stuff inside the quotes).
+                        */
+                       type = XBASE;
+                       if (f&DOBLANK) {
+                               doblank--;
+                               /* not really correct: x=; "$x$@" should
+                                * generate a null argument and
+                                * set A; "${@:+}" shouldn't.
+                                */
+                               if (dp == Xstring(ds, dp))
+                                       word = IFS_WS;
+                       }
+                       continue;
+
+                 case XSUB:
+                       if ((c = *x.str++) == 0) {
+                               type = XBASE;
+                               if (f&DOBLANK)
+                                       doblank--;
+                               continue;
+                       }
+                       break;
+
+                 case XARGSEP:
+                       type = XARG;
+                       quote = 1;
+                 case XARG:
+                       if ((c = *x.str++) == '\0') {
+                               /* force null words to be created so
+                                * set -- '' 2 ''; foo "$@" will do
+                                * the right thing
+                                */
+                               if (quote && x.split)
+                                       word = IFS_WORD;
+                               if ((x.str = *x.u.strv++) == NULL) {
+                                       type = XBASE;
+                                       if (f&DOBLANK)
+                                               doblank--;
+                                       continue;
+                               }
+                               c = ifs0;
+                               if (c == 0) {
+                                       if (quote && !x.split)
+                                               continue;
+                                       c = ' ';
+                               }
+                               if (quote && x.split) {
+                                       /* terminate word for "$@" */
+                                       type = XARGSEP;
+                                       quote = 0;
+                               }
+                       }
+                       break;
+
+                 case XCOM:
+                       if (newlines) {         /* Spit out saved nl's */
+                               c = '\n';
+                               --newlines;
+                       } else {
+                               while ((c = shf_getc(x.u.shf)) == 0 || c == '\n')
+                                   if (c == '\n')
+                                           newlines++; /* Save newlines */
+                               if (newlines && c != EOF) {
+                                       shf_ungetc(c, x.u.shf);
+                                       c = '\n';
+                                       --newlines;
+                               }
+                       }
+                       if (c == EOF) {
+                               newlines = 0;
+                               shf_close(x.u.shf);
+                               if (x.split)
+                                       subst_exstat = waitlast();
+                               type = XBASE;
+                               if (f&DOBLANK)
+                                       doblank--;
+                               continue;
+                       }
+                       break;
+               }
+
+               /* check for end of word or IFS separation */
+               if (c == 0 || (!quote && (f & DOBLANK) && doblank && !make_magic
+                              && ctype(c, C_IFS)))
+               {
+                       /* How words are broken up:
+                        *                 |       value of c
+                        *        word     |    ws      nws     0
+                        *      -----------------------------------
+                        *      IFS_WORD        w/WS    w/NWS   w
+                        *      IFS_WS          -/WS    w/NWS   -
+                        *      IFS_NWS         -/NWS   w/NWS   w
+                        *   (w means generate a word)
+                        * Note that IFS_NWS/0 generates a word (at&t ksh
+                        * doesn't do this, but POSIX does).
+                        */
+                       if (word == IFS_WORD
+                           || (!ctype(c, C_IFSWS) && (c || word == IFS_NWS)))
+                       {
+                               char *p;
+
+                               *dp++ = '\0';
+                               p = Xclose(ds, dp);
+#ifdef BRACE_EXPAND
+                               if (fdo & DOBRACE_)
+                                       /* also does globbing */
+                                       alt_expand(wp, p, p,
+                                                  p + Xlength(ds, (dp - 1)),
+                                                  fdo | (f & DOMARKDIRS));
+                               else
+#endif /* BRACE_EXPAND */
+                               if (fdo & DOGLOB)
+                                       glob(p, wp, f & DOMARKDIRS);
+                               else if ((f & DOPAT) || !(fdo & DOMAGIC_))
+                                       XPput(*wp, p);
+                               else
+                                       XPput(*wp, debunk(p, p, strlen(p) + 1));
+                               fdo = 0;
+                               saw_eq = 0;
+                               tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0;
+                               if (c != 0)
+                                       Xinit(ds, dp, 128, ATEMP);
+                       }
+                       if (c == 0)
+                               return;
+                       if (word != IFS_NWS)
+                               word = ctype(c, C_IFSWS) ? IFS_WS : IFS_NWS;
+               } else {
+                       /* age tilde_ok info - ~ code tests second bit */
+                       tilde_ok <<= 1;
+                       /* mark any special second pass chars */
+                       if (!quote)
+                               switch (c) {
+                                 case '[':
+                                 case NOT:
+                                 case '-':
+                                 case ']':
+                                       /* For character classes - doesn't hurt
+                                        * to have magic !,-,]'s outside of
+                                        * [...] expressions.
+                                        */
+                                       if (f & (DOPAT | DOGLOB)) {
+                                               fdo |= DOMAGIC_;
+                                               if (c == '[')
+                                                       fdo |= f & DOGLOB;
+                                               *dp++ = MAGIC;
+                                       }
+                                       break;
+                                 case '*':
+                                 case '?':
+                                       if (f & (DOPAT | DOGLOB)) {
+                                               fdo |= DOMAGIC_ | (f & DOGLOB);
+                                               *dp++ = MAGIC;
+                                       }
+                                       break;
+#ifdef BRACE_EXPAND
+                                 case OBRACE:
+                                 case ',':
+                                 case CBRACE:
+                                       if ((f & DOBRACE_) && (c == OBRACE
+                                               || (fdo & DOBRACE_)))
+                                       {
+                                               fdo |= DOBRACE_|DOMAGIC_;
+                                               *dp++ = MAGIC;
+                                       }
+                                       break;
+#endif /* BRACE_EXPAND */
+                                 case '=':
+                                       /* Note first unquoted = for ~ */
+                                       if (!(f & DOTEMP_) && !saw_eq) {
+                                               saw_eq = 1;
+                                               tilde_ok = 1;
+                                       }
+                                       break;
+                                 case PATHSEP: /* : */
+                                       /* Note unquoted : for ~ */
+                                       if (!(f & DOTEMP_) && (f & DOASNTILDE))
+                                               tilde_ok = 1;
+                                       break;
+                                 case '~':
+                                       /* tilde_ok is reset whenever
+                                        * any of ' " $( $(( ${ } are seen.
+                                        * Note that tilde_ok must be preserved
+                                        * through the sequence ${A=a=}~
+                                        */
+                                       if (type == XBASE
+                                           && (f & (DOTILDE|DOASNTILDE))
+                                           && (tilde_ok & 2))
+                                       {
+                                               char *p, *dp_x;
+
+                                               dp_x = dp;
+                                               p = maybe_expand_tilde(sp,
+                                                       &ds, &dp_x,
+                                                       f & DOASNTILDE);
+                                               if (p) {
+                                                       if (dp != dp_x)
+                                                               word = IFS_WORD;
+                                                       dp = dp_x;
+                                                       sp = p;
+                                                       continue;
+                                               }
+                                       }
+                                       break;
+                               }
+                       else
+                               quote &= ~2; /* undo temporary */
+
+                       if (make_magic) {
+                               make_magic = 0;
+                               fdo |= DOMAGIC_ | (f & DOGLOB);
+                               *dp++ = MAGIC;
+                       } else if (ISMAGIC(c)) {
+                               fdo |= DOMAGIC_;
+                               *dp++ = MAGIC;
+                       }
+                       *dp++ = c; /* save output char */
+                       word = IFS_WORD;
+               }
+       }
+}
+
+/*
+ * Prepare to generate the string returned by ${} substitution.
+ */
+static int
+varsub(xp, sp, word, stypep, slenp)
+       Expand *xp;
+       char *sp;
+       char *word;
+       int *stypep;    /* becomes qualifier type */
+       int *slenp;     /* " " len (=, :=, etc.) valid iff *stypep != 0 */
+{
+       int c;
+       int state;      /* next state: XBASE, XARG, XSUB, XNULLSUB */
+       int stype;      /* substitution type */
+       int slen;
+       char *p;
+       struct tbl *vp;
+
+       if (sp[0] == '\0')      /* Bad variable name */
+               return -1;
+
+       xp->var = NULL;
+
+       /* ${#var}, string length or array size */
+       if (sp[0] == '#' && (c = sp[1]) != '\0') {
+               int zero_ok = 0;
+
+               /* Can't have any modifiers for ${#...} */
+               if (*word != CSUBST)
+                       return -1;
+               sp++;
+               /* Check for size of array */
+               if ((p=strchr(sp,'[')) && (p[1]=='*'||p[1]=='@') && p[2]==']') {
+                       int n = 0;
+                       int max = 0;
+                       vp = global(arrayname(sp));
+                       if (vp->flag & (ISSET|ARRAY))
+                               zero_ok = 1;
+                       for (; vp; vp = vp->u.array)
+                               if (vp->flag & ISSET) {
+                                       max = vp->index + 1;
+                                       n++;
+                               }
+                       c = n; /* ksh88/ksh93 go for number, not max index */
+               } else if (c == '*' || c == '@')
+                       c = e->loc->argc;
+               else {
+                       p = str_val(global(sp));
+                       zero_ok = p != null;
+                       c = strlen(p);
+               }
+               if (Flag(FNOUNSET) && c == 0 && !zero_ok)
+                       errorf("%s: parameter not set", sp);
+               *stypep = 0; /* unqualified variable/string substitution */
+               xp->str = str_save(ulton((unsigned long)c, 10), ATEMP);
+               return XSUB;
+       }
+
+       /* Check for qualifiers in word part */
+       stype = 0;
+       c = word[slen = 0] == CHAR ? word[1] : 0;
+       if (c == ':') {
+               slen += 2;
+               stype = 0x80;
+               c = word[slen + 0] == CHAR ? word[slen + 1] : 0;
+       }
+       if (ctype(c, C_SUBOP1)) {
+               slen += 2;
+               stype |= c;
+       } else if (ctype(c, C_SUBOP2)) { /* Note: ksh88 allows :%, :%%, etc */
+               slen += 2;
+               stype = c;
+               if (word[slen + 0] == CHAR && c == word[slen + 1]) {
+                       stype |= 0x80;
+                       slen += 2;
+               }
+       } else if (stype)       /* : is not ok */
+               return -1;
+       if (!stype && *word != CSUBST)
+               return -1;
+       *stypep = stype;
+       *slenp = slen;
+
+       c = sp[0];
+       if (c == '*' || c == '@') {
+               switch (stype & 0x7f) {
+                 case '=':     /* can't assign to a vector */
+                 case '%':     /* can't trim a vector (yet) */
+                 case '#':
+                       return -1;
+               }
+               if (e->loc->argc == 0) {
+                       xp->u.strv = NULL;
+                       xp->str = null;
+                       state = c == '@' ? XNULLSUB : XSUB;
+               } else {
+                       char **t = &e->loc->argv[1];
+                       xp->u.strv = (void *)(uintptr_t)t;
+                       xp->str = *xp->u.strv++;
+                       xp->split = c == '@'; /* $@ */
+                       state = XARG;
+               }
+       } else {
+               if ((p=strchr(sp,'[')) && (p[1]=='*'||p[1]=='@') && p[2]==']') {
+                       XPtrV wv;
+
+                       switch (stype & 0x7f) {
+                         case '=':     /* can't assign to a vector */
+                         case '%':     /* can't trim a vector (yet) */
+                         case '#':
+                               return -1;
+                       }
+                       XPinit(wv, 32);
+                       vp = global(arrayname(sp));
+                       for (; vp; vp = vp->u.array) {
+                               if (!(vp->flag&ISSET))
+                                       continue;
+                               XPput(wv, str_val(vp));
+                       }
+                       if (XPsize(wv) == 0) {
+                               xp->str = null;
+                               state = p[1] == '@' ? XNULLSUB : XSUB;
+                               XPfree(wv);
+                       } else {
+                               XPput(wv, 0);
+                               xp->u.strv = (const char **) XPptrv(wv);
+                               xp->str = *xp->u.strv++;
+                               xp->split = p[1] == '@'; /* ${foo[@]} */
+                               state = XARG;
+                       }
+               } else {
+                       /* Can't assign things like $! or $1 */
+                       if ((stype & 0x7f) == '='
+                           && (ctype(*sp, C_VAR1) || digit(*sp)))
+                               return -1;
+                       xp->var = global(sp);
+                       xp->str = str_val(xp->var);
+                       state = XSUB;
+               }
+       }
+
+       c = stype&0x7f;
+       /* test the compiler's code generator */
+       if (ctype(c, C_SUBOP2) ||
+           (((stype&0x80) ? *xp->str=='\0' : xp->str==null) ? /* undef? */
+            c == '=' || c == '-' || c == '?' : c == '+'))
+               state = XBASE;  /* expand word instead of variable value */
+       if (Flag(FNOUNSET) && xp->str == null
+           && (ctype(c, C_SUBOP2) || (state != XBASE && c != '+')))
+               errorf("%s: parameter not set", sp);
+       return state;
+}
+
+/*
+ * Run the command in $(...) and read its output.
+ */
+static int
+comsub(xp, cp)
+       register Expand *xp;
+       char *cp;
+{
+       Source *s, *sold;
+       register struct op *t;
+       struct shf *shf;
+
+       s = pushs(SSTRING, ATEMP);
+       s->start = s->str = cp;
+       sold = source;
+       t = compile(s);
+       afree(s, ATEMP);
+       source = sold;
+
+       if (t == NULL)
+               return XBASE;
+
+       if (t != NULL && t->type == TCOM && /* $(<file) */
+           *t->args == NULL && *t->vars == NULL && t->ioact != NULL) {
+               register struct ioword *io = *t->ioact;
+               char *name;
+
+               if ((io->flag&IOTYPE) != IOREAD)
+                       errorf("funny $() command: %s",
+                               snptreef((char *) 0, 32, "%R", io));
+               shf = shf_open(name = evalstr(io->name, DOTILDE), O_RDONLY, 0,
+                       SHF_MAPHI|SHF_CLEXEC);
+               if (shf == NULL)
+                       errorf("%s: cannot open $() input", name);
+               xp->split = 0;  /* no waitlast() */
+       } else {
+               int ofd1, pv[2];
+               openpipe(pv);
+               shf = shf_fdopen(pv[0], SHF_RD, (struct shf *) 0);
+               ofd1 = savefd(1, 0);    /* fd 1 may be closed... */
+               if (pv[1] != 1) {
+                       ksh_dup2(pv[1], 1, FALSE);
+                       close(pv[1]);
+               }
+               execute(t, XFORK|XXCOM|XPIPEO);
+               restfd(1, ofd1);
+               startlast();
+               xp->split = 1;  /* waitlast() */
+       }
+
+       xp->u.shf = shf;
+       return XCOM;
+}
+
+/*
+ * perform #pattern and %pattern substitution in ${}
+ */
+
+static char *
+trimsub(str, pat, how)
+       register char *str;
+       char *pat;
+       int how;
+{
+       register char *end = strchr(str, 0);
+       register char *p, c;
+
+       switch (how&0xff) {     /* UCHAR_MAX maybe? */
+         case '#':             /* shortest at beginning */
+               for (p = str; p <= end; p++) {
+                       c = *p; *p = '\0';
+                       if (gmatch(str, pat, FALSE)) {
+                               *p = c;
+                               return p;
+                       }
+                       *p = c;
+               }
+               break;
+         case '#'|0x80:        /* longest match at beginning */
+               for (p = end; p >= str; p--) {
+                       c = *p; *p = '\0';
+                       if (gmatch(str, pat, FALSE)) {
+                               *p = c;
+                               return p;
+                       }
+                       *p = c;
+               }
+               break;
+         case '%':             /* shortest match at end */
+               for (p = end; p >= str; p--) {
+                       if (gmatch(p, pat, FALSE))
+                               return str_nsave(str, p - str, ATEMP);
+               }
+               break;
+         case '%'|0x80:        /* longest match at end */
+               for (p = str; p <= end; p++) {
+                       if (gmatch(p, pat, FALSE))
+                               return str_nsave(str, p - str, ATEMP);
+               }
+               break;
+       }
+
+       return str;             /* no match, return string */
+}
+
+/*
+ * glob
+ * Name derived from V6's /etc/glob, the program that expanded filenames.
+ */
+
+/* XXX cp not const 'cause slashes are temporarily replaced with nulls... */
+static void
+glob(cp, wp, markdirs)
+       char *cp;
+       register XPtrV *wp;
+       int markdirs;
+{
+       int oldsize = XPsize(*wp);
+
+       if (glob_str(cp, wp, markdirs) == 0)
+               XPput(*wp, debunk(cp, cp, strlen(cp) + 1));
+       else
+               qsortp(XPptrv(*wp) + oldsize, (size_t)(XPsize(*wp) - oldsize),
+                       xstrcmp);
+}
+
+#define GF_NONE                0
+#define GF_EXCHECK     BIT(0)          /* do existence check on file */
+#define GF_GLOBBED     BIT(1)          /* some globbing has been done */
+#define GF_MARKDIR     BIT(2)          /* add trailing / to directories */
+
+/* Apply file globbing to cp and store the matching files in wp.  Returns
+ * the number of matches found.
+ */
+int
+glob_str(cp, wp, markdirs)
+       char *cp;
+       XPtrV *wp;
+       int markdirs;
+{
+       int oldsize = XPsize(*wp);
+       XString xs;
+       char *xp;
+
+       Xinit(xs, xp, 256, ATEMP);
+       globit(&xs, &xp, cp, wp, markdirs ? GF_MARKDIR : GF_NONE);
+       Xfree(xs, xp);
+
+       return XPsize(*wp) - oldsize;
+}
+
+static void
+globit(xs, xpp, sp, wp, check)
+       XString *xs;            /* dest string */
+       char **xpp;             /* ptr to dest end */
+       char *sp;               /* source path */
+       register XPtrV *wp;     /* output list */
+       int check;              /* GF_* flags */
+{
+       register char *np;      /* next source component */
+       char *xp = *xpp;
+       char *se;
+       char odirsep;
+
+       /* This to allow long expansions to be interrupted */
+       intrcheck();
+
+       if (sp == NULL) {       /* end of source path */
+               /* We only need to check if the file exists if a pattern
+                * is followed by a non-pattern (eg, foo*x/bar; no check
+                * is needed for foo* since the match must exist) or if
+                * any patterns were expanded and the markdirs option is set.
+                * Symlinks make things a bit tricky...
+                */
+               if ((check & GF_EXCHECK)
+                   || ((check & GF_MARKDIR) && (check & GF_GLOBBED)))
+               {
+#define stat_check()   (stat_done ? stat_done : \
+                           (stat_done = stat(Xstring(*xs, xp), &statb) < 0 \
+                               ? -1 : 1))
+                       struct stat lstatb, statb;
+                       int stat_done = 0;       /* -1: failed, 1 ok */
+
+                       if (lstat(Xstring(*xs, xp), &lstatb) < 0)
+                               return;
+                       /* special case for systems which strip trailing
+                        * slashes from regular files (eg, /etc/passwd/).
+                        * SunOS 4.1.3 does this...
+                        */
+                       if ((check & GF_EXCHECK) && xp > Xstring(*xs, xp)
+                           && ISDIRSEP(xp[-1]) && !S_ISDIR(lstatb.st_mode)
+#ifdef S_ISLNK
+                           && (!S_ISLNK(lstatb.st_mode)
+                               || stat_check() < 0
+                               || !S_ISDIR(statb.st_mode))
+#endif /* S_ISLNK */
+                               )
+                               return;
+                       /* Possibly tack on a trailing / if there isn't already
+                        * one and if the file is a directory or a symlink to a
+                        * directory
+                        */
+                       if (((check & GF_MARKDIR) && (check & GF_GLOBBED))
+                           && xp > Xstring(*xs, xp) && !ISDIRSEP(xp[-1])
+                           && (S_ISDIR(lstatb.st_mode)
+#ifdef S_ISLNK
+                               || (S_ISLNK(lstatb.st_mode)
+                                   && stat_check() > 0
+                                   && S_ISDIR(statb.st_mode))
+#endif /* S_ISLNK */
+                                   ))
+                       {
+                               *xp++ = DIRSEP;
+                               *xp = '\0';
+                       }
+               }
+#ifdef OS2 /* Done this way to avoid bug in gcc 2.7.2... */
+    /* Ugly kludge required for command
+     * completion - see how search_access()
+     * is implemented for OS/2...
+     */
+# define KLUDGE_VAL    4
+#else /* OS2 */
+# define KLUDGE_VAL    0
+#endif /* OS2 */
+               XPput(*wp, str_nsave(Xstring(*xs, xp), Xlength(*xs, xp)
+                       + KLUDGE_VAL, ATEMP));
+               return;
+       }
+
+       if (xp > Xstring(*xs, xp))
+               *xp++ = DIRSEP;
+       while (ISDIRSEP(*sp)) {
+               Xcheck(*xs, xp);
+               *xp++ = *sp++;
+       }
+       np = ksh_strchr_dirsep(sp);
+       if (np != NULL) {
+               se = np;
+               odirsep = *np;  /* don't assume DIRSEP, can be multiple kinds */
+               *np++ = '\0';
+       } else {
+               odirsep = '\0'; /* keep gcc quiet */
+               se = sp + strlen(sp);
+       }
+
+
+       /* Check if sp needs globbing - done to avoid pattern checks for strings
+        * containing MAGIC characters, open ['s without the matching close ],
+        * etc. (otherwise opendir() will be called which may fail because the
+        * directory isn't readable - if no globbing is needed, only execute
+        * permission should be required (as per POSIX)).
+        */
+       if (!has_globbing(sp, se)) {
+               XcheckN(*xs, xp, se - sp + 1);
+               debunk(xp, sp, Xnleft(*xs, xp));
+               xp += strlen(xp);
+               *xpp = xp;
+               globit(xs, xpp, np, wp, check);
+       } else {
+               DIR *dirp;
+               struct dirent *d;
+               char *name;
+               int len;
+               int prefix_len;
+
+               /* xp = *xpp;      copy_non_glob() may have re-alloc'd xs */
+               *xp = '\0';
+               prefix_len = Xlength(*xs, xp);
+               dirp = ksh_opendir(prefix_len ? Xstring(*xs, xp) : ".");
+               if (dirp == NULL)
+                       goto Nodir;
+               while ((d = readdir(dirp)) != NULL) {
+                       name = d->d_name;
+                       if ((*name == '.' && *sp != '.')
+                           || !gmatch(name, sp, TRUE))
+                               continue;
+
+                       len = NLENGTH(d) + 1;
+                       XcheckN(*xs, xp, len);
+                       memcpy(xp, name, len);
+                       *xpp = xp + len - 1;
+                       globit(xs, xpp, np, wp,
+                               (check & GF_MARKDIR) | GF_GLOBBED
+                               | (np ? GF_EXCHECK : GF_NONE));
+                       xp = Xstring(*xs, xp) + prefix_len;
+               }
+               closedir(dirp);
+         Nodir:;
+       }
+
+       if (np != NULL)
+               *--np = odirsep;
+}
+
+#if 0
+/* Check if p contains something that needs globbing; if it does, 0 is
+ * returned; if not, p is copied into xs/xp after stripping any MAGICs
+ */
+static int     copy_non_glob ARGS((XString *xs, char **xpp, char *p));
+static int
+copy_non_glob(xs, xpp, p)
+       XString *xs;
+       char **xpp;
+       char *p;
+{
+       char *xp;
+       int len = strlen(p);
+
+       XcheckN(*xs, *xpp, len);
+       xp = *xpp;
+       for (; *p; p++) {
+               if (ISMAGIC(*p)) {
+                       int c = *++p;
+
+                       if (c == '*' || c == '?')
+                               return 0;
+                       if (*p == '[') {
+                               char *q = p + 1;
+
+                               if (ISMAGIC(*q) && q[1] == NOT)
+                                       q += 2;
+                               if (ISMAGIC(*q) && q[1] == ']')
+                                       q += 2;
+                               for (; *q; q++)
+                                       if (ISMAGIC(*q) && *++q == ']')
+                                               return 0;
+                               /* pass a literal [ through */
+                       }
+                       /* must be a MAGIC-MAGIC, or MAGIC-!, MAGIC--, etc. */
+               }
+               *xp++ = *p;
+       }
+       *xp = '\0';
+       *xpp = xp;
+       return 1;
+}
+#endif /* 0 */
+
+/* remove MAGIC from string */
+char *
+debunk(dp, sp, dlen)
+       char *dp;
+       const char *sp;
+       size_t dlen;
+{
+       char *d, *s;
+
+       if ((s = strchr(sp, MAGIC))) {
+               if (s - sp >= (ptrdiff_t)dlen)
+                       return dp;
+               memcpy(dp, sp, s - sp);
+               for (d = dp + (s - sp); *s && (d - dp < (ptrdiff_t)dlen); s++)
+                       if (!ISMAGIC(*s) || !(*++s & 0x80)
+                           || !strchr("*+?@! ", *s & 0x7f))
+                               *d++ = *s;
+                       else {
+                               /* extended pattern operators: *+?@! */
+                               if ((*s & 0x7f) != ' ')
+                                       *d++ = *s & 0x7f;
+                               if (d - dp < (ptrdiff_t)dlen)
+                                       *d++ = '(';
+                       }
+               *d = '\0';
+       } else if (dp != sp)
+               strlcpy(dp, sp, dlen);
+       return dp;
+}
+
+/* Check if p is an unquoted name, possibly followed by a / or :.  If so
+ * puts the expanded version in *dcp,dp and returns a pointer in p just
+ * past the name, otherwise returns 0.
+ */
+static char *
+maybe_expand_tilde(p, dsp, dpp, isassign)
+       char *p;
+       XString *dsp;
+       char **dpp;
+       int isassign;
+{
+       XString ts;
+       char *dp = *dpp;
+       char *tp, *r;
+
+       Xinit(ts, tp, 16, ATEMP);
+       /* : only for DOASNTILDE form */
+       while (p[0] == CHAR && !ISDIRSEP(p[1])
+              && (!isassign || p[1] != PATHSEP))
+       {
+               Xcheck(ts, tp);
+               *tp++ = p[1];
+               p += 2;
+       }
+       *tp = '\0';
+       r = (p[0] == EOS || p[0] == CHAR || p[0] == CSUBST) ? tilde(Xstring(ts, tp)) : (char *) 0;
+       Xfree(ts, tp);
+       if (r) {
+               while (*r) {
+                       Xcheck(*dsp, dp);
+                       if (ISMAGIC(*r))
+                               *dp++ = MAGIC;
+                       *dp++ = *r++;
+               }
+               *dpp = dp;
+               r = p;
+       }
+       return r;
+}
+
+/*
+ * tilde expansion
+ *
+ * based on a version by Arnold Robbins
+ */
+
+static char *
+tilde(cp)
+       char *cp;
+{
+       char *dp;
+
+       if (cp[0] == '\0')
+               dp = str_val(global("HOME"));
+       else if (cp[0] == '+' && cp[1] == '\0')
+               dp = str_val(global("PWD"));
+       else if (cp[0] == '-' && cp[1] == '\0')
+               dp = str_val(global("OLDPWD"));
+       else
+               dp = homedir(cp);
+       /* If HOME, PWD or OLDPWD are not set, don't expand ~ */
+       if (dp == null)
+               dp = (char *) 0;
+       return dp;
+}
+
+/*
+ * map userid to user's home directory.
+ * note that 4.3's getpw adds more than 6K to the shell,
+ * and the YP version probably adds much more.
+ * we might consider our own version of getpwnam() to keep the size down.
+ */
+
+static char *
+homedir(name)
+       char *name;
+{
+       register struct tbl *ap;
+
+       ap = tenter(&homedirs, name, hash(name));
+       if (!(ap->flag & ISSET)) {
+#ifdef OS2
+               /* No usernames in OS2 - punt */
+               return NULL;
+#else /* OS2 */
+               struct passwd *pw;
+               size_t n;
+
+               pw = getpwnam(name);
+               if (pw == NULL)
+                       return NULL;
+               n = strlen(pw->pw_dir);
+               if (n > 0 && '/' != pw->pw_dir[n - 1]) {
+                       ap->val.s = str_nsave(pw->pw_dir, n + 1, APERM);
+                       ap->val.s[n] = '/';
+                       ap->val.s[n + 1] = '\0';
+               } else {
+                       ap->val.s = str_save(pw->pw_dir, APERM);
+               }
+               ap->flag |= DEFINED|ISSET|ALLOC;
+#endif /* OS2 */
+       }
+       return ap->val.s;
+}
+
+#ifdef BRACE_EXPAND
+static void
+alt_expand(wp, start, exp_start, end, fdo)
+       XPtrV *wp;
+       char *start, *exp_start;
+       char *end;
+       int fdo;
+{
+       int UNINITIALIZED(count);
+       char *brace_start, *brace_end, *UNINITIALIZED(comma);
+       char *field_start;
+       char *p;
+
+       /* search for open brace */
+       for (p = exp_start; (p = strchr(p, MAGIC)) && p[1] != OBRACE; p += 2)
+               ;
+       brace_start = p;
+
+       /* find matching close brace, if any */
+       if (p) {
+               comma = (char *) 0;
+               count = 1;
+               for (p += 2; *p && count; p++) {
+                       if (ISMAGIC(*p)) {
+                               if (*++p == OBRACE)
+                                       count++;
+                               else if (*p == CBRACE)
+                                       --count;
+                               else if (*p == ',' && count == 1)
+                                       comma = p;
+                       }
+               }
+       }
+       /* no valid expansions... */
+       if (!p || count != 0) {
+               /* Note that given a{{b,c} we do not expand anything (this is
+                * what at&t ksh does.  This may be changed to do the {b,c}
+                * expansion. }
+                */
+               if (fdo & DOGLOB)
+                       glob(start, wp, fdo & DOMARKDIRS);
+               else
+                       XPput(*wp, debunk(start, start, end - start));
+               return;
+       }
+       brace_end = p;
+       if (!comma) {
+               alt_expand(wp, start, brace_end, end, fdo);
+               return;
+       }
+
+       /* expand expression */
+       field_start = brace_start + 2;
+       count = 1;
+       for (p = brace_start + 2; p != brace_end; p++) {
+               if (ISMAGIC(*p)) {
+                       if (*++p == OBRACE)
+                               count++;
+                       else if ((*p == CBRACE && --count == 0)
+                                || (*p == ',' && count == 1))
+                       {
+                               char *new;
+                               int l1, l2, l3;
+
+                               l1 = brace_start - start;
+                               l2 = (p - 1) - field_start;
+                               l3 = end - brace_end;
+                               new = (char *) alloc(l1 + l2 + l3 + 1, ATEMP);
+                               memcpy(new, start, l1);
+                               memcpy(new + l1, field_start, l2);
+                               memcpy(new + l1 + l2, brace_end, l3);
+                               new[l1 + l2 + l3] = '\0';
+                               alt_expand(wp, new, new + l1,
+                                          new + l1 + l2 + l3, fdo);
+                               field_start = p + 1;
+                       }
+               }
+       }
+       return;
+}
+#endif /* BRACE_EXPAND */
diff --git a/bin/ksh/exec.c b/bin/ksh/exec.c
new file mode 100644 (file)
index 0000000..93c1aa2
--- /dev/null
@@ -0,0 +1,1749 @@
+/*     $NetBSD: exec.c,v 1.15 2012/03/25 17:23:48 christos Exp $       */
+
+/*
+ * execute command tree
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: exec.c,v 1.15 2012/03/25 17:23:48 christos Exp $");
+#endif
+
+
+#include "sh.h"
+#include "c_test.h"
+#include <ctype.h>
+#include "ksh_stat.h"
+
+/* Does ps4 get parameter substitutions done? */
+#ifdef KSH
+# define PS4_SUBSTITUTE(s)     substitute((s), 0)
+#else
+# define PS4_SUBSTITUTE(s)     (s)
+#endif /* KSH */
+
+static int     comexec  ARGS((struct op *, struct tbl *volatile, char **,
+                             int volatile));
+static void    scriptexec ARGS((struct op *, char **));
+static int     call_builtin ARGS((struct tbl *, char **));
+static int     iosetup ARGS((struct ioword *, struct tbl *));
+static int     herein ARGS((const char *, int));
+#ifdef KSH
+static char    *do_selectargs ARGS((char **, bool_t));
+#endif /* KSH */
+#ifdef KSH
+static int     dbteste_isa ARGS((Test_env *, Test_meta));
+static const char *dbteste_getopnd ARGS((Test_env *, Test_op, int));
+static int     dbteste_eval ARGS((Test_env *, Test_op, const char *,
+                               const char *, int));
+static void    dbteste_error ARGS((Test_env *, int, const char *));
+#endif /* KSH */
+#ifdef OS2
+static int     search_access1 ARGS((const char *, int, int *));
+#endif /* OS2 */
+
+
+/*
+ * handle systems that don't have F_SETFD
+ */
+#ifndef F_SETFD
+# ifndef MAXFD
+#   define  MAXFD 64
+# endif
+/* a bit field would be smaller, but this will work */
+static char clexec_tab[MAXFD+1];
+#endif
+
+/*
+ * we now use this function always.
+ */
+int
+fd_clexec(fd)
+    int fd;
+{
+#ifndef F_SETFD
+       if (fd >= 0 && fd < sizeof(clexec_tab)) {
+               clexec_tab[fd] = 1;
+               return 0;
+       }
+       return -1;
+#else
+       return fcntl(fd, F_SETFD, 1);
+#endif
+}
+
+
+/*
+ * execute command tree
+ */
+int
+execute(t, flags)
+       struct op * volatile t;
+       volatile int flags;     /* if XEXEC don't fork */
+{
+       int i;
+       volatile int rv = 0;
+       int pv[2];
+       char ** volatile ap;
+       char *s, *cp;
+       struct ioword **iowp;
+       struct tbl *tp = NULL;
+
+       if (t == NULL)
+               return 0;
+
+       /* Is this the end of a pipeline?  If so, we want to evaluate the
+        * command arguments
+       bool_t eval_done = FALSE;
+       if ((flags&XFORK) && !(flags&XEXEC) && (flags&XPCLOSE)) {
+               eval_done = TRUE;
+               tp = eval_execute_args(t, &ap);
+       }
+        */
+       if ((flags&XFORK) && !(flags&XEXEC) && t->type != TPIPE)
+               return exchild(t, flags & ~XTIME, -1); /* run in sub-process */
+
+       newenv(E_EXEC);
+       if (trap)
+               runtraps(0);
+
+       if (t->type == TCOM) {
+               /* Clear subst_exstat before argument expansion.  Used by
+                * null commands (see comexec() and c_eval()) and by c_set().
+                */
+               subst_exstat = 0;
+
+               current_lineno = t->lineno;     /* for $LINENO */
+
+               /* POSIX says expand command words first, then redirections,
+                * and assignments last..
+                */
+               ap = eval(t->args, t->u.evalflags | DOBLANK | DOGLOB | DOTILDE);
+               if (flags & XTIME)
+                       /* Allow option parsing (bizarre, but POSIX) */
+                       timex_hook(t, &ap);
+               if (Flag(FXTRACE) && ap[0]) {
+                       shf_fprintf(shl_out, "%s",
+                               PS4_SUBSTITUTE(str_val(global("PS4"))));
+                       for (i = 0; ap[i]; i++)
+                               shf_fprintf(shl_out, "%s%s", ap[i],
+                                       ap[i + 1] ? space : newline);
+                       shf_flush(shl_out);
+               }
+               if (ap[0])
+                       tp = findcom(ap[0], FC_BI|FC_FUNC);
+       }
+       flags &= ~XTIME;
+
+       if (t->ioact != NULL || t->type == TPIPE || t->type == TCOPROC) {
+               e->savefd = (short *) alloc(sizeofN(short, NUFILE), ATEMP);
+               /* initialize to not redirected */
+               memset(e->savefd, 0, sizeofN(short, NUFILE));
+       }
+
+       /* do redirection, to be restored in quitenv() */
+       if (t->ioact != NULL)
+               for (iowp = t->ioact; *iowp != NULL; iowp++) {
+                       if (iosetup(*iowp, tp) < 0) {
+                               exstat = rv = 1;
+                               /* Redirection failures for special commands
+                                * cause (non-interactive) shell to exit.
+                                */
+                               if (tp && tp->type == CSHELL
+                                   && (tp->flag & SPEC_BI))
+                                       errorf("%s", null);
+                               /* Deal with FERREXIT, quitenv(), etc. */
+                               goto Break;
+                       }
+               }
+       
+       switch(t->type) {
+         case TCOM:
+               rv = comexec(t, tp, ap, flags);
+               break;
+
+         case TPAREN:
+               rv = execute(t->left, flags|XFORK);
+               break;
+
+         case TPIPE:
+               flags |= XFORK;
+               flags &= ~XEXEC;
+               e->savefd[0] = savefd(0, 0);
+               (void) ksh_dup2(e->savefd[0], 0, FALSE); /* stdin of first */
+               e->savefd[1] = savefd(1, 0);
+               while (t->type == TPIPE) {
+                       openpipe(pv);
+                       (void) ksh_dup2(pv[1], 1, FALSE); /* stdout of curr */
+                       /* Let exchild() close pv[0] in child
+                        * (if this isn't done, commands like
+                        *    (: ; cat /etc/termcap) | sleep 1
+                        *  will hang forever).
+                        */
+                       exchild(t->left, flags|XPIPEO|XCCLOSE, pv[0]);
+                       (void) ksh_dup2(pv[0], 0, FALSE); /* stdin of next */
+                       closepipe(pv);
+                       flags |= XPIPEI;
+                       t = t->right;
+               }
+               restfd(1, e->savefd[1]); /* stdout of last */
+               e->savefd[1] = 0; /* no need to re-restore this */
+               /* Let exchild() close 0 in parent, after fork, before wait */
+               i = exchild(t, flags|XPCLOSE, 0);
+               if (!(flags&XBGND) && !(flags&XXCOM))
+                       rv = i;
+               break;
+
+         case TLIST:
+               while (t->type == TLIST) {
+                       execute(t->left, flags & XERROK);
+                       t = t->right;
+               }
+               rv = execute(t, flags & XERROK);
+               break;
+
+#ifdef KSH
+         case TCOPROC:
+         {
+# ifdef JOB_SIGS
+               sigset_t        omask;
+# endif /* JOB_SIGS */
+
+# ifdef JOB_SIGS
+               /* Block sigchild as we are using things changed in the
+                * signal handler
+                */
+               sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+               e->type = E_ERRH;
+               i = ksh_sigsetjmp(e->jbuf, 0);
+               if (i) {
+                       sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+                       quitenv();
+                       unwind(i);
+                       /*NOTREACHED*/
+               }
+# endif /* JOB_SIGS */
+               /* Already have a (live) co-process? */
+               if (coproc.job && coproc.write >= 0)
+                       errorf("coprocess already exists");
+
+               /* Can we re-use the existing co-process pipe? */
+               coproc_cleanup(TRUE);
+
+               /* do this before opening pipes, in case these fail */
+               e->savefd[0] = savefd(0, 0);
+               e->savefd[1] = savefd(1, 0);
+
+               openpipe(pv);
+               if (pv[0] != 0) {
+                       ksh_dup2(pv[0], 0, FALSE);
+                       close(pv[0]);
+               }
+               coproc.write = pv[1];
+               coproc.job = (void *) 0;
+
+               if (coproc.readw >= 0)
+                       ksh_dup2(coproc.readw, 1, FALSE);
+               else {
+                       openpipe(pv);
+                       coproc.read = pv[0];
+                       ksh_dup2(pv[1], 1, FALSE);
+                       coproc.readw = pv[1];    /* closed before first read */
+                       coproc.njobs = 0;
+                       /* create new coprocess id */
+                       ++coproc.id;
+               }
+# ifdef JOB_SIGS
+               sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+               e->type = E_EXEC; /* no more need for error handler */
+# endif /* JOB_SIGS */
+
+               /* exchild() closes coproc.* in child after fork,
+                * will also increment coproc.njobs when the
+                * job is actually created.
+                */
+               flags &= ~XEXEC;
+               exchild(t->left, flags|XBGND|XFORK|XCOPROC|XCCLOSE,
+                       coproc.readw);
+               break;
+         }
+#endif /* KSH */
+
+         case TASYNC:
+               /* XXX non-optimal, I think - "(foo &)", forks for (),
+                * forks again for async...  parent should optimize
+                * this to "foo &"...
+                */
+               rv = execute(t->left, (flags&~XEXEC)|XBGND|XFORK);
+               break;
+
+         case TOR:
+         case TAND:
+               rv = execute(t->left, XERROK);
+               if (t->right != NULL && (rv == 0) == (t->type == TAND))
+                       rv = execute(t->right, flags & XERROK);
+               else
+                       flags |= XERROK;
+               break;
+
+         case TBANG:
+               rv = !execute(t->right, XERROK);
+               break;
+
+#ifdef KSH
+         case TDBRACKET:
+           {
+               Test_env te;
+
+               te.flags = TEF_DBRACKET;
+               te.pos.wp = t->args;
+               te.isa = dbteste_isa;
+               te.getopnd = dbteste_getopnd;
+               te.eval = dbteste_eval;
+               te.error = dbteste_error;
+
+               rv = test_parse(&te);
+               break;
+           }
+#endif /* KSH */
+
+         case TFOR:
+#ifdef KSH
+         case TSELECT:
+           {
+               volatile bool_t is_first = TRUE;
+#endif /* KSH */
+               ap = (t->vars != NULL) ?
+                         eval(t->vars, DOBLANK|DOGLOB|DOTILDE)
+                       : e->loc->argv + 1;
+               e->type = E_LOOP;
+               while (1) {
+                       i = ksh_sigsetjmp(e->jbuf, 0);
+                       if (!i)
+                               break;
+                       if ((e->flags&EF_BRKCONT_PASS)
+                           || (i != LBREAK && i != LCONTIN))
+                       {
+                               quitenv();
+                               unwind(i);
+                       } else if (i == LBREAK) {
+                               rv = 0;
+                               goto Break;
+                       }
+               }
+               rv = 0; /* in case of a continue */
+               if (t->type == TFOR) {
+                       while (*ap != NULL) {
+                               setstr(global(t->str), *ap++, KSH_UNWIND_ERROR);
+                               rv = execute(t->left, flags & XERROK);
+                       }
+               }
+#ifdef KSH
+               else { /* TSELECT */
+                       for (;;) {
+                               if (!(cp = do_selectargs(ap, is_first))) {
+                                       rv = 1;
+                                       break;
+                               }
+                               is_first = FALSE;
+                               setstr(global(t->str), cp, KSH_UNWIND_ERROR);
+                               rv = execute(t->left, flags & XERROK);
+                       }
+               }
+           }
+#endif /* KSH */
+               break;
+
+         case TWHILE:
+         case TUNTIL:
+               e->type = E_LOOP;
+               while (1) {
+                       i = ksh_sigsetjmp(e->jbuf, 0);
+                       if (!i)
+                               break;
+                       if ((e->flags&EF_BRKCONT_PASS)
+                           || (i != LBREAK && i != LCONTIN))
+                       {
+                               quitenv();
+                               unwind(i);
+                       } else if (i == LBREAK) {
+                               rv = 0;
+                               goto Break;
+                       }
+               }
+               rv = 0; /* in case of a continue */
+               while ((execute(t->left, XERROK) == 0) == (t->type == TWHILE))
+                       rv = execute(t->right, flags & XERROK);
+               break;
+
+         case TIF:
+         case TELIF:
+               if (t->right == NULL)
+                       break;  /* should be error */
+               rv = execute(t->left, XERROK) == 0 ?
+                       execute(t->right->left, flags & XERROK) :
+                       execute(t->right->right, flags & XERROK);
+               break;
+
+         case TCASE:
+               cp = evalstr(t->str, DOTILDE);
+               for (t = t->left; t != NULL && t->type == TPAT; t = t->right)
+                   for (ap = t->vars; *ap; ap++)
+                       if ((s = evalstr(*ap, DOTILDE|DOPAT))
+                           && gmatch(cp, s, FALSE))
+                               goto Found;
+               break;
+         Found:
+               rv = execute(t->left, flags & XERROK);
+               break;
+
+         case TBRACE:
+               rv = execute(t->left, flags & XERROK);
+               break;
+
+         case TFUNCT:
+               rv = define(t->str, t);
+               break;
+
+         case TTIME:
+               /* Clear XEXEC so nested execute() call doesn't exit
+                * (allows "ls -l | time grep foo").
+                */
+               rv = timex(t, flags & ~XEXEC);
+               break;
+
+         case TEXEC:           /* an eval'd TCOM */
+               s = t->args[0];
+               ap = makenv();
+#ifndef F_SETFD
+               for (i = 0; i < sizeof(clexec_tab); i++)
+                       if (clexec_tab[i]) {
+                               close(i);
+                               clexec_tab[i] = 0;
+                       }
+#endif
+               restoresigs();
+               cleanup_proc_env();
+               /* XINTACT bit is for OS2 */
+               ksh_execve(t->str, t->args, ap, (flags & XINTACT) ? 1 : 0);
+               if (errno == ENOEXEC)
+                       scriptexec(t, ap);
+               else
+                       errorf("%s: %s", s, strerror(errno));
+       }
+    Break:
+       exstat = rv;
+
+       quitenv();              /* restores IO */
+       if ((flags&XEXEC))
+               unwind(LEXIT);  /* exit child */
+       if (rv != 0 && !(flags & XERROK)) {
+               if (Flag(FERREXIT))
+                       unwind(LERROR);
+               trapsig(SIGERR_);
+       }
+       return rv;
+}
+
+/*
+ * execute simple command
+ */
+
+static int
+comexec(t, tp, ap, flags)
+       struct op *t;
+       struct tbl *volatile tp;
+       register char **ap;
+       int volatile flags;
+{
+       int i;
+       int leave = LLEAVE;
+       volatile int rv = 0;
+       register char *cp;
+       register char **lastp;
+       static struct op texec; /* Must be static (XXX but why?) */
+       int type_flags;
+       int keepasn_ok;
+       int fcflags = FC_BI|FC_FUNC|FC_PATH;
+       int bourne_function_call = 0;
+
+#ifdef KSH
+       /* snag the last argument for $_ XXX not the same as at&t ksh,
+        * which only seems to set $_ after a newline (but not in
+        * functions/dot scripts, but in interactive and script) -
+        * perhaps save last arg here and set it in shell()?.
+        */
+       if (Flag(FTALKING) && *(lastp = ap)) {
+               while (*++lastp)
+                       ;
+               /* setstr() can't fail here */
+               setstr(typeset("_", LOCAL, 0, INTEGER, 0), *--lastp,
+                      KSH_RETURN_ERROR);
+       }
+#endif /* KSH */
+
+       /* Deal with the shell builtins builtin, exec and command since
+        * they can be followed by other commands.  This must be done before
+        * we know if we should create a local block, which must be done
+        * before we can do a path search (in case the assignments change
+        * PATH).
+        * Odd cases:
+        *   FOO=bar exec > /dev/null           FOO is kept but not exported
+        *   FOO=bar exec foobar                FOO is exported
+        *   FOO=bar command exec > /dev/null   FOO is neither kept nor exported
+        *   FOO=bar command                    FOO is neither kept nor exported
+        *   PATH=... foobar                    use new PATH in foobar search
+        */
+       keepasn_ok = 1;
+       while (tp && tp->type == CSHELL) {
+               fcflags = FC_BI|FC_FUNC|FC_PATH;/* undo effects of command */
+               if (tp->val.f == c_builtin) {
+                       if ((cp = *++ap) == NULL) {
+                               tp = NULL;
+                               break;
+                       }
+                       tp = findcom(cp, FC_BI);
+                       if (tp == NULL)
+                               errorf("builtin: %s: not a builtin", cp);
+                       continue;
+               } else if (tp->val.f == c_exec) {
+                       if (ap[1] == NULL)
+                               break;
+                       ap++;
+                       flags |= XEXEC;
+               } else if (tp->val.f == c_command) {
+                       int optc, saw_p = 0;
+
+                       /* Ugly dealing with options in two places (here and
+                        * in c_command(), but such is life)
+                        */
+                       ksh_getopt_reset(&builtin_opt, 0);
+                       while ((optc = ksh_getopt(ap, &builtin_opt, ":p"))
+                                                                       == 'p')
+                               saw_p = 1;
+                       if (optc != EOF)
+                               break;  /* command -vV or something */
+                       /* don't look for functions */
+                       fcflags = FC_BI|FC_PATH;
+                       if (saw_p) {
+                               if (Flag(FRESTRICTED)) {
+                                       warningf(TRUE,
+                                               "command -p: restricted");
+                                       rv = 1;
+                                       goto Leave;
+                               }
+                               fcflags |= FC_DEFPATH;
+                       }
+                       ap += builtin_opt.optind;
+                       /* POSIX says special builtins lose their status
+                        * if accessed using command.
+                        */
+                       keepasn_ok = 0;
+                       if (!ap[0]) {
+                               /* ensure command with no args exits with 0 */
+                               subst_exstat = 0;
+                               break;
+                       }
+               } else
+                       break;
+               tp = findcom(ap[0], fcflags & (FC_BI|FC_FUNC));
+       }
+       if (keepasn_ok && (!ap[0] || (tp && (tp->flag & KEEPASN))))
+               type_flags = 0;
+       else {
+               /* create new variable/function block */
+               newblock();
+               /* ksh functions don't keep assignments, POSIX functions do. */
+               if (keepasn_ok && tp && tp->type == CFUNC
+                   && !(tp->flag & FKSH)) {
+                       bourne_function_call = 1;
+                       type_flags = 0;
+               } else
+                       type_flags = LOCAL|LOCAL_COPY|EXPORT;
+       }
+       if (Flag(FEXPORT))
+               type_flags |= EXPORT;
+       for (i = 0; t->vars[i]; i++) {
+               cp = evalstr(t->vars[i], DOASNTILDE);
+               if (Flag(FXTRACE)) {
+                       if (i == 0)
+                               shf_fprintf(shl_out, "%s",
+                                       PS4_SUBSTITUTE(str_val(global("PS4"))));
+                       shf_fprintf(shl_out, "%s%s", cp,
+                               t->vars[i + 1] ? space : newline);
+                       if (!t->vars[i + 1])
+                               shf_flush(shl_out);
+               }
+               typeset(cp, type_flags, 0, 0, 0);
+               if (bourne_function_call && !(type_flags & EXPORT))
+                       typeset(cp, LOCAL|LOCAL_COPY|EXPORT, 0, 0, 0);
+       }
+
+       if ((cp = *ap) == NULL) {
+               rv = subst_exstat;
+               goto Leave;
+       } else if (!tp) {
+               if (Flag(FRESTRICTED) && ksh_strchr_dirsep(cp)) {
+                       warningf(TRUE, "%s: restricted", cp);
+                       rv = 1;
+                       goto Leave;
+               }
+               tp = findcom(cp, fcflags);
+       }
+
+       switch (tp->type) {
+         case CSHELL:                  /* shell built-in */
+               rv = call_builtin(tp, ap);
+               break;
+
+         case CFUNC:                   /* function call */
+         {
+               volatile int old_xflag;
+               volatile Tflag old_inuse;
+               const char *volatile old_kshname;
+
+               if (!(tp->flag & ISSET)) {
+                       struct tbl *ftp;
+
+                       if (!tp->u.fpath) {
+                               if (tp->u2.errno_) {
+                                       warningf(TRUE,
+                               "%s: can't find function definition file - %s",
+                                               cp, strerror(tp->u2.errno_));
+                                       rv = 126;
+                               } else {
+                                       warningf(TRUE,
+                               "%s: can't find function definition file", cp);
+                                       rv = 127;
+                               }
+                               break;
+                       }
+                       if (include(tp->u.fpath, 0, (char **) 0, 0) < 0) {
+                               warningf(TRUE,
+                           "%s: can't open function definition file %s - %s",
+                                       cp, tp->u.fpath, strerror(errno));
+                               rv = 127;
+                               break;
+                       }
+                       if (!(ftp = findfunc(cp, hash(cp), FALSE))
+                           || !(ftp->flag & ISSET))
+                       {
+                               warningf(TRUE,
+                                       "%s: function not defined by %s",
+                                       cp, tp->u.fpath);
+                               rv = 127;
+                               break;
+                       }
+                       tp = ftp;
+               }
+
+               /* ksh functions set $0 to function name, POSIX functions leave
+                * $0 unchanged.
+                */
+               old_kshname = kshname;
+               if (tp->flag & FKSH)
+                       kshname = ap[0];
+               else
+                       ap[0] = (char *) __UNCONST(kshname);
+               e->loc->argv = ap;
+               for (i = 0; *ap++ != NULL; i++)
+                       ;
+               e->loc->argc = i - 1;
+               /* ksh-style functions handle getopts sanely,
+                * bourne/posix functions are insane...
+                */
+               if (tp->flag & FKSH) {
+                       e->loc->flags |= BF_DOGETOPTS;
+                       e->loc->getopts_state = user_opt;
+                       getopts_reset(1);
+               }
+
+               old_xflag = Flag(FXTRACE);
+               Flag(FXTRACE) = tp->flag & TRACE ? TRUE : FALSE;
+
+               old_inuse = tp->flag & FINUSE;
+               tp->flag |= FINUSE;
+
+               e->type = E_FUNC;
+               i = ksh_sigsetjmp(e->jbuf, 0);
+               if (i == 0) {
+                       /* seems odd to pass XERROK here, but at&t ksh does */
+                       exstat = execute(tp->val.t, flags & XERROK);
+                       i = LRETURN;
+               }
+               kshname = old_kshname;
+               Flag(FXTRACE) = old_xflag;
+               tp->flag = (tp->flag & ~FINUSE) | old_inuse;
+               /* Were we deleted while executing?  If so, free the execution
+                * tree.  todo: Unfortunately, the table entry is never re-used
+                * until the lookup table is expanded.
+                */
+               if ((tp->flag & (FDELETE|FINUSE)) == FDELETE) {
+                       if (tp->flag & ALLOC) {
+                               tp->flag &= ~ALLOC;
+                               tfree(tp->val.t, tp->areap);
+                       }
+                       tp->flag = 0;
+               }
+               switch (i) {
+                 case LRETURN:
+                 case LERROR:
+                       rv = exstat;
+                       break;
+                 case LINTR:
+                 case LEXIT:
+                 case LLEAVE:
+                 case LSHELL:
+                       quitenv();
+                       unwind(i);
+                       /*NOTREACHED*/
+                 default:
+                       quitenv();
+                       internal_errorf(1, "CFUNC %d", i);
+               }
+               break;
+         }
+
+         case CEXEC:           /* executable command */
+         case CTALIAS:         /* tracked alias */
+               if (!(tp->flag&ISSET)) {
+                       /* errno_ will be set if the named command was found
+                        * but could not be executed (permissions, no execute
+                        * bit, directory, etc).  Print out a (hopefully)
+                        * useful error message and set the exit status to 126.
+                        */
+                       if (tp->u2.errno_) {
+                               warningf(TRUE, "%s: cannot execute - %s", cp,
+                                       strerror(tp->u2.errno_));
+                               rv = 126;       /* POSIX */
+                       } else {
+                               warningf(TRUE, "%s: not found", cp);
+                               rv = 127;
+                       }
+                       break;
+               }
+
+#ifdef KSH
+               /* set $_ to program's full path */
+               /* setstr() can't fail here */
+               setstr(typeset("_", LOCAL|EXPORT, 0, INTEGER, 0),
+                      tp->val.s, KSH_RETURN_ERROR);
+#endif /* KSH */
+
+               if (flags&XEXEC) {
+                       j_exit();
+                       if (!(flags&XBGND) || Flag(FMONITOR)) {
+                               setexecsig(&sigtraps[SIGINT], SS_RESTORE_ORIG);
+                               setexecsig(&sigtraps[SIGQUIT], SS_RESTORE_ORIG);
+                       }
+               }
+
+               /* to fork we set up a TEXEC node and call execute */
+               texec.type = TEXEC;
+               texec.left = t; /* for tprint */
+               texec.str = tp->val.s;
+               texec.args = ap;
+               rv = exchild(&texec, flags, -1);
+               break;
+       }
+       leave = LEXIT;
+  Leave:
+       if (flags & XEXEC) {
+               exstat = rv;
+               unwind(leave);
+       }
+       return rv;
+}
+
+static void
+scriptexec(tp, ap)
+       register struct op *tp;
+       register char **ap;
+{
+       char *shellv;
+
+       shellv = str_val(global(EXECSHELL_STR));
+       if (shellv && *shellv)
+               shellv = search(shellv, path, X_OK, (int *) 0);
+       if (!shellv || !*shellv)
+               shellv = __UNCONST(EXECSHELL);
+
+       *tp->args-- = tp->str;
+#ifdef SHARPBANG
+       {
+               char buf[LINE];
+               register char *cp;
+               register int fd, n;
+
+               buf[0] = '\0';
+               if ((fd = open(tp->str, O_RDONLY)) >= 0) {
+                       if ((n = read(fd, buf, LINE - 1)) > 0)
+                               buf[n] = '\0';
+                       (void) close(fd);
+               }
+               if ((buf[0] == '#' && buf[1] == '!' && (cp = &buf[2]))
+# ifdef OS2
+                   || (strncmp(buf, "extproc", 7) == 0 && isspace((unsigned char)buf[7])
+                       && (cp = &buf[7]))
+# endif /* OS2 */
+                   )
+               {
+                       while (*cp && (*cp == ' ' || *cp == '\t'))
+                               cp++;
+                       if (*cp && *cp != '\n') {
+                               char *a0 = cp, *a1 = (char *) 0;
+# ifdef OS2
+                               char *a2 = cp;
+# endif /* OS2 */
+
+                               while (*cp && *cp != '\n' && *cp != ' '
+                                      && *cp != '\t')
+                               {
+# ifdef OS2
+                       /* Allow shell search without prepended path
+                        * if shell with / in pathname cannot be found.
+                        * Use / explicitly so \ can be used if explicit
+                        * needs to be forced.
+                        */
+                                       if (*cp == '/')
+                                               a2 = cp + 1;
+# endif /* OS2 */
+                                       cp++;
+                               }
+                               if (*cp && *cp != '\n') {
+                                       *cp++ = '\0';
+                                       while (*cp
+                                              && (*cp == ' ' || *cp == '\t'))
+                                               cp++;
+                                       if (*cp && *cp != '\n') {
+                                               a1 = cp;
+                                               /* all one argument */
+                                               while (*cp && *cp != '\n')
+                                                       cp++;
+                                       }
+                               }
+                               if (*cp == '\n') {
+                                       *cp = '\0';
+                                       if (a1)
+                                               *tp->args-- = a1;
+# ifdef OS2
+                                       if (a0 != a2) {
+                                               char *tmp_a0 = str_nsave(a0,
+                                                       strlen(a0) + 5, ATEMP);
+                                               if (search_access(tmp_a0, X_OK,
+                                                               (int *) 0))
+                                                       a0 = a2;
+                                               afree(tmp_a0, ATEMP);
+                                       }
+# endif /* OS2 */
+                                       shellv = a0;
+                               }
+                       }
+# ifdef OS2
+               } else {
+                       /* Use ksh documented shell default if present
+                        * else use OS2_SHELL which is assumed to need
+                        * the /c option and '\' as dir separator.
+                        */
+                        char *p = shellv;
+
+                        shellv = str_val(global("EXECSHELL"));
+                        if (shellv && *shellv)
+                                shellv = search(shellv, path, X_OK, (int *) 0);
+                        if (!shellv || !*shellv) {
+                                shellv = p;
+                                *tp->args-- = "/c";
+                                for (p = tp->str; *p; p++)
+                                        if (*p == '/')
+                                                *p = '\\';
+                        }
+# endif /* OS2 */
+               }
+       }
+#endif /* SHARPBANG */
+       *tp->args = shellv;
+
+       ksh_execve(tp->args[0], tp->args, ap, 0);
+
+       /* report both the program that was run and the bogus shell */
+       errorf("%s: %s: %s", tp->str, shellv, strerror(errno));
+}
+
+int
+shcomexec(wp)
+       register char **wp;
+{
+       register struct tbl *tp;
+
+       tp = tsearch(&builtins, *wp, hash(*wp));
+       if (tp == NULL)
+               internal_errorf(1, "shcomexec: %s", *wp);
+       return call_builtin(tp, wp);
+}
+
+/*
+ * Search function tables for a function.  If create set, a table entry
+ * is created if none is found.
+ */
+struct tbl *
+findfunc(name, h, create)
+       const char *name;
+       unsigned int h;
+       int create;
+{
+       struct block *l;
+       struct tbl *tp = (struct tbl *) 0;
+
+       for (l = e->loc; l; l = l->next) {
+               tp = tsearch(&l->funs, name, h);
+               if (tp)
+                       break;
+               if (!l->next && create) {
+                       tp = tenter(&l->funs, name, h);
+                       tp->flag = DEFINED;
+                       tp->type = CFUNC;
+                       tp->val.t = (struct op *) 0;
+                       break;
+               }
+       }
+       return tp;
+}
+
+/*
+ * define function.  Returns 1 if function is being undefined (t == 0) and
+ * function did not exist, returns 0 otherwise.
+ */
+int
+define(name, t)
+       const char *name;
+       struct op *t;
+{
+       struct tbl *tp;
+       int was_set = 0;
+
+       while (1) {
+               tp = findfunc(name, hash(name), TRUE);
+
+               if (tp->flag & ISSET)
+                       was_set = 1;
+               /* If this function is currently being executed, we zap this
+                * table entry so findfunc() won't see it
+                */
+               if (tp->flag & FINUSE) {
+                       tp->name[0] = '\0';
+                       tp->flag &= ~DEFINED; /* ensure it won't be found */
+                       tp->flag |= FDELETE;
+               } else
+                       break;
+       }
+
+       if (tp->flag & ALLOC) {
+               tp->flag &= ~(ISSET|ALLOC);
+               tfree(tp->val.t, tp->areap);
+       }
+
+       if (t == NULL) {                /* undefine */
+               tdelete(tp);
+               return was_set ? 0 : 1;
+       }
+
+       tp->val.t = tcopy(t->left, tp->areap);
+       tp->flag |= (ISSET|ALLOC);
+       if (t->u.ksh_func)
+               tp->flag |= FKSH;
+
+       return 0;
+}
+
+/*
+ * add builtin
+ */
+void
+builtin(name, func)
+       const char *name;
+       int (*func) ARGS((char **));
+{
+       register struct tbl *tp;
+       Tflag flag;
+
+       /* see if any flags should be set for this builtin */
+       for (flag = 0; ; name++) {
+               if (*name == '=')       /* command does variable assignment */
+                       flag |= KEEPASN;
+               else if (*name == '*')  /* POSIX special builtin */
+                       flag |= SPEC_BI;
+               else if (*name == '+')  /* POSIX regular builtin */
+                       flag |= REG_BI;
+               else
+                       break;
+       }
+
+       tp = tenter(&builtins, name, hash(name));
+       tp->flag = DEFINED | flag;
+       tp->type = CSHELL;
+       tp->val.f = func;
+}
+
+/*
+ * find command
+ * either function, hashed command, or built-in (in that order)
+ */
+struct tbl *
+findcom(name, flags)
+       const char *name;
+       int     flags;          /* FC_* */
+{
+       static struct tbl temp;
+       unsigned int h = hash(name);
+       struct tbl *tp = NULL, *tbi;
+       int insert = Flag(FTRACKALL);   /* insert if not found */
+       char *fpath;                    /* for function autoloading */
+       char *npath;
+
+       if (ksh_strchr_dirsep(name) != NULL) {
+               insert = 0;
+               /* prevent FPATH search below */
+               flags &= ~FC_FUNC;
+               goto Search;
+       }
+       tbi = (flags & FC_BI) ? tsearch(&builtins, name, h) : NULL;
+       /* POSIX says special builtins first, then functions, then
+        * POSIX regular builtins, then search path...
+        */
+       if ((flags & FC_SPECBI) && tbi && (tbi->flag & SPEC_BI))
+               tp = tbi;
+       if (!tp && (flags & FC_FUNC)) {
+               tp = findfunc(name, h, FALSE);
+               if (tp && !(tp->flag & ISSET)) {
+                       if ((fpath = str_val(global("FPATH"))) == null) {
+                               tp->u.fpath = (char *) 0;
+                               tp->u2.errno_ = 0;
+                       } else
+                               tp->u.fpath = search(name, fpath, R_OK,
+                                       &tp->u2.errno_);
+               }
+       }
+       if (!tp && (flags & FC_REGBI) && tbi && (tbi->flag & REG_BI))
+               tp = tbi;
+       /* todo: posix says non-special/non-regular builtins must
+        * be triggered by some user-controllable means like a
+        * special directory in PATH.  Requires modifications to
+        * the search() function.  Tracked aliases should be
+        * modified to allow tracking of builtin commands.
+        * This should be under control of the FPOSIX flag.
+        * If this is changed, also change c_whence...
+        */
+       if (!tp && (flags & FC_UNREGBI) && tbi)
+               tp = tbi;
+       if (!tp && (flags & FC_PATH) && !(flags & FC_DEFPATH)) {
+               tp = tsearch(&taliases, name, h);
+               if (tp && (tp->flag & ISSET) && eaccess(tp->val.s, X_OK) != 0) {
+                       if (tp->flag & ALLOC) {
+                               tp->flag &= ~ALLOC;
+                               afree(tp->val.s, APERM);
+                       }
+                       tp->flag &= ~ISSET;
+               }
+       }
+
+  Search:
+       if ((!tp || (tp->type == CTALIAS && !(tp->flag&ISSET)))
+           && (flags & FC_PATH))
+       {
+               if (!tp) {
+                       if (insert && !(flags & FC_DEFPATH)) {
+                               tp = tenter(&taliases, name, h);
+                               tp->type = CTALIAS;
+                       } else {
+                               tp = &temp;
+                               tp->type = CEXEC;
+                       }
+                       tp->flag = DEFINED;     /* make ~ISSET */
+               }
+               npath = search(name, flags & FC_DEFPATH ? def_path : path,
+                               X_OK, &tp->u2.errno_);
+               if (npath) {
+                       if (tp == &temp) {
+                           tp->val.s = npath;
+                       } else {
+                           tp->val.s = str_save(npath, APERM);
+                           afree(npath, ATEMP);
+                       }
+                       tp->flag |= ISSET|ALLOC;
+               } else if ((flags & FC_FUNC)
+                          && (fpath = str_val(global("FPATH"))) != null
+                          && (npath = search(name, fpath, R_OK,
+                                             &tp->u2.errno_)) != (char *) 0)
+               {
+                       /* An undocumented feature of at&t ksh is that it
+                        * searches FPATH if a command is not found, even
+                        * if the command hasn't been set up as an autoloaded
+                        * function (ie, no typeset -uf).
+                        */
+                       tp = &temp;
+                       tp->type = CFUNC;
+                       tp->flag = DEFINED; /* make ~ISSET */
+                       tp->u.fpath = npath;
+               }
+       }
+       return tp;
+}
+
+/*
+ * flush executable commands with relative paths
+ */
+void
+flushcom(all)
+       int all;                /* just relative or all */
+{
+       struct tbl *tp;
+       struct tstate ts;
+
+       for (twalk(&ts, &taliases); (tp = tnext(&ts)) != NULL; )
+               if ((tp->flag&ISSET) && (all || !ISDIRSEP(tp->val.s[0]))) {
+                       if (tp->flag&ALLOC) {
+                               tp->flag &= ~(ALLOC|ISSET);
+                               afree(tp->val.s, APERM);
+                       }
+                       tp->flag &= ~ISSET;
+               }
+}
+
+/* Check if path is something we want to find.  Returns -1 for failure. */
+int
+search_access(pathx, mode, errnop)
+       const char *pathx;
+       int mode;
+       int *errnop;            /* set if candidate found, but not suitable */
+{
+#ifndef OS2
+       int ret, err = 0;
+       struct stat statb;
+
+       if (stat(pathx, &statb) < 0)
+               return -1;
+       ret = eaccess(pathx, mode);
+       if (ret < 0)
+               err = errno; /* File exists, but we can't access it */
+       else if (mode == X_OK
+                && (!S_ISREG(statb.st_mode)
+                    /* This 'cause access() says root can execute everything */
+                    || !(statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))))
+       {
+               ret = -1;
+               err = S_ISDIR(statb.st_mode) ? EISDIR : EACCES;
+       }
+       if (err && errnop && !*errnop)
+               *errnop = err;
+       return ret;
+#else /* !OS2 */
+       /*
+        * NOTE: ASSUMES path can be modified and has enough room at the
+        *       end of the string for a suffix (ie, 4 extra characters).
+        *       Certain code knows this (eg, eval.c(globit()),
+        *       exec.c(search())).
+        */
+       static char *xsuffixes[] = { ".ksh", ".exe", ".", ".sh", ".cmd",
+                                    ".com", ".bat", (char *) 0
+                                  };
+       static char *rsuffixes[] = { ".ksh", ".", ".sh", ".cmd", ".bat",
+                                     (char *) 0
+                                  };
+       int i;
+       char *mpath = (char *) pathx;
+       char *tp = mpath + strlen(mpath);
+       char *p;
+       char **sfx;
+
+       /* If a suffix has been specified, check if it is one of the
+        * suffixes that indicate the file is executable - if so, change
+        * the access test to R_OK...
+        * This code assumes OS/2 files can have only one suffix...
+        */
+       if ((p = strrchr((p = ksh_strrchr_dirsep(mpath)) ? p : mpath, '.'))) {
+               if (mode == X_OK)
+                       mode = R_OK;
+               return search_access1(mpath, mode, errnop);
+       }
+       /* Try appending the various suffixes.  Different suffixes for
+        * read and execute 'cause we don't want to read an executable...
+        */
+       sfx = mode == R_OK ? rsuffixes : xsuffixes;
+       for (i = 0; sfx[i]; i++) {
+               strcpy(tp, p = sfx[i]);
+               if (search_access1(mpath, R_OK, errnop) == 0)
+                       return 0;
+               *tp = '\0';
+       }
+       return -1;
+#endif /* !OS2 */
+}
+
+#ifdef OS2
+static int
+search_access1(pathx, mode, errnop)
+       const char *pathx;
+       int mode;
+       int *errnop;            /* set if candidate found, but not suitable */
+{
+       int ret, err = 0;
+       struct stat statb;
+
+       if (stat(pathx, &statb) < 0)
+               return -1;
+       ret = eaccess(pathx, mode);
+       if (ret < 0)
+               err = errno; /* File exists, but we can't access it */
+       else if (!S_ISREG(statb.st_mode)) {
+               ret = -1;
+               err = S_ISDIR(statb.st_mode) ? EISDIR : EACCES;
+       }
+       if (err && errnop && !*errnop)
+               *errnop = err;
+       return ret;
+}
+#endif /* OS2 */
+
+/*
+ * search for command with PATH
+ */
+char *
+search(name, pathx, mode, errnop)
+       const char *name;
+       const char *pathx;
+       int mode;               /* R_OK or X_OK */
+       int *errnop;            /* set if candidate found, but not suitable */
+{
+       const char *sp, *p;
+       char *xp;
+       XString xs;
+       int namelen;
+
+       if (errnop)
+               *errnop = 0;
+#ifdef OS2
+       /* Xinit() allocates 8 additional bytes, so appended suffixes won't
+        * overflow the memory.
+        */
+       namelen = strlen(name) + 1;
+       Xinit(xs, xp, namelen, ATEMP);
+       memcpy(Xstring(xs, xp), name, namelen);
+
+       if (ksh_strchr_dirsep(name)) {
+               if (search_access(Xstring(xs, xp), mode, errnop) >= 0)
+                       return Xstring(xs, xp); /* not Xclose() - see above */
+               Xfree(xs, xp);
+               return NULL;
+       }
+
+       /* Look in current context always. (os2 style) */
+       if (search_access(Xstring(xs, xp), mode, errnop) == 0)
+               return Xstring(xs, xp); /* not Xclose() - xp may be wrong */
+#else /* OS2 */
+       if (ksh_strchr_dirsep(name)) {
+               if (search_access(name, mode, errnop) == 0)
+                       return (char *)__UNCONST(name);
+               return NULL;
+       }
+
+       namelen = strlen(name) + 1;
+       Xinit(xs, xp, 128, ATEMP);
+#endif /* OS2 */
+
+       sp = pathx;
+       while (sp != NULL) {
+               xp = Xstring(xs, xp);
+               if (!(p = strchr(sp, PATHSEP)))
+                       p = sp + strlen(sp);
+               if (p != sp) {
+                       XcheckN(xs, xp, p - sp);
+                       memcpy(xp, sp, p - sp);
+                       xp += p - sp;
+                       *xp++ = DIRSEP;
+               }
+               sp = p;
+               XcheckN(xs, xp, namelen);
+               memcpy(xp, name, namelen);
+               if (search_access(Xstring(xs, xp), mode, errnop) == 0)
+#ifdef OS2
+                       return Xstring(xs, xp); /* Not Xclose() - see above */
+#else /* OS2 */
+                       return Xclose(xs, xp + namelen);
+#endif /* OS2 */
+               if (*sp++ == '\0')
+                       sp = NULL;
+       }
+       Xfree(xs, xp);
+       return NULL;
+}
+
+static int
+call_builtin(tp, wp)
+       struct tbl *tp;
+       char **wp;
+{
+       int rv;
+
+       builtin_argv0 = wp[0];
+       builtin_flag = tp->flag;
+       shf_reopen(1, SHF_WR, shl_stdout);
+       shl_stdout_ok = 1;
+       ksh_getopt_reset(&builtin_opt, GF_ERROR);
+       rv = (*tp->val.f)(wp);
+       shf_flush(shl_stdout);
+       shl_stdout_ok = 0;
+       builtin_flag = 0;
+       builtin_argv0 = (char *) 0;
+       return rv;
+}
+
+/*
+ * set up redirection, saving old fd's in e->savefd
+ */
+static int
+iosetup(iop, tp)
+       register struct ioword *iop;
+       struct tbl *tp;
+{
+       register int u = -1;
+       char *cp = iop->name;
+       int iotype = iop->flag & IOTYPE;
+       int do_open = 1, do_close = 0, UNINITIALIZED(flags);
+       struct ioword iotmp;
+       struct stat statb;
+
+       if (iotype != IOHERE)
+               cp = evalonestr(cp, DOTILDE|(Flag(FTALKING_I) ? DOGLOB : 0));
+
+       /* Used for tracing and error messages to print expanded cp */
+       iotmp = *iop;
+       iotmp.name = (iotype == IOHERE) ? (char *) 0 : cp;
+       iotmp.flag |= IONAMEXP;
+
+       if (Flag(FXTRACE))
+               shellf("%s%s\n",
+                       PS4_SUBSTITUTE(str_val(global("PS4"))),
+                       snptreef((char *) 0, 32, "%R", &iotmp));
+
+       switch (iotype) {
+         case IOREAD:
+               flags = O_RDONLY;
+               break;
+
+         case IOCAT:
+               flags = O_WRONLY | O_APPEND | O_CREAT;
+               break;
+
+         case IOWRITE:
+               flags = O_WRONLY | O_CREAT | O_TRUNC;
+               /* The stat() is here to allow redirections to
+                * things like /dev/null without error.
+                */
+               if (Flag(FNOCLOBBER) && !(iop->flag & IOCLOB)
+                   && (stat(cp, &statb) < 0 || S_ISREG(statb.st_mode)))
+                       flags |= O_EXCL;
+               break;
+
+         case IORDWR:
+               flags = O_RDWR | O_CREAT;
+               break;
+
+         case IOHERE:
+               do_open = 0;
+               /* herein() returns -2 if error has been printed */
+               u = herein(iop->heredoc, iop->flag & IOEVAL);
+               /* cp may have wrong name */
+               break;
+
+         case IODUP:
+         {
+               const char *emsg;
+
+               do_open = 0;
+               if (*cp == '-' && !cp[1]) {
+                       u = 1009;        /* prevent error return below */
+                       do_close = 1;
+               } else if ((u = check_fd(cp,
+                               X_OK | ((iop->flag & IORDUP) ? R_OK : W_OK),
+                               &emsg)) < 0)
+               {
+                       warningf(TRUE, "%s: %s",
+                               snptreef((char *) 0, 32, "%R", &iotmp), emsg);
+                       return -1;
+               }
+               if (u == iop->unit)
+                       return 0;               /* "dup from" == "dup to" */
+               break;
+         }
+       }
+       if (do_open) {
+               if (Flag(FRESTRICTED) && (flags & O_CREAT)) {
+                       warningf(TRUE, "%s: restricted", cp);
+                       return -1;
+               }
+               u = open(cp, flags, 0666);
+#ifdef OS2
+               if (u < 0 && strcmp(cp, "/dev/null") == 0)
+                       u = open("nul", flags, 0666);
+#endif /* OS2 */
+       }
+       if (u < 0) {
+               /* herein() may already have printed message */
+               if (u == -1)
+                       warningf(TRUE, "cannot %s %s: %s",
+                              iotype == IODUP ? "dup"
+                               : (iotype == IOREAD || iotype == IOHERE) ?
+                                   "open" : "create", cp, strerror(errno));
+               return -1;
+       }
+       /* Do not save if it has already been redirected (i.e. "cat >x >y"). */
+       if (e->savefd[iop->unit] == 0) {
+               /* If these are the same, it means unit was previously closed */
+               if (u == iop->unit)
+                       e->savefd[iop->unit] = -1;
+               else
+                       /* c_exec() assumes e->savefd[fd] set for any
+                        * redirections.  Ask savefd() not to close iop->unit;
+                        * this allows error messages to be seen if iop->unit
+                        * is 2; also means we can't lose the fd (eg, both
+                        * dup2 below and dup2 in restfd() failing).
+                        */
+                       e->savefd[iop->unit] = savefd(iop->unit, 1);
+       }
+
+       if (do_close)
+               close(iop->unit);
+       else if (u != iop->unit) {
+               if (ksh_dup2(u, iop->unit, TRUE) < 0) {
+                       warningf(TRUE,
+                               "could not finish (dup) redirection %s: %s",
+                               snptreef((char *) 0, 32, "%R", &iotmp),
+                               strerror(errno));
+                       if (iotype != IODUP)
+                               close(u);
+                       return -1;
+               }
+               if (iotype != IODUP)
+                       close(u);
+#ifdef KSH
+               /* Touching any co-process fd in an empty exec
+                * causes the shell to close its copies
+                */
+               else if (tp && tp->type == CSHELL && tp->val.f == c_exec) {
+                       if (iop->flag & IORDUP) /* possible exec <&p */
+                               coproc_read_close(u);
+                       else                    /* possible exec >&p */
+                               coproc_write_close(u);
+               }
+#endif /* KSH */
+       }
+       if (u == 2) /* Clear any write errors */
+               shf_reopen(2, SHF_WR, shl_out);
+       return 0;
+}
+
+/*
+ * open here document temp file.
+ * if unquoted here, expand here temp file into second temp file.
+ */
+static int
+herein(content, sub)
+       const char *content;
+       int sub;
+{
+       volatile int fd = -1;
+       struct source *s, *volatile osource;
+       struct shf *volatile shf;
+       struct temp *h;
+       int i;
+
+       /* ksh -c 'cat << EOF' can cause this... */
+       if (content == (char *) 0) {
+               warningf(TRUE, "here document missing");
+               return -2; /* special to iosetup(): don't print error */
+       }
+
+       /* Create temp file to hold content (done before newenv so temp
+        * doesn't get removed too soon).
+        */
+       h = maketemp(ATEMP, TT_HEREDOC_EXP, &e->temps);
+       if (!(shf = h->shf) || (fd = open(h->name, O_RDONLY, 0)) < 0) {
+               warningf(TRUE, "can't %s temporary file %s: %s",
+                       !shf ? "create" : "open",
+                       h->name, strerror(errno));
+               if (shf)
+                       shf_close(shf);
+               return -2 /* special to iosetup(): don't print error */;
+       }
+
+       osource = source;
+       newenv(E_ERRH);
+       i = ksh_sigsetjmp(e->jbuf, 0);
+       if (i) {
+               source = osource;
+               quitenv();
+               shf_close(shf); /* after quitenv */
+               close(fd);
+               return -2; /* special to iosetup(): don't print error */
+       }
+       if (sub) {
+               /* Do substitutions on the content of heredoc */
+               s = pushs(SSTRING, ATEMP);
+               s->start = s->str = content;
+               source = s;
+               if (yylex(ONEWORD|HEREDOC) != LWORD)
+                       internal_errorf(1, "herein: yylex");
+               source = osource;
+               shf_puts(evalstr(yylval.cp, 0), shf);
+       } else
+               shf_puts(content, shf);
+
+       quitenv();
+
+       if (shf_close(shf) == EOF) {
+               close(fd);
+               warningf(TRUE, "error writing %s: %s", h->name,
+                       strerror(errno));
+               return -2; /* special to iosetup(): don't print error */
+       }
+
+       return fd;
+}
+
+#ifdef KSH
+/*
+ *     ksh special - the select command processing section
+ *     print the args in column form - assuming that we can
+ */
+static char *
+do_selectargs(ap, print_menu)
+       register char **ap;
+       bool_t print_menu;
+{
+       static const char *const read_args[] = {
+                                       "read", "-r", "REPLY", (char *) 0
+                                   };
+       char *s;
+       int i, argct;
+
+       for (argct = 0; ap[argct]; argct++)
+               ;
+       while (1) {
+               /* Menu is printed if
+                *      - this is the first time around the select loop
+                *      - the user enters a blank line
+                *      - the REPLY parameter is empty
+                */
+               if (print_menu || !*str_val(global("REPLY")))
+                       pr_menu(ap);
+               shellf("%s", str_val(global("PS3")));
+               if (call_builtin(findcom("read", FC_BI),
+                   (char **) __UNCONST(read_args)))
+                       return (char *) 0;
+               s = str_val(global("REPLY"));
+               if (*s) {
+                       i = atoi(s);
+                       return (i >= 1 && i <= argct) ? ap[i - 1] : null;
+               }
+               print_menu = 1;
+       }
+}
+
+struct select_menu_info {
+       char    *const *args;
+       int     arg_width;
+       int     num_width;
+} info;
+
+static char *select_fmt_entry ARGS((void *arg, int i, char *buf, int buflen));
+
+/* format a single select menu item */
+static char *
+select_fmt_entry(arg, i, buf, buflen)
+       void *arg;
+       int i;
+       char *buf;
+       int buflen;
+{
+       struct select_menu_info *smi = (struct select_menu_info *) arg;
+
+       shf_snprintf(buf, buflen, "%*d) %s",
+               smi->num_width, i + 1, smi->args[i]);
+       return buf;
+}
+
+/*
+ *     print a select style menu
+ */
+int
+pr_menu(ap)
+       char *const *ap;
+{
+       struct select_menu_info smi;
+       char *const *pp;
+       int nwidth, dwidth;
+       int i, n;
+
+       /* Width/column calculations were done once and saved, but this
+        * means select can't be used recursively so we re-calculate each
+        * time (could save in a structure that is returned, but its probably
+        * not worth the bother).
+        */
+
+       /*
+        * get dimensions of the list
+        */
+       for (n = 0, nwidth = 0, pp = ap; *pp; n++, pp++) {
+               i = strlen(*pp);
+               nwidth = (i > nwidth) ? i : nwidth;
+       }
+       /*
+        * we will print an index of the form
+        *      %d)
+        * in front of each entry
+        * get the max width of this
+        */
+       for (i = n, dwidth = 1; i >= 10; i /= 10)
+               dwidth++;
+
+       smi.args = ap;
+       smi.arg_width = nwidth;
+       smi.num_width = dwidth;
+       print_columns(shl_out, n, select_fmt_entry, (void *) &smi,
+               dwidth + nwidth + 2, 1);
+
+       return n;
+}
+
+/* XXX: horrible kludge to fit within the framework */
+
+static char *plain_fmt_entry ARGS((void *arg, int i, char *buf, int buflen));
+
+static char *
+plain_fmt_entry(arg, i, buf, buflen)
+       void *arg;
+       int i;
+       char *buf;
+       int buflen;
+{
+       shf_snprintf(buf, buflen, "%s", ((char *const *)arg)[i]);
+       return buf;
+}
+
+int
+pr_list(ap)
+       char *const *ap;
+{
+       char *const *pp;
+       int nwidth;
+       int i, n;
+
+       for (n = 0, nwidth = 0, pp = ap; *pp; n++, pp++) {
+               i = strlen(*pp);
+               nwidth = (i > nwidth) ? i : nwidth;
+       }
+       print_columns(shl_out, n, plain_fmt_entry, (void *)__UNCONST(ap),
+           nwidth + 1, 0);
+
+       return n;
+}
+#endif /* KSH */
+#ifdef KSH
+
+/*
+ *     [[ ... ]] evaluation routines
+ */
+
+extern const char *const dbtest_tokens[];
+extern const char db_close[];
+
+/* Test if the current token is a whatever.  Accepts the current token if
+ * it is.  Returns 0 if it is not, non-zero if it is (in the case of
+ * TM_UNOP and TM_BINOP, the returned value is a Test_op).
+ */
+static int
+dbteste_isa(te, meta)
+       Test_env *te;
+       Test_meta meta;
+{
+       int ret = 0;
+       int uqword;
+       char *p;
+
+       if (!*te->pos.wp)
+               return meta == TM_END;
+
+       /* unquoted word? */
+       for (p = *te->pos.wp; *p == CHAR; p += 2)
+               ;
+       uqword = *p == EOS;
+
+       if (meta == TM_UNOP || meta == TM_BINOP) {
+               if (uqword) {
+                       char buf[8];    /* longer than the longest operator */
+                       char *q = buf;
+                       for (p = *te->pos.wp; *p == CHAR
+                                             && q < &buf[sizeof(buf) - 1];
+                                             p += 2)
+                               *q++ = p[1];
+                       *q = '\0';
+                       ret = (int) test_isop(te, meta, buf);
+               }
+       } else if (meta == TM_END)
+               ret = 0;
+       else
+               ret = uqword
+                       && strcmp(*te->pos.wp, dbtest_tokens[(int) meta]) == 0;
+
+       /* Accept the token? */
+       if (ret)
+               te->pos.wp++;
+
+       return ret;
+}
+
+static const char *
+dbteste_getopnd(te, op, do_eval)
+       Test_env *te;
+       Test_op op;
+       int do_eval;
+{
+       char *s = *te->pos.wp;
+
+       if (!s)
+               return (char *) 0;
+
+       te->pos.wp++;
+
+       if (!do_eval)
+               return null;
+
+       if (op == TO_STEQL || op == TO_STNEQ)
+               s = evalstr(s, DOTILDE | DOPAT);
+       else
+               s = evalstr(s, DOTILDE);
+
+       return s;
+}
+
+static int
+dbteste_eval(te, op, opnd1, opnd2, do_eval)
+       Test_env *te;
+       Test_op op;
+       const char *opnd1;
+       const char *opnd2;
+       int do_eval;
+{
+       return test_eval(te, op, opnd1, opnd2, do_eval);
+}
+
+static void
+dbteste_error(te, offset, msg)
+       Test_env *te;
+       int offset;
+       const char *msg;
+{
+       te->flags |= TEF_ERROR;
+       internal_errorf(0, "dbteste_error: %s (offset %d)", msg, offset);
+}
+#endif /* KSH */
diff --git a/bin/ksh/expand.h b/bin/ksh/expand.h
new file mode 100644 (file)
index 0000000..a34424b
--- /dev/null
@@ -0,0 +1,108 @@
+/*     $NetBSD: expand.h,v 1.4 2001/07/26 15:05:07 wiz Exp $   */
+
+/*
+ * Expanding strings
+ */
+/* $Id: expand.h,v 1.4 2001/07/26 15:05:07 wiz Exp $ */
+
+#define X_EXTRA                8       /* this many extra bytes in X string */
+
+#if 0                          /* Usage */
+       XString xs;
+       char *xp;
+
+       Xinit(xs, xp, 128, ATEMP); /* allocate initial string */
+       while ((c = generate()) {
+               Xcheck(xs, xp); /* expand string if necessary */
+               Xput(xs, xp, c); /* add character */
+       }
+       return Xclose(xs, xp);  /* resize string */
+/*
+ * NOTE:
+ *     The Xcheck and Xinit macros have a magic + X_EXTRA in the lengths.
+ *     This is so that you can put up to X_EXTRA characters in a XString
+ *     before calling Xcheck. (See yylex in lex.c)
+ */
+#endif /* 0 */
+
+typedef struct XString {
+       char   *end, *beg;      /* end, begin of string */
+       size_t  len;            /* length */
+       Area    *areap;         /* area to allocate/free from */
+} XString;
+
+typedef char * XStringP;
+
+/* initialize expandable string */
+#define        Xinit(xs, xp, length, area) do { \
+                       (xs).len = length; \
+                       (xs).areap = (area); \
+                       (xs).beg = alloc((xs).len + X_EXTRA, (xs).areap); \
+                       (xs).end = (xs).beg + (xs).len; \
+                       xp = (xs).beg; \
+               } while (0)
+
+/* stuff char into string */
+#define        Xput(xs, xp, c) (*xp++ = (c))
+
+/* check if there are at least n bytes left */
+#define        XcheckN(xs, xp, n) do { \
+                   int more = ((xp) + (n)) - (xs).end; \
+                   if (more > 0) \
+                       xp = Xcheck_grow_(&xs, xp, more); \
+               } while (0)
+
+/* check for overflow, expand string */
+#define Xcheck(xs, xp) XcheckN(xs, xp, 1)
+
+/* free string */
+#define        Xfree(xs, xp)   afree((void*) (xs).beg, (xs).areap)
+
+/* close, return string */
+#define        Xclose(xs, xp)  (char*) aresize((void*)(xs).beg, \
+                                       (size_t)((xp) - (xs).beg), (xs).areap)
+/* begin of string */
+#define        Xstring(xs, xp) ((xs).beg)
+
+#define Xnleft(xs, xp) ((xs).end - (xp))       /* may be less than 0 */
+#define        Xlength(xs, xp) ((xp) - (xs).beg)
+#define Xsize(xs, xp) ((xs).end - (xs).beg)
+#define        Xsavepos(xs, xp) ((xp) - (xs).beg)
+#define        Xrestpos(xs, xp, n) ((xs).beg + (n))
+
+char * Xcheck_grow_    ARGS((XString *xsp, char *xp, int more));
+
+/*
+ * expandable vector of generic pointers
+ */
+
+typedef struct XPtrV {
+       void  **cur;            /* next avail pointer */
+       void  **beg, **end;     /* begin, end of vector */
+} XPtrV;
+
+#define        XPinit(x, n) do { \
+                       register void **vp__; \
+                       vp__ = (void**) alloc(sizeofN(void*, n), ATEMP); \
+                       (x).cur = (x).beg = vp__; \
+                       (x).end = vp__ + n; \
+                   } while (0)
+
+#define        XPput(x, p) do { \
+                       if ((x).cur >= (x).end) { \
+                               int n = XPsize(x); \
+                               (x).beg = (void**) aresize((void*) (x).beg, \
+                                                  sizeofN(void*, n*2), ATEMP); \
+                               (x).cur = (x).beg + n; \
+                               (x).end = (x).cur + n; \
+                       } \
+                       *(x).cur++ = (p); \
+               } while (0)
+
+#define        XPptrv(x)       ((x).beg)
+#define        XPsize(x)       ((x).cur - (x).beg)
+
+#define        XPclose(x)      (void**) aresize((void*)(x).beg, \
+                                        sizeofN(void*, XPsize(x)), ATEMP)
+
+#define        XPfree(x)       afree((void*) (x).beg, ATEMP)
diff --git a/bin/ksh/expr.c b/bin/ksh/expr.c
new file mode 100644 (file)
index 0000000..6ba91dd
--- /dev/null
@@ -0,0 +1,613 @@
+/*     $NetBSD: expr.c,v 1.9 2011/10/16 17:12:11 joerg Exp $   */
+
+/*
+ * Korn expression evaluation
+ */
+/*
+ * todo: better error handling: if in builtin, should be builtin error, etc.
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: expr.c,v 1.9 2011/10/16 17:12:11 joerg Exp $");
+#endif
+
+
+#include "sh.h"
+#include <ctype.h>
+
+
+/* The order of these enums is constrained by the order of opinfo[] */
+enum token {
+       /* some (long) unary operators */
+       O_PLUSPLUS = 0, O_MINUSMINUS,
+       /* binary operators */
+       O_EQ, O_NE,
+       /* assignments are assumed to be in range O_ASN .. O_BORASN */
+       O_ASN, O_TIMESASN, O_DIVASN, O_MODASN, O_PLUSASN, O_MINUSASN,
+              O_LSHIFTASN, O_RSHIFTASN, O_BANDASN, O_BXORASN, O_BORASN,
+       O_LSHIFT, O_RSHIFT,
+       O_LE, O_GE, O_LT, O_GT,
+       O_LAND,
+       O_LOR,
+       O_TIMES, O_DIV, O_MOD,
+       O_PLUS, O_MINUS,
+       O_BAND,
+       O_BXOR,
+       O_BOR,
+       O_TERN,
+       O_COMMA,
+       /* things after this aren't used as binary operators */
+       /* unary that are not also binaries */
+       O_BNOT, O_LNOT,
+       /* misc */
+       OPEN_PAREN, CLOSE_PAREN, CTERN,
+       /* things that don't appear in the opinfo[] table */
+       VAR, LIT, END, BAD
+    };
+#define IS_BINOP(op) (((int)op) >= (int)O_EQ && ((int)op) <= (int)O_COMMA)
+#define IS_ASSIGNOP(op)        ((int)(op) >= (int)O_ASN && (int)(op) <= (int)O_BORASN)
+
+enum prec {
+       P_PRIMARY = 0,          /* VAR, LIT, (), ~ ! - + */
+       P_MULT,                 /* * / % */
+       P_ADD,                  /* + - */
+       P_SHIFT,                /* << >> */
+       P_RELATION,             /* < <= > >= */
+       P_EQUALITY,             /* == != */
+       P_BAND,                 /* & */
+       P_BXOR,                 /* ^ */
+       P_BOR,                  /* | */
+       P_LAND,                 /* && */
+       P_LOR,                  /* || */
+       P_TERN,                 /* ?: */
+       P_ASSIGN,               /* = *= /= %= += -= <<= >>= &= ^= |= */
+       P_COMMA                 /* , */
+    };
+#define MAX_PREC       P_COMMA
+
+struct opinfo {
+       char            name[4];
+       int             len;    /* name length */
+       enum prec       prec;   /* precedence: lower is higher */
+};
+
+/* Tokens in this table must be ordered so the longest are first
+ * (eg, += before +).  If you change something, change the order
+ * of enum token too.
+ */
+static const struct opinfo opinfo[] = {
+               { "++",  2, P_PRIMARY },        /* before + */
+               { "--",  2, P_PRIMARY },        /* before - */
+               { "==",  2, P_EQUALITY },       /* before = */
+               { "!=",  2, P_EQUALITY },       /* before ! */
+               { "=",   1, P_ASSIGN },         /* keep assigns in a block */
+               { "*=",  2, P_ASSIGN },
+               { "/=",  2, P_ASSIGN },
+               { "%=",  2, P_ASSIGN },
+               { "+=",  2, P_ASSIGN },
+               { "-=",  2, P_ASSIGN },
+               { "<<=", 3, P_ASSIGN },
+               { ">>=", 3, P_ASSIGN },
+               { "&=",  2, P_ASSIGN },
+               { "^=",  2, P_ASSIGN },
+               { "|=",  2, P_ASSIGN },
+               { "<<",  2, P_SHIFT },
+               { ">>",  2, P_SHIFT },
+               { "<=",  2, P_RELATION },
+               { ">=",  2, P_RELATION },
+               { "<",   1, P_RELATION },
+               { ">",   1, P_RELATION },
+               { "&&",  2, P_LAND },
+               { "||",  2, P_LOR },
+               { "*",   1, P_MULT },
+               { "/",   1, P_MULT },
+               { "%",   1, P_MULT },
+               { "+",   1, P_ADD },
+               { "-",   1, P_ADD },
+               { "&",   1, P_BAND },
+               { "^",   1, P_BXOR },
+               { "|",   1, P_BOR },
+               { "?",   1, P_TERN },
+               { ",",   1, P_COMMA },
+               { "~",   1, P_PRIMARY },
+               { "!",   1, P_PRIMARY },
+               { "(",   1, P_PRIMARY },
+               { ")",   1, P_PRIMARY },
+               { ":",   1, P_PRIMARY },
+               { "",    0, P_PRIMARY } /* end of table */
+           };
+
+
+typedef struct expr_state Expr_state;
+struct expr_state {
+       const char *expression;         /* expression being evaluated */
+       const char *tokp;               /* lexical position */
+       enum token  tok;                /* token from token() */
+       int         noassign;           /* don't do assigns (for ?:,&&,||) */
+       struct tbl *val;                /* value from token() */
+       struct tbl *evaling;            /* variable that is being recursively
+                                        * expanded (EXPRINEVAL flag set)
+                                        */
+};
+
+enum error_type { ET_UNEXPECTED, ET_BADLIT, ET_RECURSIVE,
+                 ET_LVALUE, ET_RDONLY, ET_STR };
+
+static void        evalerr  ARGS((Expr_state *es, enum error_type type,
+                                 const char *str)) GCC_FUNC_ATTR(noreturn);
+static struct tbl *evalexpr ARGS((Expr_state *es, enum prec prec));
+static void        token    ARGS((Expr_state *es));
+static struct tbl *do_ppmm  ARGS((Expr_state *es, enum token op,
+                                 struct tbl *vasn, bool_t is_prefix));
+static void       assign_check ARGS((Expr_state *es, enum token op,
+                                     struct tbl *vasn));
+static struct tbl *tempvar  ARGS((void));
+static struct tbl *intvar   ARGS((Expr_state *es, struct tbl *vp));
+
+/*
+ * parse and evaluate expression
+ */
+int
+evaluate(expr, rval, error_ok)
+       const char *expr;
+       long *rval;
+       int error_ok;
+{
+       struct tbl v;
+       int ret;
+
+       v.flag = DEFINED|INTEGER;
+       v.type = 0;
+       ret = v_evaluate(&v, expr, error_ok);
+       *rval = v.val.i;
+       return ret;
+}
+
+/*
+ * parse and evaluate expression, storing result in vp.
+ */
+int
+v_evaluate(vp, expr, error_ok)
+       struct tbl *vp;
+       const char *expr;
+       volatile int error_ok;
+{
+       struct tbl *v;
+       Expr_state curstate;
+       Expr_state * const es = &curstate;
+       int i;
+
+       /* save state to allow recursive calls */
+       curstate.expression = curstate.tokp = expr;
+       curstate.noassign = 0;
+       curstate.evaling = (struct tbl *) 0;
+
+       newenv(E_ERRH);
+       i = ksh_sigsetjmp(e->jbuf, 0);
+       if (i) {
+               /* Clear EXPRINEVAL in of any variables we were playing with */
+               if (curstate.evaling)
+                       curstate.evaling->flag &= ~EXPRINEVAL;
+               quitenv();
+               if (i == LAEXPR) {
+                       if (error_ok == KSH_RETURN_ERROR)
+                               return 0;
+                       errorf("%s", null);
+               }
+               unwind(i);
+               /*NOTREACHED*/
+       }
+
+       token(es);
+#if 1 /* ifdef-out to disallow empty expressions to be treated as 0 */
+       if (es->tok == END) {
+               es->tok = LIT;
+               es->val = tempvar();
+       }
+#endif /* 0 */
+       v = intvar(es, evalexpr(es, MAX_PREC));
+
+       if (es->tok != END)
+               evalerr(es, ET_UNEXPECTED, (char *) 0);
+
+       if (vp->flag & INTEGER)
+               setint_v(vp, v);
+       else
+               /* can fail if readonly */
+               setstr(vp, str_val(v), error_ok);
+
+       quitenv();
+
+       return 1;
+}
+
+static void
+evalerr(es, type, str)
+       Expr_state *es;
+       enum error_type type;
+       const char *str;
+{
+       char tbuf[2];
+       const char *s;
+
+       switch (type) {
+       case ET_UNEXPECTED:
+               switch (es->tok) {
+               case VAR:
+                       s = es->val->name;
+                       break;
+               case LIT:
+                       s = str_val(es->val);
+                       break;
+               case END:
+                       s = "end of expression";
+                       break;
+               case BAD:
+                       tbuf[0] = *es->tokp;
+                       tbuf[1] = '\0';
+                       s = tbuf;
+                       break;
+               default:
+                       s = opinfo[(int)es->tok].name;
+               }
+               warningf(TRUE, "%s: unexpected `%s'", es->expression, s);
+               break;
+
+       case ET_BADLIT:
+               warningf(TRUE, "%s: bad number `%s'", es->expression, str);
+               break;
+
+       case ET_RECURSIVE:
+               warningf(TRUE, "%s: expression recurses on parameter `%s'",
+                       es->expression, str);
+               break;
+
+       case ET_LVALUE:
+               warningf(TRUE, "%s: %s requires lvalue",
+                       es->expression, str);
+               break;
+
+       case ET_RDONLY:
+               warningf(TRUE, "%s: %s applied to read only variable",
+                       es->expression, str);
+               break;
+
+       default: /* keep gcc happy */
+       case ET_STR:
+               warningf(TRUE, "%s: %s", es->expression, str);
+               break;
+       }
+       unwind(LAEXPR);
+}
+
+static struct tbl *
+evalexpr(es, prec)
+       Expr_state *es;
+       enum prec prec;
+{
+       struct tbl *vl, UNINITIALIZED(*vr), *vasn;
+       enum token op;
+       long UNINITIALIZED(res);
+
+       if (prec == P_PRIMARY) {
+               op = es->tok;
+               if (op == O_BNOT || op == O_LNOT || op == O_MINUS
+                   || op == O_PLUS)
+               {
+                       token(es);
+                       vl = intvar(es, evalexpr(es, P_PRIMARY));
+                       if (op == O_BNOT)
+                               vl->val.i = ~vl->val.i;
+                       else if (op == O_LNOT)
+                               vl->val.i = !vl->val.i;
+                       else if (op == O_MINUS)
+                               vl->val.i = -vl->val.i;
+                       /* op == O_PLUS is a no-op */
+               } else if (op == OPEN_PAREN) {
+                       token(es);
+                       vl = evalexpr(es, MAX_PREC);
+                       if (es->tok != CLOSE_PAREN)
+                               evalerr(es, ET_STR, "missing )");
+                       token(es);
+               } else if (op == O_PLUSPLUS || op == O_MINUSMINUS) {
+                       token(es);
+                       vl = do_ppmm(es, op, es->val, TRUE);
+                       token(es);
+               } else if (op == VAR || op == LIT) {
+                       vl = es->val;
+                       token(es);
+               } else {
+                       evalerr(es, ET_UNEXPECTED, (char *) 0);
+                       /*NOTREACHED*/
+               }
+               if (es->tok == O_PLUSPLUS || es->tok == O_MINUSMINUS) {
+                       vl = do_ppmm(es, es->tok, vl, FALSE);
+                       token(es);
+               }
+               return vl;
+       }
+       vl = evalexpr(es, ((int) prec) - 1);
+       for (op = es->tok; IS_BINOP(op) && opinfo[(int) op].prec == prec;
+               op = es->tok)
+       {
+               token(es);
+               vasn = vl;
+               if (op != O_ASN) /* vl may not have a value yet */
+                       vl = intvar(es, vl);
+               if (IS_ASSIGNOP(op)) {
+                       assign_check(es, op, vasn);
+                       vr = intvar(es, evalexpr(es, P_ASSIGN));
+               } else if (op != O_TERN && op != O_LAND && op != O_LOR)
+                       vr = intvar(es, evalexpr(es, ((int) prec) - 1));
+               if ((op == O_DIV || op == O_MOD || op == O_DIVASN
+                    || op == O_MODASN) && vr->val.i == 0)
+               {
+                       if (es->noassign)
+                               vr->val.i = 1;
+                       else
+                               evalerr(es, ET_STR, "zero divisor");
+               }
+               switch ((int) op) {
+               case O_TIMES:
+               case O_TIMESASN:
+                       res = vl->val.i * vr->val.i;
+                       break;
+               case O_DIV:
+               case O_DIVASN:
+                       res = vl->val.i / vr->val.i;
+                       break;
+               case O_MOD:
+               case O_MODASN:
+                       res = vl->val.i % vr->val.i;
+                       break;
+               case O_PLUS:
+               case O_PLUSASN:
+                       res = vl->val.i + vr->val.i;
+                       break;
+               case O_MINUS:
+               case O_MINUSASN:
+                       res = vl->val.i - vr->val.i;
+                       break;
+               case O_LSHIFT:
+               case O_LSHIFTASN:
+                       res = vl->val.i << vr->val.i;
+                       break;
+               case O_RSHIFT:
+               case O_RSHIFTASN:
+                       res = vl->val.i >> vr->val.i;
+                       break;
+               case O_LT:
+                       res = vl->val.i < vr->val.i;
+                       break;
+               case O_LE:
+                       res = vl->val.i <= vr->val.i;
+                       break;
+               case O_GT:
+                       res = vl->val.i > vr->val.i;
+                       break;
+               case O_GE:
+                       res = vl->val.i >= vr->val.i;
+                       break;
+               case O_EQ:
+                       res = vl->val.i == vr->val.i;
+                       break;
+               case O_NE:
+                       res = vl->val.i != vr->val.i;
+                       break;
+               case O_BAND:
+               case O_BANDASN:
+                       res = vl->val.i & vr->val.i;
+                       break;
+               case O_BXOR:
+               case O_BXORASN:
+                       res = vl->val.i ^ vr->val.i;
+                       break;
+               case O_BOR:
+               case O_BORASN:
+                       res = vl->val.i | vr->val.i;
+                       break;
+               case O_LAND:
+                       if (!vl->val.i)
+                               es->noassign++;
+                       vr = intvar(es, evalexpr(es, ((int) prec) - 1));
+                       res = vl->val.i && vr->val.i;
+                       if (!vl->val.i)
+                               es->noassign--;
+                       break;
+               case O_LOR:
+                       if (vl->val.i)
+                               es->noassign++;
+                       vr = intvar(es, evalexpr(es, ((int) prec) - 1));
+                       res = vl->val.i || vr->val.i;
+                       if (vl->val.i)
+                               es->noassign--;
+                       break;
+               case O_TERN:
+                       {
+                               int ex = vl->val.i != 0;
+                               if (!ex)
+                                       es->noassign++;
+                               vl = evalexpr(es, MAX_PREC);
+                               if (!ex)
+                                       es->noassign--;
+                               if (es->tok != CTERN)
+                                       evalerr(es, ET_STR, "missing :");
+                               token(es);
+                               if (ex)
+                                       es->noassign++;
+                               vr = evalexpr(es, P_TERN);
+                               if (ex)
+                                       es->noassign--;
+                               vl = ex ? vl : vr;
+                       }
+                       break;
+               case O_ASN:
+                       res = vr->val.i;
+                       break;
+               case O_COMMA:
+                       res = vr->val.i;
+                       break;
+               }
+               if (IS_ASSIGNOP(op)) {
+                       vr->val.i = res;
+                       if (vasn->flag & INTEGER)
+                               setint_v(vasn, vr);
+                       else
+                               setint(vasn, res);
+                       vl = vr;
+               } else if (op != O_TERN)
+                       vl->val.i = res;
+       }
+       return vl;
+}
+
+static void
+token(es)
+       Expr_state *es;
+{
+       const char *cp;
+       int c;
+       char *tvar;
+
+       /* skip white space */
+       for (cp = es->tokp; (c = *cp), isspace((unsigned char)c); cp++)
+               ;
+       es->tokp = cp;
+
+       if (c == '\0')
+               es->tok = END;
+       else if (letter(c)) {
+               for (; letnum(c); c = *cp)
+                       cp++;
+               if (c == '[') {
+                       int len;
+
+                       len = array_ref_len(cp);
+                       if (len == 0)
+                               evalerr(es, ET_STR, "missing ]");
+                       cp += len;
+               }
+#ifdef KSH
+               else if (c == '(' /*)*/ ) {
+                   /* todo: add math functions (all take single argument):
+                    * abs acos asin atan cos cosh exp int log sin sinh sqrt
+                    * tan tanh
+                    */
+                   ;
+               }
+#endif /* KSH */
+               if (es->noassign) {
+                       es->val = tempvar();
+                       es->val->flag |= EXPRLVALUE;
+               } else {
+                       tvar = str_nsave(es->tokp, cp - es->tokp, ATEMP);
+                       es->val = global(tvar);
+                       afree(tvar, ATEMP);
+               }
+               es->tok = VAR;
+       } else if (digit(c)) {
+               for (; c != '_' && (letnum(c) || c == '#'); c = *cp++)
+                       ;
+               tvar = str_nsave(es->tokp, --cp - es->tokp, ATEMP);
+               es->val = tempvar();
+               es->val->flag &= ~INTEGER;
+               es->val->type = 0;
+               es->val->val.s = tvar;
+               if (setint_v(es->val, es->val) == NULL)
+                       evalerr(es, ET_BADLIT, tvar);
+               afree(tvar, ATEMP);
+               es->tok = LIT;
+       } else {
+               int i, n0;
+
+               for (i = 0; (n0 = opinfo[i].name[0]); i++)
+                       if (c == n0
+                           && strncmp(cp, opinfo[i].name, opinfo[i].len) == 0)
+                       {
+                               es->tok = (enum token) i;
+                               cp += opinfo[i].len;
+                               break;
+                       }
+               if (!n0)
+                       es->tok = BAD;
+       }
+       es->tokp = cp;
+}
+
+/* Do a ++ or -- operation */
+static struct tbl *
+do_ppmm(es, op, vasn, is_prefix)
+       Expr_state *es;
+       enum token op;
+       struct tbl *vasn;
+       bool_t is_prefix;
+{
+       struct tbl *vl;
+       int oval;
+
+       assign_check(es, op, vasn);
+
+       vl = intvar(es, vasn);
+       oval = op == O_PLUSPLUS ? vl->val.i++ : vl->val.i--;
+       if (vasn->flag & INTEGER)
+               setint_v(vasn, vl);
+       else
+               setint(vasn, vl->val.i);
+       if (!is_prefix)         /* undo the inc/dec */
+               vl->val.i = oval;
+
+       return vl;
+}
+
+static void
+assign_check(es, op, vasn)
+       Expr_state *es;
+       enum token op;
+       struct tbl *vasn;
+{
+       if (vasn->name[0] == '\0' && !(vasn->flag & EXPRLVALUE))
+               evalerr(es, ET_LVALUE, opinfo[(int) op].name);
+       else if (vasn->flag & RDONLY)
+               evalerr(es, ET_RDONLY, opinfo[(int) op].name);
+}
+
+static struct tbl *
+tempvar()
+{
+       register struct tbl *vp;
+
+       vp = (struct tbl*) alloc(sizeof(struct tbl), ATEMP);
+       vp->flag = ISSET|INTEGER;
+       vp->type = 0;
+       vp->areap = ATEMP;
+       vp->val.i = 0;
+       vp->name[0] = '\0';
+       return vp;
+}
+
+/* cast (string) variable to temporary integer variable */
+static struct tbl *
+intvar(es, vp)
+       Expr_state *es;
+       struct tbl *vp;
+{
+       struct tbl *vq;
+
+       /* try to avoid replacing a temp var with another temp var */
+       if (vp->name[0] == '\0'
+           && (vp->flag & (ISSET|INTEGER|EXPRLVALUE)) == (ISSET|INTEGER))
+               return vp;
+
+       vq = tempvar();
+       if (setint_v(vq, vp) == NULL) {
+               if (vp->flag & EXPRINEVAL)
+                       evalerr(es, ET_RECURSIVE, vp->name);
+               es->evaling = vp;
+               vp->flag |= EXPRINEVAL;
+               v_evaluate(vq, str_val(vp), KSH_UNWIND_ERROR);
+               vp->flag &= ~EXPRINEVAL;
+               es->evaling = (struct tbl *) 0;
+       }
+       return vq;
+}
diff --git a/bin/ksh/history.c b/bin/ksh/history.c
new file mode 100644 (file)
index 0000000..e29aade
--- /dev/null
@@ -0,0 +1,1229 @@
+/*     $NetBSD: history.c,v 1.11 2011/08/31 16:24:54 plunky Exp $      */
+
+/*
+ * command history
+ *
+ * only implements in-memory history.
+ */
+
+/*
+ *     This file contains
+ *     a)      the original in-memory history  mechanism
+ *     b)      a simple file saving history mechanism done by  sjg@zen
+ *             define EASY_HISTORY to get this
+ *     c)      a more complicated mechanism done by  pc@hillside.co.uk
+ *             that more closely follows the real ksh way of doing
+ *             things. You need to have the mmap system call for this
+ *             to work on your system
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: history.c,v 1.11 2011/08/31 16:24:54 plunky Exp $");
+#endif
+
+
+#include "sh.h"
+#include "ksh_stat.h"
+
+#ifdef HISTORY
+# ifdef EASY_HISTORY
+
+#  ifndef HISTFILE
+#   ifdef OS2
+#    define HISTFILE "history.ksh"
+#   else /* OS2 */
+#    define HISTFILE ".pdksh_history"
+#   endif /* OS2 */
+#  endif
+
+# else
+/*     Defines and includes for the complicated case */
+
+#  include <sys/file.h>
+#  include <sys/mman.h>
+
+/*
+ *     variables for handling the data file
+ */
+static int     histfd;
+static int     hsize;
+
+static int hist_count_lines ARGS((unsigned char *, int));
+static int hist_shrink ARGS((unsigned char *, int));
+static unsigned char *hist_skip_back ARGS((unsigned char *,int *,int));
+static void histload ARGS((Source *, unsigned char *, int));
+static void histinsert ARGS((Source *, int, unsigned char *));
+static void writehistfile ARGS((int, char *));
+static int sprinkle ARGS((int));
+
+#  ifdef MAP_FILE
+#   define MAP_FLAGS   (MAP_FILE|MAP_PRIVATE)
+#  else
+#   define MAP_FLAGS   MAP_PRIVATE
+#  endif
+
+# endif        /* of EASY_HISTORY */
+
+static int     hist_execute ARGS((char *));
+static int     hist_replace ARGS((char **, const char *, const char *, int));
+static char   **hist_get ARGS((const char *, int, int));
+static char   **hist_get_newest ARGS((int));
+static char   **hist_get_oldest ARGS((void));
+static void    histbackup ARGS((void));
+
+static char   **current;       /* current position in history[] */
+static int     curpos;         /* current index in history[] */
+static char    *hname;         /* current name of history file */
+static int     hstarted;       /* set after hist_init() called */
+static Source  *hist_source;
+
+
+int
+c_fc(wp)
+       char **wp;
+{
+       struct shf *shf;
+       struct temp UNINITIALIZED(*tf);
+       char *p, *editor = (char *) 0;
+       int gflag = 0, lflag = 0, nflag = 0, sflag = 0, rflag = 0;
+       int optc;
+       char *first = (char *) 0, *last = (char *) 0;
+       char **hfirst, **hlast, **hp;
+
+       if (hist_source == NULL) {
+               bi_errorf("not interactive");
+               return 1;
+       }
+
+       while ((optc = ksh_getopt(wp, &builtin_opt, "e:glnrs0,1,2,3,4,5,6,7,8,9,")) != EOF)
+               switch (optc) {
+                 case 'e':
+                       p = builtin_opt.optarg;
+                       if (strcmp(p, "-") == 0)
+                               sflag++;
+                       else {
+                               size_t len = strlen(p) + 4;
+                               editor = str_nsave(p, len, ATEMP);
+                               strlcat(editor, " $_", len);
+                       }
+                       break;
+                 case 'g': /* non-at&t ksh */
+                       gflag++;
+                       break;
+                 case 'l':
+                       lflag++;
+                       break;
+                 case 'n':
+                       nflag++;
+                       break;
+                 case 'r':
+                       rflag++;
+                       break;
+                 case 's':     /* posix version of -e - */
+                       sflag++;
+                       break;
+                 /* kludge city - accept -num as -- -num (kind of) */
+                 case '0': case '1': case '2': case '3': case '4':
+                 case '5': case '6': case '7': case '8': case '9':
+                       p = shf_smprintf("-%c%s",
+                                       optc, builtin_opt.optarg);
+                       if (!first)
+                               first = p;
+                       else if (!last)
+                               last = p;
+                       else {
+                               bi_errorf("too many arguments");
+                               return 1;
+                       }
+                       break;
+                 case '?':
+                       return 1;
+               }
+       wp += builtin_opt.optind;
+
+       /* Substitute and execute command */
+       if (sflag) {
+               char *pat = (char *) 0, *rep = (char *) 0;
+
+               if (editor || lflag || nflag || rflag) {
+                       bi_errorf("can't use -e, -l, -n, -r with -s (-e -)");
+                       return 1;
+               }
+
+               /* Check for pattern replacement argument */
+               if (*wp && **wp && (p = strchr(*wp + 1, '='))) {
+                       pat = str_save(*wp, ATEMP);
+                       p = pat + (p - *wp);
+                       *p++ = '\0';
+                       rep = p;
+                       wp++;
+               }
+               /* Check for search prefix */
+               if (!first && (first = *wp))
+                       wp++;
+               if (last || *wp) {
+                       bi_errorf("too many arguments");
+                       return 1;
+               }
+
+               hp = first ? hist_get(first, FALSE, FALSE)
+                          : hist_get_newest(FALSE);
+               if (!hp)
+                       return 1;
+               return hist_replace(hp, pat, rep, gflag);
+       }
+
+       if (editor && (lflag || nflag)) {
+               bi_errorf("can't use -l, -n with -e");
+               return 1;
+       }
+
+       if (!first && (first = *wp))
+               wp++;
+       if (!last && (last = *wp))
+               wp++;
+       if (*wp) {
+               bi_errorf("too many arguments");
+               return 1;
+       }
+       if (!first) {
+               hfirst = lflag ? hist_get("-16", TRUE, TRUE)
+                              : hist_get_newest(FALSE);
+               if (!hfirst)
+                       return 1;
+               /* can't fail if hfirst didn't fail */
+               hlast = hist_get_newest(FALSE);
+       } else {
+               /* POSIX says not an error if first/last out of bounds
+                * when range is specified; at&t ksh and pdksh allow out of
+                * bounds for -l as well.
+                */
+               hfirst = hist_get(first, (lflag || last) ? TRUE : FALSE,
+                               lflag ? TRUE : FALSE);
+               if (!hfirst)
+                       return 1;
+               hlast = last ? hist_get(last, TRUE, lflag ? TRUE : FALSE)
+                           : (lflag ? hist_get_newest(FALSE) : hfirst);
+               if (!hlast)
+                       return 1;
+       }
+       if (hfirst > hlast) {
+               char **temp;
+
+               temp = hfirst; hfirst = hlast; hlast = temp;
+               rflag = !rflag; /* POSIX */
+       }
+
+       /* List history */
+       if (lflag) {
+               char *s, *t;
+               const char *nfmt = nflag ? "\t" : "%d\t";
+
+               for (hp = rflag ? hlast : hfirst;
+                    hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1)
+               {
+                       shf_fprintf(shl_stdout, nfmt,
+                               hist_source->line - (int) (histptr - hp));
+                       /* print multi-line commands correctly */
+                       for (s = *hp; (t = strchr(s, '\n')); s = t)
+                               shf_fprintf(shl_stdout, "%.*s\t", ++t - s, s);
+                       shf_fprintf(shl_stdout, "%s\n", s);
+               }
+               shf_flush(shl_stdout);
+               return 0;
+       }
+
+       /* Run editor on selected lines, then run resulting commands */
+
+       tf = maketemp(ATEMP, TT_HIST_EDIT, &e->temps);
+       if (!(shf = tf->shf)) {
+               bi_errorf("cannot create temp file %s - %s",
+                       tf->name, strerror(errno));
+               return 1;
+       }
+       for (hp = rflag ? hlast : hfirst;
+            hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1)
+               shf_fprintf(shf, "%s\n", *hp);
+       if (shf_close(shf) == EOF) {
+               bi_errorf("error writing temporary file - %s", strerror(errno));
+               return 1;
+       }
+
+       /* Ignore setstr errors here (arbitrary) */
+       setstr(local("_", FALSE), tf->name, KSH_RETURN_ERROR);
+
+       /* XXX: source should not get trashed by this.. */
+       {
+               Source *sold = source;
+               int ret;
+
+               ret = command(editor ? editor : "${FCEDIT:-/bin/ed} $_");
+               source = sold;
+               if (ret)
+                       return ret;
+       }
+
+       {
+               struct stat statb;
+               XString xs;
+               char *xp;
+               int n;
+
+               if (!(shf = shf_open(tf->name, O_RDONLY, 0, 0))) {
+                       bi_errorf("cannot open temp file %s", tf->name);
+                       return 1;
+               }
+
+               n = fstat(shf_fileno(shf), &statb) < 0 ? 128
+                       : statb.st_size + 1;
+               Xinit(xs, xp, n, hist_source->areap);
+               while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) {
+                       xp += n;
+                       if (Xnleft(xs, xp) <= 0)
+                               XcheckN(xs, xp, Xlength(xs, xp));
+               }
+               if (n < 0) {
+                       bi_errorf("error reading temp file %s - %s",
+                               tf->name, strerror(shf_errno(shf)));
+                       shf_close(shf);
+                       return 1;
+               }
+               shf_close(shf);
+               *xp = '\0';
+               strip_nuls(Xstring(xs, xp), Xlength(xs, xp));
+               return hist_execute(Xstring(xs, xp));
+       }
+}
+
+/* Save cmd in history, execute cmd (cmd gets trashed) */
+static int
+hist_execute(cmd)
+       char *cmd;
+{
+       Source *sold;
+       int ret;
+       char *p, *q;
+
+       histbackup();
+
+       for (p = cmd; p; p = q) {
+               if ((q = strchr(p, '\n'))) {
+                       *q++ = '\0'; /* kill the newline */
+                       if (!*q) /* ignore trailing newline */
+                               q = (char *) 0;
+               }
+#ifdef EASY_HISTORY
+               if (p != cmd)
+                       histappend(p, TRUE);
+               else
+#endif /* EASY_HISTORY */
+                       histsave(++(hist_source->line), p, 1);
+
+               shellf("%s\n", p); /* POSIX doesn't say this is done... */
+               if ((p = q)) /* restore \n (trailing \n not restored) */
+                       q[-1] = '\n';
+       }
+
+       /* Commands are executed here instead of pushing them onto the
+        * input 'cause posix says the redirection and variable assignments
+        * in
+        *      X=y fc -e - 42 2> /dev/null
+        * are to effect the repeated commands environment.
+        */
+       /* XXX: source should not get trashed by this.. */
+       sold = source;
+       ret = command(cmd);
+       source = sold;
+       return ret;
+}
+
+static int
+hist_replace(hp, pat, rep, globalv)
+       char **hp;
+       const char *pat;
+       const char *rep;
+       int globalv;
+{
+       char *line;
+
+       if (!pat)
+               line = str_save(*hp, ATEMP);
+       else {
+               char *s, *s1;
+               int pat_len = strlen(pat);
+               int rep_len = strlen(rep);
+               int len;
+               XString xs;
+               char *xp;
+               int any_subst = 0;
+
+               Xinit(xs, xp, 128, ATEMP);
+               for (s = *hp; (s1 = strstr(s, pat))
+                             && (!any_subst || globalv) ; s = s1 + pat_len)
+               {
+                       any_subst = 1;
+                       len = s1 - s;
+                       XcheckN(xs, xp, len + rep_len);
+                       memcpy(xp, s, len);             /* first part */
+                       xp += len;
+                       memcpy(xp, rep, rep_len);       /* replacement */
+                       xp += rep_len;
+               }
+               if (!any_subst) {
+                       bi_errorf("substitution failed");
+                       return 1;
+               }
+               len = strlen(s) + 1;
+               XcheckN(xs, xp, len);
+               memcpy(xp, s, len);
+               xp += len;
+               line = Xclose(xs, xp);
+       }
+       return hist_execute(line);
+}
+
+/*
+ * get pointer to history given pattern
+ * pattern is a number or string
+ */
+static char **
+hist_get(str, approx, allow_cur)
+       const char *str;
+       int approx;
+       int allow_cur;
+{
+       char **hp = (char **) 0;
+       int n;
+
+       if (getn(str, &n)) {
+               hp = histptr + (n < 0 ? n : (n - hist_source->line));
+               if (hp < histlist) {
+                       if (approx)
+                               hp = hist_get_oldest();
+                       else {
+                               bi_errorf("%s: not in history", str);
+                               hp = (char **) 0;
+                       }
+               } else if (hp > histptr) {
+                       if (approx)
+                               hp = hist_get_newest(allow_cur);
+                       else {
+                               bi_errorf("%s: not in history", str);
+                               hp = (char **) 0;
+                       }
+               } else if (!allow_cur && hp == histptr) {
+                       bi_errorf("%s: invalid range", str);
+                       hp = (char **) 0;
+               }
+       } else {
+               int anchored = *str == '?' ? (++str, 0) : 1;
+
+               /* the -1 is to avoid the current fc command */
+               n = findhist(histptr - histlist - 1, 0, str, anchored);
+               if (n < 0) {
+                       bi_errorf("%s: not in history", str);
+                       hp = (char **) 0;
+               } else
+                       hp = &histlist[n];
+       }
+       return hp;
+}
+
+/* Return a pointer to the newest command in the history */
+static char **
+hist_get_newest(allow_cur)
+       int allow_cur;
+{
+       if (histptr < histlist || (!allow_cur && histptr == histlist)) {
+               bi_errorf("no history (yet)");
+               return (char **) 0;
+       }
+       if (allow_cur)
+               return histptr;
+       return histptr - 1;
+}
+
+/* Return a pointer to the newest command in the history */
+static char **
+hist_get_oldest()
+{
+       if (histptr <= histlist) {
+               bi_errorf("no history (yet)");
+               return (char **) 0;
+       }
+       return histlist;
+}
+
+/******************************/
+/* Back up over last histsave */
+/******************************/
+static void
+histbackup()
+{
+       static int last_line = -1;
+
+       if (histptr >= histlist && last_line != hist_source->line) {
+               hist_source->line--;
+               afree((void*)*histptr, APERM);
+               histptr--;
+               last_line = hist_source->line;
+       }
+}
+
+/*
+ * Return the current position.
+ */
+char **
+histpos()
+{
+       return current;
+}
+
+int
+histN()
+{
+       return curpos;
+}
+
+int
+histnum(n)
+       int     n;
+{
+       int     last = histptr - histlist;
+
+       if (n < 0 || n >= last) {
+               current = histptr;
+               curpos = last;
+               return last;
+       } else {
+               current = &histlist[n];
+               curpos = n;
+               return n;
+       }
+}
+
+/*
+ * This will become unnecessary if hist_get is modified to allow
+ * searching from positions other than the end, and in either
+ * direction.
+ */
+int
+findhist(start, fwd, str, anchored)
+       int     start;
+       int     fwd;
+       const char  *str;
+       int     anchored;
+{
+       char    **hp;
+       int     maxhist = histptr - histlist;
+       int     incr = fwd ? 1 : -1;
+       int     len = strlen(str);
+
+       if (start < 0 || start >= maxhist)
+               start = maxhist;
+
+       hp = &histlist[start];
+       for (; hp >= histlist && hp <= histptr; hp += incr)
+               if ((anchored && strncmp(*hp, str, len) == 0)
+                   || (!anchored && strstr(*hp, str)))
+                       return hp - histlist;
+
+       return -1;
+}
+
+/*
+ *     set history
+ *     this means reallocating the dataspace
+ */
+void
+sethistsize(n)
+       int n;
+{
+       if (n > 0 && n != histsize) {
+               int cursize = histptr - histlist;
+
+               /* save most recent history */
+               if (n < cursize) {
+                       memmove(histlist, histptr - n, n * sizeof(char *));
+                       cursize = n;
+               }
+
+               histlist = (char **)aresize(histlist, n*sizeof(char *), APERM);
+
+               histsize = n;
+               histptr = histlist + cursize;
+       }
+}
+
+/*
+ *     set history file
+ *     This can mean reloading/resetting/starting history file
+ *     maintenance
+ */
+void
+sethistfile(name)
+       const char *name;
+{
+       /* if not started then nothing to do */
+       if (hstarted == 0)
+               return;
+
+       /* if the name is the same as the name we have */
+       if (hname && strcmp(hname, name) == 0)
+               return;
+
+       /*
+        * its a new name - possibly
+        */
+# ifdef EASY_HISTORY
+       if (hname) {
+               afree(hname, APERM);
+               hname = NULL;
+       }
+# else
+       if (histfd) {
+               /* yes the file is open */
+               (void) close(histfd);
+               histfd = 0;
+               hsize = 0;
+               afree(hname, APERM);
+               hname = NULL;
+               /* let's reset the history */
+               histptr = histlist - 1;
+               hist_source->line = 0;
+       }
+# endif
+
+       hist_init(hist_source);
+}
+
+/*
+ *     initialise the history vector
+ */
+void
+init_histvec()
+{
+       if (histlist == NULL) {
+               histsize = HISTORYSIZE;
+               histlist = (char **)alloc(histsize*sizeof (char *), APERM);
+               histptr = histlist - 1;
+       }
+}
+
+# ifdef EASY_HISTORY
+/*
+ * save command in history
+ */
+void
+histsave(lno, cmd, dowrite)
+       int lno;        /* ignored (compatibility with COMPLEX_HISTORY) */
+       const char *cmd;
+       int dowrite;    /* ignored (compatibility with COMPLEX_HISTORY) */
+{
+       register char **hp = histptr;
+       char *cp;
+
+       if (++hp >= histlist + histsize) { /* remove oldest command */
+               afree((void*)histlist[0], APERM);
+               memmove(histlist, histlist + 1,
+                       sizeof(histlist[0]) * (histsize - 1));
+               hp = &histlist[histsize - 1];
+       }
+       *hp = str_save(cmd, APERM);
+       /* trash trailing newline but allow imbedded newlines */
+       cp = *hp + strlen(*hp);
+       if (cp > *hp && cp[-1] == '\n')
+               cp[-1] = '\0';
+       histptr = hp;
+}
+
+/*
+ * Append an entry to the last saved command. Used for multiline
+ * commands
+ */
+void
+histappend(cmd, nl_separate)
+       const char *cmd;
+       int     nl_separate;
+{
+       int     hlen, clen;
+       char    *p;
+
+       hlen = strlen(*histptr);
+       clen = strlen(cmd);
+       if (clen > 0 && cmd[clen-1] == '\n')
+               clen--;
+       p = *histptr = (char *) aresize(*histptr, hlen + clen + 2, APERM);
+       p += hlen;
+       if (nl_separate)
+               *p++ = '\n';
+       memcpy(p, cmd, clen);
+       p[clen] = '\0';
+}
+
+/*
+ * 92-04-25 <sjg@zen>
+ * A simple history file implementation.
+ * At present we only save the history when we exit.
+ * This can cause problems when there are multiple shells are
+ * running under the same user-id.  The last shell to exit gets
+ * to save its history.
+ */
+void
+hist_init(s)
+       Source *s;
+{
+       char *f;
+       FILE *fh;
+
+       if (Flag(FTALKING) == 0)
+               return;
+
+       hstarted = 1;
+
+       hist_source = s;
+
+       if ((f = str_val(global("HISTFILE"))) == NULL || *f == '\0') {
+# if 1 /* Don't use history file unless the user asks for it */
+               hname = NULL;
+               return;
+# else
+               char *home = str_val(global("HOME"));
+               int len;
+
+               if (home == NULL)
+                       home = null;
+               f = HISTFILE;
+               hname = alloc(len = strlen(home) + strlen(f) + 2, APERM);
+               shf_snprintf(hname, len, "%s/%s", home, f);
+# endif
+       } else
+               hname = str_save(f, APERM);
+
+       if ((fh = fopen(hname, "r"))) {
+               int pos = 0, nread = 0;
+               int contin = 0;         /* continuation of previous command */
+               char *end;
+               char hline[LINE + 1];
+
+               while (1) {
+                       if (pos >= nread) {
+                               pos = 0;
+                               nread = fread(hline, 1, LINE, fh);
+                               if (nread <= 0)
+                                       break;
+                               hline[nread] = '\0';
+                       }
+                       end = strchr(hline + pos, 0); /* will always succeed */
+                       if (contin)
+                               histappend(hline + pos, 0);
+                       else {
+                               hist_source->line++;
+                               histsave(0, hline + pos, 0);
+                       }
+                       pos = end - hline + 1;
+                       contin = end == &hline[nread];
+               }
+               fclose(fh);
+       }
+}
+
+/*
+ * save our history.
+ * We check that we do not have more than we are allowed.
+ * If the history file is read-only we do nothing.
+ * Handy for having all shells start with a useful history set.
+ */
+
+void
+hist_finish()
+{
+  static int once;
+  int fd;
+#if !defined(O_EXLOCK) && defined(LOCK_EX)
+  int rc;
+#endif
+  FILE *fh;
+  register int i;
+  register char **hp;
+
+  if (once++)
+    return;
+  if (hname == NULL || hname[0] == 0)
+    return;
+
+  /* check how many we have */
+  i = histptr - histlist;
+  if (i >= histsize)
+    hp = &histptr[-histsize];
+  else
+    hp = histlist;
+
+  fd = open(hname, O_WRONLY | O_CREAT | O_TRUNC
+#ifdef O_EXLOCK
+                                       | O_EXLOCK
+#endif
+                                       , 0777);
+
+#if !defined(O_EXLOCK) && defined(LOCK_EX)
+  do {
+    rc = flock(fd, LOCK_EX);
+    if (rc == -1 && errno != EINTR)
+      return;
+  } while (rc == -1 && errno == EINTR); /* if interrupted, try again. */
+#endif
+
+  /* Remove anything written before we got the lock */
+  ftruncate(fd, 0);
+  if (fd >= 0 && (fh = fdopen(fd, "w"))) {
+    for (i = 0; hp + i <= histptr && hp[i]; i++)
+      fprintf(fh, "%s%c", hp[i], '\0');
+#if !defined(O_EXLOCK) && defined(LOCK_EX)
+    flock(fd, LOCK_UN);
+#endif
+    fclose(fh);
+  }
+}
+
+# else /* EASY_HISTORY */
+
+/*
+ *     Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to
+ *     a) permit HISTSIZE to control number of lines of history stored
+ *     b) maintain a physical history file
+ *
+ *     It turns out that there is a lot of ghastly hackery here
+ */
+
+
+/*
+ * save command in history
+ */
+void
+histsave(lno, cmd, dowrite)
+       int lno;
+       const char *cmd;
+       int dowrite;
+{
+       register char **hp;
+       char *c, *cp;
+
+       c = str_save(cmd, APERM);
+       if ((cp = strchr(c, '\n')) != NULL)
+               *cp = '\0';
+
+       if (histfd && dowrite)
+               writehistfile(lno, c);
+
+       hp = histptr;
+
+       if (++hp >= histlist + histsize) { /* remove oldest command */
+               afree((void*)*histlist, APERM);
+               for (hp = histlist; hp < histlist + histsize - 1; hp++)
+                       hp[0] = hp[1];
+       }
+       *hp = c;
+       histptr = hp;
+}
+
+/*
+ *     Write history data to a file nominated by HISTFILE
+ *     if HISTFILE is unset then history still happens, but
+ *     the data is not written to a file
+ *     All copies of ksh looking at the file will maintain the
+ *     same history. This is ksh behaviour.
+ *
+ *     This stuff uses mmap()
+ *     if your system ain't got it - then you'll have to undef HISTORYFILE
+ */
+
+/*
+ *     Open a history file
+ *     Format is:
+ *     Bytes 1, 2: HMAGIC - just to check that we are dealing with
+ *                 the correct object
+ *     Then follows a number of stored commands
+ *     Each command is
+ *     <command byte><command number(4 bytes)><bytes><null>
+ */
+# define HMAGIC1               0xab
+# define HMAGIC2               0xcd
+# define COMMAND               0xff
+
+void
+hist_init(s)
+       Source *s;
+{
+       unsigned char   *base;
+       int     lines;
+       int     fd;
+
+       if (Flag(FTALKING) == 0)
+               return;
+
+       hstarted = 1;
+
+       hist_source = s;
+
+       hname = str_val(global("HISTFILE"));
+       if (hname == NULL)
+               return;
+       hname = str_save(hname, APERM);
+
+  retry:
+       /* we have a file and are interactive */
+       if ((fd = open(hname, O_RDWR|O_CREAT|O_APPEND, 0600)) < 0)
+               return;
+
+       histfd = savefd(fd, 0);
+
+       (void) flock(histfd, LOCK_EX);
+
+       hsize = lseek(histfd, 0L, SEEK_END);
+
+       if (hsize == 0) {
+               /* add magic */
+               if (sprinkle(histfd)) {
+                       hist_finish();
+                       return;
+               }
+       }
+       else if (hsize > 0) {
+               /*
+                * we have some data
+                */
+               base = (unsigned char *)mmap(0, hsize, PROT_READ, MAP_FLAGS, histfd, 0);
+               /*
+                * check on its validity
+                */
+               if (base == MAP_FAILED || *base != HMAGIC1 || base[1] != HMAGIC2) {
+                       if (base != MAP_FAILED)
+                               munmap((caddr_t)base, hsize);
+                       hist_finish();
+                       unlink(hname);
+                       goto retry;
+               }
+               if (hsize > 2) {
+                       lines = hist_count_lines(base+2, hsize-2);
+                       if (lines > histsize) {
+                               /* we need to make the file smaller */
+                               if (hist_shrink(base, hsize))
+                                       unlink(hname);
+                               munmap((caddr_t)base, hsize);
+                               hist_finish();
+                               goto retry;
+                       }
+               }
+               histload(hist_source, base+2, hsize-2);
+               munmap((caddr_t)base, hsize);
+       }
+       (void) flock(histfd, LOCK_UN);
+       hsize = lseek(histfd, 0L, SEEK_END);
+}
+
+typedef enum state {
+       shdr,           /* expecting a header */
+       sline,          /* looking for a null byte to end the line */
+       sn1,            /* bytes 1 to 4 of a line no */
+       sn2, sn3, sn4
+} State;
+
+static int
+hist_count_lines(base, bytes)
+       register unsigned char *base;
+       register int bytes;
+{
+       State state = shdr;
+       int lines = 0;
+
+       while (bytes--) {
+               switch (state)
+               {
+               case shdr:
+                       if (*base == COMMAND)
+                               state = sn1;
+                       break;
+               case sn1:
+                       state = sn2; break;
+               case sn2:
+                       state = sn3; break;
+               case sn3:
+                       state = sn4; break;
+               case sn4:
+                       state = sline; break;
+               case sline:
+                       if (*base == '\0')
+                               lines++, state = shdr;
+               }
+               base++;
+       }
+       return lines;
+}
+
+/*
+ *     Shrink the history file to histsize lines
+ */
+static int
+hist_shrink(oldbase, oldbytes)
+       unsigned char *oldbase;
+       int oldbytes;
+{
+       int fd;
+       char    nfile[1024];
+       struct  stat statb;
+       unsigned char *nbase = oldbase;
+       int nbytes = oldbytes;
+
+       nbase = hist_skip_back(nbase, &nbytes, histsize);
+       if (nbase == NULL)
+               return 1;
+       if (nbase == oldbase)
+               return 0;
+
+       /*
+        *      create temp file
+        */
+       (void) shf_snprintf(nfile, sizeof(nfile), "%s.%d", hname, procpid);
+       if ((fd = creat(nfile, 0600)) < 0)
+               return 1;
+
+       if (sprinkle(fd)) {
+               close(fd);
+               unlink(nfile);
+               return 1;
+       }
+       if (write(fd, nbase, nbytes) != nbytes) {
+               close(fd);
+               unlink(nfile);
+               return 1;
+       }
+       /*
+        *      worry about who owns this file
+        */
+       if (fstat(histfd, &statb) >= 0)
+               fchown(fd, statb.st_uid, statb.st_gid);
+       close(fd);
+
+       /*
+        *      rename
+        */
+       if (rename(nfile, hname) < 0)
+               return 1;
+       return 0;
+}
+
+
+/*
+ *     find a pointer to the data `no' back from the end of the file
+ *     return the pointer and the number of bytes left
+ */
+static unsigned char *
+hist_skip_back(base, bytes, no)
+       unsigned char *base;
+       int *bytes;
+       int no;
+{
+       register int lines = 0;
+       register unsigned char *ep;
+
+       for (ep = base + *bytes; --ep > base; ) {
+               /* this doesn't really work: the 4 byte line number that is
+                * encoded after the COMMAND byte can itself contain the
+                * COMMAND byte....
+                */
+               for (; ep > base && *ep != COMMAND; ep--)
+                       ;
+               if (ep == base)
+                       break;
+               if (++lines == no) {
+                       *bytes = *bytes - ((char *)ep - (char *)base);
+                       return ep;
+               }
+       }
+       return NULL;
+}
+
+/*
+ *     load the history structure from the stored data
+ */
+static void
+histload(s, base, bytes)
+       Source *s;
+       register unsigned char *base;
+       register int bytes;
+{
+       State state;
+       int     lno = 0;
+       unsigned char   *line = NULL;
+
+       for (state = shdr; bytes-- > 0; base++) {
+               switch (state) {
+               case shdr:
+                       if (*base == COMMAND)
+                               state = sn1;
+                       break;
+               case sn1:
+                       lno = (((*base)&0xff)<<24);
+                       state = sn2;
+                       break;
+               case sn2:
+                       lno |= (((*base)&0xff)<<16);
+                       state = sn3;
+                       break;
+               case sn3:
+                       lno |= (((*base)&0xff)<<8);
+                       state = sn4;
+                       break;
+               case sn4:
+                       lno |= (*base)&0xff;
+                       line = base+1;
+                       state = sline;
+                       break;
+               case sline:
+                       if (*base == '\0') {
+                               /* worry about line numbers */
+                               if (histptr >= histlist && lno-1 != s->line) {
+                                       /* a replacement ? */
+                                       histinsert(s, lno, line);
+                               }
+                               else {
+                                       s->line = lno;
+                                       histsave(lno, (char *)line, 0);
+                               }
+                               state = shdr;
+                       }
+               }
+       }
+}
+
+/*
+ *     Insert a line into the history at a specified number
+ */
+static void
+histinsert(s, lno, line)
+       Source *s;
+       int lno;
+       unsigned char *line;
+{
+       register char **hp;
+
+       if (lno >= s->line-(histptr-histlist) && lno <= s->line) {
+               hp = &histptr[lno-s->line];
+               if (*hp)
+                       afree((void*)*hp, APERM);
+               *hp = str_save((char *)line, APERM);
+       }
+}
+
+/*
+ *     write a command to the end of the history file
+ *     This *MAY* seem easy but it's also necessary to check
+ *     that the history file has not changed in size.
+ *     If it has - then some other shell has written to it
+ *     and we should read those commands to update our history
+ */
+static void
+writehistfile(lno, cmd)
+       int lno;
+       char *cmd;
+{
+       int     sizenow;
+       unsigned char   *base;
+       unsigned char   *new;
+       int     bytes;
+       unsigned char   hdr[5];
+
+       (void) flock(histfd, LOCK_EX);
+       sizenow = lseek(histfd, 0L, SEEK_END);
+       if (sizenow != hsize) {
+               /*
+                *      Things have changed
+                */
+               if (sizenow > hsize) {
+                       /* someone has added some lines */
+                       bytes = sizenow - hsize;
+                       base = (unsigned char *)mmap(0, sizenow, PROT_READ, MAP_FLAGS, histfd, 0);
+                       if (base == MAP_FAILED)
+                               goto bad;
+                       new = base + hsize;
+                       if (*new != COMMAND) {
+                               munmap((caddr_t)base, sizenow);
+                               goto bad;
+                       }
+                       hist_source->line--;
+                       histload(hist_source, new, bytes);
+                       hist_source->line++;
+                       lno = hist_source->line;
+                       munmap((caddr_t)base, sizenow);
+                       hsize = sizenow;
+               } else {
+                       /* it has shrunk */
+                       /* but to what? */
+                       /* we'll give up for now */
+                       goto bad;
+               }
+       }
+       /*
+        *      we can write our bit now
+        */
+       hdr[0] = COMMAND;
+       hdr[1] = (lno>>24)&0xff;
+       hdr[2] = (lno>>16)&0xff;
+       hdr[3] = (lno>>8)&0xff;
+       hdr[4] = lno&0xff;
+       (void) write(histfd, hdr, 5);
+       (void) write(histfd, cmd, strlen(cmd)+1);
+       hsize = lseek(histfd, 0L, SEEK_END);
+       (void) flock(histfd, LOCK_UN);
+       return;
+bad:
+       hist_finish();
+}
+
+void
+hist_finish()
+{
+       (void) flock(histfd, LOCK_UN);
+       (void) close(histfd);
+       histfd = 0;
+}
+
+/*
+ *     add magic to the history file
+ */
+static int
+sprinkle(fd)
+       int fd;
+{
+       static unsigned char mag[] = { HMAGIC1, HMAGIC2 };
+
+       return(write(fd, mag, 2) != 2);
+}
+
+# endif
+#else /* HISTORY */
+
+/* No history to be compiled in: dummy routines to avoid lots more ifdefs */
+void
+init_histvec()
+{
+}
+void
+hist_init(s)
+       Source *s;
+{
+}
+void
+hist_finish()
+{
+}
+void
+histsave(lno, cmd, dowrite)
+       int lno;
+       const char *cmd;
+       int dowrite;
+{
+       errorf("history not enabled");
+}
+#endif /* HISTORY */
diff --git a/bin/ksh/io.c b/bin/ksh/io.c
new file mode 100644 (file)
index 0000000..b8b3c60
--- /dev/null
@@ -0,0 +1,568 @@
+/*     $NetBSD: io.c,v 1.9 2005/06/26 19:09:00 christos Exp $  */
+
+/*
+ * shell buffered IO and formatted output
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: io.c,v 1.9 2005/06/26 19:09:00 christos Exp $");
+#endif
+
+
+#include <ctype.h>
+#include "sh.h"
+#include "ksh_stat.h"
+
+static int initio_done;
+
+/*
+ * formatted output functions
+ */
+
+
+/* A shell error occurred (eg, syntax error, etc.) */
+void
+#ifdef HAVE_PROTOTYPES
+errorf(const char *fmt, ...)
+#else
+errorf(fmt, va_alist)
+       const char *fmt;
+       va_dcl
+#endif
+{
+       va_list va;
+
+       shl_stdout_ok = 0;      /* debugging: note that stdout not valid */
+       exstat = 1;
+       if (*fmt) {
+               error_prefix(TRUE);
+               SH_VA_START(va, fmt);
+               shf_vfprintf(shl_out, fmt, va);
+               va_end(va);
+               shf_putchar('\n', shl_out);
+       }
+       shf_flush(shl_out);
+       unwind(LERROR);
+}
+
+/* like errorf(), but no unwind is done */
+void
+#ifdef HAVE_PROTOTYPES
+warningf(int fileline, const char *fmt, ...)
+#else
+warningf(fileline, fmt, va_alist)
+       int fileline;
+       const char *fmt;
+       va_dcl
+#endif
+{
+       va_list va;
+
+       error_prefix(fileline);
+       SH_VA_START(va, fmt);
+       shf_vfprintf(shl_out, fmt, va);
+       va_end(va);
+       shf_putchar('\n', shl_out);
+       shf_flush(shl_out);
+}
+
+/* Used by built-in utilities to prefix shell and utility name to message
+ * (also unwinds environments for special builtins).
+ */
+void
+#ifdef HAVE_PROTOTYPES
+bi_errorf(const char *fmt, ...)
+#else
+bi_errorf(fmt, va_alist)
+       const char *fmt;
+       va_dcl
+#endif
+{
+       va_list va;
+
+       shl_stdout_ok = 0;      /* debugging: note that stdout not valid */
+       exstat = 1;
+       if (*fmt) {
+               error_prefix(TRUE);
+               /* not set when main() calls parse_args() */
+               if (builtin_argv0)
+                       shf_fprintf(shl_out, "%s: ", builtin_argv0);
+               SH_VA_START(va, fmt);
+               shf_vfprintf(shl_out, fmt, va);
+               va_end(va);
+               shf_putchar('\n', shl_out);
+       }
+       shf_flush(shl_out);
+       /* POSIX special builtins and ksh special builtins cause
+        * non-interactive shells to exit.
+        * XXX odd use of KEEPASN; also may not want LERROR here
+        */
+       if ((builtin_flag & SPEC_BI)
+           || (Flag(FPOSIX) && (builtin_flag & KEEPASN)))
+       {
+               builtin_argv0 = (char *) 0;
+               unwind(LERROR);
+       }
+}
+
+/* Called when something that shouldn't happen does */
+void
+#ifdef HAVE_PROTOTYPES
+internal_errorf(int jump, const char *fmt, ...)
+#else
+internal_errorf(jump, fmt, va_alist)
+       int jump;
+       const char *fmt;
+       va_dcl
+#endif
+{
+       va_list va;
+
+       error_prefix(TRUE);
+       shf_fprintf(shl_out, "internal error: ");
+       SH_VA_START(va, fmt);
+       shf_vfprintf(shl_out, fmt, va);
+       va_end(va);
+       shf_putchar('\n', shl_out);
+       shf_flush(shl_out);
+       if (jump)
+               unwind(LERROR);
+}
+
+/* used by error reporting functions to print "ksh: .kshrc[25]: " */
+void
+error_prefix(fileline)
+       int fileline;
+{
+       /* Avoid foo: foo[2]: ... */
+       if (!fileline || !source || !source->file
+           || strcmp(source->file, kshname) != 0)
+               shf_fprintf(shl_out, "%s: ", kshname + (*kshname == '-'));
+       if (fileline && source && source->file != NULL) {
+               shf_fprintf(shl_out, "%s[%d]: ", source->file,
+                       source->errline > 0 ? source->errline : source->line);
+               source->errline = 0;
+       }
+}
+
+/* printf to shl_out (stderr) with flush */
+void
+#ifdef HAVE_PROTOTYPES
+shellf(const char *fmt, ...)
+#else
+shellf(fmt, va_alist)
+       const char *fmt;
+       va_dcl
+#endif
+{
+       va_list va;
+
+       if (!initio_done) /* shl_out may not be set up yet... */
+               return;
+       SH_VA_START(va, fmt);
+       shf_vfprintf(shl_out, fmt, va);
+       va_end(va);
+       shf_flush(shl_out);
+}
+
+/* printf to shl_stdout (stdout) */
+void
+#ifdef HAVE_PROTOTYPES
+shprintf(const char *fmt, ...)
+#else
+shprintf(fmt, va_alist)
+       const char *fmt;
+       va_dcl
+#endif
+{
+       va_list va;
+
+       if (!shl_stdout_ok)
+               internal_errorf(1, "shl_stdout not valid");
+       SH_VA_START(va, fmt);
+       shf_vfprintf(shl_stdout, fmt, va);
+       va_end(va);
+}
+
+#ifdef KSH_DEBUG
+static struct shf *kshdebug_shf;
+
+void
+kshdebug_init_()
+{
+       if (kshdebug_shf)
+               shf_close(kshdebug_shf);
+       kshdebug_shf = shf_open("/tmp/ksh-debug.log",
+                               O_WRONLY|O_APPEND|O_CREAT, 0600,
+                               SHF_WR|SHF_MAPHI);
+       if (kshdebug_shf) {
+               shf_fprintf(kshdebug_shf, "\nNew shell[pid %d]\n", getpid());
+               shf_flush(kshdebug_shf);
+       }
+}
+
+/* print to debugging log */
+void
+# ifdef HAVE_PROTOTYPES
+kshdebug_printf_(const char *fmt, ...)
+# else
+kshdebug_printf_(fmt, va_alist)
+       const char *fmt;
+       va_dcl
+# endif
+{
+       va_list va;
+
+       if (!kshdebug_shf)
+               return;
+       SH_VA_START(va, fmt);
+       shf_fprintf(kshdebug_shf, "[%d] ", getpid());
+       shf_vfprintf(kshdebug_shf, fmt, va);
+       va_end(va);
+       shf_flush(kshdebug_shf);
+}
+
+void
+kshdebug_dump_(str, mem, nbytes)
+       const char *str;
+       const void *mem;
+       int nbytes;
+{
+       int i, j;
+       int nprow = 16;
+
+       if (!kshdebug_shf)
+               return;
+       shf_fprintf(kshdebug_shf, "[%d] %s:\n", getpid(), str);
+       for (i = 0; i < nbytes; i += nprow) {
+               char c = '\t';
+               for (j = 0; j < nprow && i + j < nbytes; j++) {
+                       shf_fprintf(kshdebug_shf, "%c%02x",
+                               c, ((const unsigned char *) mem)[i + j]);
+                       c = ' ';
+               }
+               shf_fprintf(kshdebug_shf, "\n");
+       }
+       shf_flush(kshdebug_shf);
+}
+#endif /* KSH_DEBUG */
+
+/* test if we can seek backwards fd (returns 0 or SHF_UNBUF) */
+int
+can_seek(fd)
+       int fd;
+{
+       struct stat statb;
+
+       return fstat(fd, &statb) == 0 && !S_ISREG(statb.st_mode) ?
+               SHF_UNBUF : 0;
+}
+
+struct shf     shf_iob[3];
+
+void
+initio()
+{
+       shf_fdopen(1, SHF_WR, shl_stdout);      /* force buffer allocation */
+       shf_fdopen(2, SHF_WR, shl_out);
+       shf_fdopen(2, SHF_WR, shl_spare);       /* force buffer allocation */
+       initio_done = 1;
+       kshdebug_init();
+}
+
+/* A dup2() with error checking */
+int
+ksh_dup2(ofd, nfd, errok)
+       int ofd;
+       int nfd;
+       int errok;
+{
+       int ret = dup2(ofd, nfd);
+
+       if (ret < 0 && errno != EBADF && !errok)
+               errorf("too many files open in shell");
+
+#ifdef DUP2_BROKEN
+       /* Ultrix systems like to preserve the close-on-exec flag */
+       if (ret >= 0)
+               (void) fcntl(nfd, F_SETFD, 0);
+#endif /* DUP2_BROKEN */
+
+       return ret;
+}
+
+/*
+ * move fd from user space (0<=fd<10) to shell space (fd>=10),
+ * set close-on-exec flag.
+ */
+int
+savefd(fd, noclose)
+       int fd;
+       int noclose;
+{
+       int nfd;
+
+       if (fd < FDBASE) {
+               nfd = ksh_dupbase(fd, FDBASE);
+               if (nfd < 0) {
+                       if (errno == EBADF)
+                               return -1;
+                       else
+                               errorf("too many files open in shell");
+               }
+               if (!noclose)
+                       close(fd);
+       } else
+               nfd = fd;
+       fd_clexec(nfd);
+       return nfd;
+}
+
+void
+restfd(fd, ofd)
+       int fd, ofd;
+{
+       if (fd == 2)
+               shf_flush(&shf_iob[fd]);
+       if (ofd < 0)            /* original fd closed */
+               close(fd);
+       else if (fd != ofd) {
+               ksh_dup2(ofd, fd, TRUE); /* XXX: what to do if this fails? */
+               close(ofd);
+       }
+}
+
+void
+openpipe(pv)
+       register int *pv;
+{
+       if (pipe(pv) < 0)
+               errorf("can't create pipe - try again");
+       pv[0] = savefd(pv[0], 0);
+       pv[1] = savefd(pv[1], 0);
+}
+
+void
+closepipe(pv)
+       register int *pv;
+{
+       close(pv[0]);
+       close(pv[1]);
+}
+
+/* Called by iosetup() (deals with 2>&4, etc.), c_read, c_print to turn
+ * a string (the X in 2>&X, read -uX, print -uX) into a file descriptor.
+ */
+int
+check_fd(name, mode, emsgp)
+       char *name;
+       int mode;
+       const char **emsgp;
+{
+       int fd, fl;
+
+       if (isdigit((unsigned char)name[0]) && !name[1]) {
+               fd = name[0] - '0';
+               if ((fl = fcntl(fd = name[0] - '0', F_GETFL, 0)) < 0) {
+                       if (emsgp)
+                               *emsgp = "bad file descriptor";
+                       return -1;
+               }
+               fl &= O_ACCMODE;
+#ifdef OS2
+               if (mode == W_OK ) {
+                      if (setmode(fd, O_TEXT) == -1) {
+                               if (emsgp)
+                                       *emsgp = "couldn't set write mode";
+                               return -1;
+                       }
+                } else if (mode == R_OK)
+                       if (setmode(fd, O_BINARY) == -1) {
+                               if (emsgp)
+                                       *emsgp = "couldn't set read mode";
+                               return -1;
+                       }
+#else /* OS2 */
+               /* X_OK is a kludge to disable this check for dups (x<&1):
+                * historical shells never did this check (XXX don't know what
+                * posix has to say).
+                */
+               if (!(mode & X_OK) && fl != O_RDWR
+                   && (((mode & R_OK) && fl != O_RDONLY)
+                       || ((mode & W_OK) && fl != O_WRONLY)))
+               {
+                       if (emsgp)
+                               *emsgp = (fl == O_WRONLY) ?
+                                               "fd not open for reading"
+                                             : "fd not open for writing";
+                       return -1;
+               }
+#endif /* OS2 */
+               return fd;
+       }
+#ifdef KSH
+       else if (name[0] == 'p' && !name[1])
+               return coproc_getfd(mode, emsgp);
+#endif /* KSH */
+       if (emsgp)
+               *emsgp = "illegal file descriptor name";
+       return -1;
+}
+
+#ifdef KSH
+/* Called once from main */
+void
+coproc_init()
+{
+       coproc.read = coproc.readw = coproc.write = -1;
+       coproc.njobs = 0;
+       coproc.id = 0;
+}
+
+/* Called by c_read() when eof is read - close fd if it is the co-process fd */
+void
+coproc_read_close(fd)
+       int fd;
+{
+       if (coproc.read >= 0 && fd == coproc.read) {
+               coproc_readw_close(fd);
+               close(coproc.read);
+               coproc.read = -1;
+       }
+}
+
+/* Called by c_read() and by iosetup() to close the other side of the
+ * read pipe, so reads will actually terminate.
+ */
+void
+coproc_readw_close(fd)
+       int fd;
+{
+       if (coproc.readw >= 0 && coproc.read >= 0 && fd == coproc.read) {
+               close(coproc.readw);
+               coproc.readw = -1;
+       }
+}
+
+/* Called by c_print when a write to a fd fails with EPIPE and by iosetup
+ * when co-process input is dup'd
+ */
+void
+coproc_write_close(fd)
+       int fd;
+{
+       if (coproc.write >= 0 && fd == coproc.write) {
+               close(coproc.write);
+               coproc.write = -1;
+       }
+}
+
+/* Called to check for existence of/value of the co-process file descriptor.
+ * (Used by check_fd() and by c_read/c_print to deal with -p option).
+ */
+int
+coproc_getfd(mode, emsgp)
+       int mode;
+       const char **emsgp;
+{
+       int fd = (mode & R_OK) ? coproc.read : coproc.write;
+
+       if (fd >= 0)
+               return fd;
+       if (emsgp)
+               *emsgp = "no coprocess";
+       return -1;
+}
+
+/* called to close file descriptors related to the coprocess (if any)
+ * Should be called with SIGCHLD blocked.
+ */
+void
+coproc_cleanup(reuse)
+       int reuse;
+{
+       /* This to allow co-processes to share output pipe */
+       if (!reuse || coproc.readw < 0 || coproc.read < 0) {
+               if (coproc.read >= 0) {
+                       close(coproc.read);
+                       coproc.read = -1;
+               }
+               if (coproc.readw >= 0) {
+                       close(coproc.readw);
+                       coproc.readw = -1;
+               }
+       }
+       if (coproc.write >= 0) {
+               close(coproc.write);
+               coproc.write = -1;
+       }
+}
+#endif /* KSH */
+
+
+/*
+ * temporary files
+ */
+
+struct temp *
+maketemp(ap, type, tlist)
+       Area *ap;
+       Temp_type type;
+       struct temp **tlist;
+{
+#ifndef __NetBSD__
+       static unsigned int inc;
+#endif
+       struct temp *tp;
+       int len;
+       int fd;
+       char *pathx;
+       const char *dir;
+
+       dir = tmpdir ? tmpdir : "/tmp";
+       /* The 20 + 20 is a paranoid worst case for pid/inc */
+       len = strlen(dir) + 3 + 20 + 20 + 1;
+       tp = (struct temp *) alloc(sizeof(struct temp) + len, ap);
+       tp->name = pathx = (char *) &tp[1];
+       tp->shf = (struct shf *) 0;
+       tp->type = type;
+#ifdef __NetBSD__
+       shf_snprintf(pathx, len, "%s/shXXXXXXXX", dir);
+       fd = mkstemp(pathx);
+       if (fd >= 0)
+               tp->shf = shf_fdopen(fd, SHF_WR, (struct shf *) 0);
+#else
+       while (1) {
+               /* Note that temp files need to fit 8.3 DOS limits */
+               shf_snprintf(pathx, len, "%s/sh%05u.%03x",
+                            dir, (unsigned) procpid, inc++);
+               /* Mode 0600 to be paranoid, O_TRUNC in case O_EXCL isn't
+                * really there.
+                */
+               fd = open(pathx, O_RDWR|O_CREAT|O_EXCL|O_TRUNC, 0600);
+               if (fd >= 0) {
+                       tp->shf = shf_fdopen(fd, SHF_WR, (struct shf *) 0);
+                       break;
+               }
+               if (errno != EINTR
+#ifdef EEXIST
+                   && errno != EEXIST
+#endif /* EEXIST */
+#ifdef EISDIR
+                   && errno != EISDIR
+#endif /* EISDIR */
+                       )
+                       /* Error must be printed by caller: don't know here if
+                        * errorf() or bi_errorf() should be used.
+                        */
+                       break;
+       }
+#endif /* __NetBSD__ */
+       tp->pid = procpid;
+
+       tp->next = *tlist;
+       *tlist = tp;
+
+       return tp;
+}
diff --git a/bin/ksh/jobs.c b/bin/ksh/jobs.c
new file mode 100644 (file)
index 0000000..2fb8bef
--- /dev/null
@@ -0,0 +1,1860 @@
+/*     $NetBSD: jobs.c,v 1.10 2011/10/16 17:12:11 joerg Exp $  */
+
+/*
+ * Process and job control
+ */
+
+/*
+ * Reworked/Rewritten version of Eric Gisin's/Ron Natalie's code by
+ * Larry Bouzane (larry@cs.mun.ca) and hacked again by
+ * Michael Rendell (michael@cs.mun.ca)
+ *
+ * The interface to the rest of the shell should probably be changed
+ * to allow use of vfork() when available but that would be way too much
+ * work :)
+ *
+ * Notes regarding the copious ifdefs:
+ *     - JOB_SIGS is independent of JOBS - it is defined if there are modern
+ *       signal and wait routines available.  This is preferred, even when
+ *       JOBS is not defined, since the shell will not otherwise notice when
+ *       background jobs die until the shell waits for a foreground process
+ *       to die.
+ *     - TTY_PGRP defined iff JOBS is defined - defined if there are tty
+ *       process groups
+ *     - NEED_PGRP_SYNC defined iff JOBS is defined - see comment below
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: jobs.c,v 1.10 2011/10/16 17:12:11 joerg Exp $");
+#endif
+
+
+#include "sh.h"
+#include "ksh_stat.h"
+#include "ksh_wait.h"
+#include "ksh_times.h"
+#include "tty.h"
+
+/* Start of system configuration stuff */
+
+/* We keep CHILD_MAX zombie processes around (exact value isn't critical) */
+#ifndef CHILD_MAX
+# if defined(HAVE_SYSCONF) && defined(_SC_CHILD_MAX)
+#  define CHILD_MAX sysconf(_SC_CHILD_MAX)
+# else /* _SC_CHILD_MAX */
+#  ifdef _POSIX_CHILD_MAX
+#   define CHILD_MAX   ((_POSIX_CHILD_MAX) * 2)
+#  else /* _POSIX_CHILD_MAX */
+#   define CHILD_MAX   20
+#  endif /* _POSIX_CHILD_MAX */
+# endif /* _SC_CHILD_MAX */
+#endif /* !CHILD_MAX */
+
+#ifdef JOBS
+# if defined(HAVE_TCSETPGRP) || defined(TIOCSPGRP)
+#  define TTY_PGRP
+# endif
+# ifdef BSD_PGRP
+#  define setpgid      setpgrp
+#  define getpgID()    getpgrp(0)
+# else
+#  define getpgID()    getpgrp()
+# endif
+# if defined(TTY_PGRP) && !defined(HAVE_TCSETPGRP)
+int tcsetpgrp ARGS((int fd, pid_t grp));
+int tcgetpgrp ARGS((int fd));
+
+int
+tcsetpgrp(fd, grp)
+       int fd;
+       pid_t grp;
+{
+       return ioctl(fd, TIOCSPGRP, &grp);
+}
+
+int
+tcgetpgrp(fd)
+       int     fd;
+{
+       int r, grp;
+
+       if ((r = ioctl(fd, TIOCGPGRP, &grp)) < 0)
+               return r;
+       return grp;
+}
+# endif /* !HAVE_TCSETPGRP && TIOCSPGRP */
+#else /* JOBS */
+/* These so we can use ifdef xxx instead of if defined(JOBS) && defined(xxx) */
+# undef TTY_PGRP
+# undef NEED_PGRP_SYNC
+#endif /* JOBS */
+
+/* End of system configuration stuff */
+
+
+/* Order important! */
+#define PRUNNING       0
+#define PEXITED                1
+#define PSIGNALLED     2
+#define PSTOPPED       3
+
+typedef struct proc    Proc;
+struct proc {
+       Proc    *next;          /* next process in pipeline (if any) */
+       int     state;
+       WAIT_T  status;         /* wait status */
+       pid_t   pid;            /* process id */
+       char    command[48];    /* process command string */
+};
+
+/* Notify/print flag - j_print() argument */
+#define JP_NONE                0       /* don't print anything */
+#define JP_SHORT       1       /* print signals processes were killed by */
+#define JP_MEDIUM      2       /* print [job-num] -/+ command */
+#define JP_LONG                3       /* print [job-num] -/+ pid command */
+#define JP_PGRP                4       /* print pgrp */
+
+/* put_job() flags */
+#define PJ_ON_FRONT    0       /* at very front */
+#define PJ_PAST_STOPPED        1       /* just past any stopped jobs */
+
+/* Job.flags values */
+#define JF_STARTED     0x001   /* set when all processes in job are started */
+#define JF_WAITING     0x002   /* set if j_waitj() is waiting on job */
+#define JF_W_ASYNCNOTIFY 0x004 /* set if waiting and async notification ok */
+#define JF_XXCOM       0x008   /* set for `command` jobs */
+#define JF_FG          0x010   /* running in foreground (also has tty pgrp) */
+#define JF_SAVEDTTY    0x020   /* j->ttystate is valid */
+#define JF_CHANGED     0x040   /* process has changed state */
+#define JF_KNOWN       0x080   /* $! referenced */
+#define JF_ZOMBIE      0x100   /* known, unwaited process */
+#define JF_REMOVE      0x200   /* flagged for removal (j_jobs()/j_noityf()) */
+#define JF_USETTYMODE  0x400   /* tty mode saved if process exits normally */
+#define JF_SAVEDTTYPGRP        0x800   /* j->saved_ttypgrp is valid */
+
+typedef struct job Job;
+struct job {
+       Job     *next;          /* next job in list */
+       int     job;            /* job number: %n */
+       int     flags;          /* see JF_* */
+       int     state;          /* job state */
+       int     status;         /* exit status of last process */
+       pid_t   pgrp;           /* process group of job */
+       pid_t   ppid;           /* pid of process that forked job */
+       INT32   age;            /* number of jobs started */
+       clock_t systime;        /* system time used by job */
+       clock_t usrtime;        /* user time used by job */
+       Proc    *proc_list;     /* process list */
+       Proc    *last_proc;     /* last process in list */
+#ifdef KSH
+       Coproc_id coproc_id;    /* 0 or id of coprocess output pipe */
+#endif /* KSH */
+#ifdef TTY_PGRP
+       TTY_state ttystate;     /* saved tty state for stopped jobs */
+       pid_t   saved_ttypgrp;  /* saved tty process group for stopped jobs */
+#endif /* TTY_PGRP */
+};
+
+/* Flags for j_waitj() */
+#define JW_NONE                0x00
+#define JW_INTERRUPT   0x01    /* ^C will stop the wait */
+#define JW_ASYNCNOTIFY 0x02    /* asynchronous notification during wait ok */
+#define JW_STOPPEDWAIT 0x04    /* wait even if job stopped */
+
+/* Error codes for j_lookup() */
+#define JL_OK          0
+#define JL_NOSUCH      1       /* no such job */
+#define JL_AMBIG       2       /* %foo or %?foo is ambiguous */
+#define JL_INVALID     3       /* non-pid, non-% job id */
+
+static const char      *const lookup_msgs[] = {
+                               null,
+                               "no such job",
+                               "ambiguous",
+                               "argument must be %job or process id",
+                               (char *) 0
+                           };
+clock_t        j_systime, j_usrtime;   /* user and system time of last j_waitjed job */
+
+static Job             *job_list;      /* job list */
+static Job             *last_job;
+static Job             *async_job;
+static pid_t           async_pid;
+
+static int             nzombie;        /* # of zombies owned by this process */
+static INT32           njobs;          /* # of jobs started */
+static int             child_max;      /* CHILD_MAX */
+
+
+#ifdef JOB_SIGS
+/* held_sigchld is set if sigchld occurs before a job is completely started */
+static int             held_sigchld;
+#endif /* JOB_SIGS */
+
+#ifdef JOBS
+static struct shf      *shl_j;
+#endif /* JOBS */
+
+#ifdef NEED_PGRP_SYNC
+/* On some systems, the kernel doesn't count zombie processes when checking
+ * if a process group is valid, which can cause problems in creating the
+ * pipeline "cmd1 | cmd2": if cmd1 can die (and go into the zombie state)
+ * before cmd2 is started, the kernel doesn't allow the setpgid() for cmd2
+ * to succeed.  Solution is to create a pipe between the parent and the first
+ * process; the first process doesn't do anything until the pipe is closed
+ * and the parent doesn't close the pipe until all the processes are started.
+ */
+static int             j_sync_pipe[2];
+static int             j_sync_open;
+#endif /* NEED_PGRP_SYNC */
+
+#ifdef TTY_PGRP
+static int             ttypgrp_ok;     /* set if can use tty pgrps */
+static pid_t           restore_ttypgrp = -1;
+static pid_t           our_pgrp;
+static int const       tt_sigs[] = { SIGTSTP, SIGTTIN, SIGTTOU };
+#endif /* TTY_PGRP */
+
+static void            j_set_async ARGS((Job *j));
+static void            j_startjob ARGS((Job *j));
+static int             j_waitj ARGS((Job *j, int flags, const char *where));
+static RETSIGTYPE      j_sigchld ARGS((int sig));
+static void            j_print ARGS((Job *j, int how, struct shf *shf));
+static Job             *j_lookup ARGS((const char *cp, int *ecodep));
+static Job             *new_job ARGS((void));
+static Proc            *new_proc ARGS((void));
+static void            check_job ARGS((Job *j));
+static void            put_job ARGS((Job *j, int where));
+static void            remove_job ARGS((Job *j, const char *where));
+static int             kill_job ARGS((Job *j, int sig));
+
+/* initialize job control */
+void
+j_init(mflagset)
+       int mflagset;
+{
+       child_max = CHILD_MAX; /* so syscon() isn't always being called */
+
+#ifdef JOB_SIGS
+       sigemptyset(&sm_default);
+       sigprocmask(SIG_SETMASK, &sm_default, (sigset_t *) 0);
+
+       sigemptyset(&sm_sigchld);
+       sigaddset(&sm_sigchld, SIGCHLD);
+
+       setsig(&sigtraps[SIGCHLD], j_sigchld,
+               SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP);
+#else /* JOB_SIGS */
+       /* Make sure SIGCHLD isn't ignored - can do odd things under SYSV */
+       setsig(&sigtraps[SIGCHLD], SIG_DFL, SS_RESTORE_ORIG|SS_FORCE);
+#endif /* JOB_SIGS */
+
+#ifdef JOBS
+       if (!mflagset && Flag(FTALKING))
+               Flag(FMONITOR) = 1;
+
+       /* shl_j is used to do asynchronous notification (used in
+        * an interrupt handler, so need a distinct shf)
+        */
+       shl_j = shf_fdopen(2, SHF_WR, (struct shf *) 0);
+
+# ifdef TTY_PGRP
+       if (Flag(FMONITOR) || Flag(FTALKING)) {
+               int i;
+
+               /* the TF_SHELL_USES test is a kludge that lets us know if
+                * if the signals have been changed by the shell.
+                */
+               for (i = NELEM(tt_sigs); --i >= 0; ) {
+                       sigtraps[tt_sigs[i]].flags |= TF_SHELL_USES;
+                       /* j_change() sets this to SS_RESTORE_DFL if FMONITOR */
+                       setsig(&sigtraps[tt_sigs[i]], SIG_IGN,
+                               SS_RESTORE_IGN|SS_FORCE);
+               }
+       }
+# endif /* TTY_PGRP */
+
+       /* j_change() calls tty_init() */
+       if (Flag(FMONITOR))
+               j_change();
+       else
+#endif /* JOBS */
+         if (Flag(FTALKING))
+               tty_init(TRUE);
+}
+
+/* job cleanup before shell exit */
+void
+j_exit()
+{
+       /* kill stopped, and possibly running, jobs */
+       Job     *j;
+       int     killed = 0;
+
+       for (j = job_list; j != (Job *) 0; j = j->next) {
+               if (j->ppid == procpid
+                   && (j->state == PSTOPPED
+                       || (j->state == PRUNNING
+                           && ((j->flags & JF_FG)
+                               || (Flag(FLOGIN) && !Flag(FNOHUP)
+                                   && procpid == kshpid)))))
+               {
+                       killed = 1;
+                       if (j->pgrp == 0)
+                               kill_job(j, SIGHUP);
+                       else
+                               killpg(j->pgrp, SIGHUP);
+#ifdef JOBS
+                       if (j->state == PSTOPPED) {
+                               if (j->pgrp == 0)
+                                       kill_job(j, SIGCONT);
+                               else
+                                       killpg(j->pgrp, SIGCONT);
+                       }
+#endif /* JOBS */
+               }
+       }
+       if (killed)
+               sleep(1);
+       j_notify();
+
+#ifdef JOBS
+# ifdef TTY_PGRP
+       if (kshpid == procpid && restore_ttypgrp >= 0) {
+               /* Need to restore the tty pgrp to what it was when the
+                * shell started up, so that the process that started us
+                * will be able to access the tty when we are done.
+                * Also need to restore our process group in case we are
+                * about to do an exec so that both our parent and the
+                * process we are to become will be able to access the tty.
+                */
+               tcsetpgrp(tty_fd, restore_ttypgrp);
+               setpgid(0, restore_ttypgrp);
+       }
+# endif /* TTY_PGRP */
+       if (Flag(FMONITOR)) {
+               Flag(FMONITOR) = 0;
+               j_change();
+       }
+#endif /* JOBS */
+}
+
+#ifdef JOBS
+/* turn job control on or off according to Flag(FMONITOR) */
+void
+j_change()
+{
+       int i;
+
+       if (Flag(FMONITOR)) {
+               /* Don't call get_tty() 'til we own the tty process group */
+               tty_init(FALSE);
+
+# ifdef TTY_PGRP
+               /* no controlling tty, no SIGT* */
+               ttypgrp_ok = tty_fd >= 0 && tty_devtty;
+
+               if (ttypgrp_ok && (our_pgrp = getpgID()) < 0) {
+                       warningf(FALSE, "j_init: getpgrp() failed: %s",
+                               strerror(errno));
+                       ttypgrp_ok = 0;
+               }
+               if (ttypgrp_ok) {
+                       setsig(&sigtraps[SIGTTIN], SIG_DFL,
+                               SS_RESTORE_ORIG|SS_FORCE);
+                       /* wait to be given tty (POSIX.1, B.2, job control) */
+                       while (1) {
+                               pid_t ttypgrp;
+
+                               if ((ttypgrp = tcgetpgrp(tty_fd)) < 0) {
+                                       warningf(FALSE,
+                                       "j_init: tcgetpgrp() failed: %s",
+                                               strerror(errno));
+                                       ttypgrp_ok = 0;
+                                       break;
+                               }
+                               if (ttypgrp == our_pgrp)
+                                       break;
+                               kill(0, SIGTTIN);
+                       }
+               }
+               for (i = NELEM(tt_sigs); --i >= 0; )
+                       setsig(&sigtraps[tt_sigs[i]], SIG_IGN,
+                               SS_RESTORE_DFL|SS_FORCE);
+               if (ttypgrp_ok && our_pgrp != kshpid) {
+                       if (setpgid(0, kshpid) < 0) {
+                               warningf(FALSE,
+                                       "j_init: setpgid() failed: %s",
+                                       strerror(errno));
+                               ttypgrp_ok = 0;
+                       } else {
+                               if (tcsetpgrp(tty_fd, kshpid) < 0) {
+                                       warningf(FALSE,
+                                       "j_init: tcsetpgrp() failed: %s",
+                                               strerror(errno));
+                                       ttypgrp_ok = 0;
+                               } else
+                                       restore_ttypgrp = our_pgrp;
+                               our_pgrp = kshpid;
+                       }
+               }
+#  if defined(NTTYDISC) && defined(TIOCSETD) && !defined(HAVE_TERMIOS_H) && !defined(HAVE_TERMIO_H)
+               if (ttypgrp_ok) {
+                       int ldisc = NTTYDISC;
+
+                       if (ioctl(tty_fd, TIOCSETD, &ldisc) < 0)
+                               warningf(FALSE,
+                               "j_init: can't set new line discipline: %s",
+                                       strerror(errno));
+               }
+#  endif /* NTTYDISC && TIOCSETD */
+               if (!ttypgrp_ok)
+                       warningf(FALSE, "warning: won't have full job control");
+# endif /* TTY_PGRP */
+               if (tty_fd >= 0)
+                       get_tty(tty_fd, &tty_state);
+       } else {
+# ifdef TTY_PGRP
+               ttypgrp_ok = 0;
+               if (Flag(FTALKING))
+                       for (i = NELEM(tt_sigs); --i >= 0; )
+                               setsig(&sigtraps[tt_sigs[i]], SIG_IGN,
+                                       SS_RESTORE_IGN|SS_FORCE);
+               else
+                       for (i = NELEM(tt_sigs); --i >= 0; ) {
+                               if (sigtraps[tt_sigs[i]].flags & (TF_ORIG_IGN
+                                                                 |TF_ORIG_DFL))
+                                       setsig(&sigtraps[tt_sigs[i]],
+                                               (sigtraps[tt_sigs[i]].flags & TF_ORIG_IGN) ? SIG_IGN : SIG_DFL,
+                                               SS_RESTORE_ORIG|SS_FORCE);
+                       }
+# endif /* TTY_PGRP */
+               if (!Flag(FTALKING))
+                       tty_close();
+       }
+}
+#endif /* JOBS */
+
+/* execute tree in child subprocess */
+int
+exchild(t, flags, close_fd)
+       struct op       *t;
+       int             flags;
+       int             close_fd;       /* used if XPCLOSE or XCCLOSE */
+{
+       static Proc     *last_proc;     /* for pipelines */
+
+       int             i;
+#ifdef JOB_SIGS
+       sigset_t        omask;
+#endif /* JOB_SIGS */
+       Proc            *p;
+       Job             *j;
+       int             rv = 0;
+       int             forksleep;
+       int             ischild;
+
+       if (flags & XEXEC)
+               /* Clear XFORK|XPCLOSE|XCCLOSE|XCOPROC|XPIPEO|XPIPEI|XXCOM|XBGND
+                * (also done in another execute() below)
+                */
+               return execute(t, flags & (XEXEC | XERROK));
+
+#ifdef JOB_SIGS
+       /* no SIGCHLD's while messing with job and process lists */
+       sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif /* JOB_SIGS */
+
+       p = new_proc();
+       p->next = (Proc *) 0;
+       p->state = PRUNNING;
+       WSTATUS(p->status) = 0;
+       p->pid = 0;
+
+       /* link process into jobs list */
+       if (flags&XPIPEI) {     /* continuing with a pipe */
+               if (!last_job)
+                       internal_errorf(1, "exchild: XPIPEI and no last_job - pid %d", (int) procpid);
+               j = last_job;
+               last_proc->next = p;
+               last_proc = p;
+       } else {
+#ifdef NEED_PGRP_SYNC
+               if (j_sync_open) {      /* should never happen */
+                       j_sync_open = 0;
+                       closepipe(j_sync_pipe);
+               }
+               /* don't do the sync pipe business if there is no pipeline */
+               if (flags & XPIPEO) {
+                       openpipe(j_sync_pipe);
+                       j_sync_open = 1;
+               }
+#endif /* NEED_PGRP_SYNC */
+               j = new_job(); /* fills in j->job */
+               /* we don't consider XXCOM's foreground since they don't get
+                * tty process group and we don't save or restore tty modes.
+                */
+               j->flags = (flags & XXCOM) ? JF_XXCOM
+                       : ((flags & XBGND) ? 0 : (JF_FG|JF_USETTYMODE));
+               j->usrtime = j->systime = 0;
+               j->state = PRUNNING;
+               j->pgrp = 0;
+               j->ppid = procpid;
+               j->age = ++njobs;
+               j->proc_list = p;
+#ifdef KSH
+               j->coproc_id = 0;
+#endif /* KSH */
+               last_job = j;
+               last_proc = p;
+               put_job(j, PJ_PAST_STOPPED);
+       }
+
+       snptreef(p->command, sizeof(p->command), "%T", t);
+
+       /* create child process */
+       forksleep = 1;
+       while ((i = fork()) < 0 && errno == EAGAIN && forksleep < 32) {
+               if (intrsig)     /* allow user to ^C out... */
+                       break;
+               sleep(forksleep);
+               forksleep <<= 1;
+       }
+       if (i < 0) {
+               kill_job(j, SIGKILL);
+               remove_job(j, "fork failed");
+#ifdef NEED_PGRP_SYNC
+               if (j_sync_open) {
+                       closepipe(j_sync_pipe);
+                       j_sync_open = 0;
+               }
+#endif /* NEED_PGRP_SYNC */
+#ifdef JOB_SIGS
+               sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+#endif /* JOB_SIGS */
+               errorf("cannot fork - try again");
+       }
+       ischild = i == 0;
+       if (ischild)
+               p->pid = procpid = getpid();
+       else
+               p->pid = i;
+
+#ifdef JOBS
+       /* job control set up */
+       if (Flag(FMONITOR) && !(flags&XXCOM)) {
+               int     dotty = 0;
+# ifdef NEED_PGRP_SYNC
+               int     first_child_sync = 0;
+# endif /* NEED_PGRP_SYNC */
+
+# ifdef NEED_PGRP_SYNC
+               if (j_sync_open) {
+                       /*
+                        * The Parent closes 0, keeps 1 open 'til the whole
+                        * pipeline is started.  The First child closes 1,
+                        * keeps 0 open (reads from it).  The remaining
+                        * children just have to close 1 (parent has already
+                        * closeed 0).
+                        */
+                       if (j->pgrp == 0) { /* First process */
+                               close(j_sync_pipe[ischild]);
+                               j_sync_pipe[ischild] = -1;
+                               first_child_sync = ischild;
+                       } else if (ischild) {
+                               j_sync_open = 0;
+                               closepipe(j_sync_pipe);
+                       }
+               }
+# endif /* NEED_PGRP_SYNC */
+               if (j->pgrp == 0) {     /* First process */
+                       j->pgrp = p->pid;
+                       dotty = 1;
+               }
+
+               /* set pgrp in both parent and child to deal with race
+                * condition
+                */
+               setpgid(p->pid, j->pgrp);
+# ifdef TTY_PGRP
+               /* YYY: should this be
+                  if (ttypgrp_ok && ischild && !(flags&XBGND))
+                       tcsetpgrp(tty_fd, j->pgrp);
+                  instead? (see also YYY below)
+                */
+               if (ttypgrp_ok && dotty && !(flags & XBGND))
+                       tcsetpgrp(tty_fd, j->pgrp);
+# endif /* TTY_PGRP */
+# ifdef NEED_PGRP_SYNC
+               if (first_child_sync) {
+                       char c;
+                       while (read(j_sync_pipe[0], &c, 1) == -1
+                              && errno == EINTR)
+                               ;
+                       close(j_sync_pipe[0]);
+                       j_sync_open = 0;
+               }
+# endif /* NEED_PGRP_SYNC */
+       }
+#endif /* JOBS */
+
+       /* used to close pipe input fd */
+       if (close_fd >= 0 && (((flags & XPCLOSE) && !ischild)
+                             || ((flags & XCCLOSE) && ischild)))
+               close(close_fd);
+       if (ischild) {          /* child */
+#ifdef KSH
+               /* Do this before restoring signal */
+               if (flags & XCOPROC)
+                       coproc_cleanup(FALSE);
+#endif /* KSH */
+#ifdef JOB_SIGS
+               sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+#endif /* JOB_SIGS */
+               cleanup_parents_env();
+#ifdef TTY_PGRP
+               /* If FMONITOR or FTALKING is set, these signals are ignored,
+                * if neither FMONITOR nor FTALKING are set, the signals have
+                * their inherited values.
+                */
+               if (Flag(FMONITOR) && !(flags & XXCOM)) {
+                       for (i = NELEM(tt_sigs); --i >= 0; )
+                               setsig(&sigtraps[tt_sigs[i]], SIG_DFL,
+                                       SS_RESTORE_DFL|SS_FORCE);
+               }
+#endif /* TTY_PGRP */
+#ifdef HAVE_NICE
+               if (Flag(FBGNICE) && (flags & XBGND))
+                       nice(4);
+#endif /* HAVE_NICE */
+               if ((flags & XBGND) && !Flag(FMONITOR)) {
+                       setsig(&sigtraps[SIGINT], SIG_IGN,
+                               SS_RESTORE_IGN|SS_FORCE);
+                       setsig(&sigtraps[SIGQUIT], SIG_IGN,
+                               SS_RESTORE_IGN|SS_FORCE);
+                       if (!(flags & (XPIPEI | XCOPROC))) {
+                               int fd = open("/dev/null", 0);
+                               if (fd != 0) {
+                                       (void) ksh_dup2(fd, 0, TRUE);
+                                       close(fd);
+                               }
+                       }
+               }
+               remove_job(j, "child"); /* in case of `jobs` command */
+               nzombie = 0;
+#ifdef JOBS
+               ttypgrp_ok = 0;
+               Flag(FMONITOR) = 0;
+#endif /* JOBS */
+               Flag(FTALKING) = 0;
+#ifdef OS2
+               if (tty_fd >= 0)
+                       flags |= XINTACT;
+#endif /* OS2 */
+               tty_close();
+               cleartraps();
+               execute(t, (flags & XERROK) | XEXEC); /* no return */
+               internal_errorf(0, "exchild: execute() returned");
+               unwind(LLEAVE);
+               /* NOTREACHED */
+       }
+
+       /* shell (parent) stuff */
+       /* Ensure next child gets a (slightly) different $RANDOM sequence */
+       change_random();
+       if (!(flags & XPIPEO)) {        /* last process in a job */
+#ifdef TTY_PGRP
+               /* YYY: Is this needed? (see also YYY above)
+                  if (Flag(FMONITOR) && !(flags&(XXCOM|XBGND)))
+                       tcsetpgrp(tty_fd, j->pgrp);
+               */
+#endif /* TTY_PGRP */
+               j_startjob(j);
+#ifdef KSH
+               if (flags & XCOPROC) {
+                       j->coproc_id = coproc.id;
+                       coproc.njobs++; /* n jobs using co-process output */
+                       coproc.job = (void *) j; /* j using co-process input */
+               }
+#endif /* KSH */
+               if (flags & XBGND) {
+                       j_set_async(j);
+                       if (Flag(FTALKING)) {
+                               shf_fprintf(shl_out, "[%d]", j->job);
+                               for (p = j->proc_list; p; p = p->next)
+                                       shf_fprintf(shl_out, " %d", p->pid);
+                               shf_putchar('\n', shl_out);
+                               shf_flush(shl_out);
+                       }
+               } else
+                       rv = j_waitj(j, JW_NONE, "jw:last proc");
+       }
+
+#ifdef JOB_SIGS
+       sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+#endif /* JOB_SIGS */
+
+       return rv;
+}
+
+/* start the last job: only used for `command` jobs */
+void
+startlast()
+{
+#ifdef JOB_SIGS
+       sigset_t omask;
+
+       sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif /* JOB_SIGS */
+
+       if (last_job) { /* no need to report error - waitlast() will do it */
+               /* ensure it isn't removed by check_job() */
+               last_job->flags |= JF_WAITING;
+               j_startjob(last_job);
+       }
+#ifdef JOB_SIGS
+       sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+#endif /* JOB_SIGS */
+}
+
+/* wait for last job: only used for `command` jobs */
+int
+waitlast()
+{
+       int     rv;
+       Job     *j;
+#ifdef JOB_SIGS
+       sigset_t omask;
+
+       sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif /* JOB_SIGS */
+
+       j = last_job;
+       if (!j || !(j->flags & JF_STARTED)) {
+               if (!j)
+                       warningf(TRUE, "waitlast: no last job");
+               else
+                       internal_errorf(0, "waitlast: not started");
+#ifdef JOB_SIGS
+               sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+#endif /* JOB_SIGS */
+               return 125; /* not so arbitrary, non-zero value */
+       }
+
+       rv = j_waitj(j, JW_NONE, "jw:waitlast");
+
+#ifdef JOB_SIGS
+       sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+#endif /* JOB_SIGS */
+
+       return rv;
+}
+
+/* wait for child, interruptable. */
+int
+waitfor(cp, sigp)
+       const char *cp;
+       int     *sigp;
+{
+       int     rv;
+       Job     *j;
+       int     ecode;
+       int     flags = JW_INTERRUPT|JW_ASYNCNOTIFY;
+#ifdef JOB_SIGS
+       sigset_t omask;
+
+       sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif /* JOB_SIGS */
+
+       *sigp = 0;
+
+       if (cp == (char *) 0) {
+               /* wait for an unspecified job - always returns 0, so
+                * don't have to worry about exited/signaled jobs
+                */
+               for (j = job_list; j; j = j->next)
+                       /* at&t ksh will wait for stopped jobs - we don't */
+                       if (j->ppid == procpid && j->state == PRUNNING)
+                               break;
+               if (!j) {
+#ifdef JOB_SIGS
+                       sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+#endif /* JOB_SIGS */
+                       return -1;
+               }
+       } else if ((j = j_lookup(cp, &ecode))) {
+               /* don't report normal job completion */
+               flags &= ~JW_ASYNCNOTIFY;
+               if (j->ppid != procpid) {
+#ifdef JOB_SIGS
+                       sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+#endif /* JOB_SIGS */
+                       return -1;
+               }
+       } else {
+#ifdef JOB_SIGS
+               sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+#endif /* JOB_SIGS */
+               if (ecode != JL_NOSUCH)
+                       bi_errorf("%s: %s", cp, lookup_msgs[ecode]);
+               return -1;
+       }
+
+       /* at&t ksh will wait for stopped jobs - we don't */
+       rv = j_waitj(j, flags, "jw:waitfor");
+
+#ifdef JOB_SIGS
+       sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+#endif /* JOB_SIGS */
+
+       if (rv < 0) /* we were interrupted */
+               *sigp = 128 + -rv;
+
+       return rv;
+}
+
+/* kill (built-in) a job */
+int
+j_kill(cp, sig)
+       const char *cp;
+       int     sig;
+{
+       Job     *j;
+       int     rv = 0;
+       int     ecode;
+#ifdef JOB_SIGS
+       sigset_t omask;
+
+       sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif /* JOB_SIGS */
+
+       if ((j = j_lookup(cp, &ecode)) == (Job *) 0) {
+#ifdef JOB_SIGS
+               sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+#endif /* JOB_SIGS */
+               bi_errorf("%s: %s", cp, lookup_msgs[ecode]);
+               return 1;
+       }
+
+       if (j->pgrp == 0) {     /* started when !Flag(FMONITOR) */
+               if (kill_job(j, sig) < 0) {
+                       bi_errorf("%s: %s", cp, strerror(errno));
+                       rv = 1;
+               }
+       } else {
+#ifdef JOBS
+               if (j->state == PSTOPPED && (sig == SIGTERM || sig == SIGHUP))
+                       (void) killpg(j->pgrp, SIGCONT);
+#endif /* JOBS */
+               if (killpg(j->pgrp, sig) < 0) {
+                       bi_errorf("%s: %s", cp, strerror(errno));
+                       rv = 1;
+               }
+       }
+
+#ifdef JOB_SIGS
+       sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+#endif /* JOB_SIGS */
+
+       return rv;
+}
+
+#ifdef JOBS
+/* fg and bg built-ins: called only if Flag(FMONITOR) set */
+int
+j_resume(cp, bg)
+       const char *cp;
+       int     bg;
+{
+       Job     *j;
+       Proc    *p;
+       int     ecode;
+       int     running;
+       int     rv = 0;
+       sigset_t omask;
+
+       sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+       if ((j = j_lookup(cp, &ecode)) == (Job *) 0) {
+               sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+               bi_errorf("%s: %s", cp, lookup_msgs[ecode]);
+               return 1;
+       }
+
+       if (j->pgrp == 0) {
+               sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+               bi_errorf("job not job-controlled");
+               return 1;
+       }
+
+       if (bg)
+               shprintf("[%d] ", j->job);
+
+       running = 0;
+       for (p = j->proc_list; p != (Proc *) 0; p = p->next) {
+               if (p->state == PSTOPPED) {
+                       p->state = PRUNNING;
+                       WSTATUS(p->status) = 0;
+                       running = 1;
+               }
+               shprintf("%s%s", p->command, p->next ? "| " : null);
+       }
+       shprintf("%s", newline);
+       shf_flush(shl_stdout);
+       if (running)
+               j->state = PRUNNING;
+
+       put_job(j, PJ_PAST_STOPPED);
+       if (bg)
+               j_set_async(j);
+       else {
+# ifdef TTY_PGRP
+               /* attach tty to job */
+               if (j->state == PRUNNING) {
+                       if (ttypgrp_ok && (j->flags & JF_SAVEDTTY)) {
+                               set_tty(tty_fd, &j->ttystate, TF_NONE);
+                       }
+                       /* See comment in j_waitj regarding saved_ttypgrp. */
+                       if (ttypgrp_ok && tcsetpgrp(tty_fd, (j->flags & JF_SAVEDTTYPGRP) ? j->saved_ttypgrp : j->pgrp) < 0) {
+                               if (j->flags & JF_SAVEDTTY) {
+                                       set_tty(tty_fd, &tty_state, TF_NONE);
+                               }
+                               sigprocmask(SIG_SETMASK, &omask,
+                                       (sigset_t *) 0);
+                               bi_errorf("1st tcsetpgrp(%d, %d) failed: %s",
+                                       tty_fd, (int) ((j->flags & JF_SAVEDTTYPGRP) ? j->saved_ttypgrp : j->pgrp), strerror(errno));
+                               return 1;
+                       }
+               }
+# endif /* TTY_PGRP */
+               j->flags |= JF_FG;
+               j->flags &= ~JF_KNOWN;
+               if (j == async_job)
+                       async_job = (Job *) 0;
+       }
+
+       if (j->state == PRUNNING && killpg(j->pgrp, SIGCONT) < 0) {
+               int     err = errno;
+
+               if (!bg) {
+                       j->flags &= ~JF_FG;
+# ifdef TTY_PGRP
+                       if (ttypgrp_ok && (j->flags & JF_SAVEDTTY)) {
+                               set_tty(tty_fd, &tty_state, TF_NONE);
+                       }
+                       if (ttypgrp_ok && tcsetpgrp(tty_fd, our_pgrp) < 0) {
+                               warningf(TRUE,
+                               "fg: 2nd tcsetpgrp(%d, %d) failed: %s",
+                                       tty_fd, (int) our_pgrp,
+                                       strerror(errno));
+                       }
+# endif /* TTY_PGRP */
+               }
+               sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+               bi_errorf("cannot continue job %s: %s",
+                       cp, strerror(err));
+               return 1;
+       }
+       if (!bg) {
+# ifdef TTY_PGRP
+               if (ttypgrp_ok) {
+                       j->flags &= ~(JF_SAVEDTTY | JF_SAVEDTTYPGRP);
+               }
+# endif /* TTY_PGRP */
+               rv = j_waitj(j, JW_NONE, "jw:resume");
+       }
+       sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+       return rv;
+}
+#endif /* JOBS */
+
+/* are there any running or stopped jobs ? */
+int
+j_stopped_running()
+{
+       Job     *j;
+       int     which = 0;
+
+       for (j = job_list; j != (Job *) 0; j = j->next) {
+#ifdef JOBS
+               if (j->ppid == procpid && j->state == PSTOPPED)
+                       which |= 1;
+#endif /* JOBS */
+               if (Flag(FLOGIN) && !Flag(FNOHUP) && procpid == kshpid
+                   && j->ppid == procpid && j->state == PRUNNING)
+                       which |= 2;
+       }
+       if (which) {
+               shellf("You have %s%s%s jobs\n",
+                       which & 1 ? "stopped" : "",
+                       which == 3 ? " and " : "",
+                       which & 2 ? "running" : "");
+               return 1;
+       }
+
+       return 0;
+}
+
+/* list jobs for jobs built-in */
+int
+j_jobs(cp, slp, nflag)
+       const char *cp;
+       int     slp;            /* 0: short, 1: long, 2: pgrp */
+       int     nflag;
+{
+       Job     *j, *tmp;
+       int     how;
+       int     zflag = 0;
+#ifdef JOB_SIGS
+       sigset_t omask;
+
+       sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif /* JOB_SIGS */
+
+       if (nflag < 0) { /* kludge: print zombies */
+               nflag = 0;
+               zflag = 1;
+       }
+       if (cp) {
+               int     ecode;
+
+               if ((j = j_lookup(cp, &ecode)) == (Job *) 0) {
+#ifdef JOB_SIGS
+                       sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+#endif /* JOB_SIGS */
+                       bi_errorf("%s: %s", cp, lookup_msgs[ecode]);
+                       return 1;
+               }
+       } else
+               j = job_list;
+       how = slp == 0 ? JP_MEDIUM : (slp == 1 ? JP_LONG : JP_PGRP);
+       for (; j; j = j->next) {
+               if ((!(j->flags & JF_ZOMBIE) || zflag)
+                   && (!nflag || (j->flags & JF_CHANGED)))
+               {
+                       j_print(j, how, shl_stdout);
+                       if (j->state == PEXITED || j->state == PSIGNALLED)
+                               j->flags |= JF_REMOVE;
+               }
+               if (cp)
+                       break;
+       }
+       /* Remove jobs after printing so there won't be multiple + or - jobs */
+       for (j = job_list; j; j = tmp) {
+               tmp = j->next;
+               if (j->flags & JF_REMOVE)
+                       remove_job(j, "jobs");
+       }
+#ifdef JOB_SIGS
+       sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+#endif /* JOB_SIGS */
+       return 0;
+}
+
+/* list jobs for top-level notification */
+void
+j_notify()
+{
+       Job     *j, *tmp;
+#ifdef JOB_SIGS
+       sigset_t omask;
+
+       sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif /* JOB_SIGS */
+       for (j = job_list; j; j = j->next) {
+#ifdef JOBS
+               if (Flag(FMONITOR) && (j->flags & JF_CHANGED))
+                       j_print(j, JP_MEDIUM, shl_out);
+#endif /* JOBS */
+               /* Remove job after doing reports so there aren't
+                * multiple +/- jobs.
+                */
+               if (j->state == PEXITED || j->state == PSIGNALLED)
+                       j->flags |= JF_REMOVE;
+       }
+       for (j = job_list; j; j = tmp) {
+               tmp = j->next;
+               if (j->flags & JF_REMOVE)
+                       remove_job(j, "notify");
+       }
+       shf_flush(shl_out);
+#ifdef JOB_SIGS
+       sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+#endif /* JOB_SIGS */
+}
+
+/* Return pid of last process in last asynchronous job */
+pid_t
+j_async()
+{
+#ifdef JOB_SIGS
+       sigset_t omask;
+
+       sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+#endif /* JOB_SIGS */
+
+       if (async_job)
+               async_job->flags |= JF_KNOWN;
+
+#ifdef JOB_SIGS
+       sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+#endif /* JOB_SIGS */
+
+       return async_pid;
+}
+
+/* Make j the last async process
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+j_set_async(j)
+       Job *j;
+{
+       Job     *jl, *oldest;
+
+       if (async_job && (async_job->flags & (JF_KNOWN|JF_ZOMBIE)) == JF_ZOMBIE)
+               remove_job(async_job, "async");
+       if (!(j->flags & JF_STARTED)) {
+               internal_errorf(0, "j_async: job not started");
+               return;
+       }
+       async_job = j;
+       async_pid = j->last_proc->pid;
+       while (nzombie > child_max) {
+               oldest = (Job *) 0;
+               for (jl = job_list; jl; jl = jl->next)
+                       if (jl != async_job && (jl->flags & JF_ZOMBIE)
+                           && (!oldest || jl->age < oldest->age))
+                               oldest = jl;
+               if (!oldest) {
+                       /* XXX debugging */
+                       if (!(async_job->flags & JF_ZOMBIE) || nzombie != 1) {
+                               internal_errorf(0, "j_async: bad nzombie (%d)", nzombie);
+                               nzombie = 0;
+                       }
+                       break;
+               }
+               remove_job(oldest, "zombie");
+       }
+}
+
+/* Start a job: set STARTED, check for held signals and set j->last_proc
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+j_startjob(j)
+       Job *j;
+{
+       Proc    *p;
+
+       j->flags |= JF_STARTED;
+       for (p = j->proc_list; p->next; p = p->next)
+               ;
+       j->last_proc = p;
+
+#ifdef NEED_PGRP_SYNC
+       if (j_sync_open) {
+               j_sync_open = 0;
+               closepipe(j_sync_pipe);
+       }
+#endif /* NEED_PGRP_SYNC */
+#ifdef JOB_SIGS
+       if (held_sigchld) {
+               held_sigchld = 0;
+               /* Don't call j_sigchld() as it may remove job... */
+               kill(procpid, SIGCHLD);
+       }
+#endif /* JOB_SIGS */
+}
+
+/*
+ * wait for job to complete or change state
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static int
+j_waitj(j, flags, where)
+       Job     *j;
+       int     flags;          /* see JW_* */
+       const char *where;
+{
+       int     rv;
+
+       /*
+        * No auto-notify on the job we are waiting on.
+        */
+       j->flags |= JF_WAITING;
+       if (flags & JW_ASYNCNOTIFY)
+               j->flags |= JF_W_ASYNCNOTIFY;
+
+       if (!Flag(FMONITOR))
+               flags |= JW_STOPPEDWAIT;
+
+       while ((volatile int) j->state == PRUNNING
+               || ((flags & JW_STOPPEDWAIT)
+                   && (volatile int) j->state == PSTOPPED))
+       {
+#ifdef JOB_SIGS
+               sigsuspend(&sm_default);
+#else /* JOB_SIGS */
+               j_sigchld(SIGCHLD);
+#endif /* JOB_SIGS */
+               if (fatal_trap) {
+                       int oldf = j->flags & (JF_WAITING|JF_W_ASYNCNOTIFY);
+                       j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY);
+                       runtraps(TF_FATAL);
+                       j->flags |= oldf; /* not reached... */
+               }
+               if ((flags & JW_INTERRUPT) && (rv = trap_pending())) {
+                       j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY);
+                       return -rv;
+               }
+       }
+       j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY);
+
+       if (j->flags & JF_FG) {
+#ifdef JOBS
+               WAIT_T  status;
+#endif
+
+               j->flags &= ~JF_FG;
+#ifdef TTY_PGRP
+               if (Flag(FMONITOR) && ttypgrp_ok && j->pgrp) {
+                       /*
+                        * Save the tty's current pgrp so it can be restored
+                        * when the job is foregrounded.  This is to
+                        * deal with things like the GNU su which does
+                        * a fork/exec instead of an exec (the fork means
+                        * the execed shell gets a different pid from its
+                        * pgrp, so naturally it sets its pgrp and gets hosed
+                        * when it gets foregrounded by the parent shell, which
+                        * has restored the tty's pgrp to that of the su
+                        * process).
+                        */
+                       if (j->state == PSTOPPED
+                           && (j->saved_ttypgrp = tcgetpgrp(tty_fd)) >= 0)
+                               j->flags |= JF_SAVEDTTYPGRP;
+                       if (tcsetpgrp(tty_fd, our_pgrp) < 0) {
+                               warningf(TRUE,
+                               "j_waitj: tcsetpgrp(%d, %d) failed: %s",
+                                       tty_fd, (int) our_pgrp,
+                                       strerror(errno));
+                       }
+                       if (j->state == PSTOPPED) {
+                               j->flags |= JF_SAVEDTTY;
+                               get_tty(tty_fd, &j->ttystate);
+                       }
+               }
+#endif /* TTY_PGRP */
+               if (tty_fd >= 0) {
+                       /* Only restore tty settings if job was originally
+                        * started in the foreground.  Problems can be
+                        * caused by things like `more foobar &' which will
+                        * typically get and save the shell's vi/emacs tty
+                        * settings before setting up the tty for itself;
+                        * when more exits, it restores the `original'
+                        * settings, and things go down hill from there...
+                        */
+                       if (j->state == PEXITED && j->status == 0
+                           && (j->flags & JF_USETTYMODE))
+                       {
+                               get_tty(tty_fd, &tty_state);
+                       } else {
+                               set_tty(tty_fd, &tty_state,
+                                   (j->state == PEXITED) ? 0 : TF_MIPSKLUDGE);
+                               /* Don't use tty mode if job is stopped and
+                                * later restarted and exits.  Consider
+                                * the sequence:
+                                *      vi foo (stopped)
+                                *      ...
+                                *      stty something
+                                *      ...
+                                *      fg (vi; ZZ)
+                                * mode should be that of the stty, not what
+                                * was before the vi started.
+                                */
+                               if (j->state == PSTOPPED)
+                                       j->flags &= ~JF_USETTYMODE;
+                       }
+               }
+#ifdef JOBS
+               /* If it looks like user hit ^C to kill a job, pretend we got
+                * one too to break out of for loops, etc.  (at&t ksh does this
+                * even when not monitoring, but this doesn't make sense since
+                * a tty generated ^C goes to the whole process group)
+                */
+               status = j->last_proc->status;
+               if (Flag(FMONITOR) && j->state == PSIGNALLED
+                   && WIFSIGNALED(status)
+                   && (sigtraps[WTERMSIG(status)].flags & TF_TTY_INTR))
+                       trapsig(WTERMSIG(status));
+#endif /* JOBS */
+       }
+
+       j_usrtime = j->usrtime;
+       j_systime = j->systime;
+       rv = j->status;
+
+       if (!(flags & JW_ASYNCNOTIFY)
+           && (!Flag(FMONITOR) || j->state != PSTOPPED))
+       {
+               j_print(j, JP_SHORT, shl_out);
+               shf_flush(shl_out);
+       }
+       if (j->state != PSTOPPED
+           && (!Flag(FMONITOR) || !(flags & JW_ASYNCNOTIFY)))
+               remove_job(j, where);
+
+       return rv;
+}
+
+/* SIGCHLD handler to reap children and update job states
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static RETSIGTYPE
+j_sigchld(sig)
+       int     sig;
+{
+       int             errno_ = errno;
+       Job             *j;
+       Proc            UNINITIALIZED(*p);
+       int             pid;
+       WAIT_T          status;
+       struct tms      t0, t1;
+
+#ifdef JOB_SIGS
+       /* Don't wait for any processes if a job is partially started.
+        * This is so we don't do away with the process group leader
+        * before all the processes in a pipe line are started (so the
+        * setpgid() won't fail)
+        */
+       for (j = job_list; j; j = j->next)
+               if (j->ppid == procpid && !(j->flags & JF_STARTED)) {
+                       held_sigchld = 1;
+                       return RETSIGVAL;
+               }
+#endif /* JOB_SIGS */
+
+       ksh_times(&t0);
+       do {
+#ifdef JOB_SIGS
+               pid = ksh_waitpid(-1, &status, (WNOHANG|WUNTRACED));
+#else /* JOB_SIGS */
+               pid = wait(&status);
+#endif /* JOB_SIGS */
+
+               if (pid <= 0)   /* return if would block (0) ... */
+                       break;  /* ... or no children or interrupted (-1) */
+
+               ksh_times(&t1);
+
+               /* find job and process structures for this pid */
+               for (j = job_list; j != (Job *) 0; j = j->next)
+                       for (p = j->proc_list; p != (Proc *) 0; p = p->next)
+                               if (p->pid == pid)
+                                       goto found;
+found:
+               if (j == (Job *) 0) {
+                       /* Can occur if process has kids, then execs shell
+                       warningf(TRUE, "bad process waited for (pid = %d)",
+                               pid);
+                        */
+                       t0 = t1;
+                       continue;
+               }
+
+               j->usrtime += t1.tms_cutime - t0.tms_cutime;
+               j->systime += t1.tms_cstime - t0.tms_cstime;
+               t0 = t1;
+               p->status = status;
+#ifdef JOBS
+               if (WIFSTOPPED(status))
+                       p->state = PSTOPPED;
+               else
+#endif /* JOBS */
+               if (WIFSIGNALED(status))
+                       p->state = PSIGNALLED;
+               else
+                       p->state = PEXITED;
+
+               check_job(j);   /* check to see if entire job is done */
+       }
+#ifdef JOB_SIGS
+       while (1);
+#else /* JOB_SIGS */
+       while (0);
+#endif /* JOB_SIGS */
+
+       errno = errno_;
+
+       return RETSIGVAL;
+}
+
+/*
+ * Called only when a process in j has exited/stopped (ie, called only
+ * from j_sigchld()).  If no processes are running, the job status
+ * and state are updated, asynchronous job notification is done and,
+ * if unneeded, the job is removed.
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+check_job(j)
+       Job     *j;
+{
+       int     jstate;
+       Proc    *p;
+
+       /* XXX debugging (nasty - interrupt routine using shl_out) */
+       if (!(j->flags & JF_STARTED)) {
+               internal_errorf(0, "check_job: job started (flags 0x%x)",
+                       j->flags);
+               return;
+       }
+
+       jstate = PRUNNING;
+       for (p=j->proc_list; p != (Proc *) 0; p = p->next) {
+               if (p->state == PRUNNING)
+                       return; /* some processes still running */
+               if (p->state > jstate)
+                       jstate = p->state;
+       }
+       j->state = jstate;
+
+       switch (j->last_proc->state) {
+       case PEXITED:
+               j->status = WEXITSTATUS(j->last_proc->status);
+               break;
+       case PSIGNALLED:
+               j->status = 128 + WTERMSIG(j->last_proc->status);
+               break;
+       default:
+               j->status = 0;
+               break;
+       }
+
+#ifdef KSH
+       /* Note when co-process dies: can't be done in j_wait() nor
+        * remove_job() since neither may be called for non-interactive
+        * shells.
+        */
+       if (j->state == PEXITED || j->state == PSIGNALLED) {
+               /* No need to keep co-process input any more
+                * (at least, this is what ksh93d thinks)
+                */
+               if (coproc.job == j) {
+                       coproc.job = (void *) 0;
+                       /* XXX would be nice to get the closes out of here
+                        * so they aren't done in the signal handler.
+                        * Would mean a check in coproc_getfd() to
+                        * do "if job == 0 && write >= 0, close write".
+                        */
+                       coproc_write_close(coproc.write);
+               }
+               /* Do we need to keep the output? */
+               if (j->coproc_id && j->coproc_id == coproc.id
+                   && --coproc.njobs == 0)
+                       coproc_readw_close(coproc.read);
+       }
+#endif /* KSH */
+
+       j->flags |= JF_CHANGED;
+#ifdef JOBS
+       if (Flag(FMONITOR) && !(j->flags & JF_XXCOM)) {
+               /* Only put stopped jobs at the front to avoid confusing
+                * the user (don't want finished jobs effecting %+ or %-)
+                */
+               if (j->state == PSTOPPED)
+                       put_job(j, PJ_ON_FRONT);
+               if (Flag(FNOTIFY)
+                   && (j->flags & (JF_WAITING|JF_W_ASYNCNOTIFY)) != JF_WAITING)
+               {
+                       /* Look for the real file descriptor 2 */
+                       {
+                               struct env *ep;
+                               int fd = 2;
+
+                               for (ep = e; ep; ep = ep->oenv)
+                                       if (ep->savefd && ep->savefd[2])
+                                               fd = ep->savefd[2];
+                               shf_reopen(fd, SHF_WR, shl_j);
+                       }
+                       /* Can't call j_notify() as it removes jobs.  The job
+                        * must stay in the job list as j_waitj() may be
+                        * running with this job.
+                        */
+                       j_print(j, JP_MEDIUM, shl_j);
+                       shf_flush(shl_j);
+                       if (!(j->flags & JF_WAITING) && j->state != PSTOPPED)
+                               remove_job(j, "notify");
+               }
+       }
+#endif /* JOBS */
+       if (!Flag(FMONITOR) && !(j->flags & (JF_WAITING|JF_FG))
+           && j->state != PSTOPPED)
+       {
+               if (j == async_job || (j->flags & JF_KNOWN)) {
+                       j->flags |= JF_ZOMBIE;
+                       j->job = -1;
+                       nzombie++;
+               } else
+                       remove_job(j, "checkjob");
+       }
+}
+
+/*
+ * Print job status in either short, medium or long format.
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+j_print(j, how, shf)
+       Job             *j;
+       int             how;
+       struct shf      *shf;
+{
+       Proc    *p;
+       int     state;
+       WAIT_T  status;
+       int     coredumped;
+       char    jobchar = ' ';
+       char    buf[64];
+       const char *filler;
+       int     output = 0;
+
+       if (how == JP_PGRP) {
+               /* POSIX doesn't say what to do it there is no process
+                * group leader (ie, !FMONITOR).  We arbitrarily return
+                * last pid (which is what $! returns).
+                */
+               shf_fprintf(shf, "%d\n", j->pgrp ? j->pgrp
+                               : (j->last_proc ? j->last_proc->pid : 0));
+               return;
+       }
+       j->flags &= ~JF_CHANGED;
+       filler = j->job > 10 ?  "\n       " : "\n      ";
+       if (j == job_list)
+               jobchar = '+';
+       else if (j == job_list->next)
+               jobchar = '-';
+
+       for (p = j->proc_list; p != (Proc *) 0;) {
+               coredumped = 0;
+               switch (p->state) {
+               case PRUNNING:
+                       strlcpy(buf, "Running", sizeof buf);
+                       break;
+               case PSTOPPED:
+                       strlcpy(buf, sigtraps[WSTOPSIG(p->status)].mess,
+                           sizeof buf);
+                       break;
+               case PEXITED:
+                       if (how == JP_SHORT)
+                               buf[0] = '\0';
+                       else if (WEXITSTATUS(p->status) == 0)
+                               strlcpy(buf, "Done", sizeof buf);
+                       else
+                               shf_snprintf(buf, sizeof(buf), "Done (%d)",
+                                       WEXITSTATUS(p->status));
+                       break;
+               case PSIGNALLED:
+                       if (WIFCORED(p->status))
+                               coredumped = 1;
+                       /* kludge for not reporting `normal termination signals'
+                        * (ie, SIGINT, SIGPIPE)
+                        */
+                       if (how == JP_SHORT && !coredumped
+                           && (WTERMSIG(p->status) == SIGINT
+                               || WTERMSIG(p->status) == SIGPIPE)) {
+                               buf[0] = '\0';
+                       } else
+                               strlcpy(buf, sigtraps[WTERMSIG(p->status)].mess,
+                                   sizeof buf);
+                       break;
+               }
+
+               if (how != JP_SHORT) {
+                       if (p == j->proc_list)
+                               shf_fprintf(shf, "[%d] %c ", j->job, jobchar);
+                       else
+                               shf_fprintf(shf, "%s", filler);
+               }
+
+               if (how == JP_LONG)
+                       shf_fprintf(shf, "%5d ", p->pid);
+
+               if (how == JP_SHORT) {
+                       if (buf[0]) {
+                               output = 1;
+                               shf_fprintf(shf, "%s%s ",
+                                       buf, coredumped ? " (core dumped)" : null);
+                       }
+               } else {
+                       output = 1;
+                       shf_fprintf(shf, "%-20s %s%s%s", buf, p->command,
+                               p->next ? "|" : null,
+                               coredumped ? " (core dumped)" : null);
+               }
+
+               state = p->state;
+               status = p->status;
+               p = p->next;
+               while (p && p->state == state
+                      && WSTATUS(p->status) == WSTATUS(status))
+               {
+                       if (how == JP_LONG)
+                               shf_fprintf(shf, "%s%5d %-20s %s%s", filler, p->pid,
+                                       space, p->command, p->next ? "|" : null);
+                       else if (how == JP_MEDIUM)
+                               shf_fprintf(shf, " %s%s", p->command,
+                                       p->next ? "|" : null);
+                       p = p->next;
+               }
+       }
+       if (output)
+               shf_fprintf(shf, newline);
+}
+
+/* Convert % sequence to job
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static Job *
+j_lookup(cp, ecodep)
+       const char *cp;
+       int     *ecodep;
+{
+       Job             *j, *last_match;
+       Proc            *p;
+       int             len, job = 0;
+
+       if (digit(*cp)) {
+               job = atoi(cp);
+               /* Look for last_proc->pid (what $! returns) first... */
+               for (j = job_list; j != (Job *) 0; j = j->next)
+                       if (j->last_proc && j->last_proc->pid == job)
+                               return j;
+               /* ...then look for process group (this is non-POSIX),
+                * but should not break anything (so FPOSIX isn't used).
+                */
+               for (j = job_list; j != (Job *) 0; j = j->next)
+                       if (j->pgrp && j->pgrp == job)
+                               return j;
+               if (ecodep)
+                       *ecodep = JL_NOSUCH;
+               return (Job *) 0;
+       }
+       if (*cp != '%') {
+               if (ecodep)
+                       *ecodep = JL_INVALID;
+               return (Job *) 0;
+       }
+       switch (*++cp) {
+       case '\0': /* non-standard */
+       case '+':
+       case '%':
+               if (job_list != (Job *) 0)
+                       return job_list;
+               break;
+
+       case '-':
+               if (job_list != (Job *) 0 && job_list->next)
+                       return job_list->next;
+               break;
+
+       case '0': case '1': case '2': case '3': case '4':
+       case '5': case '6': case '7': case '8': case '9':
+               job = atoi(cp);
+               for (j = job_list; j != (Job *) 0; j = j->next)
+                       if (j->job == job)
+                               return j;
+               break;
+
+       case '?':               /* %?string */
+               last_match = (Job *) 0;
+               for (j = job_list; j != (Job *) 0; j = j->next)
+                       for (p = j->proc_list; p != (Proc *) 0; p = p->next)
+                               if (strstr(p->command, cp+1) != (char *) 0) {
+                                       if (last_match) {
+                                               if (ecodep)
+                                                       *ecodep = JL_AMBIG;
+                                               return (Job *) 0;
+                                       }
+                                       last_match = j;
+                               }
+               if (last_match)
+                       return last_match;
+               break;
+
+       default:                /* %string */
+               len = strlen(cp);
+               last_match = (Job *) 0;
+               for (j = job_list; j != (Job *) 0; j = j->next)
+                       if (strncmp(cp, j->proc_list->command, len) == 0) {
+                               if (last_match) {
+                                       if (ecodep)
+                                               *ecodep = JL_AMBIG;
+                                       return (Job *) 0;
+                               }
+                               last_match = j;
+                       }
+               if (last_match)
+                       return last_match;
+               break;
+       }
+       if (ecodep)
+               *ecodep = JL_NOSUCH;
+       return (Job *) 0;
+}
+
+static Job     *free_jobs;
+static Proc    *free_procs;
+
+/* allocate a new job and fill in the job number.
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static Job *
+new_job()
+{
+       int     i;
+       Job     *newj, *j;
+
+       if (free_jobs != (Job *) 0) {
+               newj = free_jobs;
+               free_jobs = free_jobs->next;
+       } else
+               newj = (Job *) alloc(sizeof(Job), APERM);
+
+       /* brute force method */
+       for (i = 1; ; i++) {
+               for (j = job_list; j && j->job != i; j = j->next)
+                       ;
+               if (j == (Job *) 0)
+                       break;
+       }
+       newj->job = i;
+
+       return newj;
+}
+
+/* Allocate new process struct
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static Proc *
+new_proc()
+{
+       Proc    *p;
+
+       if (free_procs != (Proc *) 0) {
+               p = free_procs;
+               free_procs = free_procs->next;
+       } else
+               p = (Proc *) alloc(sizeof(Proc), APERM);
+
+       return p;
+}
+
+/* Take job out of job_list and put old structures into free list.
+ * Keeps nzombies, last_job and async_job up to date.
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+remove_job(j, where)
+       Job     *j;
+       const char *where;
+{
+       Proc    *p, *tmp;
+       Job     **prev, *curr;
+
+       prev = &job_list;
+       curr = *prev;
+       for (; curr != (Job *) 0 && curr != j; prev = &curr->next, curr = *prev)
+               ;
+       if (curr != j) {
+               internal_errorf(0, "remove_job: job not found (%s)", where);
+               return;
+       }
+       *prev = curr->next;
+
+       /* free up proc structures */
+       for (p = j->proc_list; p != (Proc *) 0; ) {
+               tmp = p;
+               p = p->next;
+               tmp->next = free_procs;
+               free_procs = tmp;
+       }
+
+       if ((j->flags & JF_ZOMBIE) && j->ppid == procpid)
+               --nzombie;
+       j->next = free_jobs;
+       free_jobs = j;
+
+       if (j == last_job)
+               last_job = (Job *) 0;
+       if (j == async_job)
+               async_job = (Job *) 0;
+}
+
+/* put j in a particular location (taking it out job_list if it is there
+ * already)
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+put_job(j, where)
+       Job     *j;
+       int     where;
+{
+       Job     **prev, *curr;
+
+       /* Remove job from list (if there) */
+       prev = &job_list;
+       curr = job_list;
+       for (; curr && curr != j; prev = &curr->next, curr = *prev)
+               ;
+       if (curr == j)
+               *prev = curr->next;
+
+       switch (where) {
+       case PJ_ON_FRONT:
+               j->next = job_list;
+               job_list = j;
+               break;
+
+       case PJ_PAST_STOPPED:
+               prev = &job_list;
+               curr = job_list;
+               for (; curr && curr->state == PSTOPPED; prev = &curr->next,
+                                                       curr = *prev)
+                       ;
+               j->next = curr;
+               *prev = j;
+               break;
+       }
+}
+
+/* nuke a job (called when unable to start full job).
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static int
+kill_job(j, sig)
+       Job     *j;
+       int     sig;
+{
+       Proc    *p;
+       int     rval = 0;
+
+       for (p = j->proc_list; p != (Proc *) 0; p = p->next)
+               if (p->pid != 0)
+                       if (kill(p->pid, sig) < 0)
+                               rval = -1;
+       return rval;
+}
diff --git a/bin/ksh/ksh.Man b/bin/ksh/ksh.Man
new file mode 100644 (file)
index 0000000..f4e9d24
--- /dev/null
@@ -0,0 +1,3617 @@
+'\" t
+.\" $NetBSD: ksh.Man,v 1.23 2011/10/18 12:36:31 reed Exp $
+.\"{{{}}}
+.\"{{{  Notes about man page
+.\"     - use the pseudo-macros .sh( and .sh) to begin and end sh-specific
+.\"       text and .ksh( and .ksh) for ksh specific text.
+.\"     - put i.e., e.g. and etc. in italics
+.\"}}}
+.\"{{{  To do
+.\" todo: Things not covered that should be:
+.\"    - distinguish (POSIX) special built-in's, (POSIX) regular built-in's,
+.\"      and sh/ksh weirdo built-in's (put S,R,X superscripts after command
+.\"      name in built-in commands section?)
+.\"    - need to be consistent about notation for `See section-name', `
+.\"      See description of foobar command', `See section section-name', etc.
+.\"    - need to use the term `external command' meaning `a command that is
+.\"       executed using execve(2)' (as opposed to a built-in command or
+.\"       function) for more clear description.
+.\"}}}
+.\"{{{  Title
+.ksh(
+.TH KSH 1 "August 19, 1996" "" "User commands"
+.ksh)
+.sh(
+.TH SH 1 "August 19, 1996" "" "User commands"
+.sh)
+.\"}}}
+.\"{{{  Name
+.SH NAME
+.ksh(
+ksh \- Public domain Korn shell
+.ksh)
+.sh(
+sh \- Public domain Bourne shell
+.sh)
+.\"}}}
+.\"{{{  Synopsis
+.SH SYNOPSIS
+.ad l
+.ksh(
+\fBksh\fP
+.ksh)
+.sh(
+\fBsh\fP
+.sh)
+[\fB\(+-abCefhikmnprsuvxX\fP] [\fB\(+-o\fP \fIoption\fP] [ [ \fB\-c\fP \fIcommand-string\fP [\fIcommand-name\fP] | \fB\-s\fP | \fIfile\fP ] [\fIargument\fP ...] ]
+.ad b
+.\"}}}
+.\"{{{  Description
+.SH DESCRIPTION
+.ksh(
+\fBksh\fP is a command interpreter that is intended for both
+interactive and shell script use.
+Its command language is a superset of the \fIsh\fP(1) shell language.
+.ksh)
+.sh(
+\fBsh\fP is a re-implementation of the Bourne shell, a command
+interpreter for both interactive and script use.
+.sh)
+.\"{{{  Shell Startup
+.SS "Shell Startup"
+The following options can be specified only on the command line:
+.IP "\fB\-c\fP \fIcommand-string\fP"
+the shell executes the command(s) contained in \fIcommand-string\fP
+.IP \fB\-i\fP
+interactive mode \(em see below
+.IP \fB\-l\fP
+login shell \(em see below
+interactive mode \(em see below
+.IP \fB\-s\fP
+the shell reads commands from standard input; all non-option arguments
+are positional parameters
+.IP \fB\-r\fP
+restricted mode \(em see below
+.PP
+In addition to the above, the options described in the \fBset\fP built-in
+command can also be used on the command line.
+.PP
+If neither the \fB\-c\fP nor the \fB\-s\fP options are specified, the
+first non-option argument specifies the name of a file the shell reads
+commands from; if there are no non-option arguments, the shell reads
+commands from standard input.
+The name of the shell (\fIi.e.\fP, the contents of the \fB$0\fP) parameter
+is determined as follows: if the \fB\-c\fP option is used and there is
+a non-option argument, it is used as the name; if commands are being
+read from a file, the file is used as the name; otherwise the name
+the shell was called with (\fIi.e.\fP, argv[0]) is used.
+.PP
+A shell is \fBinteractive\fP if the \fB\-i\fP option is used or
+if both standard input and standard error are attached to a tty.
+An interactive shell has job control enabled (if available),
+ignores the INT, QUIT and TERM signals, and prints prompts before
+reading input (see \fBPS1\fP and \fBPS2\fP parameters).
+For non-interactive shells, the \fBtrackall\fP option is on by default
+(see \fBset\fP command below).
+.PP
+A shell is \fBrestricted\fP if the \fB\-r\fP option is used or if either
+the basename of the name the shell is invoked with or the \fBSHELL\fP
+parameter match the pattern *r*sh (\fIe.g.\fP, rsh, rksh, rpdksh, \fIetc.\fP).
+The following restrictions come into effect after the shell processes
+any profile and \fB$ENV\fP files:
+.nr P2 \n(PD
+.nr PD 0
+.IP \ \ \(bu
+the \fBcd\fP command is disabled
+.IP \ \ \(bu
+the \fBSHELL\fP, \fBENV\fP and \fBPATH\fP parameters can't be changed
+.IP \ \ \(bu
+command names can't be specified with absolute or relative paths
+.IP \ \ \(bu
+the \fB\-p\fP option of the \fBcommand\fP built-in can't be used
+.IP \ \ \(bu
+redirections that create files can't be used (\fIi.e.\fP, \fB>\fP,
+\fB>|\fP, \fB>>\fP, \fB<>\fP)
+.nr PD \n(P2
+.PP
+A shell is \fBprivileged\fP if the \fB\-p\fP option is used or if
+the real user-id or group-id does not match the effective user-id
+or group-id (see \fIgetuid\fP(2), \fIgetgid\fP(2)).
+A privileged shell does not process $HOME/.profile nor the \fBENV\fP
+parameter (see below), instead the file /etc/suid_profile is processed.
+Clearing the privileged option causes the shell to set its effective
+user-id (group-id) to its real user-id (group-id).
+.PP
+If the basename of the name the shell is called with (\fIi.e.\fP, argv[0])
+starts with \fB\-\fP or if the \fB\-l\fP option is used, the shell is assumed
+to be a login shell and the shell reads and executes the contents of
+\fB/etc/profile\fP, \fB$HOME/.profile\fP and \fB$ENV\fP if they exist and are
+readable.
+.PP
+If the \fBENV\fP parameter is set when the shell starts (or, in the
+case of login shells, after any profiles are processed), its value
+is subjected to parameter, command, arithmetic and tilde substitution and
+the resulting file (if any) is read and executed.
+If the \fBENV\fP parameter is not set (and not null) the file \fB$HOME/.kshrc\fP 
+is included (after the above mentioned substitutions have been performed).
+.PP
+The exit status of the shell is 127 if the command file specified
+on the command line could not be opened, or non-zero if a fatal syntax
+error occurred during the execution of a script.
+In the absence of fatal errors, the exit status is that of the last
+command executed, or zero, if no command is executed.
+.\"}}}
+.\"{{{  Command Syntax
+.SS "Command Syntax"
+.\"{{{  words and tokens
+The shell begins parsing its input by breaking it into \fIword\fPs.
+Words, which are sequences of characters, are delimited by unquoted
+\fIwhite-space\fP characters (space, tab and newline) or \fImeta-characters\fP
+(\fB<\fP, \fB>\fP, \fB|\fP, \fB;\fP, \fB&\fP, \fB(\fP and \fB)\fP).
+Aside from delimiting words, spaces and tabs are ignored, while
+newlines usually delimit commands.
+The meta-characters are used in building the following tokens:
+\fB<\fP, \fB<&\fP, \fB<<\fP, \fB>\fP, \fB>&\fP, \fB>>\fP, \fIetc.\fP are
+used to specify redirections (see Input/Output Redirection below);
+\fB|\fP is used to create pipelines;
+.ksh(
+\fB|&\fP is used to create co-processes (see Co-Processes below);
+.ksh)
+\fB;\fP is used to separate commands;
+\fB&\fP is used to create asynchronous pipelines;
+\fB&&\fP and \fB||\fP are used to specify conditional execution;
+\fB;;\fP is used in \fBcase\fP statements;
+.ksh(
+\fB((\fP .. \fB))\fP are used in arithmetic expressions;
+.ksh)
+and lastly,
+\fB(\fP .. \fB)\fP are used to create subshells.
+.PP
+White-space and meta-characters can be quoted individually using
+backslash (\fB\e\fP), or in groups using double (\fB"\fP) or single (\fB'\fP)
+quotes.
+Note that the following characters are also treated specially by the shell and
+must be quoted if they are to represent themselves:
+\fB\e\fP, \fB"\fP, \fB'\fP, \fB#\fP, \fB$\fP, \fB`\fP, \fB~\fP, \fB{\fP,
+\fB}\fP, \fB*\fP, \fB?\fP and \fB[\fP.
+The first three of these are the above mentioned quoting characters
+(see Quoting below);
+\fB#\fP, if used at the beginning of a word, introduces a comment \(em everything
+after the \fB#\fP up to the nearest newline is ignored;
+\fB$\fP is used to introduce parameter, command and arithmetic substitutions
+(see Substitution below);
+\fB`\fP introduces an old-style command substitution
+(see Substitution below);
+\fB~\fP begins a directory expansion (see Tilde Expansion below);
+\fB{\fP and \fB}\fP delimit \fIcsh\fP(1) style alternations
+(see Brace Expansion below);
+and, finally, \fB*\fP, \fB?\fP and \fB[\fP are used in file name generation
+(see File Name Patterns below).
+.\"}}}
+.\"{{{  simple-command
+.PP
+As words and tokens are parsed, the shell builds commands, of which
+there are two basic types: \fIsimple-commands\fP, typically programs
+that are executed, and \fIcompound-commands\fP, such as \fBfor\fP and
+\fBif\fP statements, grouping constructs and function definitions.
+.PP
+A simple-command consists of some combination of parameter assignments (see
+Parameters below), input/output redirections (see Input/Output Redirections
+below), and command words; the only restriction is that parameter assignments
+come before any command words.
+The command words, if any, define the command that is to be executed and its
+arguments.
+The command may be a shell built-in command, a function or an \fIexternal
+command\fP, \fIi.e.\fP, a separate executable file that is located using the
+\fBPATH\fP parameter (see Command Execution below).
+Note that all command constructs have an \fIexit status\fP: for external
+commands, this is related to the status returned by \fIwait\fP(2) (if the
+command could not be found, the exit status is 127, if it could not be
+executed, the exit status is 126);
+the exit status of other command constructs (built-in commands, functions,
+compound-commands, pipelines, lists, \fIetc.\fP) are all well defined and are
+described where the construct is described.
+The exit status of a command consisting only of parameter assignments is that
+of the last command substitution performed during the parameter assignment
+or zero if there were no command substitutions.
+.\"}}}
+.\"{{{  pipeline
+.PP
+Commands can be chained together using the \fB|\fP token to
+form \fIpipelines\fP, in which the standard output of each command but
+the last is piped (see \fIpipe\fP(2)) to the standard input of the following
+command.
+The exit status of a pipeline is that of its last command.
+A pipeline may be prefixed by the \fB!\fP reserved word which
+causes the exit status of the pipeline to be logically
+complemented: if the original status was 0 the complemented status will
+be 1, and if the original status was not 0, then the complemented
+status will be 0.
+.\"}}}
+.\"{{{  lists
+.PP
+\fILists\fP of commands can be created by separating pipelines by
+any of the following tokens: \fB&&\fP, \fB||\fP, \fB&\fP, \fB|&\fP and \fB;\fP.
+The first two are for conditional execution: \fIcmd1\fP \fB&&\fP \fIcmd2\fP
+executes \fIcmd2\fP only if the exit status of \fIcmd1\fP is zero;
+\fB||\fP is the opposite \(em \fIcmd2\fP is executed only if the exit status
+of \fIcmd1\fP is non-zero.
+\fB&&\fP and \fB||\fP have equal precedence which is higher than that of
+\fB&\fP, \fB|&\fP and \fB;\fP, which also have equal precedence.
+The \fB&\fP token causes the preceding command to be executed asynchronously,
+that is, the shell starts the command, but does not wait for it to complete
+(the shell does keep track of the status of asynchronous commands \(em see
+Job Control below).
+When an asynchronous command is started when job control is disabled
+(\fIi.e.\fP, in most scripts), the command is started with signals INT
+and QUIT ignored and with input redirected from /dev/null
+(however, redirections specified in the asynchronous command have precedence).
+.ksh(
+The \fB|&\fP operator starts a \fIco-process\fP which is special kind of
+asynchronous process (see Co-Processes below).
+.ksh)
+Note that a command must follow the \fB&&\fP and \fB||\fP operators, while
+a command need not follow \fB&\fP, \fB|&\fP and \fB;\fP.
+The exit status of a list is that of the last command executed, with the
+exception of asynchronous lists, for which the exit status is 0.
+.\"}}}
+.\"{{{  compound-commands
+.PP
+Compound commands are created using the following reserved words \(em these
+words are only recognized if they are unquoted and if they are used as
+the first word of a command (\fIi.e.\fP, they can't be preceded by parameter
+assignments or redirections):
+.TS
+center;
+lfB lfB lfB lfB lfB .
+case   else    function        then    !
+do     esac    if      time    [[
+done   fi      in      until   {
+elif   for     select  while   }
+.TE
+\fBNote:\fP Some shells (but not this one) execute control structure commands
+in a subshell when one or more of their file descriptors are redirected, so
+any environment changes inside them may fail.
+To be portable, the \fBexec\fP statement should be used instead to redirect
+file descriptors before the control structure.
+.PP
+In the following compound command descriptions, command lists (denoted as
+\fIlist\fP) that are followed by reserved words must end with a
+semi-colon, a newline or a (syntactically correct) reserved word.
+For example,
+.RS
+\fB{ echo foo; echo bar; }\fP
+.br
+\fB{ echo foo; echo bar<newline>}\fP
+.br
+\fB{ { echo foo; echo bar; } }\fP
+.RE
+are all valid, but
+.RS
+\fB{ echo foo; echo bar }\fP
+.RE
+is not.
+.\"{{{  ( list )
+.IP "\fB(\fP \fIlist\fP \fB)\fP"
+Execute \fIlist\fP in a subshell.
+There is no implicit way to pass
+environment changes from a subshell back to its parent.
+.\"}}}
+.\"{{{  { list }
+.IP "\fB{\fP \fIlist\fP \fB}\fP"
+Compound construct; \fIlist\fP is executed, but not in a subshell.
+Note that \fB{\fP and \fB}\fP are reserved words, not meta-characters.
+.\"}}}
+.\"{{{  case word in [ [ ( ] pattern [ | pattern ] ... ) list ;; ] ... esac
+.IP "\fBcase\fP \fIword\fP \fBin\fP [ [\fB(\fP] \fIpattern\fP [\fB|\fP \fIpattern\fP] ... \fB)\fP \fIlist\fP \fB;;\fP ] ... \fBesac\fP"
+The \fBcase\fP statement attempts to match \fIword\fP against the specified
+\fIpattern\fPs; the \fIlist\fP associated with the first successfully matched
+pattern is executed.
+Patterns used in \fBcase\fP statements are the same as
+those used for file name patterns except that the restrictions regarding
+\fB\&.\fP and \fB/\fP are dropped.
+Note that any unquoted space before and
+after a pattern is stripped; any space with a pattern must be quoted.
+Both the word and the patterns are subject to parameter, command, and
+arithmetic substitution as well as tilde substitution.
+For historical reasons, open and close braces may be used instead
+of \fBin\fP and \fBesac\fP (\fIe.g.\fP, \fBcase $foo { *) echo bar; }\fP).
+The exit status of a \fBcase\fP statement is that of the executed \fIlist\fP;
+if no \fIlist\fP is executed, the exit status is zero.
+.\"}}}
+.\"{{{  for name [ in word ... term ] do list done
+.IP "\fBfor\fP \fIname\fP [ \fBin\fP \fIword\fP ... \fIterm\fP ] \fBdo\fP \fIlist\fP \fBdone\fP"
+where \fIterm\fP is either a newline or a \fB;\fP.
+For each \fIword\fP in the specified word list, the parameter \fIname\fP is
+set to the word and \fIlist\fP is executed.
+If \fBin\fP is not used to
+specify a word list, the positional parameters (\fB"$1"\fP, \fB"$2"\fP,
+\fIetc.\fP) are used instead.
+For historical reasons, open and close braces may be used instead
+of \fBdo\fP and \fBdone\fP (\fIe.g.\fP, \fBfor i; { echo $i; }\fP).
+The exit status of a \fBfor\fP statement is the last exit status
+of \fIlist\fP; if \fIlist\fP is never executed, the exit status is zero.
+.\"}}}
+.\"{{{  if list then list [ elif list then list ] ... [ else list ] fi
+.IP "\fBif\fP \fIlist\fP \fBthen\fP \fIlist\fP [\fBelif\fP \fIlist\fP \fBthen\fP \fIlist\fP] ... [\fBelse\fP \fIlist\fP] \fBfi\fP"
+If the exit status of the first \fIlist\fP is zero, the second \fIlist\fP
+is executed; otherwise the \fIlist\fP following the \fBelif\fP, if any, is
+executed with similar consequences.
+If all the lists following the \fBif\fP
+and \fBelif\fPs fail (\fIi.e.\fP, exit with non-zero status), the \fIlist\fP
+following the \fBelse\fP is executed.
+The exit status of an \fBif\fP statement is that
+of non-conditional \fIlist\fP that is executed; if no non-conditional
+\fIlist\fP is executed, the exit status is zero.
+.\"}}}
+.\"{{{  select name [ in word ... ] do list done
+.ksh(
+.IP "\fBselect\fP \fIname\fP [ \fBin\fP \fIword\fP ... \fIterm\fP ] \fBdo\fP \fIlist\fP \fBdone\fP"
+where \fIterm\fP is either a newline or a \fB;\fP.
+The \fBselect\fP statement provides an automatic method of presenting
+the user with a menu and selecting from it.
+An enumerated list of the specified \fIwords\fP is printed on standard
+error, followed by a prompt (\fBPS3\fP, normally `\fB#? \fP').
+A number corresponding to one of the enumerated words is then read
+from standard input, \fIname\fP is set to the selected word (or is
+unset if the selection is not valid), \fBREPLY\fP
+is set to what was read (leading/trailing space is stripped),
+and \fIlist\fP is executed.
+If a blank line (\fIi.e.\fP, zero or more \fBIFS\fP characters) is entered,
+the menu is re-printed without executing \fIlist\fP.
+When \fIlist\fP completes, the enumerated list is printed if \fBREPLY\fP
+is null, the prompt is printed and so on.
+This process is continues until an end-of-file is read, an interrupt is
+received or a break statement is executed inside the loop.
+If \fBin\fP \fIword\fP \fB\&...\fP is omitted, the positional parameters
+are used (\fIi.e.\fP, \fB"$1"\fP, \fB"$2"\fP, \fIetc.\fP).
+For historical reasons, open and close braces may be used instead
+of \fBdo\fP and \fBdone\fP (\fIe.g.\fP, \fBselect i; { echo $i; }\fP).
+The exit status of a \fBselect\fP statement is zero if a break statement
+is used to exit the loop, non-zero otherwise.
+.ksh)
+.\"}}}
+.\"{{{  until list do list done
+.IP "\fBuntil\fP \fIlist\fP \fBdo\fP \fIlist\fP \fBdone\fP"
+This works like \fBwhile\fP, except that the body is executed only while the
+exit status of the first \fIlist\fP is non-zero.
+.\"}}}
+.\"{{{  while list do list done
+.IP "\fBwhile\fP \fIlist\fP \fBdo\fP \fIlist\fP \fBdone\fP"
+A \fBwhile\fP is a prechecked loop.
+Its body is executed as often
+as the exit status of the first \fIlist\fP is zero.
+The exit status of a \fBwhile\fP statement is the last exit status
+of the \fIlist\fP in the body of the loop; if the body is not executed,
+the exit status is zero.
+.\"}}}
+.\"{{{  function name { list }
+.IP "\fBfunction\fP \fIname\fP \fB{\fP \fIlist\fP \fB}\fP"
+Defines the function \fIname\fP.
+See Functions below.
+Note that redirections specified after a function definition are
+performed whenever the function is executed, not when the function
+definition is executed.
+.\"}}}
+.\"{{{  name () command
+.IP "\fIname\fP \fB()\fP \fIcommand\fP"
+Mostly the same as \fBfunction\fP.
+See Functions below.
+.\"}}}
+.\"{{{  time [-p] [ pipeline ]
+.IP "\fBtime\fP [ \fB-p\fP ] [ \fIpipeline\fP ]"
+The \fBtime\fP reserved word is described in the Command Execution section.
+.\"}}}
+.\"{{{  (( expression ))
+.ksh(
+.IP "\fB((\fP \fIexpression\fP \fB))\fP"
+The arithmetic expression \fIexpression\fP is evaluated;
+equivalent to \fBlet "\fP\fIexpression\fP\fB"\fP.
+See Arithmetic Expressions and the \fBlet\fP command below.
+.ksh)
+.\"}}}
+.\"{{{  [[ expression ]]
+.ksh(
+.IP "\fB[[\fP \fIexpression\fP \fB]]\fP"
+Similar to the \fBtest\fP and \fB[\fP \&... \fB]\fP commands (described later),
+with the following exceptions:
+.RS
+.nr P2 \n(PD
+.nr PD 0
+.IP \ \ \(bu
+Field splitting and file name generation are not performed on
+arguments.
+.IP \ \ \(bu
+The \fB\-a\fP (and) and \fB\-o\fP (or) operators are replaced with
+\fB&&\fP and \fB||\fP, respectively.
+.IP \ \ \(bu
+Operators (\fIe.g.\fP, \fB\-f\fP, \fB=\fP, \fB!\fP, \fIetc.\fP) must be unquoted.
+.IP \ \ \(bu
+The second operand of \fB!=\fP and \fB=\fP
+expressions are patterns (\fIe.g.\fP, the comparison in
+.ce
+\fB[[ foobar = f*r ]]\fP
+succeeds).
+.IP \ \ \(bu
+There are two additional binary operators: \fB<\fP and \fB>\fP
+which return true if their first string operand is less than,
+or greater than, their second string operand, respectively.
+.IP \ \ \(bu
+The single argument form
+of \fBtest\fP, which tests if the argument has non-zero length, is not valid
+- explicit operators must always be used, \fIe.g.\fP, instead of
+.ce
+\fB[\fP \fIstr\fP \fB]\fP
+use
+.ce
+\fB[[ \-n \fP\fIstr\fP\fB ]]\fP
+.IP \ \ \(bu
+Parameter, command and arithmetic substitutions are performed as
+expressions are evaluated and lazy expression evaluation is used for
+the \fB&&\fP and \fB||\fP operators.
+This means that in the statement
+.ce
+\fB[[ -r foo && $(< foo) = b*r ]]\fP
+the \fB$(< foo)\fP is evaluated if and only if the file \fBfoo\fP exists
+and is readable.
+.nr PD \n(P2
+.RE
+.ksh)
+.\"}}}
+.\"}}}
+.\"}}}
+.\"{{{  Quoting
+.SS Quoting
+Quoting is used to prevent the shell from treating characters or words
+specially.
+There are three methods of quoting: First, \fB\e\fP quotes
+the following character, unless it is at the end of a line, in which
+case both the \fB\e\fP and the newline are stripped.
+Second, a single quote (\fB'\fP) quotes everything up to the next single
+quote (this may span lines).
+Third, a double quote (\fB"\fP) quotes all characters,
+except \fB$\fP, \fB`\fP and \fB\e\fP, up to the next unquoted double quote.
+\fB$\fP and \fB`\fP inside double quotes have their usual meaning (\fIi.e.\fP,
+parameter, command or arithmetic substitution) except no field splitting
+is carried out on the results of double-quoted substitutions.
+If a \fB\e\fP inside a double-quoted string is followed by \fB\e\fP, \fB$\fP,
+\fB`\fP or \fB"\fP, it is replaced by the second character; if it is
+followed by a newline, both the \fB\e\fP and the newline are stripped;
+otherwise, both the \fB\e\fP and the character following are unchanged.
+.PP
+Note: An earlier version of ksh(1) changed the interpretation of sequences
+of the form \fB"\fP...\fB`\fP...\fB\e"\fP...\fB`\fP..\fB"\fP
+according to whether or not POSIX mode was in effect.
+In the current implementation, the backslash in \fB\e"\fP
+is seen and removed by the outer \fB"\fP...\fB"\fP,
+so the backslash is not seen by the inner \fB`\fP...\fB`\fP.
+.\"}}}
+.\"{{{  Aliases
+.SS "Aliases"
+There are two types of aliases: normal command aliases and tracked aliases.
+Command aliases are normally used as a short hand for a long
+or often used command.
+The shell expands command aliases (\fIi.e.\fP,
+substitutes the alias name for its value) when it reads the first word
+of a command.
+An expanded alias is re-processed to check for more
+aliases.
+If a command alias ends in a space or tab, the following word
+is also checked for alias expansion.
+The alias expansion process stops
+when a word that is not an alias is found, when a quoted word is found
+or when an alias word that is currently being expanded is found.
+.PP
+The following command aliases are defined automatically by the shell:
+.ft B
+.RS
+.ksh(
+autoload='typeset \-fu'
+.br
+functions='typeset \-f'
+.br
+.ksh)
+hash='alias \-t'
+.ksh(
+.br
+history='fc \-l'
+.br
+integer='typeset \-i'
+.br
+local='typeset'
+.br
+login='exec login'
+.\" ifndef __NetBSD__
+.\" .br
+.\" newgrp='exec newgrp'
+.\" endif
+.br
+nohup='nohup '
+.br
+r='fc \-e \-'
+.br
+stop='kill \-STOP'
+.br
+suspend='kill \-STOP $$'
+.ksh)
+.br
+type='whence \-v'
+.RE
+.ft P
+.PP
+Tracked aliases allow the shell to remember where it found a particular
+command.
+The first time the shell does a path search for a command that
+is marked as a tracked alias, it saves the full path of the command.
+The next time the command is executed, the shell checks the saved path
+to see that it is still valid, and if so, avoids repeating the path search.
+Tracked aliases can be listed and created using \fBalias \-t\fP.
+Note that changing the \fBPATH\fP parameter clears the saved
+paths for all tracked aliases.
+If the \fBtrackall\fP option is set (\fIi.e.\fP,
+\fBset \-o trackall\fP or \fBset \-h\fP), the shell tracks all commands.
+This option is set automatically for non-interactive shells.
+For interactive shells, only the following commands are automatically
+tracked: \fBcat\fP, \fBcc\fP, \fBchmod\fP, \fBcp\fP, \fBdate\fP, \fBed\fP,
+\fBemacs\fP, \fBgrep\fP, \fBls\fP, \fBmail\fP, \fBmake\fP, \fBmv\fP,
+\fBpr\fP, \fBrm\fP, \fBsed\fP, \fBsh\fP, \fBvi\fP and \fBwho\fP.
+.\"}}}
+.\"{{{  Substitution
+.SS "Substitution"
+The first step the shell takes in executing a simple-command is to
+perform substitutions on the words of the command.
+There are three kinds of substitution: parameter, command and arithmetic.
+Parameter substitutions, which are described in detail in the next section,
+take the form \fB$name\fP or \fB${\fP...\fB}\fP; command substitutions take
+the form \fB$(\fP\fIcommand\fP\fB)\fP or \fB`\fP\fIcommand\fP\fB`\fP;
+and arithmetic substitutions take the form \fB$((\fP\fIexpression\fP\fB))\fP.
+.PP
+If a substitution appears outside of double quotes, the results of the
+substitution are generally subject to word or field splitting according to
+the current value of the \fBIFS\fP parameter.
+The \fBIFS\fP parameter specifies a list of characters which
+are used to break a string up into several words;
+any characters from the set space, tab and newline that appear in the
+IFS characters are called \fIIFS white space\fP.
+Sequences of one or more IFS white space characters, in combination with
+zero or one non-IFS white space characters delimit a field.
+As a special case, leading and trailing IFS white space is stripped (\fIi.e.\fP,
+no leading or trailing empty field is created by it); leading or trailing
+non-IFS white space does create an empty field.
+Example: if \fBIFS\fP is set to `<space>:', the sequence of characters
+`<space>A<space>:<space><space>B::D' contains four fields: `A', `B', `' and `D'.
+Note that if the \fBIFS\fP parameter is set to the null string, no
+field splitting is done; if the parameter is unset, the default value
+of space, tab and newline is used.
+.PP
+The results of substitution are, unless otherwise specified, also subject
+to brace expansion and file name expansion (see the relevant sections
+below).
+.PP
+A command substitution is replaced by the output generated by the specified
+command, which is run in a subshell.
+For \fB$(\fP\fIcommand\fP\fB)\fP substitutions, normal quoting rules
+are used when \fIcommand\fP is parsed, however, for the
+\fB`\fP\fIcommand\fP\fB`\fP form, a \fB\e\fP followed by any of
+\fB$\fP, \fB`\fP or \fB\e\fP is stripped (a \fB\e\fP followed by any other
+character is unchanged).
+As a special case in command substitutions, a command of the form
+\fB<\fP \fIfile\fP is interpreted to mean substitute the contents
+of \fIfile\fP ($(< foo) has the same effect as $(cat foo), but it
+is carried out more efficiently because no process is started).
+.br
+.\"todo: fix this( $(..) parenthesis counting).
+NOTE: \fB$(\fP\fIcommand\fP\fB)\fP expressions are currently parsed by
+finding the matching parenthesis, regardless of quoting.
+This will hopefully be fixed soon.
+.PP
+Arithmetic substitutions are replaced by the value of the specified
+expression.
+For example, the command \fBecho $((2+3*4))\fP prints 14.
+See Arithmetic Expressions for a description of an \fIexpression\fP.
+.\"}}}
+.\"{{{  Parameters
+.SS "Parameters"
+Parameters are shell variables; they can be assigned values and
+their values can be accessed using a parameter substitution.
+A parameter name is either one of the special single punctuation or digit
+character parameters described below, or a letter followed by zero or more
+letters or digits (`_' counts as a letter).
+The later form can be treated as arrays by appending an array
+index of the form: \fB[\fP\fIexpr\fP\fB]\fP where \fIexpr\fP is
+an arithmetic expression.
+Array indices are currently limited to the range 0 through 1023, inclusive.
+Parameter substitutions take the form \fB$\fP\fIname\fP,
+\fB${\fP\fIname\fP\fB}\fP or
+\fB${\fP\fIname\fP\fB[\fP\fIexpr\fP\fB]}\fP, where \fIname\fP is a
+parameter name.
+If substitution is performed on a parameter (or an array parameter element)
+that is not set, a null
+string is substituted unless the \fBnounset\fP option (\fBset \-o nounset\fP
+or \fBset \-u\fP) is set, in which case an error occurs.
+.PP
+.\"{{{  parameter assignment
+Parameters can be assigned values in a number of ways.
+First, the shell implicitly sets some parameters like \fB#\fP, \fBPWD\fP,
+etc.; this is the only way the special single character parameters are
+set.
+Second, parameters are imported from the shell's environment at startup.
+Third, parameters can be assigned values on the command line, for example,
+`\fBFOO=bar\fP' sets the parameter FOO to bar; multiple parameter
+assignments can be given on a single command line and they can
+be followed by a simple-command, in which case the assignments are
+in effect only for the duration of the command (such assignments are
+also exported, see below for implications of this).
+Note that both the parameter name and the \fB=\fP must be unquoted for
+the shell to recognize a parameter assignment.
+The fourth way of setting a parameter is with the \fBexport\fP, \fBreadonly\fP
+and \fBtypeset\fP commands; see their descriptions in the Command Execution
+section.
+Fifth, \fBfor\fP and \fBselect\fP loops set parameters as well as
+the \fBgetopts\fP, \fBread\fP and \fBset \-A\fP commands.
+Lastly, parameters can be assigned values using assignment operators
+inside arithmetic expressions (see Arithmetic Expressions below) or
+using the \fB${\fP\fIname\fP\fB=\fP\fIvalue\fP\fB}\fP form
+of parameter substitution (see below).
+.\"}}}
+.PP
+.\"{{{  environment
+Parameters with the export attribute (set using the \fBexport\fP or
+\fBtypeset \-x\fP commands, or by parameter assignments followed by simple
+commands) are put in the environment (see \fIenviron\fP(7)) of commands
+run by the shell as \fIname\fP\fB=\fP\fIvalue\fP pairs.
+The order in which parameters appear in the environment of a command
+is unspecified.
+When the shell starts up, it extracts parameters and their values from its
+environment and automatically sets the export attribute for those parameters.
+.\"}}}
+.\"{{{  ${name[:][-+=?]word}
+.PP
+Modifiers can be applied to the \fB${\fP\fIname\fP\fB}\fP form of parameter
+substitution:
+.IP \fB${\fP\fIname\fP\fB:-\fP\fIword\fP\fB}\fP
+if \fIname\fP is set and not null, it is substituted, otherwise \fIword\fP is
+substituted.
+.IP \fB${\fP\fIname\fP\fB:+\fP\fIword\fP\fB}\fP
+if \fIname\fP is set and not null, \fIword\fP is substituted, otherwise nothing is substituted.
+.IP \fB${\fP\fIname\fP\fB:=\fP\fIword\fP\fB}\fP
+if \fIname\fP is set and not null, it is substituted, otherwise it is
+assigned \fIword\fP and the resulting value of \fIname\fP is substituted.
+.IP \fB${\fP\fIname\fP\fB:?\fP\fIword\fP\fB}\fP
+if \fIname\fP is set and not null, it is substituted, otherwise \fIword\fP
+is printed on standard error (preceded by \fIname\fP:) and an error occurs
+(normally causing termination of a shell script, function or \&.-script).
+If word is omitted the string `parameter null or not set' is used instead.
+.PP
+In the above modifiers, the \fB:\fP can be omitted, in which case the
+conditions only depend on \fIname\fP being set (as opposed to set and
+not null).
+If \fIword\fP is needed, parameter, command, arithmetic and tilde substitution
+are performed on it; if \fIword\fP is not needed, it is not evaluated.
+.\"}}}
+.PP
+The following forms of parameter substitution can also be used:
+.\"{{{  ${#name}
+.IP \fB${#\fP\fIname\fP\fB}\fP
+The number of positional parameters if \fIname\fP is \fB*\fP, \fB@\fP or
+is not specified,
+or the length of the string value of parameter \fIname\fP.
+.\"}}}
+.\"{{{  ${#name[*]}, ${#name[@]}
+.IP "\fB${#\fP\fIname\fP\fB[*]}\fP, \fB${#\fP\fIname\fP\fB[@]}\fP"
+The number of elements in the array \fIname\fP.
+.\"}}}
+.\"{{{  ${name#pattern}, ${name##pattern}
+.IP "\fB${\fP\fIname\fP\fB#\fP\fIpattern\fP\fB}\fP, \fB${\fP\fIname\fP\fB##\fP\fIpattern\fP\fB}\fP"
+If \fIpattern\fP matches the beginning of the value of parameter \fIname\fP,
+the matched text is deleted from the result of substitution.
+A single \fB#\fP results in the shortest match, two \fB#\fP's results in the
+longest match.
+.\"}}}
+.\"{{{  ${name%pattern}, ${name%%pattern}
+.IP "\fB${\fP\fIname\fP\fB%\fP\fIpattern\fP\fB}\fP, \fB${\fP\fIname\fP\fB%%\fP\fIpattern\fP\fB}\fP"
+Like \fB${\fP..\fB#\fP..\fB}\fP substitution, but it deletes from the end of the
+value.
+.\"}}}
+.\"{{{  special shell parameters
+.PP
+The following special parameters are implicitly set by the shell and cannot be
+set directly using assignments:
+.\"{{{  !
+.IP \fB!\fP
+Process id of the last background process started.
+If no background processes have been started, the parameter is not set.
+.\"}}}
+.\"{{{  #
+.IP \fB#\fP
+The number of positional parameters (\fIi.e.\fP, \fB$1\fP, \fB$2\fP,
+\fIetc.\fP).
+.\"}}}
+.\"{{{  $
+.IP \fB$\fP
+The process ID of the shell, or the PID of the original shell if
+it is a subshell.
+.\"}}}
+.\"{{{  -
+.IP \fB\-\fP
+The concatenation of the current single letter options
+(see \fBset\fP command below for list of options).
+.\"}}}
+.\"{{{  ?
+.IP \fB?\fP
+The exit status of the last non-asynchronous command executed.
+If the last command was killed by a signal, \fB$?\fP is set to 128 plus
+the signal number.
+.\"}}}
+.\"{{{  0
+.IP "\fB0\fP"
+The name the shell was invoked with (\fIi.e.\fP, \fBargv[0]\fP), or the
+\fBcommand-name\fP if it was invoked with the \fB\-c\fP option and the
+\fBcommand-name\fP was supplied, or the \fIfile\fP argument, if it was
+supplied.
+If the \fBposix\fP option is not set, \fB$0\fP is the name of the current
+function or script.
+.\"}}}
+.\"{{{  1-9
+.IP "\fB1\fP ... \fB9\fP"
+The first nine positional parameters that were supplied to the shell,
+function or \fB.\fP-script.
+Further positional parameters may be accessed using
+\fB${\fP\fInumber\fP\fB}\fP.
+.\"}}}
+.\"{{{  *
+.IP \fB*\fP
+All positional parameters (except parameter 0),
+\fIi.e.\fP, \fB$1 $2 $3\fP....
+If used outside of double quotes, parameters are separate words
+(which are subjected to word splitting); if used within double quotes,
+parameters are separated by the first character of the \fBIFS\fP parameter
+(or the empty string if \fBIFS\fP is null).
+.\"}}}
+.\"{{{  @
+.IP \fB@\fP
+Same as \fB$*\fP, unless it is used inside double quotes, in which case
+a separate word is generated for each positional parameter \- if there
+are no positional parameters, no word is generated ("$@" can be used
+to access arguments, verbatim, without losing null arguments or
+splitting arguments with spaces).
+.\"}}}
+.\"}}}
+.\"{{{  general shell parameters
+.PP
+The following parameters are set and/or used by the shell:
+.\"{{{  _
+.ksh(
+.IP "\fB_\fP \fI(underscore)\fP"
+When an external command is executed by the shell, this parameter is
+set in the environment of the new process to the path of the executed
+command.
+In interactive use, this parameter is also set in the parent shell to
+the last word of the previous command.
+When \fBMAILPATH\fP messages are evaluated, this parameter contains
+the name of the file that changed (see \fBMAILPATH\fP parameter below).
+.ksh)
+.\"}}}
+.\"{{{  CDPATH
+.IP \fBCDPATH\fP
+Search path for the \fBcd\fP built-in command.
+Works the same way as
+\fBPATH\fP for those directories not beginning with \fB/\fP in \fBcd\fP
+commands.
+Note that if CDPATH is set and does not contain \fB.\fP nor an empty path,
+the current directory is not searched.
+.\"}}}
+.\"{{{  COLUMNS
+.IP \fBCOLUMNS\fP
+Set to the number of columns on the terminal or window.
+Currently set to the \fBcols\fP value as reported by \fIstty\fP(1) if that
+value is non-zero.
+This parameter is used by the interactive line editing modes, and by
+\fBselect\fP, \fBset \-o\fP and \fBkill \-l\fP commands
+to format information in columns.
+.\"}}}
+.\"{{{  EDITOR
+.ksh(
+.IP \fBEDITOR\fP
+If the \fBVISUAL\fP parameter is not set, this parameter controls the
+command line editing mode for interactive shells.
+See \fBVISUAL\fP parameter below for how this works.
+.ksh)
+.\"}}}
+.\"{{{  ENV
+.IP \fBENV\fP
+If this parameter is found to be set after any profile files are
+executed, the expanded value is used as a shell start-up file.
+It typically contains function and alias definitions.
+.\"}}}
+.\"{{{  ERRNO
+.IP \fBERRNO\fP
+Integer value of the shell's errno variable \(em indicates the reason
+the last system call failed.
+.\" todo: ERRNO variable
+.sp
+Not implemented yet.
+.\"}}}
+.\"{{{  EXECSHELL
+.IP \fBEXECSHELL\fP
+If set, this parameter is assumed to contain the shell that is to be
+used to execute commands that \fIexecve\fP(2) fails to execute and
+which do not start with a `\fB#!\fP \fIshell\fP' sequence.
+.\"}}}
+.\"{{{  FCEDIT
+.IP \fBFCEDIT\fP
+The editor used by the \fBfc\fP command (see below).
+.\"}}}
+.\"{{{  FPATH
+.IP \fBFPATH\fP
+Like \fBPATH\fP, but used when an undefined function is executed to locate
+the file defining the function.
+It is also searched when a command can't be found using \fBPATH\fP.
+See Functions below for more information.
+.\"}}}
+.\"{{{  HISTFILE
+.ksh(
+.IP \fBHISTFILE\fP
+The name of the file used to store history.
+When assigned to, history is loaded from the specified file.
+Also, several invocations of the
+shell running on the same machine will share history if their
+\fBHISTFILE\fP parameters all point at the same file.
+.br
+NOTE: if HISTFILE isn't set, no history file is used.
+This is
+different from the original Korn shell, which uses \fB$HOME/.sh_history\fP;
+in future, pdksh may also use a default history file.
+.ksh)
+.\"}}}
+.\"{{{  HISTSIZE
+.ksh(
+.IP \fBHISTSIZE\fP
+The number of commands normally stored for history, default 128.
+.ksh)
+.\"}}}
+.\"{{{  HOME
+.IP \fBHOME\fP
+The default directory for the \fBcd\fP command and the value
+substituted for an unqualified \fB~\fP (see Tilde Expansion below).
+.\"}}}
+.\"{{{  IFS
+.IP \fBIFS\fP
+Internal field separator, used during substitution and by the \fBread\fP
+command, to split values into distinct arguments; normally set to
+space, tab and newline.
+See Substitution above for details.
+.br
+\fBNote:\fP this parameter is not imported from the environment
+when the shell is started.
+.\"}}}
+.\"{{{  KSH_VERSION
+.ksh(
+.IP \fBKSH_VERSION\fP
+The version of shell and the date the version was created (readonly).
+See also the version commands in Emacs Editing Mode
+and Vi Editing Mode sections, below.
+.ksh)
+.\"}}}
+.\"{{{  SH_VERSION
+.sh(
+.IP \fBSH_VERSION\fP
+The version of shell and the date the version was created (readonly).
+.sh)
+.\"}}}
+.\"{{{  LINENO
+.IP \fBLINENO\fP
+The line number of the function or shell script that is currently being
+executed.
+.\"}}}
+.\"{{{  LINES
+.IP \fBLINES\fP
+Set to the number of lines on the terminal or window.
+.\"Currently set to the \fBrows\fP value as reported by \fIstty\fP(1) if that
+.\"value is non-zero.
+.\" todo: LINES variable
+.sp
+Not implemented yet.
+.\"}}}
+.\"{{{  MAIL
+.ksh(
+.IP \fBMAIL\fP
+If set, the user will be informed of the arrival of mail in the named file.
+This parameter is ignored if the \fBMAILPATH\fP parameter is set.
+.ksh)
+.\"}}}
+.\"{{{  MAILCHECK
+.ksh(
+.IP \fBMAILCHECK\fP
+How often, in seconds, the shell will check for mail in the file(s)
+specified by \fBMAIL\fP or \fBMAILPATH\fP.
+If 0, the shell checks before each prompt.
+The default is 600 (10 minutes).
+.ksh)
+.\"}}}
+.\"{{{  MAILPATH
+.ksh(
+.IP \fBMAILPATH\fP
+A list of files to be checked for mail.
+The list is colon separated,
+and each file may be followed by a \fB?\fP and a message to be printed
+if new mail has arrived.
+Command, parameter and arithmetic substitution is
+performed on the message, and, during substitution, the parameter \fB$_\fP
+contains the name of the file.
+The default message is \fByou have mail in $_\fP.
+.ksh)
+.\"}}}
+.\"{{{  OLDPWD
+.IP \fBOLDPWD\fP
+The previous working directory.
+Unset if \fBcd\fP has not successfully changed directories since the
+shell started, or if the shell doesn't know where it is.
+.\"}}}
+.\"{{{  OPTARG
+.IP \fBOPTARG\fP
+When using \fBgetopts\fP, it contains the argument for a parsed option,
+if it requires one.
+.\"}}}
+.\"{{{  OPTIND
+.IP \fBOPTIND\fP
+The index of the last argument processed when using \fBgetopts\fP.
+Assigning 1 to this parameter causes \fBgetopts\fP to
+process arguments from the beginning the next time it is invoked.
+.\"}}}
+.\"{{{  PATH
+.IP \fBPATH\fP
+A colon separated list of directories that are searched when looking
+for commands and \fB.\fP'd files.
+An empty string resulting from a leading or trailing colon, or two adjacent
+colons is treated as a `.', the current directory.
+.\"}}}
+.\"{{{  POSIXLY_CORRECT
+.IP \fBPOSIXLY_CORRECT\fP
+If set, this parameter causes the \fBposix\fP option to be enabled.
+See POSIX Mode below.
+.\"}}}
+.\"{{{  PPID
+.IP \fBPPID\fP
+The process ID of the shell's parent (readonly).
+.\"}}}
+.\"{{{  PS1
+.IP \fBPS1\fP
+\fBPS1\fP is the primary prompt for interactive shells.
+.ksh(
+Parameter, command and arithmetic substitutions are performed, and
+\fB!\fP is replaced with the current command number (see \fBfc\fP
+command below).
+A literal ! can be put in the prompt by placing !! in PS1.
+Note that since the command line editors try to figure out how long the
+prompt is (so they know how far it is to edge of the screen),
+escape codes in the prompt tend to mess things up.
+You can tell the shell not to count certain sequences (such as escape codes)
+by prefixing your prompt with a non-printing character (such as control-A)
+followed by a carriage return and then delimiting the escape codes with
+this non-printing character.
+If you don't have any non-printing characters, you're out of luck...
+BTW, don't blame me for this hack; it's in the original ksh.
+.ksh)
+.sh(
+The prompt is printed verbatim (\fIi.e.\fP, no substitutions are done).
+.sh)
+Default is `\fB$\ \fP' for non-root users, `\fB#\ \fP' for root.
+.\"}}}
+.\"{{{  PS2
+.IP \fBPS2\fP
+Secondary prompt string, by default `\fB>\fP ', used when more input is
+needed to complete a command.
+.\"}}}
+.\"{{{  PS3
+.ksh(
+.IP \fBPS3\fP
+Prompt used by \fBselect\fP statement when reading a menu selection.
+Default is `\fB#?\ \fP'.
+.ksh)
+.\"}}}
+.\"{{{  PS4
+.IP \fBPS4\fP
+Used to prefix commands that are printed during execution tracing
+(see \fBset \-x\fP command below).
+.ksh(
+Parameter, command and arithmetic substitutions are performed
+before it is printed.
+.ksh)
+.sh(
+The prompt is printed verbatim (\fIi.e.\fP, no substitutions are done).
+.sh)
+Default is `\fB+\ \fP'.
+.\"}}}
+.\"{{{  PWD
+.IP \fBPWD\fP
+The current working directory.
+Maybe unset or null if shell doesn't know where it is.
+.\"}}}
+.\"{{{  RANDOM
+.ksh(
+.IP \fBRANDOM\fP
+A simple random number generator.
+Every time \fBRANDOM\fP is
+referenced, it is assigned the next number in a random number series.
+The point in the series can be set by assigning a number to
+\fBRANDOM\fP (see \fIrand\fP(3)).
+.ksh)
+.\"}}}
+.\"{{{  REPLY
+.IP \fBREPLY\fP
+Default parameter for the \fBread\fP command if no names are given.
+Also used in \fBselect\fP loops to store the value that is read from
+standard input.
+.\"}}}
+.\"{{{  SECONDS
+.ksh(
+.IP \fBSECONDS\fP
+The number of seconds since the shell started or, if the parameter has been
+assigned an integer value, the number of seconds since the assignment plus
+the value that was assigned.
+.ksh)
+.\"}}}
+.\"{{{  TMOUT
+.ksh(
+.IP \fBTMOUT\fP
+If set to a positive integer in an interactive shell, it specifies
+the maximum number of seconds the shell will wait for input after
+printing the primary prompt (\fBPS1\fP).
+If the time is exceeded, the shell exits.
+.ksh)
+.\"}}}
+.\"{{{  TMPDIR
+.IP \fBTMPDIR\fP
+The directory shell temporary files are created in.
+If this parameter is not set, or does not contain the absolute path of a
+writable directory, temporary files are created in \fB/tmp\fP.
+.\"}}}
+.\"{{{  VISUAL
+.ksh(
+.IP \fBVISUAL\fP
+If set, this parameter controls the command line editing mode for
+interactive shells.
+If the last component of the path specified in this
+parameter contains the string \fBvi\fP, \fBemacs\fP or \fBgmacs\fP, the
+vi, emacs or gmacs (Gosling emacs) editing mode is enabled, respectively.
+.ksh)
+.\"}}}
+.\"}}}
+.\"}}}
+.\"{{{  Tilde Expansion
+.SS "Tilde Expansion"
+Tilde expansion, which is done in parallel with parameter substitution,
+is done on words starting with an unquoted \fB~\fP.
+The characters following the tilde, up to the first \fB/\fP, if any,
+are assumed to be a login name.
+If the login name is empty, \fB+\fP or \fB\-\fP, the
+value of the \fBHOME\fP, \fBPWD\fP, or \fBOLDPWD\fP parameter is
+substituted, respectively.
+Otherwise, the password file is searched for the login name, and the
+tilde expression is substituted with the user's home directory.
+If the login name is not found in the password
+file or if any quoting or parameter substitution occurs in the login name,
+no substitution is performed.
+.PP
+In parameter assignments (those preceding a simple-command or those
+occurring in the arguments of \fBalias\fP, \fBexport\fP, \fBreadonly\fP,
+and \fBtypeset\fP), tilde expansion is done after any unquoted colon
+(\fB:\fP), and login names are also delimited by colons.
+.PP
+The home directory of previously expanded login names are cached and
+re-used.
+The \fBalias \-d\fP command may be used to list, change and
+add to this cache (\fIe.g.\fP, `alias \-d fac=/usr/local/facilities; cd
+~fac/bin').
+.\"}}}
+.\"{{{  Brace Expansion
+.ksh(
+.SS "Brace Expansion (alternation)"
+Brace expressions, which take the form
+.RS
+\fIprefix\fP\fB{\fP\fIstr\fP1\fB,\fP...\fB,\fP\fIstr\fPN\fB}\fP\fIsuffix\fP
+.RE
+are expanded to N words, each of which is the concatenation of
+\fIprefix\fP, \fIstr\fPi and \fIsuffix\fP
+(\fIe.g.\fP, `a{c,b{X,Y},d}e' expands to four word: ace, abXe, abYe, and ade).
+As noted in the example, brace expressions can be nested and the resulting
+words are not sorted.
+Brace expressions must contain an unquoted comma (\fB,\fP) for expansion
+to occur (\fIi.e.\fP, \fB{}\fP and \fB{foo}\fP are not expanded).
+Brace expansion is carried out after parameter substitution and before
+file name generation.
+.ksh)
+.\"}}}
+.\"{{{  File Name Patterns
+.SS "File Name Patterns"
+.PP
+A file name pattern is a word containing one or more unquoted \fB?\fP or
+\fB*\fP characters or \fB[\fP..\fB]\fP sequences.
+Once brace expansion has
+been performed, the shell replaces file name patterns with the sorted names
+of all the files that match the pattern (if no files match, the word is
+left unchanged).
+The pattern elements have the following meaning:
+.IP \fB?\fP
+matches any single character.
+.IP \fB*\fP
+matches any sequence of characters.
+.IP \fB[\fP..\fB]\fP
+matches any of the characters inside the brackets.
+Ranges of characters
+can be specified by separating two characters by a \fB\-\fP, \fIe.g.\fP,
+\fB[a0\-9]\fP matches the letter \fBa\fP or any digit.
+In order to represent itself, a
+\fB\-\fP must either be quoted or the first or last character in the character
+list.
+Similarly, a \fB]\fP must be quoted or the first character in the list
+if it is represent itself instead of the end of the list.
+Also, a \fB!\fP
+appearing at the start of the list has special meaning (see below), so to
+represent itself it must be quoted or appear later in the list.
+.IP \fB[!\fP..\fB]\fP
+like \fB[\fP..\fB]\fP, except it matches any character not inside the brackets.
+.ksh(
+.IP "\fB*(\fP\fIpattern\fP\fB|\fP ... \fP|\fP\fIpattern\fP\fB)\fP"
+matches any string of characters that matches zero or more occurrences
+of the specified patterns.
+Example: the pattern \fB*(foo|bar)\fP matches the strings
+`', `foo', `bar', `foobarfoo', \fIetc.\fP.
+.IP "\fB+(\fP\fIpattern\fP\fB|\fP ... \fP|\fP\fIpattern\fP\fB)\fP"
+matches any string of characters that matches one or more occurrences
+of the specified patterns.
+Example: the pattern \fB+(foo|bar)\fP matches the strings
+`foo', `bar', `foobarfoo', \fIetc.\fP.
+.IP "\fB?(\fP\fIpattern\fP\fB|\fP ... \fP|\fP\fIpattern\fP\fB)\fP"
+matches the empty string or a string that matches one of the
+specified patterns.
+Example: the pattern \fB?(foo|bar)\fP only matches the strings
+`', `foo' and `bar'.
+.IP "\fB@(\fP\fIpattern\fP\fB|\fP ... \fP|\fP\fIpattern\fP\fB)\fP"
+matches a string that matches one of the
+specified patterns.
+Example: the pattern \fB@(foo|bar)\fP only matches the strings
+`foo' and `bar'.
+.IP "\fB!(\fP\fIpattern\fP\fB|\fP ... \fP|\fP\fIpattern\fP\fB)\fP"
+matches any string that does not match one of the specified
+patterns.
+Examples: the pattern \fB!(foo|bar)\fP matches all strings except
+`foo' and `bar'; the pattern \fB!(*)\fP matches no strings;
+the pattern \fB!(?)*\fP matches all strings (think about it).
+.ksh)
+.PP
+Note that pdksh currently never matches \fB.\fP and \fB..\fP, but the original
+ksh, Bourne sh and bash do, so this may have to change (too bad).
+.PP
+Note that none of the above pattern elements match either a period (\fB.\fP)
+at the start of a file name or a slash (\fB/\fP), even if they are explicitly
+used in a \fB[\fP..\fB]\fP sequence; also, the names \fB.\fP and \fB..\fP
+are never matched, even by the pattern \fB.*\fP.
+.PP
+If the \fBmarkdirs\fP option is set, any directories that result from
+file name generation are marked with a trailing \fB/\fP.
+.PP
+.\" todo: implement this ([[:alpha:]], \fIetc.\fP)
+The POSIX character classes (\fIi.e.\fP,
+\fB[:\fP\fIclass-name\fP\fB:]\fP inside a \fB[\fP..\fB]\fP expression)
+are not yet implemented.
+.\"}}}
+.\"{{{  Input/Output Redirection
+.SS "Input/Output Redirection"
+When a command is executed, its standard input, standard output and
+standard error (file descriptors 0, 1 and 2, respectively) are normally
+inherited from the shell.
+Three exceptions to this are commands in pipelines, for which standard input
+and/or standard output are those set up by the pipeline, asynchronous commands
+created when job control is disabled, for which standard input is initially
+set to be from \fB/dev/null\fP, and commands for which any of the following
+redirections have been specified:
+.IP "\fB>\fP \fIfile\fP"
+standard output is redirected to \fIfile\fP.
+If \fIfile\fP does not exist,
+it is created; if it does exist, is a regular file and the \fBnoclobber\fP
+option is set, an error occurs, otherwise the file is truncated.
+Note that this means the command \fIcmd < foo > foo\fP will open
+\fIfoo\fP for reading and then truncate it when it opens it for writing,
+before \fIcmd\fP gets a chance to actually read \fIfoo\fP.
+.IP "\fB>|\fP \fIfile\fP"
+same as \fB>\fP, except the file is truncated, even if the \fBnoclobber\fP
+option is set.
+.IP "\fB>>\fP \fIfile\fP"
+same as \fB>\fP, except the file an existing file is appended to instead
+of being truncated.
+Also, the file is opened in append mode, so writes
+always go to the end of the file (see \fIopen\fP(2)).
+.IP "\fB<\fP \fIfile\fP"
+standard input is redirected from \fIfile\fP, which is opened for reading.
+.IP "\fB<>\fP \fIfile\fP"
+same as \fB<\fP, except the file is opened for reading and writing.
+.IP "\fB<<\fP \fImarker\fP"
+after reading the command line containing this kind of redirection (called a
+here document), the shell copies lines from the command source into a temporary
+file until a line matching \fImarker\fP is read.
+When the command is executed, standard input is redirected from the temporary
+file.
+If \fImarker\fP contains no quoted characters, the contents of the
+temporary file are processed as if enclosed in double quotes each time
+the command is executed, so parameter, command and arithmetic substitutions
+are performed, along with backslash (\fB\e\fP) escapes for
+\fB$\fP, \fB`\fP, \fB\e\fP and \fB\enewline\fP.
+If multiple here documents are used on the same command line, they are
+saved in order.
+.IP "\fB<<-\fP \fImarker\fP"
+same as \fB<<\fP, except leading tabs are stripped from lines in the
+here document.
+.IP "\fB<&\fP \fIfd\fP"
+standard input is duplicated from file descriptor \fIfd\fP.
+\fIfd\fP can be a single digit, indicating the number of an existing
+file descriptor, the letter \fBp\fP, indicating the file descriptor
+associated with the output of the current co-process, or
+the character \fB\-\fP, indicating standard input is to be closed.
+.IP "\fB>&\fP \fIfd\fP"
+same as \fB<&\fP, except the operation is done on standard output.
+.PP
+In any of the above redirections, the file descriptor that is redirected
+(\fIi.e.\fP, standard input or standard output) can be explicitly given by
+preceding the redirection with a single digit.
+Parameter, command and arithmetic substitutions, tilde substitutions and
+(if the shell is interactive) file name generation are all performed
+on the \fIfile\fP, \fImarker\fP and \fIfd\fP arguments of redirections.
+Note however, that the results of any file name generation are only used
+if a single file is matched; if multiple files match, the word with the
+unexpanded file name generation characters is used.
+Note that in restricted shells, redirections which can create files cannot
+be used.
+.PP
+For simple-commands, redirections may appear anywhere in the command, for
+compound-commands (\fBif\fP statements, \fIetc.\fP), any redirections must
+appear at the end.
+Redirections are processed after pipelines are created and in the order they
+are given, so
+.RS
+\fBcat /foo/bar 2>&1 > /dev/null | cat \-n\fP
+.RE
+will print an error with a line number prepended to it.
+.\"}}}
+.\"{{{  Arithmetic Expressions
+.SS "Arithmetic Expressions"
+Integer arithmetic expressions can be used
+.ksh(
+with the \fBlet\fP command,
+.ksh)
+inside \fB$((\fP..\fB))\fP expressions,
+inside array references (\fIe.g.\fP, \fIname\fP\fB[\fP\fIexpr\fP\fB]\fP),
+as numeric arguments to the \fBtest\fP command,
+and as the value of an assignment to an integer parameter.
+.PP
+Expression may contain alpha-numeric parameter identifiers, array
+references, and integer constants and may be combined with the
+following C operators (listed and grouped in increasing order of precedence).
+.TP
+Unary operators:
+\fB+ \- ! ~ ++ --\fP
+.TP
+Binary operators:
+\fB,\fP
+.br
+\fB= *= /= %= += \-= <<= >>= &= ^= |=\fP
+.br
+\fB||\fP
+.br
+\fB&&\fP
+.br
+\fB|\fP
+.br
+\fB^\fP
+.br
+\fB&\fP
+.br
+\fB== !=\fP
+.br
+\fB< <= >= >\fP
+.br
+\fB<< >>\fP
+.br
+\fB+ \-\fP
+.br
+\fB* / %\fP
+.TP
+Ternary operator:
+\fB?:\fP (precedence is immediately higher than assignment)
+.TP
+Grouping operators:
+\fB( )\fP
+.PP
+Integer constants may be specified with arbitrary bases using the notation
+\fIbase\fP\fB#\fP\fInumber\fP, where \fIbase\fP is a decimal integer specifying
+the base, and \fInumber\fP is a number in the specified base.
+.LP
+The operators are evaluated as follows:
+.RS
+.IP "unary \fB+\fP"
+result is the argument (included for completeness).
+.IP "unary \fB\-\fP"
+negation.
+.IP "\fB!\fP"
+logical not; the result is 1 if argument is zero, 0 if not.
+.IP "\fB~\fP"
+arithmetic (bit-wise) not.
+.IP "\fB++\fP"
+increment; must be applied to a parameter (not a literal or other
+expression) - the parameter is incremented by 1.
+When used as a prefix operator, the result is the incremented value of
+the parameter, when used as a postfix operator, the result is the
+original value of the parameter.
+.IP "\fB--\fP"
+similar to \fB++\fP, except the parameter is decremented by 1.
+.IP "\fB,\fP"
+separates two arithmetic expressions; the left hand side is evaluated first,
+then the right.
+The result is value of the expression on the right hand side.
+.IP "\fB=\fP"
+assignment; variable on the left is set to the value on the right.
+.IP "\fB*= /= %= += \-= <<= >>= &= ^= |=\fP"
+assignment operators; \fI<var> <op>\fP\fB=\fP \fI<expr>\fP is the same as
+\fI<var>\fP \fB=\fP \fI<var> <op>\fP \fB(\fP \fI<expr>\fP \fB)\fP.
+.IP "\fB||\fP"
+logical or; the result is 1 if either argument is non-zero, 0 if not.
+The right argument is evaluated only if the left argument is zero.
+.IP "\fB&&\fP"
+logical and; the result is 1 if both arguments are non-zero, 0 if not.
+The right argument is evaluated only if the left argument is non-zero.
+.IP "\fB|\fP"
+arithmetic (bit-wise) or.
+.IP "\fB^\fP"
+arithmetic (bit-wise) exclusive-or.
+.IP "\fB&\fP"
+arithmetic (bit-wise) and.
+.IP "\fB==\fP"
+equal; the result is 1 if both arguments are equal, 0 if not.
+.IP "\fB!=\fP"
+not equal; the result is 0 if both arguments are equal, 1 if not.
+.IP "\fB<\fP"
+less than; the result is 1 if the left argument is less than the right,
+0 if not.
+.IP "\fB<= >= >\fP"
+less than or equal, greater than or equal, greater than.
+See <.
+.IP "\fB<< >>\fP"
+shift left (right); the result is the left argument with its bits shifted
+left (right) by the amount given in the right argument.
+.IP "\fB+ - * /\fP"
+addition, subtraction, multiplication, and division.
+.IP "\fB%\fP"
+remainder; the result is the remainder of the division of the left argument
+by the right.
+The sign of the result is unspecified if either argument is negative.
+.IP "\fI<arg1>\fP \fB?\fP \fI<arg2>\fP \fB:\fP \fI<arg3>\fP"
+if \fI<arg1>\fP is non-zero, the result is \fI<arg2>\fP,
+otherwise \fI<arg3>\fP.
+.RE
+.\"}}}
+.\"{{{  Co-Processes
+.ksh(
+.SS "Co-Processes"
+A co-process, which is a pipeline created with the \fB|&\fP operator,
+is an asynchronous process that the shell can both write to
+(using \fBprint \-p\fP) and read from (using \fBread \-p\fP).
+The input and output of the co-process can also be manipulated
+using \fB>&p\fP and \fB<&p\fP redirections, respectively.
+Once a co-process has been started, another can't be started until
+the co-process exits, or until the co-process input has been redirected using
+an \fBexec \fP\fIn\fP\fB>&p\fP redirection.
+If a co-process's input is redirected in this way, the next
+co-process to be started will share the output with the first co-process,
+unless the output of the initial co-process has been redirected using an
+\fBexec \fP\fIn\fP\fB<&p\fP redirection.
+.PP
+Some notes concerning co-processes:
+.nr P2 \n(PD
+.nr PD 0
+.IP \ \ \(bu
+the only way to close the co-process input (so the co-process reads
+an end-of-file) is to redirect the input to a numbered file descriptor
+and then close that file descriptor (\fIe.g.\fP, \fBexec 3>&p;exec 3>&-\fP).
+.IP \ \ \(bu
+in order for co-processes to share a common output, the shell must keep
+the write portion of the output pipe open.
+This means that end of file will not be detected until all co-processes
+sharing the co-process output have exited (when they all exit, the shell
+closes its copy of the pipe).
+This can be avoided by redirecting the output to a numbered
+file descriptor (as this also causes the shell to close its copy).
+Note that this behaviour is slightly different from the original Korn shell
+which closes its copy of the write portion of the co-processes' output when the
+most recently started co-process (instead of when all sharing co-processes)
+exits.
+.IP \ \ \(bu
+\fBprint \-p\fP will ignore SIGPIPE signals during writes
+if the signal is not being trapped or ignored; the same is not true if
+the co-process input has been duplicated to another file descriptor and
+\fBprint \-u\fP\fIn\fP is used.
+.nr PD \n(P2
+.ksh)
+.\"}}}
+.\"{{{  Functions
+.SS "Functions"
+Functions are defined using either Korn shell \fBfunction\fP \fIname\fP
+syntax or the Bourne/POSIX shell \fIname\fP\fB()\fP syntax
+(see below for the difference between the two forms).
+Functions are like \fB.\fP-scripts in that they are executed in
+the current environment, however, unlike \fB.\fP-scripts, shell arguments
+(\fIi.e.\fP, positional parameters, \fB$1\fP, \fIetc.\fP) are never visible
+inside them.
+When the shell is determining the location of a command, functions are
+searched after special built-in commands, and before regular and non-regular
+built-ins, and before the \fBPATH\fP is searched.
+.PP
+An existing function may be deleted using \fBunset \-f\fP \fIfunction-name\fP.
+A list of functions can be obtained using \fBtypeset +f\fP and the
+function definitions can be listed using \fBtypeset \-f\fP.
+\fBautoload\fP (which is an alias for \fBtypeset \-fu\fP) may be used to
+create undefined functions; when an undefined function is executed, the
+shell searches the path specified in the \fBFPATH\fP parameter for a file
+with the same name as the function, which, if found is read and executed.
+If after executing the file, the named function is found to be defined, the
+function is executed, otherwise, the normal command search is continued
+(\fIi.e.\fP, the shell searches the regular built-in command table
+and \fBPATH\fP).
+Note that if a command is not found using \fBPATH\fP, an attempt is
+made to autoload a function using \fBFPATH\fP (this is an undocumented
+feature of the original Korn shell).
+.PP
+Functions can have two attributes, trace and export, which can be set
+with \fBtypeset \-ft\fP and \fBtypeset \-fx\fP, respectively.
+When a traced function is executed, the shell's \fBxtrace\fP option is turned
+on for the functions duration, otherwise the \fBxtrace\fP option is turned off.
+The export attribute of functions is currently not used.
+In the original
+Korn shell, exported functions are visible to shell scripts that are executed.
+.PP
+Since functions are executed in the current shell environment, parameter
+assignments made inside functions are visible after the function completes.
+If this is not the desired effect, the \fBtypeset\fP command can be used
+inside a function to create a local parameter.
+Note that special parameters (\fIe.g.\fP, \fB$$\fP, \fB$!\fP) can't be
+scoped in this way.
+.PP
+The exit status of a function is that of the last command executed in
+the function.
+A function can be made to finish immediately using the \fBreturn\fP command;
+this may also be used to explicitly specify the exit status.
+.PP
+Functions defined with the \fBfunction\fP reserved word are
+treated differently in the following ways from functions defined with
+the \fB()\fP notation:
+.nr P2 \n(PD
+.nr PD 0
+.IP \ \ \(bu
+the \fB$0\fP parameter is set to the name of the function
+(Bourne-style functions leave \fB$0\fP untouched).
+.IP \ \ \(bu
+parameter assignments preceding function calls are not kept in
+the shell environment
+(executing Bourne-style functions will keep assignments).
+.IP \ \ \(bu
+\fBOPTIND\fP is saved/reset and restored on entry and exit from the function
+so \fBgetopts\fP can be used properly both inside and outside the function
+(Bourne-style functions leave \fBOPTIND\fP untouched, so using \fBgetopts\fP
+inside a function interferes with using \fBgetopts\fP outside the function).
+.nr PD \n(P2
+In the future, the following differences will also be added:
+.nr P2 \n(PD
+.nr PD 0
+.IP \ \ \(bu
+A separate trap/signal environment will be used during the execution of
+functions.
+This will mean that traps set inside a function will not affect the shell's
+traps and signals that are not ignored in the shell (but may be trapped) will
+have their default effect in a function.
+.IP \ \ \(bu
+The EXIT trap, if set in a function, will be executed after the function
+returns.
+.nr PD \n(P2
+.\"}}}
+.\"{{{  POSIX mode
+.SS "POSIX Mode"
+The shell is intended to be POSIX compliant, however, in some cases, POSIX
+behaviour is contrary either to the original Korn shell behaviour or to
+user convenience.
+How the shell behaves in these cases is determined by the state of
+the posix option (\fBset \-o posix\fP) \(em if it is on, the POSIX behaviour
+is followed, otherwise it is not.
+The \fBposix\fP option is set automatically when the shell starts up
+if the environment contains the \fBPOSIXLY_CORRECT\fP parameter.
+(The shell can also be compiled so that it is in POSIX mode by default,
+however this is usually not desirable).
+.PP
+The following is a list of things that are affected by the state of
+the \fBposix\fP option:
+.nr P2 \n(PD
+.nr PD 0
+.sh(
+.IP \ \ \(bu
+reading of \fB$ENV\fP: if not in posix mode, the \fBENV\fP parameter
+is not expanded and included when the shell starts.
+.sh)
+.\" The following behaviour is not useful and has been removed in NetBSD
+.\" .IP \ \ \(bu
+.\" \fB\e"\fP inside double quoted \fB`\fP..\fB`\fP command substitutions:
+.\" in posix mode, the \fB\e"\fP is interpreted when the command is interpreted;
+.\" in non-posix mode, the backslash is stripped before the command substitution
+.\" is interpreted.
+.\" For example, \fBecho "`echo \e"hi\e"`"\fP produces `"hi"' in
+.\" posix mode, `hi' in non-posix mode.
+.\" To avoid problems, use the \fB$(...\fP)
+.\" form of command substitution.
+.IP \ \ \(bu
+\fBkill \-l\fP output: in posix mode, signal names are listed one a single line;
+in non-posix mode, signal numbers, names and descriptions are printed in
+columns.
+In future, a new option (\fB\-v\fP perhaps) will be added to distinguish
+the two behaviours.
+.IP \ \ \(bu
+\fBfg\fP exit status: in posix mode, the exit status is 0 if no errors occur;
+in non-posix mode, the exit status is that of the last foregrounded job.
+.IP \ \ \(bu
+\fBeval\fP exit status: if eval gets to see an empty command (\fIe.g.\fP,
+\fBeval "`false`"\fP), its exit status in posix mode will be 0.
+In non-posix mode, it will be the exit status of the last
+command substitution that was done in the processing of the arguments to eval
+(or 0 if there were no command substitutions).
+.IP \ \ \(bu
+\fBgetopts\fP: in posix mode, options must start with a \fB\-\fP; in non-posix
+mode, options can start with either \fB\-\fP or \fB+\fP.
+.IP \ \ \(bu
+brace expansion (also known as alternation): in posix mode, brace expansion
+is disabled; in non-posix mode, brace expansion enabled.
+Note that \fBset \-o posix\fP (or setting the \fBPOSIXLY_CORRECT\fP parameter)
+automatically turns the \fBbraceexpand\fP option off, however it can be
+explicitly turned on later.
+.IP \ \ \(bu
+\fBset \-\fP: in posix mode, this does not clear the \fBverbose\fP or
+\fBxtrace\fP options; in non-posix mode, it does.
+.IP \ \ \(bu
+\fBset\fP exit status: in posix mode, the exit status of set is 0
+if there are no errors; in non-posix mode, the exit status is that of
+any command substitutions performed in generating the set command.
+For example, `\fBset \-\- `false`; echo $?\fP' prints 0 in posix mode,
+1 in non-posix mode.
+This construct is used in most shell scripts that
+use the old \fIgetopt\fP(1) command.
+.IP \ \ \(bu
+argument expansion of \fBalias\fP, \fBexport\fP, \fBreadonly\fP, and
+\fBtypeset\fP commands: in posix mode, normal argument expansion done;
+in non-posix mode, field splitting, file globing, brace expansion and
+(normal) tilde expansion are turned off, and assignment tilde expansion
+is turned on.
+.IP \ \ \(bu
+signal specification: in posix mode, signals can be specified as digits only
+if signal numbers match POSIX values (\fIi.e.\fP, HUP=1, INT=2, QUIT=3, ABRT=6,
+KILL=9, ALRM=14, and TERM=15); in non-posix mode, signals can be always digits.
+.IP \ \ \(bu
+alias expansion: in posix mode, alias expansion is only carried out when
+reading command words; in non-posix mode, alias expansion is carried out
+on any word following an alias that ended in a space.
+For example, the following for loop
+.RS
+.ft B
+alias a='for ' i='j'
+.br
+a i in 1 2; do echo i=$i j=$j; done
+.ft P
+.RE
+uses parameter \fBi\fP in posix mode, \fBj\fP in non-posix mode.
+.IP \ \ \(bu
+test: in posix mode, the expression "\fB-t\fP" (preceded by
+some number of "\fB!\fP" arguments) is always true as it is a non-zero length
+string; in non-posix mode, it tests if file descriptor 1 is a tty (\fIi.e.\fP,
+the \fIfd\fP argument to the \fB-t\fP test may be left out and defaults to 1).
+.nr PD \n(P2
+.\"}}}
+.\"{{{  Command Execution (built-in commands)
+.SS "Command Execution"
+After evaluation of command line arguments, redirections and parameter
+assignments, the type of command is determined: a special built-in,
+a function, a regular built-in or the name of a file to execute found
+using the \fBPATH\fP parameter.
+The checks are made in the above order.
+Special built-in commands differ from other commands in that
+the \fBPATH\fP parameter is not used to find them, an error
+during their execution can cause a non-interactive shell to exit and
+parameter assignments that are specified before the command are
+kept after the command completes.
+Just to confuse things, if the posix option is turned off (see \fBset\fP
+command below) some special commands are very special in that
+no field splitting, file globing, brace expansion nor tilde expansion
+is performed on arguments that look like assignments.
+Regular built-in commands are different only in that the \fBPATH\fP
+parameter is not used to find them.
+.PP
+The original ksh and POSIX differ somewhat in which commands are considered
+special or regular:
+.IP "POSIX special commands"
+.TS
+lw(8m)fB lw(8m)fB lw(8m)fB lw(8m)fB lw(8m)fB .
+\&.    continue        exit    return  trap
+:      eval    export  set     unset
+break  exec    readonly        shift
+.TE
+.IP "Additional ksh special commands"
+.TS
+lw(8m)fB lw(8m)fB lw(8m)fB lw(8m)fB lw(8m)fB .
+builtin        times   typeset
+.TE
+.IP "Very special commands (non-posix mode)"
+.TS
+lw(8m)fB lw(8m)fB lw(8m)fB lw(8m)fB lw(8m)fB .
+alias  readonly        set     typeset
+.TE
+.IP "POSIX regular commands"
+.TS
+lw(8m)fB lw(8m)fB lw(8m)fB lw(8m)fB lw(8m)fB .
+alias  command fg      kill    umask
+bg     false   getopts read    unalias
+cd     fc      jobs    true    wait
+.TE
+.IP "Additional ksh regular commands"
+.TS
+lw(8m)fB lw(8m)fB lw(8m)fB lw(8m)fB lw(8m)fB .
+[      let     pwd     ulimit
+echo   print   test    whence
+.TE
+.PP
+In the future, the additional ksh special and regular commands may
+be treated differently from the POSIX special and regular commands.
+.PP
+Once the type of the command has been determined, any command line parameter
+assignments are performed and exported for the duration of the command.
+.PP
+The following describes the special and regular built-in commands:
+.\"{{{  . file [ arg1 ... ]
+.IP "\fB\&.\fP \fIfile\fP [\fIarg1\fP ...]"
+Execute the commands in \fIfile\fP in the current environment.
+The file is searched for in the directories of \fBPATH\fP.
+If arguments are given, the positional parameters may be used to
+access them while \fIfile\fP is being executed.
+If no arguments are given, the positional parameters are those of the
+environment the command is used in.
+.\"}}}
+.\"{{{  : [ ... ]
+.IP "\fB:\fP [ ... ]"
+The null command.
+Exit status is set to zero.
+.\"}}}
+.\"{{{  alias [ -d | +-t [ -r ] ] [+-px] [+-] [name1[=value1] ...]
+.IP "\fBalias\fP [ \fB\-d\fP | \fB\(+-t\fP [\fB\-r\fP] ] [\fB\(+-px\fP] [\fB\(+-\fP] [\fIname1\fP[\fB=\fP\fIvalue1\fP] ...]"
+Without arguments, \fBalias\fP lists all aliases.
+For any name without a value, the existing alias is listed.
+Any name with a value defines an alias (see Aliases above).
+.sp
+When listing aliases, one of two formats is used:
+normally, aliases are listed as \fIname\fP\fB=\fP\fIvalue\fP, where
+\fIvalue\fP is quoted; if options were preceded with \fB+\fP
+or a lone \fB+\fP is given on the command line, only \fIname\fP
+is printed.
+In addition, if the \fB\-p\fP option is used, each alias
+is prefixed with the string "\fBalias\fP\ ".
+.sp
+The \fB\-x\fP option sets (\fB+x\fP clears) the export attribute of an alias,
+or, if no names are given, lists the aliases with the export attribute
+(exporting an alias has no affect).
+.sp
+The \fB\-t\fP option indicates that tracked aliases are to be listed/set
+(values specified on the command line are ignored for tracked aliases).
+The \fB\-r\fP option indicates that all tracked aliases are to be reset.
+.sp
+The \fB\-d\fP causes directory aliases, which are used in tilde expansion,
+to be listed or set (see Tilde Expansion above).
+.\"}}}
+.\"{{{  bg [job ...]
+.IP "\fBbg\fP [\fIjob\fP ...]"
+Resume the specified stopped job(s) in the background.
+If no jobs are specified, \fB%+\fP is assumed.
+This command is only available on systems which support job control.
+See Job Control below for more information.
+.\"}}}
+.\"{{{  bind [-l] [-m] [key[=editing-command] ...]
+.IP "\fBbind\fP [\fB\-l\fP] [\fB\-m\fP] [\fIkey\fP[\fB=\fP\fIediting-command\fP] ...]"
+Set or view the current emacs command editing key bindings/macros.
+See Emacs Editing Mode below for a complete description.
+.\"}}}
+.\"{{{  break [level]
+.IP "\fBbreak\fP [\fIlevel\fP]"
+\fBbreak\fP exits the \fIlevel\fPth inner most for, select, until, or while
+loop.
+\fIlevel\fP defaults to 1.
+.\"}}}
+.\"{{{  builtin command [arg1 ...]
+.IP "\fBbuiltin\fP \fIcommand\fP [\fIarg1\fP ...]"
+Execute the built-in command \fIcommand\fP.
+.\"}}}
+.\"{{{  cd [-LP] [dir]
+.IP "\fBcd\fP [\fB\-LP\fP] [\fIdir\fP]"
+Set the working directory to \fIdir\fP.
+If the parameter \fBCDPATH\fP
+is set, it lists directories to search in for \fIdir\fP.
+An empty entry in the \fBCDPATH\fP entry means the current directory.
+If a non-empty directory from \fBCDPATH\fP is used, the resulting full
+path is printed to standard output.
+If \fIdir\fP is
+missing, the home directory \fB$HOME\fP is used.
+If \fIdir\fP is
+\fB\-\fP, the previous working directory is used (see OLDPWD parameter).
+If \fB\-L\fP option (logical path) is used or if the \fBphysical\fP option
+(see \fBset\fP command below) isn't set, references to \fB..\fP in \fIdir\fP
+are relative to the path used get to the directory.
+If \fB\-P\fP option (physical path) is used or if the \fBphysical\fP option
+is set, \fB..\fP is relative to the filesystem directory tree.
+The \fBPWD\fP and \fBOLDPWD\fP parameters are updated to reflect the
+current and old wording directory, respectively.
+.\"}}}
+.\"{{{  cd [-LP] old new
+.IP "\fBcd\fP [\fB\-LP\fP] \fIold new\fP"
+The string \fInew\fP is substituted for \fIold\fP in the current
+directory, and the shell attempts to change to the new directory.
+.\"}}}
+.\"{{{  command [ -pvV ] cmd [arg1 ...]
+.ksh(
+.IP "\fBcommand\fP [\fB\-pvV\fP] \fIcmd\fP [\fIarg1\fP ...]"
+If neither the \fB\-v\fP nor \fB\-V\fP options are given,
+.ksh)
+.sh(
+.IP "\fBcommand\fP [\fB\-p\fP] \fIcmd\fP [\fIarg1\fP ...]"
+.sh)
+\fIcmd\fP
+is executed exactly as if the \fBcommand\fP had not been specified,
+with two exceptions: first, \fIcmd\fP cannot be a shell function, and
+second, special built-in commands lose their specialness (\fIi.e.\fP,
+redirection and utility errors do not cause the shell to exit, and command
+assignments are not permanent).
+If the \fB\-p\fP option is given, a default search path is used instead of
+the current value of \fBPATH\fP (the actual value of the default path is
+system dependent: on POSIXish systems, it is the value returned by
+.ce
+\fBgetconf CS_PATH\fP
+).
+.sp
+.ksh(
+If the \fB\-v\fP option is given, instead of executing \fIcmd\fP, information
+about what would be executed is given (and the same is done for
+\fIarg1\fP ...):
+for special and regular built-in commands and functions,
+their names are simply printed,
+for aliases, a command that defines them is printed,
+and for commands found by searching the \fBPATH\fP parameter,
+the full path of the command is printed.
+If no command is found, (\fIi.e.\fP, the path search fails), nothing
+is printed and \fBcommand\fP exits with a non-zero status.
+The \fB\-V\fP option is like the \fB\-v\fP option, except it is more verbose.
+.ksh)
+.\"}}}
+.\"{{{  continue [levels]
+.IP "\fBcontinue\fP [\fIlevels\fP]"
+\fBcontinue\fP jumps to the beginning of the \fIlevel\fPth inner most for,
+select, until, or while loop.
+\fIlevel\fP defaults to 1.
+.\"}}}
+.\"{{{  echo [-neE] [arg ...]
+.IP "\fBecho\fP [\fB\-neE\fP] [\fIarg\fP ...]"
+Prints its arguments (separated by spaces) followed by a newline, to
+standard out.
+The newline is suppressed if any of the arguments contain the backslash
+sequence \fB\ec\fP.
+See \fBprint\fP command below for a list of other backslash sequences
+that are recognized.
+.sp
+The options are provided for compatibility with BSD shell scripts:
+\fB\-n\fP suppresses the trailing newline, \fB\-e\fP enables backslash
+interpretation (a no-op, since this is normally done), and \fB\-E\fP
+suppresses backslash interpretation.
+.\"}}}
+.\"{{{  eval command ...
+.IP "\fBeval\fP \fIcommand ...\fP"
+The arguments are concatenated (with spaces between them) to form
+a single string which the shell then parses and executes
+in the current environment.
+.\"}}}
+.\"{{{  exec [command [arg ...]]
+.IP "\fBexec\fP [\fIcommand\fP [\fIarg\fP ...]]"
+The command is executed without forking, replacing the shell process.
+.sp
+If no arguments are given, any IO redirection is permanent and the shell
+is not replaced.
+.ksh(
+Any file descriptors greater than 2 which are opened or \fIdup\fP(2)-ed
+in this way are not
+made available to other executed commands (\fIi.e.\fP,
+commands that are not built-in to the shell).
+Note that the Bourne shell differs here: it does pass these
+file descriptors on.
+.ksh)
+.sh(
+Any file descriptors which are opened or \fIdup\fP(2)-ed
+in this way are made available to other executed commands
+(note that the Korn shell differs here: it does not pass on
+file descriptors greater than 2).
+.sh)
+.\"}}}
+.\"{{{  exit [status]
+.IP "\fBexit\fP [\fIstatus\fP]"
+The shell exits with the specified exit status.
+If \fIstatus\fP is not specified, the exit status is the current
+value of the \fB?\fP parameter.
+.\"}}}
+.\"{{{  export [-p] [parameter[=value] ...]
+.IP "\fBexport\fP [\fB\-p\fP] [\fIparameter\fP[\fB=\fP\fIvalue\fP]] ..."
+Sets the export attribute of the named parameters.
+Exported parameters are passed in the environment to executed commands.
+If values are specified, the named parameters also assigned.
+.sp
+If no parameters are specified, the names of all parameters with the export
+attribute are printed one per line, unless the \fB\-p\fP option is used,
+in which case \fBexport\fP commands defining all exported
+parameters, including their values, are printed.
+.\"}}}
+.\"{{{  false
+.IP "\fBfalse\fP"
+A command that exits with a non-zero status.
+.\"}}}
+.\"{{{  fc [-e editor | -l [-n]] [-r] [first [ last ]]
+.ksh(
+.IP "\fBfc\fP [\fB\-e\fP \fIeditor\fP | \fB\-l\fP [\fB\-n\fP]] [\fB\-r\fP] [\fIfirst\fP [\fIlast\fP]]"
+\fIfirst\fP and \fIlast\fP select commands from the history.
+Commands can be selected by
+history number, or a string specifying the most recent command starting
+with that string.
+The \fB\-l\fP option lists the command on stdout,
+and \fB\-n\fP inhibits the default command numbers.
+The \fB\-r\fP option reverses the order of the list.
+Without \fB\-l\fP, the selected
+commands are edited by the editor specified with the \fB\-e\fP
+option, or if no \fB\-e\fP is specified, the editor specified by the
+\fBFCEDIT\fP parameter (if this parameter is not set, \fB/bin/ed\fP is used),
+and then executed by the shell.
+.ksh)
+.\"}}}
+.\"{{{  fc [-e - | -s] [-g] [old=new] [prefix]
+.IP "\fBfc\fP [\fB\-e \-\fP | \fB\-s\fP] [\fB\-g\fP] [\fIold\fP\fB=\fP\fInew\fP] [\fIprefix\fP]"
+Re-execute the selected command (the previous command by default) after
+performing the optional substitution of \fIold\fP with \fInew\fP.
+If \fB\-g\fP is specified, all occurrences of \fIold\fP are replaced with
+\fInew\fP.
+This command is usually accessed with the predefined alias
+\fBr='fc \-e \-'\fP.
+.\"}}}
+.\"{{{  fg [job ...]
+.IP "\fBfg\fP [\fIjob\fP ...]"
+Resume the specified job(s) in the foreground.
+If no jobs are specified, \fB%+\fP is assumed.
+This command is only available on systems which support job control.
+See Job Control below for more information.
+.\"}}}
+.\"{{{  getopts optstring name [arg ...]
+.IP "\fBgetopts\fP \fIoptstring\fP \fIname\fP [\fIarg\fP ...]"
+\fBgetopts\fP is used by shell procedures to parse the specified arguments
+(or positional parameters, if no arguments are given) and to check for legal
+options.
+\fIoptstring\fP contains the option letters that
+\fBgetopts\fP is to recognize.
+If a letter is followed by a colon, the option is expected to have an argument.
+Options that do not take arguments may be grouped in a single argument.
+If an option takes an argument and the option character is not the last
+character of the argument it is found in, the remainder of the argument
+is taken to be the option's argument, otherwise, the next argument is
+the option's argument.
+.sp
+Each time \fBgetopts\fP is invoked, it places the next option in
+the shell parameter \fIname\fP and the index of the next argument to be
+processed in the shell parameter \fBOPTIND\fP.
+If the option was introduced with a \fB+\fP, the option placed in
+\fIname\fP is prefixed with a \fB+\fP.
+When an option requires an argument, \fBgetopts\fP places it in the
+shell parameter \fBOPTARG\fP.
+When an illegal option or a missing option argument is
+encountered a question mark or a colon is placed in \fIname\fP
+(indicating an illegal option or missing argument, respectively)
+and \fBOPTARG\fP is set to the option character that caused the problem.
+An error message is also printed to standard error if \fIoptstring\fP
+does not begin with a colon.
+.sp
+When the end of the options is encountered, \fBgetopts\fP exits with a
+non-zero exit status.
+Options end at the first (non-option) argument that does not
+start with a \-, or when a \fB\-\-\fP argument is encountered.
+.sp
+Option parsing can be reset by setting \fBOPTIND\fP to 1 (this is done
+automatically whenever the shell or a shell procedure is invoked).
+.sp
+Warning: Changing the value of the shell parameter \fBOPTIND\fP to
+a value other than 1, or parsing different sets of arguments without
+resetting \fBOPTIND\fP may lead to unexpected results.
+.\"}}}
+.\"{{{  hash [-r] [name ...]
+.IP "\fBhash\fP [\fB\-r\fP] [\fIname ...\fP]"
+Without arguments, any hashed executable command pathnames are listed.
+The \fB\-r\fP option causes all hashed commands to be removed
+from the hash table.
+Each \fIname\fP is searched as if it where a command name and added to the
+hash table if it is an executable command.
+.\"}}}
+.\"{{{  jobs [-lpn] [job ...]
+.IP "\fBjobs\fP [\fB\-lpn\fP] [\fIjob\fP ...]"
+Display information about the specified jobs; if no jobs are specified,
+all jobs are displayed.
+The \fB\-n\fP option causes information to be displayed only for jobs
+that have changed state since the last notification.
+If the \fB\-l\fP option is used, the process-id of each process in a job
+is also listed.
+The \fB\-p\fP option causes only the process group of each job to be printed.
+See Job Control below for the format of \fIjob\fP and the displayed job.
+.\"}}}
+.\"{{{  kill [-s signame | -signum | -signame] { job | pid | -pgrp } ...
+.IP "\fBkill\fP [\fB\-s\fP \fIsigname\fP | \fB\-signum\fP | \fB\-signame\fP ] { \fIjob\fP | \fIpid\fP | \fB\-\fP\fIpgrp\fP } ..."
+Send the specified signal to the specified jobs, process ids, or process groups.
+If no signal is specified, the signal TERM is sent.
+If a job is specified, the signal is sent to the job's process group.
+See Job Control below for the format of \fIjob\fP.
+.\"}}}
+.\"{{{  kill -l [exit-status ...]
+.IP "\fBkill \-l\fP [\fIexit-status\fP ...]"
+Print the name of the signal that killed a process which exited with
+the specified \fIexit-status\fPes.
+If no arguments are specified, a list of all the signals, their numbers and
+a short description of them are printed.
+.\"}}}
+.\"{{{  let [expression ...]
+.ksh(
+.IP "\fBlet\fP [\fIexpression\fP ...]"
+Each expression is evaluated, see Arithmetic Expressions above.
+If all expressions are successfully evaluated, the exit status
+is 0 (1) if the last expression evaluated to non-zero (zero).
+If an error occurs during the parsing or evaluation of an expression,
+the exit status is greater than 1.
+Since expressions may need to be
+quoted, \fB((\fP \fIexpr\fP \fB))\fP is syntactic sugar for \fBlet
+"\fP\fIexpr\fP\fB"\fP.
+.ksh)
+.\"}}}
+.\"{{{  print [-nprsun | -R [-en]] [argument ...]
+.IP "\fBprint\fP [\fB\-nprsu\fP\fIn\fP | \fB\-R\fP [\fB\-en\fP]] [\fIargument ...\fP]"
+\fBPrint\fP prints its arguments on the standard output, separated by
+spaces, and terminated with a newline.
+The \fB\-n\fP option suppresses the newline.
+By default, certain C escapes are translated.
+These include \eb, \ef, \en, \er, \et, \ev, and \e0### (# is an octal digit,
+of which there may be 0 to 3).
+\ec is equivalent to using the \fB\-n\fP option.
+\e expansion may be inhibited with the \fB\-r\fP option.
+The \fB\-s\fP option prints to the history file instead of standard output,
+the \fB\-u\fP option prints to file descriptor \fIn\fP (\fIn\fP
+defaults to 1 if omitted), and the \fB\-p\fP option prints to the co-process
+(see Co-Processes above).
+.sp
+The \fB\-R\fP option is used to emulate, to some degree, the BSD echo
+command, which does not process \e sequences unless the \fB\-e\fP option
+is given.
+As above, the \fB\-n\fP option suppresses the trailing newline.
+.\"}}}
+.\"{{{  pwd [-LP]
+.IP "\fBpwd\fP [\fB\-LP\fP]"
+Print the present working directory.
+If \fB\-L\fP option is used or if the \fBphysical\fP option
+(see \fBset\fP command below) isn't set, the logical path is printed
+(\fIi.e.\fP, the path used to \fBcd\fP to the current directory).
+If \fB\-P\fP option (physical path) is used or if the \fBphysical\fP option
+is set, the path determined from the filesystem (by following \fB..\fP
+directories to the root directory) is printed.
+.\"}}}
+.\"{{{  read [-prsun] [parameter ...]
+.IP "\fBread\fP [\fB\-prsu\fP\fIn\fP] [\fIparameter ...\fP]"
+Reads a line of input from standard input, separate the line into fields using
+the \fBIFS\fP parameter (see Substitution above), and assign each field to the
+specified parameters.
+If there are more parameters than fields, the extra parameters are set to null,
+or alternatively, if there are more fields than parameters, the last parameter
+is assigned the remaining fields (inclusive of any separating spaces).
+If no parameters are specified, the \fBREPLY\fP parameter is used.
+If the input line ends in a backslash and the \fB\-r\fP option was not used, the
+backslash and newline are stripped and more input is read.
+If no input is read, \fBread\fP exits with a non-zero status.
+.sp
+The first parameter may have a question mark and a string appended to it, in
+which case the string is used as a prompt (printed to standard error before
+any input is read) if the input is a tty
+(\fIe.g.\fP, \fBread nfoo?'number of foos: '\fP).
+.sp
+The \fB\-u\fP\fIn\fP and \fB\-p\fP options cause input to be read
+from file descriptor \fIn\fP or the current co-process (see Co-Processes above
+for comments on this), respectively.
+If the \fB\-s\fP option is used, input is saved to the history file.
+.\"}}}
+.\"{{{  readonly [-p] [parameter[=value] ...]
+.IP "\fBreadonly\fP [\fB\-p\fP] [\fIparameter\fP[\fB=\fP\fIvalue\fP]] ..."
+Sets the readonly attribute of the named parameters.
+If values are given,
+parameters are set to them before setting the attribute.
+Once a parameter is made readonly, it cannot be unset and its value cannot
+be changed.
+.sp
+If no parameters are specified, the names of all parameters with the readonly
+attribute are printed one per line, unless the \fB\-p\fP option is used,
+in which case \fBreadonly\fP commands defining all readonly
+parameters, including their values, are printed.
+.\"}}}
+.\"{{{  return [status]
+.IP "\fBreturn\fP [\fIstatus\fP]"
+Returns from a function or \fB.\fP script, with exit status \fIstatus\fP.
+If no \fIstatus\fP is given, the exit status of the last executed command
+is used.
+If used outside of a function or \fB.\fP script, it has the same effect
+as \fBexit\fP.
+Note that pdksh treats both profile and \fB$ENV\fP files as \fB.\fP scripts,
+while the original Korn shell only treats profiles as \fB.\fP scripts.
+.\"}}}
+.\"{{{  set [+-abCefhkmnpsuvxX] [+-o [option]] [+-A name] [--] [arg ...]
+.IP "\fBset\fP [\fB\(+-abCefhkmnpsuvxX\fP] [\fB\(+-o\fP [\fIoption\fP]] [\fB\(+-A\fP \fIname\fP] [\fB\-\-\fP] [\fIarg\fP ...]"
+The set command can be used to set (\fB\-\fP) or clear (\fB+\fP) shell options,
+set the positional parameters, or set an array parameter.
+Options can be changed using the \fB\(+-o\fP \fIoption\fP syntax,
+where \fIoption\fP is the long name of an option, or using
+the \fB\(+-\fP\fIletter\fP syntax, where \fIletter\fP is the
+option's single letter name (not all options have a single letter name).
+The following table lists both option letters (if they exist) and long names
+along with a description of what the option does.
+.sp
+.TS
+expand;
+afB lfB lw(3i).
+\-A            T{
+Sets the elements of the array parameter \fIname\fP to \fIarg\fP ...;
+If \fB\-A\fP is used, the array is reset (\fIi.e.\fP, emptied) first;
+if \fB+A\fP is used, the first N elements are set (where N is the number
+of \fIarg\fPs), the rest are left untouched.
+T}
+\-a    allexport       T{
+all new parameters are created with the export attribute
+T}
+\-b    notify  T{
+Print job notification messages asynchronously, instead of just before the
+prompt.
+Only used if job control is enabled (\fB\-m\fP).
+T}
+\-C    noclobber       T{
+Prevent \fB>\fP redirection from overwriting existing files (\fB>|\fP must
+be used to force an overwrite).
+T}
+\-e    errexit T{
+Exit (after executing the \fBERR\fP trap) as soon as an error occurs or
+a command fails (\fIi.e.\fP, exits with a non-zero status).
+This does not apply to commands whose exit status is explicitly tested by a
+shell construct such as \fBif\fP, \fBuntil\fP, \fBwhile\fP, \fB&&\fP or
+\fB||\fP statements.
+T}
+\-f    noglob  T{
+Do not expand file name patterns.
+T}
+\-h    trackall        T{
+Create tracked aliases for all executed commands (see Aliases above).
+On by default for non-interactive shells.
+T}
+\-i    interactive     T{
+Enable interactive mode \- this can only be set/unset when the shell is
+invoked.
+T}
+\-k    keyword T{
+Parameter assignments are recognized anywhere in a command.
+T}
+\-l    login   T{
+The shell is a login shell \- this can only be set/unset when the shell is
+invoked (see Shell Startup above).
+T}
+\-m    monitor T{
+Enable job control (default for interactive shells).
+T}
+\-n    noexec  T{
+Do not execute any commands \- useful for checking the syntax of scripts
+(ignored if interactive).
+T}
+\-p    privileged      T{
+Set automatically if, when the shell starts, the real uid or gid does not
+match the effective uid or gid, respectively.
+See Shell Startup above for a description of what this means.
+T}
+-r     restricted      T{
+Enable restricted mode \(em this option can only be used when the shell is
+invoked.
+See Shell Startup above for a description of what this
+means.
+T}
+\-s    stdin   T{
+If used when the shell is invoked, commands are read from standard input.
+Set automatically if the shell is invoked with no arguments.
+.sp
+When \fB\-s\fP is used in the \fBset\fP command, it causes the specified
+arguments to be sorted before assigning them to the positional parameters
+(or to array \fIname\fP, if \fB\-A\fP is used).
+T}
+\-u    nounset T{
+Referencing of an unset parameter is treated as an error, unless
+one of the \fB\-\fP, \fB+\fP or \fB=\fP modifiers is used.
+T}
+\-v    verbose T{
+Write shell input to standard error as it is read.
+T}
+\-x    xtrace  T{
+Print commands and parameter assignments when they are executed,
+preceded by the value of \fBPS4\fP.
+T}
+\-X    markdirs        T{
+Mark directories with a trailing \fB/\fP during file name generation.
+T}
+       bgnice  T{
+Background jobs are run with lower priority.
+T}
+.ksh(
+       braceexpand     T{
+Enable brace expansion (aka, alternation).
+T}
+.ksh)
+.ksh(
+       emacs   T{
+Enable BRL emacs-like command line editing (interactive shells only);
+see Emacs Editing Mode.
+T}
+       emacs-usemeta   T{
+In emacs command-line editing, use the 8th bit
+as meta (^[) prefix.  This is the default if 
+LC_CTYPE is unset or POSIX respectively C.
+8 
+T}
+       gmacs   T{
+Enable gmacs-like (Gosling emacs) command line editing (interactive shells
+only);
+currently identical to emacs editing except that transpose (^T) acts
+slightly differently.
+T}
+.ksh)
+       ignoreeof       T{
+The shell will not (easily) exit on when end-of-file is read, \fBexit\fP must
+be used.
+To avoid infinite loops, the shell will exit if eof is read 13 times in
+a row.
+T}
+       nohup   T{
+Do not kill running jobs with a \fBHUP\fP signal when a login shell exists.
+Currently set by default, but this will change in the future to be compatible
+with the original Korn shell (which doesn't have this option, but does
+send the \fBHUP\fP signal).
+T}
+       nolog   T{
+No effect \- in the original Korn shell, this prevents function definitions
+from being stored in the history file.
+T}
+       physical        T{
+Causes the \fBcd\fP and \fBpwd\fP commands to use `physical'
+(\fIi.e.\fP, the filesystem's) \fB..\fP directories instead of `logical'
+directories (\fIi.e.\fP,  the shell handles \fB..\fP, which allows the user
+to be oblivious of symlink links to directories).
+Clear by default.
+Note that setting
+this option does not effect the current value of the \fBPWD\fP parameter;
+only the \fBcd\fP command changes \fBPWD\fP.
+See the \fBcd\fP and \fBpwd\fP commands above for more details.
+T}
+       posix   T{
+Enable posix mode.
+See POSIX Mode above.
+T}
+       vi      T{
+Enable vi-like command line editing (interactive shells only).
+T}
+       viraw   T{
+No effect \- in the original Korn shell, unless viraw was set, the vi command
+line mode would let the tty driver do the work until ESC (^[) was entered.
+pdksh is always in viraw mode.
+T}
+       vi-esccomplete  T{
+In vi command line editing, do command / file name completion when
+escape (^[) is entered in command mode.
+T}
+       vi-show8        T{
+Prefix characters with the eighth bit set with `M-'.
+If this option is not set, characters in the range
+128-160 are printed as is, which may cause problems.
+T}
+       vi-tabcomplete  T{
+In vi command line editing, do command / file name completion when
+tab (^I) is entered in insert mode.  This is the default.
+T}
+.TE
+.sp
+These options can also be used upon invocation of the shell.
+The current set of options (with single letter names) can be found in the
+parameter \fB\-\fP.
+\fBset -o\fP with no option name will list all the options and whether each
+is on or off; \fBset +o\fP will print the long names of all options that
+are currently on.
+.sp
+Remaining arguments, if any, are positional parameters and are assigned,
+in order, to the
+positional parameters (\fIi.e.\fP, \fB1\fP, \fB2\fP, \fIetc.\fP).
+If options are ended with \fB\-\-\fP and there are no remaining arguments,
+all positional parameters are cleared.
+If no options or arguments are given, then the values of all names are printed.
+For unknown historical reasons, a lone \fB\-\fP option is treated specially:
+it clears both the \fB\-x\fP and \fB\-v\fP options.
+.\"}}}
+.\"{{{  shift [number]
+.IP "\fBshift\fP [\fInumber\fP]"
+The positional parameters \fInumber\fP+1, \fInumber\fP+2 \fIetc.\fP\& are
+renamed to \fB1\fP, \fB2\fP, \fIetc.\fP
+\fInumber\fP defaults to 1.
+.\"}}}
+.\"{{{  test expression, [ expression ]
+.IP "\fBtest\fP \fIexpression\fP"
+.IP "\fB[\fP \fIexpression\fP \fB]\fP"
+\fBtest\fP evaluates the \fIexpression\fP and returns zero status if
+true, 1 if false, and greater than 1 if there was an error.
+It is normally used as the
+condition command of \fBif\fP and \fBwhile\fP statements.
+The following basic expressions are available:
+.sp
+.TS
+afB ltw(3.2i).
+\fIstr\fP      T{
+\fIstr\fP has non-zero length.
+Note that there is the potential
+for problems if \fIstr\fP turns out to be an operator (\fIe.g.\fP, \fB-r\fP)
+- it is generally better to use a test like
+.ce
+\fB[ X"\fP\fIstr\fP\fB" != X ]\fP
+instead (double quotes are used in case \fIstr\fP contains spaces or file
+globing characters).
+T}
+\-r \fIfile\fP T{
+\fIfile\fP exists and is readable.
+T}
+\-w \fIfile\fP T{
+\fIfile\fP exists and is writable.
+T}
+\-x \fIfile\fP T{
+\fIfile\fP exists and is executable.
+T}
+\-a \fIfile\fP T{
+\fIfile\fP exists.
+T}
+\-e \fIfile\fP T{
+\fIfile\fP exists.
+T}
+\-f \fIfile\fP T{
+\fIfile\fP is a regular file.
+T}
+\-d \fIfile\fP T{
+\fIfile\fP is a directory.
+T}
+\-c \fIfile\fP T{
+\fIfile\fP is a character special device.
+T}
+\-b \fIfile\fP T{
+\fIfile\fP is a block special device.
+T}
+\-p \fIfile\fP T{
+\fIfile\fP is a named pipe.
+T}
+\-u \fIfile\fP T{
+\fIfile\fP's mode has setuid bit set.
+T}
+\-g \fIfile\fP T{
+\fIfile\fP's mode has setgid bit set.
+T}
+\-k \fIfile\fP T{
+\fIfile\fP's mode has sticky bit set.
+T}
+\-s \fIfile\fP T{
+\fIfile\fP is not empty.
+T}
+\-O \fIfile\fP T{
+\fIfile\fP's owner is the shell's effective user-ID.
+T}
+\-G \fIfile\fP T{
+\fIfile\fP's group is the shell's effective group-ID.
+T}
+\-h \fIfile\fP T{
+\fIfile\fP is a symbolic link.
+T}
+\-H \fIfile\fP T{
+\fIfile\fP is a context dependent directory (only useful on HP-UX).
+T}
+\-L \fIfile\fP T{
+\fIfile\fP is a symbolic link.
+T}
+\-S \fIfile\fP T{
+\fIfile\fP is a socket.
+T}
+\-o \fIoption\fP       T{
+shell \fIoption\fP is set (see \fBset\fP command above for list of options).
+As a non-standard extension, if the option starts with a \fB!\fP, the test
+is negated; the test always fails if option doesn't exist (thus
+.ce
+\fB[ -o \fP\fIfoo\fP \fB-o -o !\fP\fIfoo\fP \fB]\fP
+returns true if and only if option \fIfoo\fP exists).
+T}
+\fIfile\fP \-nt \fIfile\fP     T{
+first \fIfile\fP is newer than second \fIfile\fP or first
+\fIfile\fP exists and the second \fIfile\fP does not.
+T}
+\fIfile\fP \-ot \fIfile\fP     T{
+first \fIfile\fP is older than second \fIfile\fP or second \fIfile\fP
+exists and the first \fIfile\fP does not.
+T}
+\fIfile\fP \-ef \fIfile\fP     T{
+first \fIfile\fP is the same file as second \fIfile\fP.
+T}
+\-t\ [\fIfd\fP]        T{
+file descriptor is a tty device.
+If the posix option (\fBset \-o posix\fP, see POSIX Mode above) is not
+set, \fIfd\fP may be left out, in which case it is taken to be 1
+(the behaviour differs due to the special POSIX rules described below).
+T}
+\fIstring\fP   T{
+\fIstring\fP is not empty.
+T}
+\-z\ \fIstring\fP      T{
+\fIstring\fP is empty.
+T}
+\-n\ \fIstring\fP      T{
+\fIstring\fP is not empty.
+T}
+\fIstring\fP\ =\ \fIstring\fP  T{
+strings are equal.
+T}
+.ksh(
+\fIstring\fP\ ==\ \fIstring\fP T{
+strings are equal.
+T}
+.ksh)
+\fIstring\fP\ !=\ \fIstring\fP T{
+strings are not equal.
+T}
+\fInumber\fP\ \-eq\ \fInumber\fP       T{
+numbers compare equal.
+T}
+\fInumber\fP\ \-ne\ \fInumber\fP       T{
+numbers compare not equal.
+T}
+\fInumber\fP\ \-ge\ \fInumber\fP       T{
+numbers compare greater than or equal.
+T}
+\fInumber\fP\ \-gt\ \fInumber\fP       T{
+numbers compare greater than.
+T}
+\fInumber\fP\ \-le\ \fInumber\fP       T{
+numbers compare less than or equal.
+T}
+\fInumber\fP\ \-lt\ \fInumber\fP       T{
+numbers compare less than.
+T}
+.TE
+.sp
+The above basic expressions, in which unary operators have precedence over
+binary operators, may be combined with the following operators
+(listed in increasing order of precedence):
+.sp
+.TS
+afB l.
+\fIexpr\fP \-o \fIexpr\fP      logical or
+\fIexpr\fP \-a \fIexpr\fP      logical and
+! \fIexpr\fP   logical not
+( \fIexpr\fP ) grouping
+.TE
+.sp
+On operating systems not supporting \fB/dev/fd/\fP\fIn\fP devices
+(where \fIn\fP is a file descriptor number),
+the \fBtest\fP command will attempt to fake it for all tests that
+operate on files (except the \fB-e\fP test).
+I.e., \fB[ -w /dev/fd/2 ]\fP tests if file descriptor 2 is writable.
+.sp
+Note that some special rules are applied (courtesy of POSIX) if the
+number of arguments to \fBtest\fP or \fB[\fP \&... \fB]\fP is less than
+five: if leading \fB!\fP arguments can be stripped such that only one
+argument remains then a string length test is performed (again, even if
+the argument is a unary operator);
+if leading \fB!\fP arguments can be stripped such that three
+arguments remain and the second argument is a binary operator, then the
+binary operation is performed (even if first argument is a unary
+operator, including an unstripped \fB!\fP).
+.sp
+\fBNote:\fP A common mistake is to use \fBif [ $foo = bar ]\fP which
+fails if parameter \fBfoo\fP is null or unset, if it has embedded spaces
+(\fIi.e.\fP, \fBIFS\fP characters), or if it is a unary operator like \fB!\fP or
+\fB\-n\fP.
+Use tests like \fBif [ "X$foo" = Xbar ]\fP instead.
+.\"}}}
+.\"{{{  time [-p] [pipeline]
+.IP "\fBtime\fP [\fB-p\fP] [ \fIpipeline\fP ]"
+If a pipeline is given, the times used to execute the pipeline are reported.
+If no pipeline is given, then the user and system time used by the shell
+itself, and all the commands it has run since it was started, are reported.
+The times reported are
+the real time (elapsed time from start to finish),
+the user CPU time (time spent running in user mode)
+and the system CPU time (time spent running in kernel mode).
+Times are reported to standard error; the format of the output is:
+.nf
+    0.00s real     0.00s user     0.00s system
+.fi
+unless the -p option is given (only possible if \fIpipeline\fP is a simple
+command), in which case the output is slightly longer:
+.nf
+    real   0.00
+    user   0.00
+    sys    0.00
+.fi
+(the number of digits after the decimal may vary from system to system).
+Note that simple redirections of standard error do not effect the output
+of the time command:
+.ce
+\fBtime sleep 1 2> \fP\fIafile\fP
+.ce
+\fB{ time sleep 1; } 2> \fP\fIafile\fP
+times for the first command do not go to \fIafile\fP, but those of the
+second command do.
+.\"}}}
+.\"{{{  times
+.IP \fBtimes\fP
+Print the accumulated user and system times used by the shell and by
+processes which have exited that the shell started.
+.\"}}}
+.\"{{{  trap [handler signal ...]
+.IP "\fBtrap\fP [\fIhandler\fP \fIsignal ...\fP]"
+Sets trap handler that is to be executed when any of the specified signals
+are received.
+\fBHandler\fP is either a null string, indicating the signals are to
+be ignored, a minus (\fB\-\fP), indicating that the default action is to
+be taken for the signals (see signal(3)), or a string containing shell
+commands to be evaluated and executed at the first opportunity (\fIi.e.\fP,
+when the current command completes, or before printing the next \fBPS1\fP
+prompt) after receipt of one of the signals.
+\fBSignal\fP is the name of a signal (\fIe.g.\fP, PIPE or ALRM) or the number
+of the signal (see \fBkill \-l\fP command above).
+There are two special signals: \fBEXIT\fP (also known as \fB0\fP), which
+is executed when the shell is about to exit, and \fBERR\fP which is
+executed after an error occurs (an error is something that would cause
+the shell to exit if the \fB\-e\fP or \fBerrexit\fP option were set \(em
+see \fBset\fP command above).
+\fBEXIT\fP handlers are executed in the environment of the last executed
+command.
+Note that for non-interactive shells, the trap handler cannot be changed for
+signals that were ignored when the shell started.
+.sp
+With no arguments, \fBtrap\fP lists, as a series of \fBtrap\fP commands,
+the current state of the traps that have been set since the shell started.
+Note that the output of \fBtrap\fP can not be usefully piped to another process
+(an artifact of the fact that traps are cleared when subprocesses are
+created).
+.sp
+.\" todo: add these features (trap DEBUG, trap ERR/EXIT in function)
+The original Korn shell's \fBDEBUG\fP trap and the handling of \fBERR\fP and
+\fBEXIT\fP traps in functions are not yet implemented.
+.\"}}}
+.\"{{{  true
+.IP \fBtrue\fP
+A command that exits with a zero value.
+.\"}}}
+.\"{{{  typeset [[+-Ulprtux] [-L[n]] [-R[n]] [-Z[n]] [-i[n]] | -f [-tux]] [name[=value] ...]
+.IP "\fBtypeset\fP [[\(+-Ulprtux] [\fB\-L\fP[\fIn\fP]] [\fB\-R\fP[\fIn\fP]] [\fB\-Z\fP[\fIn\fP]] [\fB\-i\fP[\fIn\fP]] | \fB\-f\fP [\fB\-tux\fP]] [\fIname\fP[\fB=\fP\fIvalue\fP] ...]"
+Display or set parameter attributes.
+With no \fIname\fP arguments, parameter attributes are displayed: if no options
+arg used, the current attributes of all parameters are printed as typeset
+commands; if an option is given (or \fB\-\fP with no option letter)
+all parameters and their values with the specified attributes are printed;
+if options are introduced with \fB+\fP, parameter values are not printed.
+.sp
+If \fIname\fP arguments are given, the attributes of the named parameters
+are set (\fB\-\fP) or cleared (\fB+\fP).
+Values for parameters may optionally be specified.
+If typeset is used inside a function, any newly created parameters are local
+to the function.
+.sp
+When \fB\-f\fP is used, typeset operates on the attributes of functions.
+As with parameters, if no \fIname\fPs are given, functions are listed
+with their values (\fIi.e.\fP, definitions) unless options are introduced with
+\fB+\fP, in which case only the function names are reported.
+.sp
+.TS
+expand;
+afB lw(4.5i).
+\-L\fIn\fP     T{
+Left justify attribute: \fIn\fP specifies the field width.
+If \fIn\fP is not specified, the current width of a parameter (or the
+width of its first assigned value) is used.
+Leading white space (and zeros, if used with the \fB\-Z\fP option) is stripped.
+If necessary, values are either truncated or space padded to fit the
+field width.
+T}
+\-R\fIn\fP     T{
+Right justify attribute: \fIn\fP specifies the field width.
+If \fIn\fP is not specified, the current width of a parameter (or the
+width of its first assigned value) is used.
+Trailing white space are stripped.
+If necessary, values are either stripped of leading characters
+or space padded to make them fit the field width.
+T}
+\-Z\fIn\fP     T{
+Zero fill attribute: if not combined with \fB\-L\fP, this is the
+same as \fB\-R\fP, except zero padding is used instead of space padding.
+T}
+\-i\fIn\fP     T{
+integer attribute:
+\fIn\fP specifies the base to use when displaying the integer
+(if not specified, the base given in the first assignment is used).
+Parameters with this attribute may be assigned values containing
+arithmetic expressions.
+T}
+\-U    T{
+unsigned integer attribute: integers are printed as unsigned values
+(only useful when combined with the \fB\-i\fP option).
+This option is not in the original Korn shell.
+T}
+\-f    T{
+Function mode: display or set functions and their attributes, instead of
+parameters.
+T}
+\-l    T{
+Lower case attribute: all  upper case characters in values are converted to
+lower case.
+(In the original Korn shell, this parameter meant `long integer' when used
+with the \fB\-i\fP option).
+T}
+\-p    T{
+Print complete typeset commands that can be used to re-create the
+attributes (but not the values) of parameters.
+This is the default action (option exists for ksh93 compatibility).
+T}
+\-r    T{
+Readonly attribute: parameters with the this attribute may not be assigned to
+or unset.
+Once this attribute is set, it can not be turned off.
+T}
+\-t    T{
+Tag attribute: has no meaning to the shell; provided for application use.
+.sp
+For functions, \fB\-t\fP is the trace attribute.
+When functions with the trace attribute are executed, the \fBxtrace\fP (\fB\-x\fP) shell option is temporarily turned on.
+T}
+\-u    T{
+Upper case attribute: all lower case characters in values are converted to
+upper case.
+(In the original Korn shell, this parameter meant `unsigned integer' when used
+with the \fB\-i\fP option, which meant upper case letters would never be used
+for bases greater than 10.
+See the \fB\-U\fP option).
+.sp
+For functions, \fB\-u\fP is the undefined attribute.
+See Functions above for the implications of this.
+T}
+\-x    T{
+Export attribute: parameters (or functions) are placed in the environment of
+any executed commands.
+Exported functions are not implemented yet.
+T}
+.TE
+.\"}}}
+.\"{{{  ulimit [-abcdfHlmnpsStvw] [value]
+.IP "\fBulimit\fP [\fB\-abcdfHlmnpsStvw\fP] [\fIvalue\fP]"
+Display or set process limits.
+If no options are used, the file size limit (\fB\-f\fP) is assumed.
+\fBvalue\fP, if specified, may be either be an arithmetic expression or the
+word \fBunlimited\fP.
+The limits affect the shell and any processes created by the shell after
+a limit is imposed.
+Note that some systems may not allow limits to be increased once they
+are set.
+Also note that the types of limits available are system dependent \- some
+systems have only the \fB\-f\fP limit.
+.RS
+.IP \fB\-a\fP
+Displays all limits; unless \fB\-H\fP is used, soft limits are displayed.
+.IP \fB\-H\fP
+Set the hard limit only (default is to set both hard and soft limits).
+.IP \fB\-S\fP
+Set the soft limit only (default is to set both hard and soft limits).
+.IP \fB\-b\fP
+Impose a size limit of \fIn\fP bytes on the size of socket buffers.
+.IP \fB\-c\fP
+Impose a size limit of \fIn\fP blocks on the size of core dumps.
+.IP \fB\-d\fP
+Impose a size limit of \fIn\fP kbytes on the size of the data area.
+.IP \fB\-f\fP
+Impose a size limit of \fIn\fP blocks on files written by the shell and
+its child processes (files of any size may be read).
+.IP \fB\-l\fP
+Impose a limit of \fIn\fP kbytes on the amount of locked (wired) physical
+memory.
+.IP \fB\-m\fP
+Impose a limit of \fIn\fP kbytes on the amount of physical memory used.
+.IP \fB\-n\fP
+Impose a limit of \fIn\fP file descriptors that can be open at once.
+.IP \fB\-p\fP
+Impose a limit of \fIn\fP processes that can be run by the user at any one
+time.
+.IP \fB\-s\fP
+Impose a size limit of \fIn\fP kbytes on the size of the stack area.
+.IP \fB\-t\fP
+Impose a time limit of \fIn\fP CPU seconds to be used by each process.
+.IP \fB\-v\fP
+Impose a limit of \fIn\fP kbytes on the amount of virtual memory used;
+on some systems this is the maximum allowable virtual address (in bytes,
+not kbytes).
+.IP \fB\-w\fP
+Impose a limit of \fIn\fP kbytes on the amount of swap space used.
+.PP
+As far as \fBulimit\fP is concerned, a block is 512 bytes.
+.RE
+.\"}}}
+.\"{{{  umask [-S] [mask]
+.IP "\fBumask\fP [\fB\-S\fP] [\fImask\fP]"
+.RS
+Display or set the file permission creation mask, or umask (see \fIumask\fP(2)).
+If the \fB\-S\fP option is used, the mask displayed or set is symbolic,
+otherwise it is an octal number.
+.sp
+Symbolic masks are like those used by \fIchmod\fP(1):
+.RS
+[\fBugoa\fP]{{\fB=+-\fP}{\fBrwx\fP}*}+[\fB,\fP...]
+.RE
+in which the first group of characters is the \fIwho\fP part, the second
+group is the \fIop\fP part, and the last group is the \fIperm\fP part.
+The \fIwho\fP part specifies which part of the umask is to be modified.
+The letters mean:
+.RS
+.IP \fBu\fP
+the user permissions
+.IP \fBg\fP
+the group permissions
+.IP \fBo\fP
+the other permissions (non-user, non-group)
+.IP \fBa\fP
+all permissions (user, group and other)
+.RE
+.sp
+The \fIop\fP part indicates how the \fIwho\fP permissions are to be modified:
+.RS
+.IP \fB=\fP
+set
+.IP \fB+\fP
+added to
+.IP \fB\-\fP
+removed from
+.RE
+.sp
+The \fIperm\fP part specifies which permissions are to be set, added or removed:
+.RS
+.IP \fBr\fP
+read permission
+.IP \fBw\fP
+write permission
+.IP \fBx\fP
+execute permission
+.RE
+.sp
+When symbolic masks are used, they describe what permissions may
+be made available (as opposed to octal masks in which a set bit means
+the corresponding bit is to be cleared).
+Example: `ug=rwx,o=' sets the mask so files will not be readable, writable
+or executable by `others', and is equivalent (on most systems) to the octal
+mask `07'.
+.RE
+.\"}}}
+.\"{{{  unalias [-adt] name ...
+.IP "\fBunalias\fP [\fB\-adt\fP] [\fIname1\fP ...]"
+The aliases for the given names are removed.
+If the \fB\-a\fP option is used, all aliases are removed.
+If the \fB\-t\fP or \fB\-d\fP options are used, the indicated operations
+are carried out on tracked or directory aliases, respectively.
+.\"}}}
+.\"{{{  unset [-fv] parameter ...
+.IP "\fBunset\fP [\fB\-fv\fP] \fIparameter\fP ..."
+Unset the named parameters (\fB\-v\fP, the default) or functions (\fB\-f\fP).
+The exit status is non-zero if any of the parameters were already unset,
+zero otherwise.
+.\"}}}
+.\"{{{  wait [job]
+.IP "\fBwait\fP [\fIjob\fP]"
+Wait for the specified job(s) to finish.
+The exit status of wait is that of the last specified job:
+if the last job is killed by a signal, the exit status is 128 + the
+number of the signal (see \fBkill \-l\fP \fIexit-status\fP above); if the last
+specified job can't be found (because it never existed, or had already
+finished), the exit status of wait is 127.
+See Job Control below for the format of \fIjob\fP.
+\fBWait\fP will return if a signal for which a trap has been set is received,
+or if a HUP, INT or QUIT signal is received.
+.sp
+If no jobs are specified, \fBwait\fP waits for all currently running jobs
+(if any) to finish and exits with a zero status.
+If job monitoring is enabled, the completion status of jobs is
+printed (this is not the case when jobs are explicitly specified).
+.\"}}}
+.\"{{{  whence [-pv] [name ...]
+.IP "\fBwhence\fP [\fB\-pv\fP] [name ...]"
+For each name, the type of command is listed (reserved word, built-in, alias,
+function, tracked alias or executable).
+If the \fB\-p\fP option is used, a path search done even if \fIname\fP
+is a reserved word, alias, \fIetc.\fP
+Without the \fB\-v\fP option, \fBwhence\fP is similar to \fBcommand \-v\fP
+except that \fBwhence\fP will find reserved words and won't print aliases
+as alias commands;
+with the \fB\-v\fP option, \fBwhence\fP is the same as \fBcommand \-V\fP.
+Note that for \fBwhence\fP, the \fB\-p\fP option does not affect the search
+path used, as it does for \fBcommand\fP.
+If the type of one or more of the names could not be determined, the
+exit status is non-zero.
+.\"}}}
+.\"}}}
+.\"{{{  job control (and its built-in commands)
+.SS "Job Control"
+Job control refers to the shell's ability to monitor and control \fBjobs\fP,
+which are processes or groups of processes created for commands or pipelines.
+At a minimum, the shell keeps track of the status of the background
+(\fIi.e.\fP, asynchronous) jobs that currently exist; this information can be
+displayed using the \fBjobs\fP command.
+If job control is fully enabled (using \fBset \-m\fP or
+\fBset \-o monitor\fP), as it is for interactive shells,
+the processes of a job are placed in their own process group,
+foreground jobs can be stopped by typing the suspend character from the
+terminal (normally ^Z),
+jobs can be restarted in either the foreground
+or background, using the \fBfg\fP and \fBbg\fP commands, respectively,
+and the state of the terminal is saved or restored when a foreground
+job is stopped or restarted, respectively.
+.sp
+Note that only commands that create processes (\fIe.g.\fP,
+asynchronous commands, subshell commands, and non-built-in,
+non-function commands) can be stopped; commands like \fBread\fP cannot be.
+.sp
+When a job is created, it is assigned a job-number.
+For interactive shells, this number is printed inside \fB[\fP..\fB]\fP,
+followed by the process-ids of the processes in the job when an asynchronous
+command is run.
+A job may be referred to in \fBbg\fP, \fBfg\fP, \fBjobs\fP, \fBkill\fP and
+\fBwait\fP commands either by the process id of the last process in the
+command pipeline (as stored in the \fB$!\fP parameter) or by prefixing the
+job-number with a percent sign (\fB%\fP).
+Other percent sequences can also be used to refer to jobs:
+.sp
+.TS
+expand;
+afB lw(4.5i).
+%+     T{
+The most recently stopped job, or, if there are no stopped jobs, the oldest
+running job.
+T}
+%%\fR, \fP%    T{
+Same as \fB%+\fP.
+T}
+%\-    T{
+The job that would be the \fB%+\fP job, if the later did not exist.
+T}
+%\fIn\fP       T{
+The job with job-number \fIn\fP.
+T}
+%?\fIstring\fP T{
+The job containing the string \fIstring\fP (an error occurs if multiple jobs
+are matched).
+T}
+%\fIstring\fP  T{
+The job starting with string \fIstring\fP (an error occurs if multiple jobs
+are matched).
+T}
+.TE
+.sp
+When a job changes state (\fIe.g.\fP, a background job finishes or foreground
+job is stopped), the shell prints the following status information:
+.RS
+\fB[\fP\fInumber\fP\fB]\fP \fIflag status command\fP
+.RE
+where
+.IP "\ \fInumber\fP"
+is the job-number of the job.
+.IP "\ \fIflag\fP"
+is \fB+\fP or \fB-\fP if the job is the \fB%+\fP or \fB%-\fP job,
+respectively, or space if it is neither.
+.IP "\ \fIstatus\fP"
+indicates the current state of the job and can be
+.RS
+.IP "\fBRunning\fP"
+the job has neither stopped or exited (note that running does not
+necessarily mean consuming CPU time \(em the process could be blocked waiting
+for some event).
+.IP "\fBDone\fP [\fB(\fP\fInumber\fP\fB)\fP]"
+the job exited.
+\fInumber\fP is the exit status of the job, which is
+omitted if the status is zero.
+.IP "\fBStopped\fP [\fB(\fP\fIsignal\fP\fB)\fP]"
+the job was stopped by the indicated \fIsignal\fP (if no signal is given,
+the job was stopped by SIGTSTP).
+.IP "\fIsignal-description\fP [\fB(core dumped)\fP]"
+the job was killed by a signal (\fIe.g.\fP, Memory\ fault,
+Hangup, \fIetc.\fP \(em use
+\fBkill \-l\fP for a list of signal descriptions).
+The \fB(core\ dumped)\fP message indicates the process created a core file.
+.RE
+.IP "\ \fIcommand\fP"
+is the command that created the process.
+If there are multiple processes in the job, then each process will
+have a line showing its \fIcommand\fP and possibly its \fIstatus\fP,
+if it is different from the status of the previous process.
+.PP
+When an attempt is made to exit the shell while there are jobs in
+the stopped state, the shell warns the user that there are stopped jobs
+and does not exit.
+If another attempt is immediately made to exit the shell, the stopped
+jobs are sent a \fBHUP\fP signal and the shell exits.
+Similarly, if the \fBnohup\fP option is not set and there are running
+jobs when an attempt is made to exit a login shell, the shell warns the
+user and does not exit.
+If another attempt is immediately made to exit the shell, the running
+jobs are sent a \fBHUP\fP signal and the shell exits.
+.\"}}}
+.\"{{{  Interactive Input Line Editing
+.ksh(
+.\"{{{  introduction
+.SS "Interactive Input Line Editing"
+The shell supports three modes of reading command lines from a tty
+in an interactive session.
+Which is used is controlled by the \fBemacs\fP, \fBgmacs\fP and \fBvi\fP
+\fBset\fP options (at most one of these can be set at once).
+If none of these options is enabled, the shell simply reads lines
+using the normal tty driver.
+If the \fBemacs\fP or \fBgmacs\fP option is set, the shell allows
+emacs like editing of the command; similarly, if the \fBvi\fP option
+is set, the shell allows vi like editing of the command.
+These modes are described in detail in the following sections.
+.\"}}}
+.\"{{{  display
+.PP
+In these editing modes, if a line is longer that the screen width
+(see \fBCOLUMNS\fP parameter),
+a \fB>\fP, \fB+\fP or \fB<\fP character is displayed in the last column
+indicating that there are more characters after, before and after, or
+before the current position, respectively.
+The line is scrolled horizontally as necessary.
+.\"}}}
+.\"{{{  Emacs Editing Mode
+.SS "Emacs Editing Mode"
+When the \fBemacs\fP option is set, interactive input line editing is
+enabled.
+\fBWarning\fP: This mode is slightly different from the emacs
+mode in the original Korn shell and the 8th bit is stripped in emacs mode.
+In this mode various editing commands (typically bound to one or more
+control characters) cause immediate actions without waiting for a
+new-line.
+Several editing commands are bound to particular control
+characters when the shell is invoked; these bindings can be changed
+using the following commands:
+.\"{{{  bind
+.IP \fBbind\fP
+The current bindings are listed.
+.\"}}}
+.\"{{{  bind string=[editing-command]
+.IP "\fBbind\fP \fIstring\fP\fB=\fP[\fIediting-command\fP]"
+The specified editing command is bound to the given \fBstring\fP, which
+should consist of a control character (which may be written using caret
+notation \fB^\fP\fIX\fP), optionally preceded by one of the two prefix
+characters.
+Future input of the \fIstring\fP will cause the editing
+command to be immediately invoked.
+Note that although only two prefix
+characters (usually ESC and ^X) are supported, some multi-character
+sequences can be supported.
+The following binds the arrow keys on
+an ANSI terminal, or xterm (these are in the default bindings).
+Of course some escape sequences won't work out quite this nicely:
+.sp
+.RS
+\fBbind '^[['=prefix\-2
+.br
+bind '^XA'=up\-history
+.br
+bind '^XB'=down\-history
+.br
+bind '^XC'=forward\-char
+.br
+bind '^XD'=backward\-char\fP
+.RE
+.\"}}}
+.\"{{{  bind -l
+.IP "\fBbind \-l\fP"
+Lists the names of the functions to which keys may be bound.
+.\"}}}
+.\"{{{  bind -m string=[substitute]
+.IP "\fBbind \-m\fP \fIstring\fP\fB=\fP[\fIsubstitute\fP]"
+The specified input \fIstring\fP will afterwards be immediately
+replaced by the given \fIsubstitute\fP string, which may contain
+editing commands.
+.\"}}}
+.PP
+The following is a list of editing commands available.
+Each description starts with the name of the command,
+a \fIn\fP, if the command can be prefixed with a count,
+and any keys the command is bound to by default (written using
+caret notation, \fIe.g.\fP, ASCII ESC character is written as ^[).
+A count prefix for a command is entered using the sequence
+\fB^[\fP\fIn\fP, where \fIn\fP is a sequence of 1 or more digits;
+unless otherwise specified, if a count is omitted, it defaults to 1.
+Note that editing command names are
+used only with the \fBbind\fP command.
+Furthermore, many editing
+commands are useful only on terminals with a visible cursor.
+The default bindings were chosen to resemble corresponding EMACS key
+bindings.
+The users tty characters (\fIe.g.\fP, ERASE) are bound to
+reasonable substitutes and override the default bindings.
+.\"{{{  abort ^G
+.IP "\fBabort ^G\fP"
+Useful as a response to a request for a \fBsearch-history\fP pattern in
+order to abort the search.
+.\"}}}
+.\"{{{  auto-insert n
+.IP "\fBauto-insert\fP \fIn\fP"
+Simply causes the character to appear as literal input.
+Most ordinary characters are bound to this.
+.\"}}}
+.\"{{{  backward-char  n ^B
+.IP "\fBbackward-char\fP  \fIn\fP \fB^B\fP"
+Moves the cursor backward \fIn\fP characters.
+.\"}}}
+.\"{{{  backward-word  n ^[B
+.IP "\fBbackward-word\fP  \fIn\fP \fB^[B\fP"
+Moves the cursor backward to the beginning of a word; words consist
+of alphanumerics, underscore (_) and dollar ($).
+.\"}}}
+.\"{{{  beginning-of-history ^[<
+.IP "\fBbeginning-of-history ^[<\fP"
+Moves to the beginning of the history.
+.\"}}}
+.\"{{{  beginning-of-line ^A
+.IP "\fBbeginning-of-line ^A\fP"
+Moves the cursor to the beginning of the edited input line.
+.\"}}}
+.\"{{{  capitalize-word n ^[c, ^[C
+.IP "\fBcapitalize-word\fP \fIn\fP \fB^[c\fP, \fB^[C\fP"
+Uppercase the first character in the next \fIn\fP words,
+leaving the cursor past the end of the last word.
+.\"}}}
+.\"{{{  comment ^[#
+If the current line does not begin with a comment character, one
+is added at the beginning of the line and the line is entered (as if
+return had been pressed), otherwise the existing comment characters
+are removed and the cursor is placed at the beginning of the line.
+.\"}}}
+.\"{{{  complete ^[^[
+.IP "\fBcomplete ^[^[\fP"
+.IP "\fBcomplete ^I\fP"
+Automatically completes as much as is unique of the command name
+or the file name containing the cursor.
+If the entire remaining command
+or file name is unique a space is printed after its completion, unless
+it is a directory name in which case \fB/\fP is appended.
+If there is no command or file name with the current partial word as its
+prefix, a bell character is output (usually causing a audio beep).
+.\"}}}
+.\"{{{  complete-command ^X^[
+.IP "\fBcomplete-command ^X^[\fP"
+Automatically completes as much as is unique of the command name
+having the partial word up to the cursor as its prefix, as in the
+\fBcomplete\fP command described above.
+.\"}}}
+.\"{{{  complete-file ^[^X
+.IP "\fBcomplete-file ^[^X\fP"
+Automatically completes as much as is unique of the file name having
+the partial word up to the cursor as its prefix, as in the
+\fBcomplete\fP command described above.
+.\"}}}
+.\"{{{  complete-list ^[=
+.IP "\fBcomplete-list ^[=\fP"
+List the possible completions for the current word.
+.\"}}}
+.\"{{{  delete-char-backward n ERASE, ^?, ^H
+.IP "\fBdelete-char-backward\fP \fIn\fP \fBERASE\fP, \fB^?\fP, \fB^H\fP"
+Deletes \fIn\fP characters before the cursor.
+.\"}}}
+.\"{{{  delete-char-forward n
+.IP "\fBdelete-char-forward\fP \fIn\fP"
+Deletes \fIn\fP characters after the cursor.
+.\"}}}
+.\"{{{  delete-word-backward n ^[ERASE, ^[^?, ^[^H, ^[h
+.IP "\fBdelete-word-backward\fP \fIn\fP \fB^[ERASE\fP, \fB^[^?\fP, \fB^[^H\fP, \fB^[h\fP"
+Deletes \fIn\fP words before the cursor.
+.\"}}}
+.\"{{{  delete-word-forward n ^[d
+.IP "\fBdelete-word-forward\fP \fIn\fP \fB^[d\fP"
+Deletes characters after the cursor up to the end of \fIn\fP words.
+.\"}}}
+.\"{{{  down-history n ^N
+.IP "\fBdown-history\fP \fIn\fP \fB^N\fP"
+Scrolls the history buffer forward \fIn\fP lines (later).
+Each input line
+originally starts just after the last entry in the history buffer, so
+\fBdown-history\fP is not useful until either \fBsearch-history\fP or
+\fBup-history\fP has been performed.
+.\"}}}
+.\"{{{  downcase-word n ^[L, ^[l
+.IP "\fBdowncase-word\fP \fIn\fP \fB^[L\fP, \fB^[l\fP"
+Lowercases the next \fIn\fP words.
+.\"}}}
+.\"{{{  end-of-history ^[>
+.IP "\fBend-of-history ^[>\fP"
+Moves to the end of the history.
+.\"}}}
+.\"{{{  end-of-line ^E
+.IP "\fBend-of-line ^E\fP"
+Moves the cursor to the end of the input line.
+.\"}}}
+.\"{{{  eot ^_
+.IP "\fBeot ^_\fP"
+Acts as an end-of-file; this is useful because edit-mode input disables
+normal terminal input canonicalization.
+.\"}}}
+.\"{{{  eot-or-delete n ^D
+.IP "\fBeot-or-delete\fP \fIn\fP \fB^D\fP"
+Acts as eot if alone on a line; otherwise acts as delete-char-forward.
+.\"}}}
+.\"{{{  error
+.IP "\fBerror\fP"
+Error (ring the bell).
+.\"}}}
+.\"{{{  exchange-point-and-mark ^X^X
+.IP "\fBexchange-point-and-mark ^X^X\fP"
+Places the cursor where the mark is, and sets the mark to where the
+cursor was.
+.\"}}}
+.\"{{{  expand-file ^[*
+.IP "\fBexpand-file ^[*\fP"
+Appends a * to the current word and replaces the word with
+the result of performing file globbing on the word.
+If no files match the pattern, the bell is rung.
+.\"}}}
+.\"{{{  forward-char n ^F
+.IP "\fBforward-char\fP \fIn\fP \fB^F\fP"
+Moves the cursor forward \fIn\fP characters.
+.\"}}}
+.\"{{{  forward-word n ^[f
+.IP "\fBforward-word\fP \fIn\fP \fB^[f\fP"
+Moves the cursor forward to the end of the \fIn\fPth word.
+.\"}}}
+.\"{{{  goto-history n ^[g
+.IP "\fBgoto-history\fP \fIn\fP \fB^[g\fP"
+Goes to history number \fIn\fP.
+.\"}}}
+.\"{{{  kill-line KILL
+.IP "\fBkill-line KILL\fP"
+Deletes the entire input line.
+.\"}}}
+.\"{{{  kill-region ^W
+.IP "\fBkill-region ^W\fP"
+Deletes the input between the cursor and the mark.
+.\"}}}
+.\"{{{  kill-to-eol n ^K
+.IP "\fBkill-to-eol\fP \fIn\fP \fB^K\fP"
+Deletes the input from the cursor to the end of the line if \fIn\fP is
+not specified, otherwise deletes characters between the cursor and
+column \fIn\fP.
+.\"}}}
+.\"{{{  list ^[?
+.IP "\fBlist ^[?\fP"
+Prints a sorted, columnated list of command names or file names
+(if any) that can complete the partial word containing the cursor.
+Directory names have \fB/\fP appended to them.
+.\"}}}
+.\"{{{  list-command ^X?
+.IP "\fBlist-command ^X?\fP"
+Prints a sorted, columnated list of command names (if any) that
+can complete the partial word containing the cursor.
+.\"}}}
+.\"{{{  list-file ^X^Y
+.IP "\fBlist-file ^X^Y\fP"
+Prints a sorted, columnated list of file names (if any) that can
+complete the partial word containing the cursor.
+File type indicators
+are appended as described under \fBlist\fP above.
+.\"}}}
+.\"{{{  newline ^J and ^M
+.IP "\fBnewline ^J\fP, \fB^M\fP"
+Causes the current input line to be processed by the shell.
+The current cursor position may be anywhere on the line.
+.\"}}}
+.\"{{{  newline-and-next ^O
+.IP "\fBnewline-and-next ^O\fP"
+Causes the current input line to be processed by the shell, and
+the next line from history becomes the current line.
+This is only useful after an up-history or search-history.
+.\"}}}
+.\"{{{  no-op QUIT
+.IP "\fBno-op QUIT\fP"
+This does nothing.
+.\"}}}
+.\"{{{  prefix-1 ^[
+.IP "\fBprefix-1 ^[\fP"
+Introduces a 2-character command sequence.
+.\"}}}
+.\"{{{  prefix-2 ^X and ^[[
+.IP "\fBprefix-2 ^X\fP"
+.IP "\fBprefix-2 ^[[\fP"
+Introduces a 2-character command sequence.
+.\"}}}
+.\"{{{  prev-hist-word ^[. ^[_
+.IP "\fBprev-hist-word\fP \fIn\fP \fB^[.\fP, \fB^[_\fP"
+The last (\fIn\fPth) word of the previous command is inserted at the cursor.
+.\"}}}
+.\"{{{  quote ^^
+.IP "\fBquote ^^\fP"
+The following character is taken literally rather than as an editing
+command.
+.\"}}}
+.\"{{{  redraw ^L
+.IP "\fBredraw ^L\fP"
+Reprints the prompt string and the current input line.
+.\"}}}
+.\"{{{  search-character-backward n ^[^]
+.IP "\fBsearch-character-backward\fP \fIn\fP \fB^[^]\fP"
+Search backward in the current line for the \fIn\fPth occurrence of the
+next character typed.
+.\"}}}
+.\"{{{  search-character-forward n ^]
+.IP "\fBsearch-character-forward\fP \fIn\fP \fB^]\fP"
+Search forward in the current line for the \fIn\fPth occurrence of the
+next character typed.
+.\"}}}
+.\"{{{  search-history ^R
+.IP "\fBsearch-history ^R\fP"
+Enter incremental search mode.
+The internal history list is searched
+backwards for commands matching the input.
+An initial \fB^\fP in the search string anchors the search.
+The abort key will leave search mode.
+Other commands will be executed after leaving search mode.
+Successive \fBsearch-history\fP commands continue searching backward to
+the next previous occurrence of the pattern.
+The history buffer retains only a
+finite number of lines; the oldest are discarded as necessary.
+.\"}}}
+.\"{{{  set-mark-command ^[<space>
+.IP "\fBset-mark-command ^[\fP<space>"
+Set the mark at the cursor position.
+.\"}}}
+.\"{{{  stuff
+.IP "\fBstuff\fP"
+On systems supporting it, pushes the bound character back onto the
+terminal input where it may receive special processing by the terminal
+handler.
+This is useful for the BRL \fB^T\fP mini-systat feature, for example.
+.\"}}}
+.\"{{{  stuff-reset
+.IP "\fBstuff-reset\fP"
+Acts like \fBstuff\fP, then aborts input the same as an interrupt.
+.\"}}}
+.\"{{{  transport-chars ^T
+.IP "\fBtranspose-chars ^T\fP"
+If at the end of line, or if the \fBgmacs\fP option is set,
+this exchanges the two previous characters; otherwise, it
+exchanges the previous and current characters and moves the cursor
+one character to the right.
+.\"}}}
+.\"{{{  up-history n ^P
+.IP "\fBup-history\fP \fIn\fP \fB^P\fP"
+Scrolls the history buffer backward \fIn\fP lines (earlier).
+.\"}}}
+.\"{{{  upcase-word n ^[U, ^[u
+.IP "\fBupcase-word\fP \fIn\fP \fB^[U\fP, \fB^[u\fP"
+Uppercases the next \fIn\fP words.
+.\"}}}
+.\"{{{  version ^V
+.IP "\fBversion ^V\fP"
+Display the version of ksh.
+The current edit buffer is restored as soon
+as any key is pressed (the key is then processed, unless it is a space).
+.\"}}}
+.\"{{{  yank ^Y
+.IP "\fByank ^Y\fP"
+Inserts the most recently killed text string at the current cursor position.
+.\"}}}
+.\"{{{  yank-pop ^[y
+.IP "\fByank-pop ^[y\fP"
+Immediately after a \fByank\fP, replaces the inserted text string with
+the next previous killed text string.
+.\"}}}
+.\"}}}
+.\"{{{  Vi Editing Mode
+.\"{{{  introduction
+.SS "Vi Editing Mode"
+The vi command line editor in ksh has basically the same commands as the
+vi editor (see \fIvi\fP(1)), with the following exceptions:
+.nr P2 \n(PD
+.IP \ \ \(bu
+you start out in insert mode,
+.IP \ \ \(bu
+there are file name and command completion commands
+(\fB=\fP, \fB\e\fP, \fB*\fP, \fB^X\fP, \fB^E\fP, \fB^F\fP and,
+optionally, \fB<tab>\fP),
+.IP \ \ \(bu
+the \fB_\fP command is different (in ksh it is the last argument command,
+in vi it goes to the start of the current line),
+.IP \ \ \(bu
+the \fB/\fP and \fBG\fP commands move in the opposite direction as the \fBj\fP
+command
+.IP \ \ \(bu
+and commands which don't make sense in a single line editor are not available
+(\fIe.g.\fP, screen movement commands, ex \fB:\fP commands, \fIetc.\fP).
+.nr PD \n(P2
+.LP
+Note that the \fB^X\fP stands for control-X; also \fB<esc>\fP, \fB<space>\fP
+and \fB<tab>\fP are used for escape, space and tab, respectively (no kidding).
+.\"}}}
+.\"{{{  modes
+.PP
+Like vi, there are two modes: insert mode and command mode.
+In insert mode, most characters are simply put in the buffer at the
+current cursor position as they are typed, however, some characters
+are treated specially.
+In particular, the following characters are taken from current tty settings
+(see \fIstty\fP(1)) and have their usual meaning (normal values are in
+parentheses):
+kill (\fB^U\fP), erase (\fB^?\fP), werase (\fB^W\fP), eof (\fB^D\fP),
+intr (\fB^C\fP) and quit (\fB^\e\fP).
+In addition to the above, the following characters are also treated
+specially in insert mode:
+.TS
+expand;
+afB lw(4.5i).
+^H     T{
+erases previous character
+T}
+^V     T{
+literal next: the next character typed is not treated specially (can be
+used to insert the characters being described here)
+T}
+^J ^M  T{
+end of line: the current line is read, parsed and executed by the shell
+T}
+<esc>  T{
+puts the editor in command mode (see below)
+T}
+^E     T{
+command and file name enumeration (see below)
+T}
+^F     T{
+command and file name completion (see below).
+If used twice in a row, the list of possible completions is displayed;
+if used a third time, the completion is undone.
+T}
+^X     T{
+command and file name expansion (see below)
+T}
+<tab>  T{
+optional file name and command completion (see \fB^F\fP above), enabled with
+\fBset \-o vi-tabcomplete\fP
+T}
+.TE
+.\"}}}
+.\"{{{  command mode
+.PP
+In command mode, each character is interpreted as a command.
+Characters that don't correspond to commands, are illegal combinations of
+commands or are commands that can't be carried out all cause beeps.
+In the following command descriptions, a \fIn\fP indicates the
+command may be prefixed by a number (\fIe.g.\fP, \fB10l\fP moves right 10
+characters); if no number prefix is used, \fIn\fP is assumed to be 1
+unless otherwise specified.
+The term `current position' refers to the position between the cursor
+and the character preceding the cursor.
+A `word' is a sequence of letters, digits and underscore characters or a
+sequence of non-letter, non-digit, non-underscore, non-white-space characters
+(\fIe.g.\fP, ab2*&^ contains two words) and a `big-word' is a sequence of
+non-white-space characters.
+.\"{{{  Special ksh vi commands
+.IP "Special ksh vi commands"
+The following commands are not in, or are different from, the normal vi file
+editor:
+.RS
+.IP "\fIn\fP\fB_\fP"
+insert a space followed by the \fIn\fPth big-word from the last command in the
+history at the current position and enter insert mode; if \fIn\fP is not
+specified, the last word is inserted.
+.IP "\fB#\fP"
+insert the comment character (\fB#\fP) at the start of the current line and
+return the line to the shell (equivalent to \fBI#^J\fP).
+.IP "\fIn\fP\fBg\fP"
+like \fBG\fP, except if \fIn\fP is not specified, it goes to the most recent
+remembered line.
+.IP "\fIn\fP\fBv\fP"
+edit line \fIn\fP using the vi editor;
+if \fIn\fP is not specified, the current line is edited.
+The actual command executed is
+`\fBfc \-e ${VISUAL:-${EDITOR:-vi}}\fP \fIn\fP'.
+.IP "\fB*\fP and \fB^X\fP"
+command or file name expansion is applied to the current big-word
+(with an appended *, if the word contains no file globing characters) - the
+big-word is replaced with the resulting words.
+If the current big-word is the first on the line (or follows one
+of the following characters: \fB;\fP, \fB|\fP, \fB&\fP, \fB(\fP, \fB)\fP)
+and does not contain a slash (\fB/\fP) then command expansion is done,
+otherwise file name expansion is done.
+Command expansion will match the big-word against all aliases, functions
+and built-in commands as well as any executable files found by searching
+the directories in the \fBPATH\fP parameter.
+File name expansion matches the big-word against the files in the
+current directory.
+After expansion, the cursor is placed just past the last word and the editor
+is in insert mode.
+.IP "\fIn\fP\fB\e\fP, \fIn\fP\fB^F\fP, \fIn\fP\fB<tab>\fP and \fIn\fP\fB<esc>\fP"
+command/file name completion:
+replace the current big-word with the longest unique
+match obtained after performing command/file name expansion.
+\fB<tab>\fP is only recognized if the \fBvi-tabcomplete\fP option is set,
+while \fB<esc>\fP is only recognized if the \fBvi-esccomplete\fP option
+is set (see \fBset \-o\fP).
+If \fIn\fP is specified, the \fIn\fPth possible
+completion is selected (as reported by the command/file name enumeration
+command).
+.IP "\fB=\fP and \fB^E\fP"
+command/file name enumeration: list all the commands or files that match
+the current big-word.
+.IP "\fB^V\fP"
+display the version of pdksh; it is displayed until another key is pressed
+(this key is ignored).
+.IP "\fB@\fP\fIc\fP"
+macro expansion: execute the commands found in the alias _\fIc\fP.
+.RE
+.\"}}}
+.\"{{{  Intra-line movement commands
+.IP "Intra-line movement commands"
+.RS
+.IP "\fIn\fP\fBh\fP and \fIn\fP\fB^H\fP"
+move left \fIn\fP characters.
+.IP "\fIn\fP\fBl\fP and \fIn\fP\fB<space>\fP"
+move right \fIn\fP characters.
+.IP "\fB0\fP"
+move to column 0.
+.IP "\fB^\fP"
+move to the first non white-space character.
+.IP "\fIn\fP\fB|\fP"
+move to column \fIn\fP.
+.IP "\fB$\fP"
+move to the last character.
+.IP "\fIn\fP\fBb\fP"
+move back \fIn\fP words.
+.IP "\fIn\fP\fBB\fP"
+move back \fIn\fP big-words.
+.IP "\fIn\fP\fBe\fP"
+move forward to the end the word, \fIn\fP times.
+.IP "\fIn\fP\fBE\fP"
+move forward to the end the big-word, \fIn\fP times.
+.IP "\fIn\fP\fBw\fP"
+move forward \fIn\fP words.
+.IP "\fIn\fP\fBW\fP"
+move forward \fIn\fP big-words.
+.IP "\fB%\fP"
+find match: the editor looks forward for the nearest parenthesis,
+bracket or brace and then moves the to the matching parenthesis, bracket or
+brace.
+.IP "\fIn\fP\fBf\fP\fIc\fP"
+move forward to the \fIn\fPth occurrence of the character \fIc\fP.
+.IP "\fIn\fP\fBF\fP\fIc\fP"
+move backward to the \fIn\fPth occurrence of the character \fIc\fP.
+.IP "\fIn\fP\fBt\fP\fIc\fP"
+move forward to just before the \fIn\fPth occurrence of the character \fIc\fP.
+.IP "\fIn\fP\fBT\fP\fIc\fP"
+move backward to just before the \fIn\fPth occurrence of the character \fIc\fP.
+.IP "\fIn\fP\fB;\fP"
+repeats the last \fBf\fP, \fBF\fP, \fBt\fP or \fBT\fP command.
+.IP "\fIn\fP\fB,\fP"
+repeats the last \fBf\fP, \fBF\fP, \fBt\fP or \fBT\fP command, but moves
+in the opposite direction.
+.RE
+.\"}}}
+.\"{{{  Inter-line movement commands
+.IP "Inter-line movement commands"
+.RS
+.IP "\fIn\fP\fBj\fP and \fIn\fP\fB+\fP and \fIn\fP\fB^N\fP"
+move to the \fIn\fPth next line in the history.
+.IP "\fIn\fP\fBk\fP and \fIn\fP\fB-\fP and \fIn\fP\fB^P\fP"
+move to the \fIn\fPth previous line in the history.
+.IP "\fIn\fP\fBG\fP"
+move to line \fIn\fP in the history; if \fIn\fP is not specified, the
+number first remembered line is used.
+.IP "\fIn\fP\fBg\fP"
+like \fBG\fP, except if \fIn\fP is not specified, it goes to the most recent
+remembered line.
+.IP "\fIn\fP\fB/\fP\fIstring\fP"
+search backward through the history for the \fIn\fPth line containing
+\fIstring\fP; if \fIstring\fP starts with \fB^\fP, the remainder of the
+string must appear at the start of the history line for it to match.
+.IP "\fIn\fP\fB?\fP\fIstring\fP"
+same as \fB/\fP, except it searches forward through the history.
+.IP "\fIn\fP\fBn\fP"
+search for the \fIn\fPth occurrence of the last search string; the
+direction of the search is the same as the last search.
+.IP "\fIn\fP\fBN\fP"
+search for the \fIn\fPth occurrence of the last search string; the
+direction of the search is the opposite of the last search.
+.RE
+.\"}}}
+.\"{{{  Edit commands
+.IP "Edit commands"
+.RS
+.IP "\fIn\fP\fBa\fP"
+append text \fIn\fP times: goes into insert mode just after the current
+position.
+The append is only replicated if command mode is re-entered (\fIi.e.\fP,
+<esc> is used).
+.IP "\fIn\fP\fBA\fP"
+same as \fBa\fP, except it appends at the end of the line.
+.IP "\fIn\fP\fBi\fP"
+insert text \fIn\fP times: goes into insert mode at the current
+position.
+The insertion is only replicated if command mode is re-entered (\fIi.e.\fP,
+<esc> is used).
+.IP "\fIn\fP\fBI\fP"
+same as \fBi\fP, except the insertion is done just before the first non-blank
+character.
+.IP "\fIn\fP\fBs\fP"
+substitute the next \fIn\fP characters (\fIi.e.\fP, delete the characters
+and go into insert mode).
+.IP "\fBS\fP"
+substitute whole line: all characters from the first non-blank character
+to the end of line are deleted and insert mode is entered.
+.IP "\fIn\fP\fBc\fP\fImove-cmd\fP"
+change from the current position to the position resulting from \fIn\fP
+\fImove-cmd\fPs (\fIi.e.\fP, delete the indicated region and go into insert
+mode);
+if \fImove-cmd\fP is \fBc\fP, the line starting from the first non-blank
+character is changed.
+.IP "\fBC\fP"
+change from the current position to the end of the line (\fIi.e.\fP, delete to
+the end of the line and go into insert mode).
+.IP "\fIn\fP\fBx\fP"
+delete the next \fIn\fP characters.
+.IP "\fIn\fP\fBX\fP"
+delete the previous \fIn\fP characters.
+.IP "\fBD\fP"
+delete to the end of the line.
+.IP "\fIn\fP\fBd\fP\fImove-cmd\fP"
+delete from the current position to the position resulting from
+\fIn\fP \fImove-cmd\fPs;
+\fImove-cmd\fP is a movement command (see above) or \fBd\fP, in which case
+the current line is deleted.
+.IP "\fIn\fP\fBr\fP\fIc\fP"
+replace the next \fIn\fP characters with the character \fIc\fP.
+.IP "\fIn\fP\fBR\fP"
+replace: enter insert mode but overwrite existing characters instead of
+inserting before existing characters.
+The replacement is repeated \fIn\fP times.
+.IP "\fIn\fP\fB~\fP"
+change the case of the next \fIn\fP characters.
+.IP "\fIn\fP\fBy\fP\fImove-cmd\fP"
+yank from the current position to the position resulting from \fIn\fP
+\fImove-cmd\fPs into the yank buffer; if \fImove-cmd\fP is \fBy\fP, the
+whole line is yanked.
+.IP "\fBY\fP"
+yank from the current position to the end of the line.
+.IP "\fIn\fP\fBp\fP"
+paste the contents of the yank buffer just after the current position,
+\fIn\fP times.
+.IP "\fIn\fP\fBP\fP"
+same as \fBp\fP, except the buffer is pasted at the current position.
+.RE
+.\"}}}
+.\"{{{  Miscellaneous vi commands
+.IP "Miscellaneous vi commands"
+.RS
+.IP "\fB^J\fP and \fB^M\fP"
+the current line is read, parsed and executed by the shell.
+.IP "\fB^L\fP and \fB^R\fP"
+redraw the current line.
+.IP "\fIn\fP\fB.\fP"
+redo the last edit command \fIn\fP times.
+.IP "\fBu\fP"
+undo the last edit command.
+.IP "\fBU\fP"
+undo all changes that have been made to the current line.
+.IP "\fIintr\fP and \fIquit\fP"
+the interrupt and quit terminal characters cause the current line to
+be deleted and a new prompt to be printed.
+.RE
+.\"Has all vi commands except:
+.\"    movement: { } [[ ]] ^E ^Y ^U ^D ^F ^B H L M ()
+.\"    tag commands: ^T ^]
+.\"    mark commands: m ` '
+.\"    named-buffer commands: " @
+.\"    file/shell/ex-commands: Q ZZ ^^ : ! &
+.\"    multi-line change commands: o O J
+.\"    shift commands: << >>
+.\"    status command: ^G
+.\"}}}
+.\"}}}
+.\"}}}
+.ksh)
+.\"}}}
+.\"}}}
+.\"{{{  Files
+.SH FILES
+~/.kshrc
+.br
+~/.profile
+.br
+/etc/profile
+.br
+/etc/suid_profile
+.\"}}}
+.\"{{{  Bugs
+.SH BUGS
+Any bugs in pdksh should be reported to pdksh@cs.mun.ca.
+Please
+include the version of pdksh (echo $KSH_VERSION shows it), the machine,
+operating system and compiler you are using and a description of how to
+repeat the bug (a small shell script that demonstrates the bug is
+best).
+The following, if relevant (if you are not sure, include them),
+can also helpful: options you are using (both options.h options and set
+\-o options) and a copy of your config.h (the file generated by the
+configure script).
+New versions of pdksh can be obtained from
+ftp://ftp.cs.mun.ca/pub/pdksh/.
+.PP
+BTW, the most frequently reported bug is
+.RS
+\fB echo hi | read a; echo $a\fP\ \ \ # Does not print hi
+.RE
+I'm aware of this and there is no need to report it.
+.\"}}}
+.\"{{{  Version
+.SH VERSION
+This page documents version
+.ce
+ @(#)PD KSH v5.2.14 99/07/13.2
+of the public domain korn shell.
+.\"}}}
+.\"{{{  Authors
+.SH AUTHORS
+This shell is based on the public domain 7th edition Bourne shell clone by
+Charles Forsyth and parts of the BRL shell by Doug A.\& Gwyn, Doug Kingston,
+Ron Natalie, Arnold Robbins, Lou Salkind and others.
+The first release
+of pdksh was created by Eric Gisin, and it was subsequently maintained by
+John R.\& MacMillan (chance!john@sq.sq.com), and
+Simon J.\& Gerraty (sjg@zen.void.oz.au).
+The current maintainer is Michael Rendell (michael@cs.mun.ca).
+The CONTRIBUTORS file in the source distribution contains a more complete
+list of people and their part in the shell's development.
+.\"}}}
+.\"{{{  See also
+.SH "SEE ALSO"
+awk(1),
+.ksh(
+sh(1),
+.ksh)
+.sh(
+ksh(1),
+.sh)
+csh(1), ed(1), getconf(1), getopt(1), sed(1), stty(1), vi(1),
+dup(2), execve(2), getgid(2), getuid(2), open(2), pipe(2), wait(2),
+getopt(3), rand(3), signal(3), system(3),
+environ(7)
+.PP
+.IR "The KornShell Command and Programming Language" ,
+Morris Bolsky and David Korn, 1989, ISBN 0-13-516972-0.
+.PP
+.\" XXX ISBN missing
+.IR "UNIX Shell Programming" ,
+Stephen G.\& Kochan, Patrick H.\& Wood, Hayden.
+.PP
+.IR "IEEE Standard for information Technology \- Portable Operating System Interface (POSIX) \- Part 2: Shell and Utilities" ,
+IEEE Inc, 1993, ISBN 1-55937-255-9.
+.\"}}}
diff --git a/bin/ksh/ksh_dir.h b/bin/ksh/ksh_dir.h
new file mode 100644 (file)
index 0000000..b0b3db6
--- /dev/null
@@ -0,0 +1,27 @@
+/*     $NetBSD: ksh_dir.h,v 1.2 1997/01/12 19:11:59 tls Exp $  */
+
+/* Wrapper around the ugly dir includes/ifdefs */
+/* $NetBSD: ksh_dir.h,v 1.2 1997/01/12 19:11:59 tls Exp $ */
+
+#if defined(HAVE_DIRENT_H)
+# include <dirent.h>
+# define NLENGTH(dirent)       (strlen(dirent->d_name))
+#else
+# define dirent direct
+# define NLENGTH(dirent)       (dirent->d_namlen)
+# ifdef HAVE_SYS_NDIR_H
+#  include <sys/ndir.h>
+# endif /* HAVE_SYS_NDIR_H */
+# ifdef HAVE_SYS_DIR_H
+#  include <sys/dir.h>
+# endif /* HAVE_SYSDIR_H */
+# ifdef HAVE_NDIR_H
+#  include <ndir.h>
+# endif /* HAVE_NDIR_H */
+#endif /* HAVE_DIRENT_H */
+
+#ifdef OPENDIR_DOES_NONDIR
+extern DIR *ksh_opendir ARGS((const char *d));
+#else /* OPENDIR_DOES_NONDIR */
+# define ksh_opendir(d)        opendir(d)
+#endif /* OPENDIR_DOES_NONDIR */
diff --git a/bin/ksh/ksh_limval.h b/bin/ksh/ksh_limval.h
new file mode 100644 (file)
index 0000000..42e38bb
--- /dev/null
@@ -0,0 +1,25 @@
+/*     $NetBSD: ksh_limval.h,v 1.2 1997/01/12 19:11:59 tls Exp $       */
+
+/* Wrapper around the values.h/limits.h includes/ifdefs */
+/* $NetBSD: ksh_limval.h,v 1.2 1997/01/12 19:11:59 tls Exp $ */
+
+#ifdef HAVE_VALUES_H
+# include <values.h>
+#endif /* HAVE_VALUES_H */
+/* limits.h is included in sh.h */
+
+#ifndef DMAXEXP
+# define DMAXEXP       128     /* should be big enough */
+#endif
+
+#ifndef BITSPERBYTE
+# ifdef CHAR_BIT
+#  define BITSPERBYTE  CHAR_BIT
+# else
+#  define BITSPERBYTE  8       /* probably true.. */
+# endif
+#endif
+
+#ifndef BITS
+# define BITS(t)       (BITSPERBYTE * sizeof(t))
+#endif
diff --git a/bin/ksh/ksh_stat.h b/bin/ksh/ksh_stat.h
new file mode 100644 (file)
index 0000000..5f78406
--- /dev/null
@@ -0,0 +1,60 @@
+/*     $NetBSD: ksh_stat.h,v 1.2 1997/01/12 19:12:00 tls Exp $ */
+
+/* Wrapper around the ugly sys/stat includes/ifdefs */
+/* $NetBSD: ksh_stat.h,v 1.2 1997/01/12 19:12:00 tls Exp $ */
+
+/* assumes <sys/types.h> already included */
+#include <sys/stat.h>
+
+#ifndef HAVE_LSTAT
+# define lstat(path, buf)      stat(path, buf)
+#endif /* HAVE_LSTAT */
+
+#ifdef STAT_MACROS_BROKEN
+# undef S_ISREG
+# undef S_ISDIR
+# undef S_ISCHR
+# undef S_ISBLK
+# undef S_ISFIFO
+# undef S_ISSOCK
+# undef S_ISLNK
+#endif /* STAT_MACROS_BROKEN */
+
+#if !defined(S_ISREG) && defined(S_IFREG)
+# define S_ISREG(m)    (((m) & S_IFMT) == S_IFREG)
+#endif /* S_ISREG */
+#if !defined(S_ISDIR) && defined(S_IFDIR)
+# define S_ISDIR(m)    (((m) & S_IFMT) == S_IFDIR)
+#endif /* S_ISDIR */
+#if !defined(S_ISCHR) && defined(S_IFCHR)
+# define S_ISCHR(m)    (((m) & S_IFMT) == S_IFCHR)
+#endif /* S_ISCHR */
+#if !defined(S_ISBLK) && defined(S_IFBLK)
+# define S_ISBLK(m)    (((m) & S_IFMT) == S_IFBLK)
+#endif /* S_ISBLK */
+#if !defined(S_ISFIFO) && defined(S_IFIFO)
+# define S_ISFIFO(m)   (((m) & S_IFMT) == S_IFIFO)
+#endif /* S_ISFIFO */
+#if !defined(S_ISLNK) && defined(S_IFLNK)
+# define S_ISLNK(m)    (((m) & S_IFMT) == S_IFLNK)
+#endif /* S_ISLNK */
+#if !defined(S_ISSOCK) && defined(S_IFSOCK)
+# define S_ISSOCK(m)   (((m) & S_IFMT) == S_IFSOCK)
+#endif /* S_ISSOCK */
+#if !defined(S_ISCDF) && defined(S_CDF)
+# define S_ISCDF(m)    (S_ISDIR(m) && ((m) & S_CDF))
+#endif /* S_ISSOCK */
+
+#ifndef S_ISVTX
+# define S_ISVTX       01000   /* sticky bit */
+#endif /* S_ISVTX */
+
+#ifndef S_IXUSR
+# define S_IXUSR       00100   /* user execute bit */
+#endif /* S_IXUSR */
+#ifndef S_IXGRP
+# define S_IXGRP       00010   /* user execute bit */
+#endif /* S_IXGRP */
+#ifndef S_IXOTH
+# define S_IXOTH       00001   /* user execute bit */
+#endif /* S_IXOTH */
diff --git a/bin/ksh/ksh_time.h b/bin/ksh/ksh_time.h
new file mode 100644 (file)
index 0000000..79a7b15
--- /dev/null
@@ -0,0 +1,27 @@
+/*     $NetBSD: ksh_time.h,v 1.2 1997/01/12 19:12:01 tls Exp $ */
+
+#ifndef KSH_TIME_H
+# define KSH_TIME_H
+
+/* Wrapper around the ugly time.h,sys/time.h includes/ifdefs */
+/* $NetBSD: ksh_time.h,v 1.2 1997/01/12 19:12:01 tls Exp $ */
+
+#ifdef TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else /* TIME_WITH_SYS_TIME */
+# ifdef HAVE_SYS_TIME_H
+#  include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif /* TIME_WITH_SYS_TIME */
+
+#ifndef TIME_DECLARED
+extern time_t time ARGS((time_t *));
+#endif
+
+#ifndef CLK_TCK
+# define CLK_TCK 60                    /* 60HZ */
+#endif
+#endif /* KSH_TIME_H */
diff --git a/bin/ksh/ksh_times.h b/bin/ksh/ksh_times.h
new file mode 100644 (file)
index 0000000..e01215a
--- /dev/null
@@ -0,0 +1,20 @@
+/*     $NetBSD: ksh_times.h,v 1.2 1997/01/12 19:12:02 tls Exp $        */
+
+#ifndef KSH_TIMES_H
+# define KSH_TIMES_H
+
+/* Needed for clock_t on some systems (ie, NeXT in non-posix mode) */
+#include "ksh_time.h"
+
+#include <sys/times.h>
+
+#ifdef TIMES_BROKEN
+extern clock_t ksh_times ARGS((struct tms *));
+#else /* TIMES_BROKEN */
+# define ksh_times times
+#endif /* TIMES_BROKEN */
+
+#ifdef HAVE_TIMES
+extern clock_t times ARGS((struct tms *));
+#endif /* HAVE_TIMES */
+#endif /* KSH_TIMES_H */
diff --git a/bin/ksh/ksh_wait.h b/bin/ksh/ksh_wait.h
new file mode 100644 (file)
index 0000000..dba7a27
--- /dev/null
@@ -0,0 +1,52 @@
+/*     $NetBSD: ksh_wait.h,v 1.2 1997/01/12 19:12:03 tls Exp $ */
+
+/* Wrapper around the ugly sys/wait includes/ifdefs */
+/* $NetBSD: ksh_wait.h,v 1.2 1997/01/12 19:12:03 tls Exp $ */
+
+#ifdef HAVE_SYS_WAIT_H
+# include <sys/wait.h>
+#endif
+
+#ifndef POSIX_SYS_WAIT
+/* Get rid of system macros (which probably use union wait) */
+# undef WIFCORED
+# undef WIFEXITED
+# undef WEXITSTATUS
+# undef WIFSIGNALED
+# undef WTERMSIG
+# undef WIFSTOPPED
+# undef WSTOPSIG
+#endif /* POSIX_SYS_WAIT */
+
+typedef int WAIT_T;
+
+#ifndef WIFCORED
+# define WIFCORED(s)   ((s) & 0x80)
+#endif
+#define WSTATUS(s)     (s)
+
+#ifndef WIFEXITED
+# define WIFEXITED(s)  (((s) & 0xff) == 0)
+#endif
+#ifndef WEXITSTATUS
+# define WEXITSTATUS(s)        (((s) >> 8) & 0xff)
+#endif
+#ifndef WIFSIGNALED
+# define WIFSIGNALED(s)        (((s) & 0xff) != 0 && ((s) & 0xff) != 0x7f)
+#endif
+#ifndef WTERMSIG
+# define WTERMSIG(s)   ((s) & 0x7f)
+#endif
+#ifndef WIFSTOPPED
+# define WIFSTOPPED(s) (((s) & 0xff) == 0x7f)
+#endif
+#ifndef WSTOPSIG
+# define WSTOPSIG(s)   (((s) >> 8) & 0xff)
+#endif
+
+#if !defined(HAVE_WAITPID) && defined(HAVE_WAIT3)
+  /* always used with p == -1 */
+# define ksh_waitpid(p, s, o)  wait3((s), (o), (struct rusage *) 0)
+#else /* !HAVE_WAITPID && HAVE_WAIT3 */
+# define ksh_waitpid(p, s, o)  waitpid((p), (s), (o))
+#endif /* !HAVE_WAITPID && HAVE_WAIT3 */
diff --git a/bin/ksh/lex.c b/bin/ksh/lex.c
new file mode 100644 (file)
index 0000000..80ded4a
--- /dev/null
@@ -0,0 +1,1398 @@
+/*     $NetBSD: lex.c,v 1.15 2011/10/16 17:12:11 joerg Exp $   */
+
+/*
+ * lexical analysis and source input
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: lex.c,v 1.15 2011/10/16 17:12:11 joerg Exp $");
+#endif
+
+
+#include "sh.h"
+#include <ctype.h>
+
+
+/* Structure to keep track of the lexing state and the various pieces of info
+ * needed for each particular state.
+ */
+typedef struct lex_state Lex_state;
+struct lex_state {
+       int ls_state;
+       union {
+           /* $(...) */
+           struct scsparen_info {
+                   int nparen;         /* count open parenthesis */
+                   int csstate; /* XXX remove */
+#define ls_scsparen ls_info.u_scsparen
+           } u_scsparen;
+
+           /* $((...)) */
+           struct sasparen_info {
+                   int nparen;         /* count open parenthesis */
+                   int start;          /* marks start of $(( in output str */
+#define ls_sasparen ls_info.u_sasparen
+           } u_sasparen;
+
+           /* ((...)) */
+           struct sletparen_info {
+                   int nparen;         /* count open parenthesis */
+#define ls_sletparen ls_info.u_sletparen
+           } u_sletparen;
+
+           /* `...` */
+           struct sbquote_info {
+                   int indquotes;      /* true if in double quotes: "`...`" */
+#define ls_sbquote ls_info.u_sbquote
+           } u_sbquote;
+
+           Lex_state *base;            /* used to point to next state block */
+       } ls_info;
+};
+
+typedef struct State_info State_info;
+struct State_info {
+       Lex_state       *base;
+       Lex_state       *end;
+};
+
+
+static void    readhere ARGS((struct ioword *iop));
+static int     getsc__ ARGS((void));
+static void    getsc_line ARGS((Source *s));
+static int     getsc_bn ARGS((void));
+static char    *get_brace_var ARGS((XString *wsp, char *wp));
+static int     arraysub ARGS((char **strp));
+static const char *ungetsc ARGS((int c));
+static void    gethere ARGS((void));
+static Lex_state *push_state_ ARGS((State_info *si, Lex_state *old_end));
+static Lex_state *pop_state_ ARGS((State_info *si, Lex_state *old_end));
+
+static int backslash_skip;
+static int ignore_backslash_newline;
+
+/* optimized getsc_bn() */
+#define getsc()                (*source->str != '\0' && *source->str != '\\' \
+                        && !backslash_skip ? *source->str++ : getsc_bn())
+/* optimized getsc__() */
+#define        getsc_()        ((*source->str != '\0') ? *source->str++ : getsc__())
+
+#define STATE_BSIZE    32
+
+#define PUSH_STATE(s)  do { \
+                           if (++statep == state_info.end) \
+                               statep = push_state_(&state_info, statep); \
+                           state = statep->ls_state = (s); \
+                       } while (0)
+
+#define POP_STATE()    do { \
+                           if (--statep == state_info.base) \
+                               statep = pop_state_(&state_info, statep); \
+                           state = statep->ls_state; \
+                       } while (0)
+
+
+
+/*
+ * Lexical analyzer
+ *
+ * tokens are not regular expressions, they are LL(1).
+ * for example, "${var:-${PWD}}", and "$(size $(whence ksh))".
+ * hence the state stack.
+ */
+
+int
+yylex(cf)
+       int cf;
+{
+       Lex_state states[STATE_BSIZE], *statep;
+       State_info state_info;
+       register int c, state;
+       XString ws;             /* expandable output word */
+       register char *wp;      /* output word pointer */
+       char *sp, *dp;
+       int c2;
+
+
+  Again:
+       states[0].ls_state = -1;
+       states[0].ls_info.base = (Lex_state *) 0;
+       statep = &states[1];
+       state_info.base = states;
+       state_info.end = &states[STATE_BSIZE];
+
+       Xinit(ws, wp, 64, ATEMP);
+
+       backslash_skip = 0;
+       ignore_backslash_newline = 0;
+
+       if (cf&ONEWORD)
+               state = SWORD;
+#ifdef KSH
+       else if (cf&LETEXPR) {
+               *wp++ = OQUOTE;  /* enclose arguments in (double) quotes */
+               state = SLETPAREN;      
+               statep->ls_sletparen.nparen = 0;
+       }
+#endif /* KSH */
+       else {          /* normal lexing */
+               state = (cf & HEREDELIM) ? SHEREDELIM : SBASE;
+               while ((c = getsc()) == ' ' || c == '\t')
+                       ;
+               if (c == '#') {
+                       ignore_backslash_newline++;
+                       while ((c = getsc()) != '\0' && c != '\n')
+                               ;
+                       ignore_backslash_newline--;
+               }
+               ungetsc(c);
+       }
+       if (source->flags & SF_ALIAS) { /* trailing ' ' in alias definition */
+               source->flags &= ~SF_ALIAS;
+               /* In POSIX mode, a trailing space only counts if we are
+                * parsing a simple command
+                */
+               if (!Flag(FPOSIX) || (cf & CMDWORD))
+                       cf |= ALIAS;
+       }
+
+       /* Initial state: one of SBASE SHEREDELIM SWORD SASPAREN */
+       statep->ls_state = state;
+
+       /* collect non-special or quoted characters to form word */
+       while (!((c = getsc()) == 0
+                || ((state == SBASE || state == SHEREDELIM)
+                    && ctype(c, C_LEX1))))
+       {
+               Xcheck(ws, wp);
+               switch (state) {
+                 case SBASE:
+                       if (c == '[' && (cf & (VARASN|ARRAYVAR))) {
+                               *wp = EOS; /* temporary */
+                               if (is_wdvarname(Xstring(ws, wp), FALSE))
+                               {
+                                       char *p, *tmp;
+
+                                       if (arraysub(&tmp)) {
+                                               *wp++ = CHAR;
+                                               *wp++ = c;
+                                               for (p = tmp; *p; ) {
+                                                       Xcheck(ws, wp);
+                                                       *wp++ = CHAR;
+                                                       *wp++ = *p++;
+                                               }
+                                               afree(tmp, ATEMP);
+                                               break;
+                                       } else {
+                                               Source *s;
+
+                                               s = pushs(SREREAD,
+                                                         source->areap);
+                                               s->start = s->str
+                                                       = s->u.freeme = tmp;
+                                               s->next = source;
+                                               source = s;
+                                       }
+                               }
+                               *wp++ = CHAR;
+                               *wp++ = c;
+                               break;
+                       }
+                       /* fall through.. */
+                 Sbase1:       /* includes *(...|...) pattern (*+?@!) */
+#ifdef KSH
+                       if (c == '*' || c == '@' || c == '+' || c == '?'
+                           || c == '!')
+                       {
+                               c2 = getsc();
+                               if (c2 == '(' /*)*/ ) {
+                                       *wp++ = OPAT;
+                                       *wp++ = c;
+                                       PUSH_STATE(SPATTERN);
+                                       break;
+                               }
+                               ungetsc(c2);
+                       }
+#endif /* KSH */
+                       /* fall through.. */
+                 Sbase2:       /* doesn't include *(...|...) pattern (*+?@!) */
+                       switch (c) {
+                         case '\\':
+                               c = getsc();
+#ifdef OS2
+                               if (isalnum((unsigned char)c)) {
+                                       *wp++ = CHAR, *wp++ = '\\';
+                                       *wp++ = CHAR, *wp++ = c;
+                               } else
+#endif
+                               if (c) /* trailing \ is lost */
+                                       *wp++ = QCHAR, *wp++ = c;
+                               break;
+                         case '\'':
+                               *wp++ = OQUOTE;
+                               ignore_backslash_newline++;
+                               PUSH_STATE(SSQUOTE);
+                               break;
+                         case '"':
+                               *wp++ = OQUOTE;
+                               PUSH_STATE(SDQUOTE);
+                               break;
+                         default:
+                               goto Subst;
+                       }
+                       break;
+
+                 Subst:
+                       switch (c) {
+                         Lex_state *s;
+                         Lex_state *base;
+
+                         case '\\':
+                               c = getsc();
+                               switch (c) {
+                                 case '\\':
+                                 case '$': case '`':
+                                       *wp++ = QCHAR, *wp++ = c;
+                                       break;
+                                 case '"':
+                                       if ((cf & HEREDOC) == 0) {
+                                               *wp++ = QCHAR, *wp++ = c;
+                                               break;
+                                       }
+                                       /* FALLTROUGH */
+                                 default:
+                                       Xcheck(ws, wp);
+                                       if (c) { /* trailing \ is lost */
+                                               *wp++ = CHAR, *wp++ = '\\';
+                                               *wp++ = CHAR, *wp++ = c;
+                                       }
+                                       break;
+                               }
+                               break;
+                         case '$':
+                               c = getsc();
+                               if (c == '(') /*)*/ {
+                                       c = getsc();
+                                       if (c == '(') /*)*/ {
+                                               PUSH_STATE(SASPAREN);
+                                               statep->ls_sasparen.nparen = 2;
+                                               statep->ls_sasparen.start =
+                                                       Xsavepos(ws, wp);
+                                               *wp++ = EXPRSUB;
+                                       } else {
+                                               ungetsc(c);
+                                               PUSH_STATE(SCSPAREN);
+                                               statep->ls_scsparen.nparen = 1;
+                                               statep->ls_scsparen.csstate = 0;
+                                               *wp++ = COMSUB;
+                                       }
+                               } else if (c == '{') /*}*/ {
+                                       *wp++ = OSUBST;
+                                       *wp++ = '{'; /*}*/
+                                       wp = get_brace_var(&ws, wp);
+                                       c = getsc();
+                                       /* allow :# and :% (ksh88 compat) */
+                                       if (c == ':') {
+                                               *wp++ = CHAR, *wp++ = c;
+                                               c = getsc();
+                                       }
+                                       /* If this is a trim operation,
+                                        * treat (,|,) specially in STBRACE.
+                                        */
+                                       if (c == '#' || c == '%') {
+                                               ungetsc(c);
+                                               PUSH_STATE(STBRACE);
+                                       } else {
+                                               ungetsc(c);
+                                               PUSH_STATE(SBRACE);
+                                       }
+                               } else if (ctype(c, C_ALPHA)) {
+                                       *wp++ = OSUBST;
+                                       *wp++ = 'X';
+                                       do {
+                                               Xcheck(ws, wp);
+                                               *wp++ = c;
+                                               c = getsc();
+                                       } while (ctype(c, C_ALPHA|C_DIGIT));
+                                       *wp++ = '\0';
+                                       *wp++ = CSUBST;
+                                       *wp++ = 'X';
+                                       ungetsc(c);
+                               } else if (ctype(c, C_DIGIT|C_VAR1)) {
+                                       Xcheck(ws, wp);
+                                       *wp++ = OSUBST;
+                                       *wp++ = 'X';
+                                       *wp++ = c;
+                                       *wp++ = '\0';
+                                       *wp++ = CSUBST;
+                                       *wp++ = 'X';
+                               } else {
+                                       *wp++ = CHAR, *wp++ = '$';
+                                       ungetsc(c);
+                               }
+                               break;
+                         case '`':
+                               PUSH_STATE(SBQUOTE);
+                               *wp++ = COMSUB;
+                               /* Need to know if we are inside double quotes
+                                * since sh/at&t-ksh translate the \" to " in
+                                * "`..\"..`".  POSIX also requires this.
+                                * An earlier version of ksh misinterpreted
+                                * the POSIX specification and performed
+                                * removal of backslash escapes only if
+                                * posix mode was not in effect.
+                                */
+                               statep->ls_sbquote.indquotes = 0;
+                               s = statep;
+                               base = state_info.base;
+                               while (1) {
+                                       for (; s != base; s--) {
+                                               if (s->ls_state == SDQUOTE) {
+                                                       statep->ls_sbquote.indquotes = 1;
+                                                       break;
+                                               }
+                                       }
+                                       if (s != base)
+                                               break;
+                                       if (!(s = s->ls_info.base))
+                                               break;
+                                       base = s-- - STATE_BSIZE;
+                               }
+                               break;
+                         default:
+                               *wp++ = CHAR, *wp++ = c;
+                       }
+                       break;
+
+                 case SSQUOTE:
+                       if (c == '\'') {
+                               POP_STATE();
+                               *wp++ = CQUOTE;
+                               ignore_backslash_newline--;
+                       } else
+                               *wp++ = QCHAR, *wp++ = c;
+                       break;
+
+                 case SDQUOTE:
+                       if (c == '"') {
+                               POP_STATE();
+                               *wp++ = CQUOTE;
+                       } else
+                               goto Subst;
+                       break;
+
+                 case SCSPAREN: /* $( .. ) */
+                       /* todo: deal with $(...) quoting properly
+                        * kludge to partly fake quoting inside $(..): doesn't
+                        * really work because nested $(..) or ${..} inside
+                        * double quotes aren't dealt with.
+                        */
+                       switch (statep->ls_scsparen.csstate) {
+                         case 0: /* normal */
+                               switch (c) {
+                                 case '(':
+                                       statep->ls_scsparen.nparen++;
+                                       break;
+                                 case ')':
+                                       statep->ls_scsparen.nparen--;
+                                       break;
+                                 case '\\':
+                                       statep->ls_scsparen.csstate = 1;
+                                       break;
+                                 case '"':
+                                       statep->ls_scsparen.csstate = 2;
+                                       break;
+                                 case '\'':
+                                       statep->ls_scsparen.csstate = 4;
+                                       ignore_backslash_newline++;
+                                       break;
+                               }
+                               break;
+
+                         case 1: /* backslash in normal mode */
+                         case 3: /* backslash in double quotes */
+                               --statep->ls_scsparen.csstate;
+                               break;
+
+                         case 2: /* double quotes */
+                               if (c == '"')
+                                       statep->ls_scsparen.csstate = 0;
+                               else if (c == '\\')
+                                       statep->ls_scsparen.csstate = 3;
+                               break;
+
+                         case 4: /* single quotes */
+                               if (c == '\'') {
+                                       statep->ls_scsparen.csstate = 0;
+                                       ignore_backslash_newline--;
+                               }
+                               break;
+                       }
+                       if (statep->ls_scsparen.nparen == 0) {
+                               POP_STATE();
+                               *wp++ = 0; /* end of COMSUB */
+                       } else
+                               *wp++ = c;
+                       break;
+
+                 case SASPAREN: /* $(( .. )) */
+                       /* todo: deal with $((...); (...)) properly */
+                       /* XXX should nest using existing state machine
+                        *     (embed "..", $(...), etc.) */
+                       if (c == '(')
+                               statep->ls_sasparen.nparen++;
+                       else if (c == ')') {
+                               statep->ls_sasparen.nparen--;
+                               if (statep->ls_sasparen.nparen == 1) {
+                                       /*(*/
+                                       if ((c2 = getsc()) == ')') {
+                                               POP_STATE();
+                                               *wp++ = 0; /* end of EXPRSUB */
+                                               break;
+                                       } else {
+                                               char *s;
+
+                                               ungetsc(c2);
+                                               /* mismatched parenthesis -
+                                                * assume we were really
+                                                * parsing a $(..) expression
+                                                */
+                                               s = Xrestpos(ws, wp,
+                                                    statep->ls_sasparen.start);
+                                               memmove(s + 1, s, wp - s);
+                                               *s++ = COMSUB;
+                                               *s = '('; /*)*/
+                                               wp++;
+                                               statep->ls_scsparen.nparen = 1;
+                                               statep->ls_scsparen.csstate = 0;
+                                               state = statep->ls_state
+                                                       = SCSPAREN;
+                                               
+                                       }
+                               }
+                       }
+                       *wp++ = c;
+                       break;
+
+                 case SBRACE:
+                       /*{*/
+                       if (c == '}') {
+                               POP_STATE();
+                               *wp++ = CSUBST;
+                               *wp++ = /*{*/ '}';
+                       } else
+                               goto Sbase1;
+                       break;
+
+                 case STBRACE:
+                       /* Same as SBRACE, except (,|,) treated specially */
+                       /*{*/
+                       if (c == '}') {
+                               POP_STATE();
+                               *wp++ = CSUBST;
+                               *wp++ = /*{*/ '}';
+                       } else if (c == '|') {
+                               *wp++ = SPAT;
+                       } else if (c == '(') {
+                               *wp++ = OPAT;
+                               *wp++ = ' ';    /* simile for @ */
+                               PUSH_STATE(SPATTERN);
+                       } else
+                               goto Sbase1;
+                       break;
+
+                 case SBQUOTE:
+                       if (c == '`') {
+                               *wp++ = 0;
+                               POP_STATE();
+                       } else if (c == '\\') {
+                               switch (c = getsc()) {
+                                 case '\\':
+                                 case '$': case '`':
+                                       *wp++ = c;
+                                       break;
+                                 case '"':
+                                       if (statep->ls_sbquote.indquotes) {
+                                               *wp++ = c;
+                                               break;
+                                       }
+                                       /* fall through.. */
+                                 default:
+                                       if (c) { /* trailing \ is lost */
+                                               *wp++ = '\\';
+                                               *wp++ = c;
+                                       }
+                                       break;
+                               }
+                       } else
+                               *wp++ = c;
+                       break;
+
+                 case SWORD:   /* ONEWORD */
+                       goto Subst;
+
+#ifdef KSH
+                 case SLETPAREN:       /* LETEXPR: (( ... )) */
+                       /*(*/
+                       if (c == ')') {
+                               if (statep->ls_sletparen.nparen > 0)
+                                   --statep->ls_sletparen.nparen;
+                               /*(*/
+                               else if ((c2 = getsc()) == ')') {
+                                       c = 0;
+                                       *wp++ = CQUOTE;
+                                       goto Done;
+                               } else
+                                       ungetsc(c2);
+                       } else if (c == '(')
+                               /* parenthesis inside quotes and backslashes
+                                * are lost, but at&t ksh doesn't count them
+                                * either
+                                */
+                               ++statep->ls_sletparen.nparen;
+                       goto Sbase2;
+#endif /* KSH */
+
+                 case SHEREDELIM:      /* <<,<<- delimiter */
+                       /* XXX chuck this state (and the next) - use
+                        * the existing states ($ and \`..` should be
+                        * stripped of their specialness after the
+                        * fact).
+                        */
+                       /* here delimiters need a special case since
+                        * $ and `..` are not to be treated specially
+                        */
+                       if (c == '\\') {
+                               c = getsc();
+                               if (c) { /* trailing \ is lost */
+                                       *wp++ = QCHAR;
+                                       *wp++ = c;
+                               }
+                       } else if (c == '\'') {
+                               PUSH_STATE(SSQUOTE);
+                               *wp++ = OQUOTE;
+                               ignore_backslash_newline++;
+                       } else if (c == '"') {
+                               state = statep->ls_state = SHEREDQUOTE;
+                               *wp++ = OQUOTE;
+                       } else {
+                               *wp++ = CHAR;
+                               *wp++ = c;
+                       }
+                       break;
+
+                 case SHEREDQUOTE:     /* " in <<,<<- delimiter */
+                       if (c == '"') {
+                               *wp++ = CQUOTE;
+                               state = statep->ls_state = SHEREDELIM;
+                       } else {
+                               if (c == '\\') {
+                                       switch (c = getsc()) {
+                                         case '\\': case '"':
+                                         case '$': case '`':
+                                               break;
+                                         default:
+                                               if (c) { /* trailing \ lost */
+                                                       *wp++ = CHAR;
+                                                       *wp++ = '\\';
+                                               }
+                                               break;
+                                       }
+                               }
+                               *wp++ = CHAR;
+                               *wp++ = c;
+                       }
+                       break;
+
+                 case SPATTERN:        /* in *(...|...) pattern (*+?@!) */
+                       if ( /*(*/ c == ')') {
+                               *wp++ = CPAT;
+                               POP_STATE();
+                       } else if (c == '|') {
+                               *wp++ = SPAT;
+                       } else if (c == '(') {
+                               *wp++ = OPAT;
+                               *wp++ = ' ';    /* simile for @ */
+                               PUSH_STATE(SPATTERN);
+                       } else
+                               goto Sbase1;
+                       break;
+               }
+       }
+Done:
+       Xcheck(ws, wp);
+       if (statep != &states[1])
+               /* XXX figure out what is missing */
+               yyerror("no closing quote\n");
+
+       /* This done to avoid tests for SHEREDELIM wherever SBASE tested */
+       if (state == SHEREDELIM)
+               state = SBASE;
+
+       dp = Xstring(ws, wp);
+       if ((c == '<' || c == '>') && state == SBASE
+           && ((c2 = Xlength(ws, wp)) == 0
+               || (c2 == 2 && dp[0] == CHAR && digit(dp[1]))))
+       {
+               struct ioword *iop =
+                               (struct ioword *) alloc(sizeof(*iop), ATEMP);
+
+               if (c2 == 2)
+                       iop->unit = dp[1] - '0';
+               else
+                       iop->unit = c == '>'; /* 0 for <, 1 for > */
+
+               c2 = getsc();
+               /* <<, >>, <> are ok, >< is not */
+               if (c == c2 || (c == '<' && c2 == '>')) {
+                       iop->flag = c == c2 ?
+                                 (c == '>' ? IOCAT : IOHERE) : IORDWR;
+                       if (iop->flag == IOHERE) {
+                               if ((c2 = getsc()) == '-') {
+                                       iop->flag |= IOSKIP;
+                               } else {
+                                       ungetsc(c2);
+                               }
+                       }
+               } else if (c2 == '&')
+                       iop->flag = IODUP | (c == '<' ? IORDUP : 0);
+               else {
+                       iop->flag = c == '>' ? IOWRITE : IOREAD;
+                       if (c == '>' && c2 == '|')
+                               iop->flag |= IOCLOB;
+                       else
+                               ungetsc(c2);
+               }
+
+               iop->name = (char *) 0;
+               iop->delim = (char *) 0;
+               iop->heredoc = (char *) 0;
+               Xfree(ws, wp);  /* free word */
+               yylval.iop = iop;
+               return REDIR;
+       }
+
+       if (wp == dp && state == SBASE) {
+               Xfree(ws, wp);  /* free word */
+               /* no word, process LEX1 character */
+               switch (c) {
+                 default:
+                       return c;
+
+                 case '|':
+                 case '&':
+                 case ';':
+                       if ((c2 = getsc()) == c)
+                               c = (c == ';') ? BREAK :
+                                   (c == '|') ? LOGOR :
+                                   (c == '&') ? LOGAND :
+                                   YYERRCODE;
+#ifdef KSH
+                       else if (c == '|' && c2 == '&')
+                               c = COPROC;
+#endif /* KSH */
+                       else
+                               ungetsc(c2);
+                       return c;
+
+                 case '\n':
+                       gethere();
+                       if (cf & CONTIN)
+                               goto Again;
+                       return c;
+
+                 case '(':  /*)*/
+#ifdef KSH
+                       if ((c2 = getsc()) == '(') /*)*/
+                               /* XXX need to handle ((...); (...)) */
+                               c = MDPAREN;
+                       else
+                               ungetsc(c2);
+#endif /* KSH */
+                       return c;
+                 /*(*/
+                 case ')':
+                       return c;
+               }
+       }
+
+       *wp++ = EOS;            /* terminate word */
+       yylval.cp = Xclose(ws, wp);
+       if (state == SWORD
+#ifdef KSH
+               || state == SLETPAREN
+#endif /* KSH */
+               )       /* ONEWORD? */
+               return LWORD;
+       ungetsc(c);             /* unget terminator */
+
+       /* copy word to unprefixed string ident */
+       for (sp = yylval.cp, dp = ident; dp < ident+IDENT && (c = *sp++) == CHAR; )
+               *dp++ = *sp++;
+       /* Make sure the ident array stays '\0' padded */
+       memset(dp, 0, (ident+IDENT) - dp + 1);
+       if (c != EOS)
+               *ident = '\0';  /* word is not unquoted */
+
+       if (*ident != '\0' && (cf&(KEYWORD|ALIAS))) {
+               struct tbl *p;
+               int h = hash(ident);
+
+               /* { */
+               if ((cf & KEYWORD) && (p = tsearch(&keywords, ident, h))
+                   && (!(cf & ESACONLY) || p->val.i == ESAC || p->val.i == '}'))
+               {
+                       afree(yylval.cp, ATEMP);
+                       return p->val.i;
+               }
+               if ((cf & ALIAS) && (p = tsearch(&aliases, ident, h))
+                   && (p->flag & ISSET))
+               {
+                       register Source *s;
+
+                       for (s = source; s->type == SALIAS; s = s->next)
+                               if (s->u.tblp == p)
+                                       return LWORD;
+                       /* push alias expansion */
+                       s = pushs(SALIAS, source->areap);
+                       s->start = s->str = p->val.s;
+                       s->u.tblp = p;
+                       s->next = source;
+                       source = s;
+                       afree(yylval.cp, ATEMP);
+                       goto Again;
+               }
+       }
+
+       return LWORD;
+}
+
+static void
+gethere()
+{
+       register struct ioword **p;
+
+       for (p = heres; p < herep; p++)
+               readhere(*p);
+       herep = heres;
+}
+
+/*
+ * read "<<word" text into temp file
+ */
+
+static void
+readhere(iop)
+       struct ioword *iop;
+{
+       register int c;
+       char *volatile eof;
+       char *eofp;
+       int skiptabs;
+       XString xs;
+       char *xp;
+       int xpos;
+
+       eof = evalstr(iop->delim, 0);
+
+       if (!(iop->flag & IOEVAL))
+               ignore_backslash_newline++;
+
+       Xinit(xs, xp, 256, ATEMP);
+
+       for (;;) {
+               eofp = eof;
+               skiptabs = iop->flag & IOSKIP;
+               xpos = Xsavepos(xs, xp);
+               while ((c = getsc()) != 0) {
+                       if (skiptabs) {
+                               if (c == '\t')
+                                       continue;
+                               skiptabs = 0;
+                       }
+                       if (c != *eofp)
+                               break;
+                       Xcheck(xs, xp);
+                       Xput(xs, xp, c);
+                       eofp++;
+               }
+               /* Allow EOF here so commands with out trailing newlines
+                * will work (eg, ksh -c '...', $(...), etc).
+                */
+               if (*eofp == '\0' && (c == 0 || c == '\n')) {
+                       xp = Xrestpos(xs, xp, xpos);
+                       break;
+               }
+               ungetsc(c);
+               while ((c = getsc()) != '\n') {
+                       if (c == 0)
+                               yyerror("here document `%s' unclosed\n", eof);
+                       Xcheck(xs, xp);
+                       Xput(xs, xp, c);
+               }
+               Xcheck(xs, xp);
+               Xput(xs, xp, c);
+       }
+       Xput(xs, xp, '\0');
+       iop->heredoc = Xclose(xs, xp);
+
+       if (!(iop->flag & IOEVAL))
+               ignore_backslash_newline--;
+}
+
+void
+#ifdef HAVE_PROTOTYPES
+yyerror(const char *fmt, ...)
+#else
+yyerror(fmt, va_alist)
+       const char *fmt;
+       va_dcl
+#endif
+{
+       va_list va;
+
+       /* pop aliases and re-reads */
+       while (source->type == SALIAS || source->type == SREREAD)
+               source = source->next;
+       source->str = null;     /* zap pending input */
+
+       error_prefix(TRUE);
+       SH_VA_START(va, fmt);
+       shf_vfprintf(shl_out, fmt, va);
+       va_end(va);
+       errorf("%s", null);
+}
+
+/*
+ * input for yylex with alias expansion
+ */
+
+Source *
+pushs(type, areap)
+       int type;
+       Area *areap;
+{
+       register Source *s;
+
+       s = (Source *) alloc(sizeof(Source), areap);
+       s->type = type;
+       s->str = null;
+       s->start = NULL;
+       s->line = 0;
+       s->errline = 0;
+       s->file = NULL;
+       s->flags = 0;
+       s->next = NULL;
+       s->areap = areap;
+       if (type == SFILE || type == SSTDIN) {
+               char *dummy;
+               Xinit(s->xs, dummy, 256, s->areap);
+       } else
+               memset(&s->xs, 0, sizeof(s->xs));
+       return s;
+}
+
+static int
+getsc__()
+{
+       register Source *s = source;
+       register int c;
+
+       while ((c = *s->str++) == 0) {
+               s->str = NULL;          /* return 0 for EOF by default */
+               switch (s->type) {
+                 case SEOF:
+                       s->str = null;
+                       return 0;
+
+                 case SSTDIN:
+                 case SFILE:
+                       getsc_line(s);
+                       break;
+
+                 case SWSTR:
+                       break;
+
+                 case SSTRING:
+                       break;
+
+                 case SWORDS:
+                       s->start = s->str = *s->u.strv++;
+                       s->type = SWORDSEP;
+                       break;
+
+                 case SWORDSEP:
+                       if (*s->u.strv == NULL) {
+                               s->start = s->str = newline;
+                               s->type = SEOF;
+                       } else {
+                               s->start = s->str = space;
+                               s->type = SWORDS;
+                       }
+                       break;
+
+                 case SALIAS:
+                       if (s->flags & SF_ALIASEND) {
+                               /* pass on an unused SF_ALIAS flag */
+                               source = s->next;
+                               source->flags |= s->flags & SF_ALIAS;
+                               s = source;
+                       } else if (*s->u.tblp->val.s
+                                && isspace((unsigned char)strchr(s->u.tblp->val.s, 0)[-1]))
+                       {
+                               source = s = s->next;   /* pop source stack */
+                               /* Note that this alias ended with a space,
+                                * enabling alias expansion on the following
+                                * word.
+                                */
+                               s->flags |= SF_ALIAS;
+                       } else {
+                               /* At this point, we need to keep the current
+                                * alias in the source list so recursive
+                                * aliases can be detected and we also need
+                                * to return the next character.  Do this
+                                * by temporarily popping the alias to get
+                                * the next character and then put it back
+                                * in the source list with the SF_ALIASEND
+                                * flag set.
+                                */
+                               source = s->next;       /* pop source stack */
+                               source->flags |= s->flags & SF_ALIAS;
+                               c = getsc__();
+                               if (c) {
+                                       s->flags |= SF_ALIASEND;
+                                       s->ugbuf[0] = c; s->ugbuf[1] = '\0';
+                                       s->start = s->str = s->ugbuf;
+                                       s->next = source;
+                                       source = s;
+                               } else {
+                                       s = source;
+                                       /* avoid reading eof twice */
+                                       s->str = NULL;
+                                       break;
+                               }
+                       }
+                       continue;
+
+                 case SREREAD:
+                       if (s->start != s->ugbuf) /* yuck */
+                               afree(s->u.freeme, ATEMP);
+                       source = s = s->next;
+                       continue;
+               }
+               if (s->str == NULL) {
+                       s->type = SEOF;
+                       s->start = s->str = null;
+                       return '\0';
+               }
+               if (s->flags & SF_ECHO) {
+                       shf_puts(s->str, shl_out);
+                       shf_flush(shl_out);
+               }
+       }
+       return c;
+}
+
+static void
+getsc_line(s)
+       Source *s;
+{
+       char *xp = Xstring(s->xs, xp);
+       int interactive = Flag(FTALKING) && s->type == SSTDIN;
+       int have_tty = interactive && (s->flags & SF_TTY);
+
+       /* Done here to ensure nothing odd happens when a timeout occurs */
+       XcheckN(s->xs, xp, LINE);
+       *xp = '\0';
+       s->start = s->str = xp;
+
+#ifdef KSH
+       if (have_tty && ksh_tmout) {
+               ksh_tmout_state = TMOUT_READING;
+               alarm(ksh_tmout);
+       }
+#endif /* KSH */
+#ifdef EDIT
+       if (have_tty && (0
+# ifdef VI
+                        || Flag(FVI)
+# endif /* VI */
+# ifdef EMACS
+                        || Flag(FEMACS) || Flag(FGMACS)
+# endif /* EMACS */
+               ))
+       {
+               int nread;
+
+               nread = x_read(xp, LINE);
+               if (nread < 0)  /* read error */
+                       nread = 0;
+               xp[nread] = '\0';
+               xp += nread;
+       }
+       else
+#endif /* EDIT */
+       {
+               if (interactive) {
+                       pprompt(prompt, 0);
+               } else
+                       s->line++;
+
+               while (1) {
+                       char *p = shf_getse(xp, Xnleft(s->xs, xp), s->u.shf);
+
+                       if (!p && shf_error(s->u.shf)
+                           && shf_errno(s->u.shf) == EINTR)
+                       {
+                               shf_clearerr(s->u.shf);
+                               if (trap)
+                                       runtraps(0);
+                               continue;
+                       }
+                       if (!p || (xp = p, xp[-1] == '\n'))
+                               break;
+                       /* double buffer size */
+                       xp++; /* move past null so doubling works... */
+                       XcheckN(s->xs, xp, Xlength(s->xs, xp));
+                       xp--; /* ...and move back again */
+               }
+               /* flush any unwanted input so other programs/builtins
+                * can read it.  Not very optimal, but less error prone
+                * than flushing else where, dealing with redirections,
+                * etc..
+                * todo: reduce size of shf buffer (~128?) if SSTDIN
+                */
+               if (s->type == SSTDIN)
+                       shf_flush(s->u.shf);
+       }
+       /* XXX: temporary kludge to restore source after a
+        * trap may have been executed.
+        */
+       source = s;
+#ifdef KSH
+       if (have_tty && ksh_tmout)
+       {
+               ksh_tmout_state = TMOUT_EXECUTING;
+               alarm(0);
+       }
+#endif /* KSH */
+       s->start = s->str = Xstring(s->xs, xp);
+       strip_nuls(Xstring(s->xs, xp), Xlength(s->xs, xp));
+       /* Note: if input is all nulls, this is not eof */
+       if (Xlength(s->xs, xp) == 0) { /* EOF */
+               if (s->type == SFILE)
+                       shf_fdclose(s->u.shf);
+               s->str = NULL;
+       } else if (interactive) {
+#ifdef HISTORY
+               char *p = Xstring(s->xs, xp);
+               if (cur_prompt == PS1)
+                       while (*p && ctype(*p, C_IFS) && ctype(*p, C_IFSWS))
+                               p++;
+               if (*p) {
+# ifdef EASY_HISTORY
+                       if (cur_prompt == PS2)
+                               histappend(Xstring(s->xs, xp), 1);
+                       else
+# endif /* EASY_HISTORY */
+                       {
+                               s->line++;
+                               histsave(s->line, s->str, 1);
+                       }
+               }
+#endif /* HISTORY */
+       }
+       if (interactive)
+               set_prompt(PS2, (Source *) 0);
+}
+
+void
+set_prompt(to, s)
+       int to;
+       Source *s;
+{
+       cur_prompt = to;
+
+       switch (to) {
+       case PS1: /* command */
+#ifdef KSH
+               /* Substitute ! and !! here, before substitutions are done
+                * so ! in expanded variables are not expanded.
+                * NOTE: this is not what at&t ksh does (it does it after
+                * substitutions, POSIX doesn't say which is to be done.
+                */
+               {
+                       struct shf *shf;
+                       char * volatile ps1;
+                       Area *saved_atemp;
+
+                       ps1 = str_val(global("PS1"));
+                       shf = shf_sopen((char *) 0, strlen(ps1) * 2,
+                               SHF_WR | SHF_DYNAMIC, (struct shf *) 0);
+                       while (*ps1) {
+                               if (*ps1 != '!' || *++ps1 == '!')
+                                       shf_putchar(*ps1++, shf);
+                               else
+                                       shf_fprintf(shf, "%d",
+                                               s ? s->line + 1 : 0);
+                       }
+                       ps1 = shf_sclose(shf);
+                       saved_atemp = ATEMP;
+                       newenv(E_ERRH);
+                       if (ksh_sigsetjmp(e->jbuf, 0)) {
+                               prompt = safe_prompt;
+                               /* Don't print an error - assume it has already
+                                * been printed.  Reason is we may have forked
+                                * to run a command and the child may be
+                                * unwinding its stack through this code as it
+                                * exits.
+                                */
+                       } else
+                               prompt = str_save(substitute(ps1, 0),
+                                                saved_atemp);
+                       quitenv();
+               }
+#else /* KSH */
+               prompt = str_val(global("PS1"));
+#endif /* KSH */
+               break;
+
+       case PS2: /* command continuation */
+               prompt = str_val(global("PS2"));
+               break;
+       }
+}
+
+/* See also related routine, promptlen() in edit.c */
+void
+pprompt(cp, ntruncate)
+       const char *cp;
+       int ntruncate;
+{
+#if 0
+       char nbuf[32];
+       int c;
+
+       while (*cp != 0) {
+               if (*cp != '!')
+                       c = *cp++;
+               else if (*++cp == '!')
+                       c = *cp++;
+               else {
+                       int len;
+                       char *p;
+
+                       shf_snprintf(p = nbuf, sizeof(nbuf), "%d",
+                               source->line + 1);
+                       len = strlen(nbuf);
+                       if (ntruncate) {
+                               if (ntruncate >= len) {
+                                       ntruncate -= len;
+                                       continue;
+                               }
+                               p += ntruncate;
+                               len -= ntruncate;
+                               ntruncate = 0;
+                       }
+                       shf_write(p, len, shl_out);
+                       continue;
+               }
+               if (ntruncate)
+                       --ntruncate;
+               else
+                       shf_putc(c, shl_out);
+       }
+#endif /* 0 */
+       shf_puts(cp + ntruncate, shl_out);
+       shf_flush(shl_out);
+}
+
+/* Read the variable part of a ${...} expression (ie, up to but not including
+ * the :[-+?=#%] or close-brace.
+ */
+static char *
+get_brace_var(wsp, wp)
+       XString *wsp;
+       char *wp;
+{
+       enum parse_state {
+                          PS_INITIAL, PS_SAW_HASH, PS_IDENT,
+                          PS_NUMBER, PS_VAR1, PS_END
+                        }
+               state;
+       char c;
+
+       state = PS_INITIAL;
+       while (1) {
+               c = getsc();
+               /* State machine to figure out where the variable part ends. */
+               switch (state) {
+                 case PS_INITIAL:
+                       if (c == '#') {
+                               state = PS_SAW_HASH;
+                               break;
+                       }
+                       /* fall through.. */
+                 case PS_SAW_HASH:
+                       if (letter(c))
+                               state = PS_IDENT;
+                       else if (digit(c))
+                               state = PS_NUMBER;
+                       else if (ctype(c, C_VAR1))
+                               state = PS_VAR1;
+                       else
+                               state = PS_END;
+                       break;
+                 case PS_IDENT:
+                       if (!letnum(c)) {
+                               state = PS_END;
+                               if (c == '[') {
+                                       char *tmp, *p;
+
+                                       if (!arraysub(&tmp))
+                                               yyerror("missing ]\n");
+                                       *wp++ = c;
+                                       for (p = tmp; *p; ) {
+                                               Xcheck(*wsp, wp);
+                                               *wp++ = *p++;
+                                       }
+                                       afree(tmp, ATEMP);
+                                       c = getsc(); /* the ] */
+                               }
+                       }
+                       break;
+                 case PS_NUMBER:
+                       if (!digit(c))
+                               state = PS_END;
+                       break;
+                 case PS_VAR1:
+                       state = PS_END;
+                       break;
+                 case PS_END: /* keep gcc happy */
+                       break;
+               }
+               if (state == PS_END) {
+                       *wp++ = '\0';   /* end of variable part */
+                       ungetsc(c);
+                       break;
+               }
+               Xcheck(*wsp, wp);
+               *wp++ = c;
+       }
+       return wp;
+}
+
+/*
+ * Save an array subscript - returns true if matching bracket found, false
+ * if eof or newline was found.
+ * (Returned string double null terminated)
+ */
+static int
+arraysub(strp)
+       char **strp;
+{
+       XString ws;
+       char    *wp;
+       char    c;
+       int     depth = 1;      /* we are just past the initial [ */
+
+       Xinit(ws, wp, 32, ATEMP);
+
+       do {
+               c = getsc();
+               Xcheck(ws, wp);
+               *wp++ = c;
+               if (c == '[')
+                       depth++;
+               else if (c == ']')
+                       depth--;
+       } while (depth > 0 && c && c != '\n');
+
+       *wp++ = '\0';
+       *strp = Xclose(ws, wp);
+
+       return depth == 0 ? 1 : 0;
+}
+
+/* Unget a char: handles case when we are already at the start of the buffer */
+static const char *
+ungetsc(c)
+       int c;
+{
+       if (backslash_skip)
+               backslash_skip--;
+       /* Don't unget eof... */
+       if (source->str == null && c == '\0')
+               return source->str;
+       if (source->str > source->start)
+               source->str--;
+       else {
+               Source *s;
+
+               s = pushs(SREREAD, source->areap);
+               s->ugbuf[0] = c; s->ugbuf[1] = '\0';
+               s->start = s->str = s->ugbuf;
+               s->next = source;
+               source = s;
+       }
+       return source->str;
+}
+
+
+/* Called to get a char that isn't a \newline sequence. */
+static int
+getsc_bn ARGS((void))
+{
+       int c, c2;
+
+       if (ignore_backslash_newline)
+               return getsc_();
+
+       if (backslash_skip == 1) {
+               backslash_skip = 2;
+               return getsc_();
+       }
+
+       backslash_skip = 0;
+
+       while (1) {
+               c = getsc_();
+               if (c == '\\') {
+                       if ((c2 = getsc_()) == '\n')
+                               /* ignore the \newline; get the next char... */
+                               continue;
+                       ungetsc(c2);
+                       backslash_skip = 1;
+               }
+               return c;
+       }
+}
+
+static Lex_state *
+push_state_(si, old_end)
+       State_info *si;
+       Lex_state *old_end;
+{
+       Lex_state       *new = alloc(sizeof(Lex_state) * STATE_BSIZE, ATEMP);
+
+       new[0].ls_info.base = old_end;
+       si->base = &new[0];
+       si->end = &new[STATE_BSIZE];
+       return &new[1];
+}
+
+static Lex_state *
+pop_state_(si, old_end)
+       State_info *si;
+       Lex_state *old_end;
+{
+       Lex_state *old_base = si->base;
+
+       si->base = old_end->ls_info.base - STATE_BSIZE;
+       si->end = old_end->ls_info.base;
+
+       afree(old_base, ATEMP);
+
+       return si->base + STATE_BSIZE - 1;
+}
diff --git a/bin/ksh/lex.h b/bin/ksh/lex.h
new file mode 100644 (file)
index 0000000..99c06da
--- /dev/null
@@ -0,0 +1,133 @@
+/*     $NetBSD: lex.h,v 1.7 2005/09/11 22:16:00 christos Exp $ */
+
+/*
+ * Source input, lexer and parser
+ */
+
+/* $Id: lex.h,v 1.7 2005/09/11 22:16:00 christos Exp $ */
+
+#define        IDENT   64
+
+typedef struct source Source;
+struct source {
+       const char *str;        /* input pointer */
+       int     type;           /* input type */
+       const char *start;      /* start of current buffer */
+       union {
+               char **strv;    /* string [] */
+               struct shf *shf; /* shell file */
+               struct tbl *tblp; /* alias (SALIAS) */
+               char *freeme;   /* also for SREREAD */
+       } u;
+       char    ugbuf[2];       /* buffer for ungetsc() (SREREAD) and
+                                * alias (SALIAS) */
+       int     line;           /* line number */
+       int     errline;        /* line the error occurred on (0 if not set) */
+       const char *file;       /* input file name */
+       int     flags;          /* SF_* */
+       Area    *areap;
+       XString xs;             /* input buffer */
+       Source *next;           /* stacked source */
+};
+
+/* Source.type values */
+#define        SEOF            0       /* input EOF */
+#define        SFILE           1       /* file input */
+#define SSTDIN         2       /* read stdin */
+#define        SSTRING         3       /* string */
+#define        SWSTR           4       /* string without \n */
+#define        SWORDS          5       /* string[] */
+#define        SWORDSEP        6       /* string[] separator */
+#define        SALIAS          7       /* alias expansion */
+#define SREREAD                8       /* read ahead to be re-scanned */
+
+/* Source.flags values */
+#define SF_ECHO                BIT(0)  /* echo input to shlout */
+#define SF_ALIAS       BIT(1)  /* faking space at end of alias */
+#define SF_ALIASEND    BIT(2)  /* faking space at end of alias */
+#define SF_TTY         BIT(3)  /* type == SSTDIN & it is a tty */
+
+/*
+ * states while lexing word
+ */
+#define        SBASE   0               /* outside any lexical constructs */
+#define        SWORD   1               /* implicit quoting for substitute() */
+#ifdef KSH
+#define        SLETPAREN 2             /* inside (( )), implicit quoting */
+#endif /* KSH */
+#define        SSQUOTE 3               /* inside '' */
+#define        SDQUOTE 4               /* inside "" */
+#define        SBRACE  5               /* inside ${} */
+#define        SCSPAREN 6              /* inside $() */
+#define        SBQUOTE 7               /* inside `` */
+#define        SASPAREN 8              /* inside $(( )) */
+#define SHEREDELIM 9           /* parsing <<,<<- delimiter */
+#define SHEREDQUOTE 10         /* parsing " in <<,<<- delimiter */
+#define SPATTERN 11            /* parsing *(...|...) pattern (*+?@!) */
+#define STBRACE 12             /* parsing ${..[#%]..} */
+
+typedef union {
+       int     i;
+       char   *cp;
+       char  **wp;
+       struct op *o;
+       struct ioword *iop;
+} YYSTYPE;
+
+/* If something is added here, add it to tokentab[] in syn.c as well */
+#define        LWORD   256
+#define        LOGAND  257             /* && */
+#define        LOGOR   258             /* || */
+#define        BREAK   259             /* ;; */
+#define        IF      260
+#define        THEN    261
+#define        ELSE    262
+#define        ELIF    263
+#define        FI      264
+#define        CASE    265
+#define        ESAC    266
+#define        FOR     267
+#define SELECT 268
+#define        WHILE   269
+#define        UNTIL   270
+#define        DO      271
+#define        DONE    272
+#define        IN      273
+#define        FUNCTION 274
+#define        TIME    275
+#define        REDIR   276
+#ifdef KSH
+#define MDPAREN        277             /* (( )) */
+#endif /* KSH */
+#define BANG   278             /* ! */
+#define DBRACKET 279           /* [[ .. ]] */
+#define COPROC 280             /* |& */
+#define        YYERRCODE 300
+
+/* flags to yylex */
+#define        CONTIN  BIT(0)          /* skip new lines to complete command */
+#define        ONEWORD BIT(1)          /* single word for substitute() */
+#define        ALIAS   BIT(2)          /* recognize alias */
+#define        KEYWORD BIT(3)          /* recognize keywords */
+#define LETEXPR        BIT(4)          /* get expression inside (( )) */
+#define VARASN BIT(5)          /* check for var=word */
+#define ARRAYVAR BIT(6)                /* parse x[1 & 2] as one word */
+#define ESACONLY BIT(7)                /* only accept esac keyword */
+#define CMDWORD BIT(8)         /* parsing simple command (alias related) */
+#define HEREDELIM BIT(9)       /* parsing <<,<<- delimiter */
+#define HEREDOC BIT(10)                /* parsing heredoc */
+
+#define        HERES   10              /* max << in line */
+
+EXTERN Source *source;         /* yyparse/yylex source */
+EXTERN YYSTYPE yylval;         /* result from yylex */
+EXTERN struct ioword *heres [HERES], **herep;
+EXTERN char    ident [IDENT+1];
+
+#ifdef HISTORY
+# define HISTORYSIZE   128     /* size of saved history */
+
+EXTERN char  **histlist;       /* saved commands */
+EXTERN char  **histptr;        /* last history item */
+EXTERN int     histsize;       /* history size */
+#endif /* HISTORY */
diff --git a/bin/ksh/mail.c b/bin/ksh/mail.c
new file mode 100644 (file)
index 0000000..e6a51f9
--- /dev/null
@@ -0,0 +1,202 @@
+/*     $NetBSD: mail.c,v 1.5 2006/01/15 18:16:30 jschauma Exp $        */
+
+/*
+ * Mailbox checking code by Robert J. Gibson, adapted for PD ksh by
+ * John R. MacMillan
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: mail.c,v 1.5 2006/01/15 18:16:30 jschauma Exp $");
+#endif
+
+
+#include "config.h"
+
+#ifdef KSH
+#include "sh.h"
+#include "ksh_stat.h"
+#include "ksh_time.h"
+
+#define MBMESSAGE      "You have mail in $_"
+
+typedef struct mbox {
+       struct mbox    *mb_next;        /* next mbox in list */
+       char           *mb_path;        /* path to mail file */
+       char           *mb_msg;         /* to announce arrival of new mail */
+       time_t          mb_mtime;       /* mtime of mail file */
+} mbox_t;
+
+/*
+ * $MAILPATH is a linked list of mboxes.  $MAIL is a treated as a
+ * special case of $MAILPATH, where the list has only one node.  The
+ * same list is used for both since they are exclusive.
+ */
+
+static mbox_t  *mplist;
+static mbox_t  mbox;
+static time_t  mlastchkd;      /* when mail was last checked */
+static time_t  mailcheck_interval;
+
+static void     munset      ARGS((mbox_t *mlist)); /* free mlist and mval */
+static mbox_t * mballoc     ARGS((char *p, char *m)); /* allocate a new mbox */
+static void     mprintit    ARGS((mbox_t *mbp));
+
+void
+mcheck()
+{
+       register mbox_t *mbp;
+       time_t           now;
+       struct tbl      *vp;
+       struct stat      stbuf;
+
+       now = time((time_t *) 0);
+       if (mlastchkd == 0)
+               mlastchkd = now;
+       if (now - mlastchkd >= mailcheck_interval) {
+               mlastchkd = now;
+
+               if (mplist)
+                       mbp = mplist;
+               else if ((vp = global("MAIL")) && (vp->flag & ISSET))
+                       mbp = &mbox;
+               else
+                       mbp = NULL;
+
+               while (mbp) {
+                       if (mbp->mb_path && stat(mbp->mb_path, &stbuf) == 0
+                           && S_ISREG(stbuf.st_mode))
+                       {
+                               if (stbuf.st_size
+                                   && mbp->mb_mtime != stbuf.st_mtime
+                                   && stbuf.st_atime <= stbuf.st_mtime)
+                                       mprintit(mbp);
+                               mbp->mb_mtime = stbuf.st_mtime;
+                       } else {
+                               /*
+                                * Some mail readers remove the mail
+                                * file if all mail is read.  If file
+                                * does not exist, assume this is the
+                                * case and set mtime to zero.
+                                */
+                               mbp->mb_mtime = 0;
+                       }
+                       mbp = mbp->mb_next;
+               }
+       }
+}
+
+void
+mcset(interval)
+       long interval;
+{
+       mailcheck_interval = interval;
+}
+
+void
+mbset(p)
+       register char   *p;
+{
+       struct stat     stbuf;
+
+       if (mbox.mb_msg)
+               afree((void *)mbox.mb_msg, APERM);
+       if (mbox.mb_path)
+               afree((void *)mbox.mb_path, APERM);
+       /* Save a copy to protect from export (which munges the string) */
+       mbox.mb_path = str_save(p, APERM);
+       mbox.mb_msg = NULL;
+       if (p && stat(p, &stbuf) == 0 && S_ISREG(stbuf.st_mode))
+               mbox.mb_mtime = stbuf.st_mtime;
+       else
+               mbox.mb_mtime = 0;
+}
+
+void
+mpset(mptoparse)
+       register char   *mptoparse;
+{
+       register mbox_t *mbp;
+       register char   *mpath, *mmsg, *mval;
+       char *p;
+
+       munset( mplist );
+       mplist = NULL;
+       mval = str_save(mptoparse, APERM);
+       while (mval) {
+               mpath = mval;
+               if ((mval = strchr(mval, PATHSEP)) != NULL) {
+                       *mval = '\0', mval++;
+               }
+               /* POSIX/bourne-shell say file%message */
+               for (p = mpath; (mmsg = strchr(p, '%')); ) {
+                       /* a literal percent? (POSIXism) */
+                       if (mmsg[-1] == '\\') {
+                               /* use memmove() to avoid overlap problems */
+                               memmove(mmsg - 1, mmsg, strlen(mmsg) + 1);
+                               p = mmsg + 1;
+                               continue;
+                       }
+                       break;
+               }
+               /* at&t ksh says file?message */
+               if (!mmsg && !Flag(FPOSIX))
+                       mmsg = strchr(mpath, '?');
+               if (mmsg) {
+                       *mmsg = '\0';
+                       mmsg++;
+               }
+               mbp = mballoc(mpath, mmsg);
+               mbp->mb_next = mplist;
+               mplist = mbp;
+       }
+}
+
+static void
+munset(mlist)
+register mbox_t        *mlist;
+{
+       register mbox_t *mbp;
+
+       while (mlist != NULL) {
+               mbp = mlist;
+               mlist = mbp->mb_next;
+               if (!mlist)
+                       afree((void *)mbp->mb_path, APERM);
+               afree((void *)mbp, APERM);
+       }
+}
+
+static mbox_t *
+mballoc(p, m)
+       char    *p;
+       char    *m;
+{
+       struct stat     stbuf;
+       register mbox_t *mbp;
+
+       mbp = (mbox_t *)alloc(sizeof(mbox_t), APERM);
+       mbp->mb_next = NULL;
+       mbp->mb_path = p;
+       mbp->mb_msg = m;
+       if (stat(mbp->mb_path, &stbuf) == 0 && S_ISREG(stbuf.st_mode))
+               mbp->mb_mtime = stbuf.st_mtime;
+       else
+               mbp->mb_mtime = 0;
+       return(mbp);
+}
+
+static void
+mprintit( mbp )
+mbox_t *mbp;
+{
+       struct tbl      *vp;
+
+       /* Ignore setstr errors here (arbitrary) */
+       setstr((vp = local("_", FALSE)), mbp->mb_path, KSH_RETURN_ERROR);
+
+       shellf("%s\n", substitute(mbp->mb_msg ? mbp->mb_msg : MBMESSAGE, 0));
+
+       unset(vp, 0);
+}
+#endif /* KSH */
diff --git a/bin/ksh/main.c b/bin/ksh/main.c
new file mode 100644 (file)
index 0000000..1dca7e1
--- /dev/null
@@ -0,0 +1,869 @@
+/*     $NetBSD: main.c,v 1.15 2011/10/16 17:12:11 joerg Exp $  */
+
+/*
+ * startup, main loop, environments and error handling
+ */
+#include <sys/cdefs.h>
+#include <locale.h>
+
+#ifndef lint
+__RCSID("$NetBSD: main.c,v 1.15 2011/10/16 17:12:11 joerg Exp $");
+#endif
+
+
+#define        EXTERN                          /* define EXTERNs in sh.h */
+
+#include "sh.h"
+#include "ksh_stat.h"
+#include "ksh_time.h"
+
+extern char **environ;
+
+/*
+ * global data
+ */
+
+static void    reclaim ARGS((void));
+static void    remove_temps ARGS((struct temp *tp));
+static int     is_restricted ARGS((char *name));
+
+/*
+ * shell initialization
+ */
+
+static const char initifs[] = "IFS= \t\n";
+
+static const char initsubs[] = "${PS2=> } ${PS3=#? } ${PS4=+ }";
+
+static const char version_param[] =
+#ifdef KSH
+       "KSH_VERSION"
+#else /* KSH */
+       "SH_VERSION"
+#endif /* KSH */
+       ;
+
+static const char *const initcoms [] = {
+       "typeset", "-x", "SHELL", "PATH", "HOME", NULL,
+       "typeset", "-r", version_param, NULL,
+       "typeset", "-i", "PPID", NULL,
+       "typeset", "-i", "OPTIND=1", NULL,
+#ifdef KSH
+       "eval", "typeset -i RANDOM MAILCHECK=\"${MAILCHECK-600}\" SECONDS=\"${SECONDS-0}\" TMOUT=\"${TMOUT-0}\"", NULL,
+#endif /* KSH */
+       "alias",
+        /* Standard ksh aliases */
+         "hash=alias -t",      /* not "alias -t --": hash -r needs to work */
+         "type=whence -v",
+#ifdef JOBS
+         "stop=kill -STOP",
+         "suspend=kill -STOP $$",
+#endif
+#ifdef KSH
+         "autoload=typeset -fu",
+         "functions=typeset -f",
+# ifdef HISTORY
+         "history=fc -l",
+# endif /* HISTORY */
+         "integer=typeset -i",
+         "nohup=nohup ",
+         "local=typeset",
+         "r=fc -e -",
+#endif /* KSH */
+#ifdef KSH
+        /* Aliases that are builtin commands in at&t */
+         "login=exec login",
+#ifndef __NetBSD__
+         "newgrp=exec newgrp",
+#endif /* __NetBSD__ */
+#endif /* KSH */
+         NULL,
+       /* this is what at&t ksh seems to track, with the addition of emacs */
+       "alias", "-tU",
+         "cat", "cc", "chmod", "cp", "date", "ed", "emacs", "grep", "ls",
+         "mail", "make", "mv", "pr", "rm", "sed", "sh", "vi", "who",
+         NULL,
+#ifdef EXTRA_INITCOMS
+       EXTRA_INITCOMS, NULL,
+#endif /* EXTRA_INITCOMS */
+       NULL
+};
+
+int
+main(int argc, char *argv[])
+{
+       register int i;
+       int argi;
+       Source *s;
+       struct block *l;
+       int restricted, errexit;
+       char **wp;
+       struct env env;
+       pid_t ppid;
+
+#ifdef MEM_DEBUG
+       chmem_set_defaults("ct", 1);
+       /* chmem_push("+c", 1); */
+#endif /* MEM_DEBUG */
+
+#ifdef OS2
+       setmode (0, O_BINARY);
+       setmode (1, O_TEXT);
+#endif
+
+       /* make sure argv[] is sane */
+       if (!*argv) {
+               static const char       *empty_argv[] = {
+                                           "pdksh", (char *) 0
+                                       };
+
+               argv = (char **)__UNCONST(empty_argv);
+               argc = 1;
+       }
+       kshname = *argv;
+
+       ainit(&aperm);          /* initialize permanent Area */
+
+       /* set up base environment */
+       memset(&env, 0, sizeof(env));
+       env.type = E_NONE;
+       ainit(&env.area);
+       e = &env;
+       newblock();             /* set up global l->vars and l->funs */
+
+       /* Do this first so output routines (eg, errorf, shellf) can work */
+       initio();
+
+       initvar();
+
+       initctypes();
+
+       inittraps();
+
+#ifdef KSH
+       coproc_init();
+#endif /* KSH */
+
+       /* set up variable and command dictionaries */
+       tinit(&taliases, APERM, 0);
+       tinit(&aliases, APERM, 0);
+       tinit(&homedirs, APERM, 0);
+
+       /* define shell keywords */
+       initkeywords();
+
+       /* define built-in commands */
+       tinit(&builtins, APERM, 64); /* must be 2^n (currently 40 builtins) */
+       for (i = 0; shbuiltins[i].name != NULL; i++)
+               builtin(shbuiltins[i].name, shbuiltins[i].func);
+       for (i = 0; kshbuiltins[i].name != NULL; i++)
+               builtin(kshbuiltins[i].name, kshbuiltins[i].func);
+
+       init_histvec();
+
+       def_path = DEFAULT__PATH;
+#if defined(HAVE_CONFSTR) && defined(_CS_PATH)
+       {
+               size_t len = confstr(_CS_PATH, (char *) 0, 0);
+               char *new;
+
+               if (len > 0) {
+                       confstr(_CS_PATH, new = alloc(len + 1, APERM), len + 1);
+                       def_path = new;
+               }
+       }
+#endif /* HAVE_CONFSTR && _CS_PATH */
+
+       /* Set PATH to def_path (will set the path global variable).
+        * (import of environment below will probably change this setting).
+        */
+       {
+               struct tbl *vp = global("PATH");
+               /* setstr can't fail here */
+               setstr(vp, def_path, KSH_RETURN_ERROR);
+       }
+
+
+       /* Turn on nohup by default for now - will change to off
+        * by default once people are aware of its existence
+        * (at&t ksh does not have a nohup option - it always sends
+        * the hup).
+        */
+       Flag(FNOHUP) = 1;
+
+       /* Turn on brace expansion by default.  At&t ksh's that have
+        * alternation always have it on.  BUT, posix doesn't have
+        * brace expansion, so set this before setting up FPOSIX
+        * (change_flag() clears FBRACEEXPAND when FPOSIX is set).
+        */
+#ifdef BRACE_EXPAND
+       Flag(FBRACEEXPAND) = 1;
+#endif /* BRACE_EXPAND */
+
+       /* set posix flag just before environment so that it will have
+        * exactly the same effect as the POSIXLY_CORRECT environment
+        * variable.  If this needs to be done sooner to ensure correct posix
+        * operation, an initial scan of the environment will also have
+        * done sooner.
+        */
+#ifdef POSIXLY_CORRECT
+       change_flag(FPOSIX, OF_SPECIAL, 1);
+#endif /* POSIXLY_CORRECT */
+
+       /* Set edit mode to emacs by default, may be overridden
+        * by the environment or the user.  Also, we want tab completion
+        * on in vi by default. */
+#if defined(EDIT) && defined(EMACS)
+       change_flag(FEMACS, OF_SPECIAL, 1);
+#endif /* EDIT && EMACS */
+#if defined(EDIT) && defined(VI)
+       Flag(FVITABCOMPLETE) = 1;
+#endif /* EDIT && VI */
+
+       /* import environment */
+       if (environ != NULL)
+               for (wp = environ; *wp != NULL; wp++)
+                       typeset(*wp, IMPORT|EXPORT, 0, 0, 0);
+
+       kshpid = procpid = getpid();
+       typeset(initifs, 0, 0, 0, 0);   /* for security */
+
+       /* assign default shell variable values */
+       substitute(initsubs, 0);
+
+       /* Figure out the current working directory and set $PWD */
+       {
+               struct stat s_pwd, s_dot;
+               struct tbl *pwd_v = global("PWD");
+               char *pwd = str_val(pwd_v);
+               char *pwdx = pwd;
+
+               /* Try to use existing $PWD if it is valid */
+               if (!ISABSPATH(pwd)
+                   || stat(pwd, &s_pwd) < 0 || stat(".", &s_dot) < 0
+                   || s_pwd.st_dev != s_dot.st_dev
+                   || s_pwd.st_ino != s_dot.st_ino)
+                       pwdx = (char *) 0;
+               set_current_wd(pwdx);
+               if (current_wd[0])
+                       simplify_path(current_wd);
+               /* Only set pwd if we know where we are or if it had a
+                * bogus value
+                */
+               if (current_wd[0] || pwd != null)
+                       /* setstr can't fail here */
+                       setstr(pwd_v, current_wd, KSH_RETURN_ERROR);
+       }
+       ppid = getppid();
+       setint(global("PPID"), (long) ppid);
+#ifdef KSH
+       setint(global("RANDOM"), (long) (time((time_t *)0) * kshpid * ppid));
+#endif /* KSH */
+       /* setstr can't fail here */
+       setstr(global(version_param), ksh_version, KSH_RETURN_ERROR);
+
+       /* execute initialization statements */
+       for (wp = (char**)__UNCONST(initcoms); *wp != NULL; wp++) {
+               shcomexec(wp);
+               for (; *wp != NULL; wp++)
+                       ;
+       }
+
+
+       ksheuid = geteuid();
+       safe_prompt = ksheuid ? "$ " : "# ";
+       {
+               struct tbl *vp = global("PS1");
+
+               /* Set PS1 if it isn't set, or we are root and prompt doesn't
+                * contain a #.
+                */
+               if (!(vp->flag & ISSET)
+                   || (!ksheuid && !strchr(str_val(vp), '#')))
+                       /* setstr can't fail here */
+                       setstr(vp, safe_prompt, KSH_RETURN_ERROR);
+       }
+
+       /* Set this before parsing arguments */
+       Flag(FPRIVILEGED) = getuid() != ksheuid || getgid() != getegid();
+
+       /* this to note if monitor is set on command line (see below) */
+       Flag(FMONITOR) = 127;
+       argi = parse_args(argv, OF_CMDLINE, (int *) 0);
+       if (argi < 0) {
+               exit(1);
+               /* NOTREACHED */
+       }
+
+       if (Flag(FCOMMAND)) {
+               s = pushs(SSTRING, ATEMP);
+               if (!(s->start = s->str = argv[argi++]))
+                       errorf("-c requires an argument");
+               if (argv[argi])
+                       kshname = argv[argi++];
+       } else if (argi < argc && !Flag(FSTDIN)) {
+               s = pushs(SFILE, ATEMP);
+#ifdef OS2
+               /* a bug in os2 extproc shell processing doesn't
+                * pass full pathnames so we have to search for it.
+                * This changes the behavior of 'ksh arg' to search
+                * the users search path but it can't be helped.
+                */
+               s->file = search(argv[argi++], path, R_OK, (int *) 0);
+               if (!s->file || !*s->file)
+                       s->file = argv[argi - 1];
+#else
+               s->file = argv[argi++];
+#endif /* OS2 */
+               s->u.shf = shf_open(s->file, O_RDONLY, 0, SHF_MAPHI|SHF_CLEXEC);
+               if (s->u.shf == NULL) {
+                       exstat = 127; /* POSIX */
+                       errorf("%s: %s", s->file, strerror(errno));
+               }
+               kshname = s->file;
+       } else {
+               Flag(FSTDIN) = 1;
+               s = pushs(SSTDIN, ATEMP);
+               s->file = "<stdin>";
+               s->u.shf = shf_fdopen(0, SHF_RD | can_seek(0),
+                                     (struct shf *) 0);
+               if (isatty(0) && isatty(2)) {
+                       Flag(FTALKING) = Flag(FTALKING_I) = 1;
+                       /* The following only if isatty(0) */
+                       s->flags |= SF_TTY;
+                       s->u.shf->flags |= SHF_INTERRUPT;
+                       s->file = (char *) 0;
+               }
+       }
+
+       /* This bizarreness is mandated by POSIX */
+       {
+               struct stat s_stdin;
+
+               if (fstat(0, &s_stdin) >= 0 && S_ISCHR(s_stdin.st_mode) &&
+                   Flag(FTALKING))
+                       reset_nonblock(0);
+       }
+
+       /* initialize job control */
+       i = Flag(FMONITOR) != 127;
+       Flag(FMONITOR) = 0;
+       j_init(i);
+#ifdef EDIT
+       /* Do this after j_init(), as tty_fd is not initialized 'til then */
+       if (Flag(FTALKING))
+               x_init();
+#endif
+
+       l = e->loc;
+       l->argv = &argv[argi - 1];
+       l->argc = argc - argi;
+       l->argv[0] = (char *)__UNCONST(kshname);
+       getopts_reset(1);
+
+       /* Disable during .profile/ENV reading */
+       restricted = Flag(FRESTRICTED);
+       Flag(FRESTRICTED) = 0;
+       errexit = Flag(FERREXIT);
+       Flag(FERREXIT) = 0;
+
+       /* Do this before profile/$ENV so that if it causes problems in them,
+        * user will know why things broke.
+        */
+       if (!current_wd[0] && Flag(FTALKING))
+               warningf(FALSE, "Cannot determine current working directory");
+
+       if (Flag(FLOGIN)) {
+#ifdef OS2
+               char *profile;
+
+               /* Try to find a profile - first see if $INIT has a value,
+                * then try /etc/profile.ksh, then c:/usr/etc/profile.ksh.
+                */
+               if (!Flag(FPRIVILEGED)
+                   && strcmp(profile = substitute("$INIT/profile.ksh", 0),
+                             "/profile.ksh"))
+                       include(profile, 0, (char **) 0, 1);
+               else if (include("/etc/profile.ksh", 0, (char **) 0, 1) < 0)
+                       include("c:/usr/etc/profile.ksh", 0, (char **) 0, 1);
+               if (!Flag(FPRIVILEGED))
+                       include(substitute("$HOME/profile.ksh", 0), 0,
+                               (char **) 0, 1);
+#else /* OS2 */
+               include(KSH_SYSTEM_PROFILE, 0, (char **) 0, 1);
+               if (!Flag(FPRIVILEGED))
+                       include(substitute("$HOME/.profile", 0), 0,
+                               (char **) 0, 1);
+#endif /* OS2 */
+       }
+
+       if (Flag(FPRIVILEGED))
+               include("/etc/suid_profile", 0, (char **) 0, 1);
+       else {
+               char *env_file;
+
+#ifndef KSH
+               if (!Flag(FPOSIX))
+                       env_file = null;
+               else
+#endif /* !KSH */
+                       /* include $ENV */
+                       env_file = str_val(global("ENV"));
+
+#ifdef DEFAULT_ENV
+               /* If env isn't set, include default environment */
+               if (env_file == null)
+                       env_file = __UNCONST(DEFAULT_ENV);
+#endif /* DEFAULT_ENV */
+               env_file = substitute(env_file, DOTILDE);
+               if (*env_file != '\0')
+                       include(env_file, 0, (char **) 0, 1);
+#ifdef OS2
+               else if (Flag(FTALKING))
+                       include(substitute("$HOME/kshrc.ksh", 0), 0,
+                               (char **) 0, 1);
+#endif /* OS2 */
+       }
+
+       if (is_restricted(argv[0]) || is_restricted(str_val(global("SHELL"))))
+               restricted = 1;
+       if (restricted) {
+               static const char *const restr_com[] = {
+                                               "typeset", "-r", "PATH",
+                                                   "ENV", "SHELL",
+                                               (char *) 0
+                                           };
+               shcomexec((char **)__UNCONST(restr_com));
+               /* After typeset command... */
+               Flag(FRESTRICTED) = 1;
+       }
+       if (errexit)
+               Flag(FERREXIT) = 1;
+
+       if (Flag(FTALKING)) {
+               hist_init(s);
+#ifdef KSH
+               alarm_init();
+#endif /* KSH */
+       } else
+               Flag(FTRACKALL) = 1;    /* set after ENV */
+
+       setlocale(LC_CTYPE, "");
+       shell(s, TRUE); /* doesn't return */
+       return 0;
+}
+
+int
+include(name, argc, argv, intr_ok)
+       const char *name;
+       int argc;
+       char **argv;
+       int intr_ok;
+{
+       register Source *volatile s = NULL;
+       struct shf *shf;
+       char **volatile old_argv;
+       volatile int old_argc;
+       int i;
+
+       shf = shf_open(name, O_RDONLY, 0, SHF_MAPHI|SHF_CLEXEC);
+       if (shf == NULL)
+               return -1;
+
+       if (argv) {
+               old_argv = e->loc->argv;
+               old_argc = e->loc->argc;
+       } else {
+               old_argv = (char **) 0;
+               old_argc = 0;
+       }
+       newenv(E_INCL);
+       i = ksh_sigsetjmp(e->jbuf, 0);
+       if (i) {
+               if (s) /* Do this before quitenv(), which frees the memory */
+                       shf_close(s->u.shf);
+               quitenv();
+               if (old_argv) {
+                       e->loc->argv = old_argv;
+                       e->loc->argc = old_argc;
+               }
+               switch (i) {
+                 case LRETURN:
+                 case LERROR:
+                       return exstat & 0xff; /* see below */
+                 case LINTR:
+                       /* intr_ok is set if we are including .profile or $ENV.
+                        * If user ^C's out, we don't want to kill the shell...
+                        */
+                       if (intr_ok && (exstat - 128) != SIGTERM)
+                               return 1;
+                       /* fall through... */
+                 case LEXIT:
+                 case LLEAVE:
+                 case LSHELL:
+                       unwind(i);
+                       /*NOREACHED*/
+                 default:
+                       internal_errorf(1, "include: %d", i);
+                       /*NOREACHED*/
+               }
+       }
+       if (argv) {
+               e->loc->argv = argv;
+               e->loc->argc = argc;
+       }
+       s = pushs(SFILE, ATEMP);
+       s->u.shf = shf;
+       s->file = str_save(name, ATEMP);
+       i = shell(s, FALSE);
+       shf_close(s->u.shf);
+       quitenv();
+       if (old_argv) {
+               e->loc->argv = old_argv;
+               e->loc->argc = old_argc;
+       }
+       return i & 0xff;        /* & 0xff to ensure value not -1 */
+}
+
+int
+command(comm)
+       const char *comm;
+{
+       register Source *s;
+       int r;
+
+       s = pushs(SSTRING, ATEMP);
+       s->start = s->str = comm;
+       r = shell(s, FALSE);
+       afree(s, ATEMP);
+       return r;
+}
+
+/*
+ * run the commands from the input source, returning status.
+ */
+int
+shell(s, toplevel)
+       Source *volatile s;             /* input source */
+       int volatile toplevel;
+{
+       struct op *t;
+       volatile int wastty = s->flags & SF_TTY;
+       volatile int attempts = 13;
+       volatile int interactive = Flag(FTALKING) && toplevel;
+       Source *volatile old_source = source;
+       int i;
+
+       newenv(E_PARSE);
+       if (interactive)
+               really_exit = 0;
+       i = ksh_sigsetjmp(e->jbuf, 0);
+       if (i) {
+               switch (i) {
+                 case LINTR: /* we get here if SIGINT not caught or ignored */
+                 case LERROR:
+                 case LSHELL:
+                       if (interactive) {
+                               if (i == LINTR)
+                                       shellf("%s", newline);
+                               /* Reset any eof that was read as part of a
+                                * multiline command.
+                                */
+                               if (Flag(FIGNOREEOF) && s->type == SEOF
+                                   && wastty)
+                                       s->type = SSTDIN;
+                               /* Used by exit command to get back to
+                                * top level shell.  Kind of strange since
+                                * interactive is set if we are reading from
+                                * a tty, but to have stopped jobs, one only
+                                * needs FMONITOR set (not FTALKING/SF_TTY)...
+                                */
+                               /* toss any input we have so far */
+                               s->start = s->str = null;
+                               break;
+                       }
+                       /* fall through... */
+                 case LEXIT:
+                 case LLEAVE:
+                 case LRETURN:
+                       source = old_source;
+                       quitenv();
+                       unwind(i);      /* keep on going */
+                       /*NOREACHED*/
+                 default:
+                       source = old_source;
+                       quitenv();
+                       internal_errorf(1, "shell: %d", i);
+                       /*NOREACHED*/
+               }
+       }
+
+       while (1) {
+               if (trap)
+                       runtraps(0);
+
+               if (s->next == NULL) {
+                       if (Flag(FVERBOSE))
+                               s->flags |= SF_ECHO;
+                       else
+                               s->flags &= ~SF_ECHO;
+               }
+
+               if (interactive) {
+                       j_notify();
+#ifdef KSH
+                       mcheck();
+#endif /* KSH */
+                       set_prompt(PS1, s);
+               }
+
+               t = compile(s);
+               if (t != NULL && t->type == TEOF) {
+                       if (wastty && Flag(FIGNOREEOF) && --attempts > 0) {
+                               shellf("Use `exit' to leave ksh\n");
+                               s->type = SSTDIN;
+                       } else if (wastty && !really_exit
+                                  && j_stopped_running())
+                       {
+                               really_exit = 1;
+                               s->type = SSTDIN;
+                       } else {
+                               /* this for POSIX, which says EXIT traps
+                                * shall be taken in the environment
+                                * immediately after the last command
+                                * executed.
+                                */
+                               if (toplevel)
+                                       unwind(LEXIT);
+                               break;
+                       }
+               }
+
+               if (t && (!Flag(FNOEXEC) || (s->flags & SF_TTY)))
+                       exstat = execute(t, 0);
+
+               if (t != NULL && t->type != TEOF && interactive && really_exit)
+                       really_exit = 0;
+
+               reclaim();
+       }
+       quitenv();
+       source = old_source;
+       return exstat;
+}
+
+/* return to closest error handler or shell(), exit if none found */
+void
+unwind(i)
+       int i;
+{
+       /* ordering for EXIT vs ERR is a bit odd (this is what at&t ksh does) */
+       if (i == LEXIT || (Flag(FERREXIT) && (i == LERROR || i == LINTR)
+                          && sigtraps[SIGEXIT_].trap))
+       {
+               runtrap(&sigtraps[SIGEXIT_]);
+               i = LLEAVE;
+       } else if (Flag(FERREXIT) && (i == LERROR || i == LINTR)) {
+               runtrap(&sigtraps[SIGERR_]);
+               i = LLEAVE;
+       }
+       while (1) {
+               switch (e->type) {
+                 case E_PARSE:
+                 case E_FUNC:
+                 case E_INCL:
+                 case E_LOOP:
+                 case E_ERRH:
+                       ksh_siglongjmp(e->jbuf, i);
+                       /*NOTREACHED*/
+
+                 case E_NONE:
+                       if (i == LINTR)
+                               e->flags |= EF_FAKE_SIGDIE;
+                       /* Fall through... */
+
+                 default:
+                       quitenv();
+               }
+       }
+}
+
+void
+newenv(type)
+       int type;
+{
+       register struct env *ep;
+
+       ep = (struct env *) alloc(sizeof(*ep), ATEMP);
+       ep->type = type;
+       ep->flags = 0;
+       ainit(&ep->area);
+       ep->loc = e->loc;
+       ep->savefd = NULL;
+       ep->oenv = e;
+       ep->temps = NULL;
+       e = ep;
+}
+
+void
+quitenv()
+{
+       register struct env *ep = e;
+       register int fd;
+
+       if (ep->oenv && ep->oenv->loc != ep->loc)
+               popblock();
+       if (ep->savefd != NULL) {
+               for (fd = 0; fd < NUFILE; fd++)
+                       /* if ep->savefd[fd] < 0, means fd was closed */
+                       if (ep->savefd[fd])
+                               restfd(fd, ep->savefd[fd]);
+               if (ep->savefd[2]) /* Clear any write errors */
+                       shf_reopen(2, SHF_WR, shl_out);
+       }
+       reclaim();
+
+       /* Bottom of the stack.
+        * Either main shell is exiting or cleanup_parents_env() was called.
+        */
+       if (ep->oenv == NULL) {
+               if (ep->type == E_NONE) {       /* Main shell exiting? */
+                       if (Flag(FTALKING))
+                               hist_finish();
+                       j_exit();
+                       if (ep->flags & EF_FAKE_SIGDIE) {
+                               int sig = exstat - 128;
+
+                               /* ham up our death a bit (at&t ksh
+                                * only seems to do this for SIGTERM)
+                                * Don't do it for SIGQUIT, since we'd
+                                * dump a core..
+                                */
+                               if (sig == SIGINT || sig == SIGTERM) {
+                                       setsig(&sigtraps[sig], SIG_DFL,
+                                               SS_RESTORE_CURR|SS_FORCE);
+                                       kill(0, sig);
+                               }
+                       }
+#ifdef MEM_DEBUG
+                       chmem_allfree();
+#endif /* MEM_DEBUG */
+               }
+               exit(exstat);
+       }
+
+       e = e->oenv;
+       afree(ep, ATEMP);
+}
+
+/* Called after a fork to cleanup stuff left over from parents environment */
+void
+cleanup_parents_env()
+{
+       struct env *ep;
+       int fd;
+
+       /* Don't clean up temporary files - parent will probably need them.
+        * Also, can't easily reclaim memory since variables, etc. could be
+        * anywhere.
+        */
+
+       /* close all file descriptors hiding in savefd */
+       for (ep = e; ep; ep = ep->oenv) {
+               if (ep->savefd) {
+                       for (fd = 0; fd < NUFILE; fd++)
+                               if (ep->savefd[fd] > 0)
+                                       close(ep->savefd[fd]);
+                       afree(ep->savefd, &ep->area);
+                       ep->savefd = (short *) 0;
+               }
+       }
+       e->oenv = (struct env *) 0;
+}
+
+/* Called just before an execve cleanup stuff temporary files */
+void
+cleanup_proc_env()
+{
+       struct env *ep;
+
+       for (ep = e; ep; ep = ep->oenv)
+               remove_temps(ep->temps);
+}
+
+/* remove temp files and free ATEMP Area */
+static void
+reclaim()
+{
+       remove_temps(e->temps);
+       e->temps = NULL;
+       afreeall(&e->area);
+}
+
+static void
+remove_temps(tp)
+       struct temp *tp;
+{
+#ifdef OS2
+       static struct temp *delayed_remove;
+       struct temp *t, **tprev;
+
+       if (delayed_remove) {
+               for (tprev = &delayed_remove, t = delayed_remove; t; t = *tprev)
+                       /* No need to check t->pid here... */
+                       if (unlink(t->name) >= 0 || errno == ENOENT) {
+                               *tprev = t->next;
+                               afree(t, APERM);
+                       } else
+                               tprev = &t->next;
+       }
+#endif /* OS2 */
+
+       for (; tp != NULL; tp = tp->next)
+               if (tp->pid == procpid) {
+#ifdef OS2
+                       /* OS/2 (and dos) do not allow files that are currently
+                        * open to be removed, so we cache it away for future
+                        * removal.
+                        * XXX should only do this if errno
+                        *     is Efile-still-open-can't-remove
+                        *     (but I don't know what that is...)
+                        */
+                       if (unlink(tp->name) < 0 && errno != ENOENT) {
+                               t = (struct temp *) alloc(
+                                   sizeof(struct temp) + strlen(tp->name) + 1,
+                                   APERM);
+                               memset(t, 0, sizeof(struct temp));
+                               t->name = (char *) &t[1];
+                               strlcpy(t->name, tp->name, strlen(tp->name) + 1);
+                               t->next = delayed_remove;
+                               delayed_remove = t;
+                       }
+#else /* OS2 */
+                       unlink(tp->name);
+#endif /* OS2 */
+               }
+}
+
+/* Returns true if name refers to a restricted shell */
+static int
+is_restricted(name)
+       char *name;
+{
+       char *p;
+
+       if ((p = ksh_strrchr_dirsep(name)))
+               name = p;
+       /* accepts rsh, rksh, rpdksh, pdrksh, etc. */
+       return (p = strchr(name, 'r')) && strstr(p, "sh");
+}
+
+void
+aerror(ap, msg)
+       Area *ap;
+       const char *msg;
+{
+       internal_errorf(1, "alloc: %s", msg);
+       errorf("%s", null); /* this is never executed - keeps gcc quiet */
+       /*NOTREACHED*/
+}
diff --git a/bin/ksh/misc.c b/bin/ksh/misc.c
new file mode 100644 (file)
index 0000000..94ae44b
--- /dev/null
@@ -0,0 +1,1364 @@
+/*     $NetBSD: misc.c,v 1.15 2011/10/16 17:12:11 joerg Exp $  */
+
+/*
+ * Miscellaneous functions
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: misc.c,v 1.15 2011/10/16 17:12:11 joerg Exp $");
+#endif
+
+
+#include "sh.h"
+#include <ctype.h>     /* for FILECHCONV */
+#ifdef HAVE_LIMITS_H
+# include <limits.h>
+#endif
+
+#ifndef UCHAR_MAX
+# define UCHAR_MAX     0xFF
+#endif
+
+short ctypes [UCHAR_MAX+1];    /* type bits for unsigned char */
+
+static int     do_gmatch ARGS((const unsigned char *s, const unsigned char *p,
+                       const unsigned char *se, const unsigned char *pe,
+                       int isfile));
+static const unsigned char *cclass ARGS((const unsigned char *p, int sub));
+
+/*
+ * Fast character classes
+ */
+void
+setctypes(s, t)
+       register const char *s;
+       register int t;
+{
+       register int i;
+
+       if (t & C_IFS) {
+               for (i = 0; i < UCHAR_MAX+1; i++)
+                       ctypes[i] &= ~C_IFS;
+               ctypes[0] |= C_IFS; /* include \0 in C_IFS */
+       }
+       while (*s != 0)
+               ctypes[(unsigned char) *s++] |= t;
+}
+
+void
+initctypes()
+{
+       register int c;
+
+       for (c = 'a'; c <= 'z'; c++)
+               ctypes[c] |= C_ALPHA;
+       for (c = 'A'; c <= 'Z'; c++)
+               ctypes[c] |= C_ALPHA;
+       ctypes['_'] |= C_ALPHA;
+       setctypes("0123456789", C_DIGIT);
+       setctypes(" \t\n|&;<>()", C_LEX1); /* \0 added automatically */
+       setctypes("*@#!$-?", C_VAR1);
+       setctypes(" \t\n", C_IFSWS);
+       setctypes("=-+?", C_SUBOP1);
+       setctypes("#%", C_SUBOP2);
+       setctypes(" \n\t\"#$&'()*;<>?[\\`|", C_QUOTE);
+}
+
+/* convert unsigned long to base N string */
+
+char *
+ulton(n, base)
+       register unsigned long n;
+       int base;
+{
+       register char *p;
+       static char buf [20];
+
+       p = &buf[sizeof(buf)];
+       *--p = '\0';
+       do {
+               *--p = "0123456789ABCDEF"[n%base];
+               n /= base;
+       } while (n != 0);
+       return p;
+}
+
+char *
+str_save(s, ap)
+       register const char *s;
+       Area *ap;
+{
+       size_t len;
+       char *p;
+
+       if (!s)
+               return NULL;
+       len = strlen(s)+1;
+       p = alloc(len, ap);
+       strlcpy(p, s, len);
+       return (p);
+}
+
+/* Allocate a string of size n+1 and copy upto n characters from the possibly
+ * null terminated string s into it.  Always returns a null terminated string
+ * (unless n < 0).
+ */
+char *
+str_nsave(s, n, ap)
+       register const char *s;
+       int n;
+       Area *ap;
+{
+       char *ns;
+
+       if (n < 0)
+               return 0;
+       ns = alloc(n + 1, ap);
+       ns[0] = '\0';
+       return strncat(ns, s, n);
+}
+
+/* called from expand.h:XcheckN() to grow buffer */
+char *
+Xcheck_grow_(xsp, xp, more)
+       XString *xsp;
+       char *xp;
+       int more;
+{
+       char *old_beg = xsp->beg;
+
+       xsp->len += (size_t)more > xsp->len ? (size_t)more : xsp->len;
+       xsp->beg = aresize(xsp->beg, xsp->len + 8, xsp->areap);
+       xsp->end = xsp->beg + xsp->len;
+       return xsp->beg + (xp - old_beg);
+}
+
+const struct option goptions[] = {
+       /* Special cases (see parse_args()): -A, -o, -s.
+        * Options are sorted by their longnames - the order of these
+        * entries MUST match the order of sh_flag F* enumerations in sh.h.
+        */
+       { "allexport",  'a',            OF_ANY },
+#ifdef BRACE_EXPAND
+       { "braceexpand",  0,            OF_ANY }, /* non-standard */
+#endif
+       { "bgnice",       0,            OF_ANY },
+       { (char *) 0,   'c',        OF_CMDLINE },
+#ifdef EMACS
+       { "emacs",        0,            OF_ANY },
+       { "emacs-usemeta",  0,          OF_ANY }, /* non-standard */
+#endif
+       { "errexit",    'e',            OF_ANY },
+#ifdef EMACS
+       { "gmacs",        0,            OF_ANY },
+#endif
+       { "ignoreeof",    0,            OF_ANY },
+       { "interactive",'i',        OF_CMDLINE },
+       { "keyword",    'k',            OF_ANY },
+       { "login",      'l',        OF_CMDLINE },
+       { "markdirs",   'X',            OF_ANY },
+#ifdef JOBS
+       { "monitor",    'm',            OF_ANY },
+#else /* JOBS */
+       { (char *) 0,   'm',                 0 }, /* so FMONITOR not ifdef'd */
+#endif /* JOBS */
+       { "noclobber",  'C',            OF_ANY },
+       { "noexec",     'n',            OF_ANY },
+       { "noglob",     'f',            OF_ANY },
+       { "nohup",        0,            OF_ANY },
+       { "nolog",        0,            OF_ANY }, /* no effect */
+#ifdef JOBS
+       { "notify",     'b',            OF_ANY },
+#endif /* JOBS */
+       { "nounset",    'u',            OF_ANY },
+       { "physical",     0,            OF_ANY }, /* non-standard */
+       { "posix",        0,            OF_ANY }, /* non-standard */
+       { "privileged", 'p',            OF_ANY },
+       { "restricted", 'r',        OF_CMDLINE },
+       { "stdin",      's',        OF_CMDLINE }, /* pseudo non-standard */
+       { "trackall",   'h',            OF_ANY },
+       { "verbose",    'v',            OF_ANY },
+#ifdef VI
+       { "vi",           0,            OF_ANY },
+       { "viraw",        0,            OF_ANY }, /* no effect */
+       { "vi-show8",     0,            OF_ANY }, /* non-standard */
+       { "vi-tabcomplete",  0,         OF_ANY }, /* non-standard */
+       { "vi-esccomplete",  0,         OF_ANY }, /* non-standard */
+#endif
+       { "xtrace",     'x',            OF_ANY },
+       /* Anonymous flags: used internally by shell only
+        * (not visible to user)
+        */
+       { (char *) 0,   0,              OF_INTERNAL }, /* FTALKING_I */
+};
+
+/*
+ * translate -o option into F* constant (also used for test -o option)
+ */
+int
+option(n)
+       const char *n;
+{
+       int i;
+
+       for (i = 0; i < (int)NELEM(goptions); i++)
+               if (goptions[i].name && strcmp(goptions[i].name, n) == 0)
+                       return i;
+
+       return -1;
+}
+
+struct options_info {
+       int opt_width;
+       struct {
+               const char *name;
+               int     flag;
+       } opts[NELEM(goptions)];
+};
+
+static char *options_fmt_entry ARGS((void *arg, int i, char *buf, int buflen));
+static void printoptions ARGS((int verbose));
+
+/* format a single select menu item */
+static char *
+options_fmt_entry(arg, i, buf, buflen)
+       void *arg;
+       int i;
+       char *buf;
+       int buflen;
+{
+       struct options_info *oi = (struct options_info *) arg;
+
+       shf_snprintf(buf, buflen, "%-*s %s",
+               oi->opt_width, oi->opts[i].name,
+               Flag(oi->opts[i].flag) ? "on" : "off");
+       return buf;
+}
+
+static void
+printoptions(verbose)
+       int verbose;
+{
+       int i;
+
+       if (verbose) {
+               struct options_info oi;
+               int n, len;
+
+               /* verbose version */
+               shprintf("Current option settings\n");
+
+               for (i = n = oi.opt_width = 0; i < (int)NELEM(goptions); i++)
+                       if (goptions[i].name) {
+                               len = strlen(goptions[i].name);
+                               oi.opts[n].name = goptions[i].name;
+                               oi.opts[n++].flag = i;
+                               if (len > oi.opt_width)
+                                       oi.opt_width = len;
+                       }
+               print_columns(shl_stdout, n, options_fmt_entry, &oi,
+                             oi.opt_width + 5, 1);
+       } else {
+               /* short version ala ksh93 */
+               shprintf("set");
+               for (i = 0; i < (int)NELEM(goptions); i++)
+                       if (Flag(i) && goptions[i].name)
+                               shprintf(" -o %s", goptions[i].name);
+               shprintf("%s", newline);
+       }
+}
+
+char *
+getoptions()
+{
+       size_t i;
+       char m[(int) FNFLAGS + 1];
+       register char *cp = m;
+
+       for (i = 0; i < NELEM(goptions); i++)
+               if (goptions[i].c && Flag(i))
+                       *cp++ = goptions[i].c;
+       *cp = 0;
+       return str_save(m, ATEMP);
+}
+
+/* change a Flag(*) value; takes care of special actions */
+void
+change_flag(f, what, newval)
+       enum sh_flag f; /* flag to change */
+       int what;       /* what is changing the flag (command line vs set) */
+       int newval;
+{
+       int oldval;
+
+       oldval = Flag(f);
+       Flag(f) = newval;
+#ifdef JOBS
+       if (f == FMONITOR) {
+               if (what != OF_CMDLINE && newval != oldval)
+                       j_change();
+       } else
+#endif /* JOBS */
+#ifdef EDIT
+       if (0
+# ifdef VI
+           || f == FVI
+# endif /* VI */
+# ifdef EMACS
+           || f == FEMACS || f == FGMACS
+# endif /* EMACS */
+          )
+       {
+               if (newval) {
+# ifdef VI
+                       Flag(FVI) = 0;
+# endif /* VI */
+# ifdef EMACS
+                       Flag(FEMACS) = Flag(FGMACS) = 0;
+# endif /* EMACS */
+                       Flag(f) = newval;
+               }
+       } else
+#endif /* EDIT */
+       /* Turning off -p? */
+       if (f == FPRIVILEGED && oldval && !newval) {
+#ifdef OS2
+               ;
+#else /* OS2 */
+               seteuid(ksheuid = getuid());
+               setuid(ksheuid);
+               setegid(getgid());
+               setgid(getgid());
+#endif /* OS2 */
+       } else if (f == FPOSIX && newval) {
+#ifdef BRACE_EXPAND
+               Flag(FBRACEEXPAND) = 0
+#endif /* BRACE_EXPAND */
+               ;
+       }
+       /* Changing interactive flag? */
+       if (f == FTALKING) {
+               if ((what == OF_CMDLINE || what == OF_SET) && procpid == kshpid)
+                       Flag(FTALKING_I) = newval;
+       }
+}
+
+/* parse command line & set command arguments.  returns the index of
+ * non-option arguments, -1 if there is an error.
+ */
+int
+parse_args(argv, what, setargsp)
+       char **argv;
+       int     what;           /* OF_CMDLINE or OF_SET */
+       int     *setargsp;
+{
+       static char cmd_opts[NELEM(goptions) + 3]; /* o:\0 */
+       static char set_opts[NELEM(goptions) + 5]; /* Ao;s\0 */
+       char *opts;
+       char *array = (char *) 0;
+       Getopt go;
+       int i, optc, set, sortargs = 0, arrayset = 0;
+
+       /* First call?  Build option strings... */
+       if (cmd_opts[0] == '\0') {
+               char *p, *q;
+
+               /* see cmd_opts[] declaration */
+               strlcpy(cmd_opts, "o:", sizeof cmd_opts);
+               p = cmd_opts + strlen(cmd_opts);
+               /* see set_opts[] declaration */
+               strlcpy(set_opts, "A:o;s", sizeof set_opts);
+               q = set_opts + strlen(set_opts);
+               for (i = 0; i < (int)NELEM(goptions); i++) {
+                       if (goptions[i].c) {
+                               if (goptions[i].flags & OF_CMDLINE)
+                                       *p++ = goptions[i].c;
+                               if (goptions[i].flags & OF_SET)
+                                       *q++ = goptions[i].c;
+                       }
+               }
+               *p = '\0';
+               *q = '\0';
+       }
+
+       if (what == OF_CMDLINE) {
+               char *p;
+               /* Set FLOGIN before parsing options so user can clear
+                * flag using +l.
+                */
+               Flag(FLOGIN) = (argv[0][0] == '-'
+                               || ((p = ksh_strrchr_dirsep(argv[0]))
+                                    && *++p == '-'));
+               opts = cmd_opts;
+       } else
+               opts = set_opts;
+       ksh_getopt_reset(&go, GF_ERROR|GF_PLUSOPT);
+       while ((optc = ksh_getopt(argv, &go, opts)) != EOF) {
+               set = (go.info & GI_PLUS) ? 0 : 1;
+               switch (optc) {
+                 case 'A':
+                       arrayset = set ? 1 : -1;
+                       array = go.optarg;
+                       break;
+
+                 case 'o':
+                       if (go.optarg == (char *) 0) {
+                               /* lone -o: print options
+                                *
+                                * Note that on the command line, -o requires
+                                * an option (ie, can't get here if what is
+                                * OF_CMDLINE).
+                                */
+                               printoptions(set);
+                               break;
+                       }
+                       i = option(go.optarg);
+                       if (i >= 0 && set == Flag(i))
+                               /* Don't check the context if the flag
+                                * isn't changing - makes "set -o interactive"
+                                * work if you're already interactive.  Needed
+                                * if the output of "set +o" is to be used.
+                                */
+                               ;
+                       else if (i >= 0 && (goptions[i].flags & what))
+                               change_flag((enum sh_flag) i, what, set);
+                       else {
+                               bi_errorf("%s: bad option", go.optarg);
+                               return -1;
+                       }
+                       break;
+
+                 case '?':
+                       return -1;
+
+                 default:
+                       /* -s: sort positional params (at&t ksh stupidity) */
+                       if (what == OF_SET && optc == 's') {
+                               sortargs = 1;
+                               break;
+                       }
+                       for (i = 0; i < (int)NELEM(goptions); i++)
+                               if (optc == goptions[i].c
+                                   && (what & goptions[i].flags))
+                               {
+                                       change_flag((enum sh_flag) i, what,
+                                                   set);
+                                       break;
+                               }
+                       if (i == NELEM(goptions)) {
+                               internal_errorf(1, "parse_args: `%c'", optc);
+                               return -1; /* not reached */
+                       }
+               }
+       }
+       if (!(go.info & GI_MINUSMINUS) && argv[go.optind]
+           && (argv[go.optind][0] == '-' || argv[go.optind][0] == '+')
+           && argv[go.optind][1] == '\0')
+       {
+               /* lone - clears -v and -x flags */
+               if (argv[go.optind][0] == '-' && !Flag(FPOSIX))
+                       Flag(FVERBOSE) = Flag(FXTRACE) = 0;
+               /* set skips lone - or + option */
+               go.optind++;
+       }
+       if (setargsp)
+               /* -- means set $#/$* even if there are no arguments */
+               *setargsp = !arrayset && ((go.info & GI_MINUSMINUS)
+                                         || argv[go.optind]);
+
+       if (arrayset && (!*array || *skip_varname(array, FALSE))) {
+               bi_errorf("%s: is not an identifier", array);
+               return -1;
+       }
+       if (sortargs) {
+               for (i = go.optind; argv[i]; i++)
+                       ;
+               qsortp((void **) &argv[go.optind], (size_t) (i - go.optind),
+                       xstrcmp);
+       }
+       if (arrayset) {
+               set_array(array, arrayset, argv + go.optind);
+               for (; argv[go.optind]; go.optind++)
+                       ;
+       }
+
+       return go.optind;
+}
+
+/* parse a decimal number: returns 0 if string isn't a number, 1 otherwise */
+int
+getn(as, ai)
+       const char *as;
+       int *ai;
+{
+       char *p;
+       long n;
+
+       n = strtol(as, &p, 10);
+
+       if (!*as || *p || INT_MIN >= n || n >= INT_MAX)
+               return 0;
+
+       *ai = (int)n;
+       return 1;
+}
+
+/* getn() that prints error */
+int
+bi_getn(as, ai)
+       const char *as;
+       int *ai;
+{
+       int rv = getn(as, ai);
+
+       if (!rv)
+               bi_errorf("%s: bad number", as);
+       return rv;
+}
+
+/* -------- gmatch.c -------- */
+
+/*
+ * int gmatch(string, pattern)
+ * char *string, *pattern;
+ *
+ * Match a pattern as in sh(1).
+ * pattern character are prefixed with MAGIC by expand.
+ */
+
+int
+gmatch(s, p, isfile)
+       const char *s, *p;
+       int isfile;
+{
+       const char *se, *pe;
+
+       if (s == NULL || p == NULL)
+               return 0;
+       se = s + strlen(s);
+       pe = p + strlen(p);
+       /* isfile is false iff no syntax check has been done on
+        * the pattern.  If check fails, just to a strcmp().
+        */
+       if (!isfile && !has_globbing(p, pe)) {
+               int len = pe - p + 1;
+               char tbuf[64];
+               char *t = len <= (int)sizeof(tbuf) ? tbuf
+                               : (char *) alloc(len, ATEMP);
+               debunk(t, p, len);
+               return !strcmp(t, s);
+       }
+       return do_gmatch((const unsigned char *) s, (const unsigned char *) se,
+                        (const unsigned char *) p, (const unsigned char *) pe,
+                        isfile);
+}
+
+/* Returns if p is a syntacticly correct globbing pattern, false
+ * if it contains no pattern characters or if there is a syntax error.
+ * Syntax errors are:
+ *     - [ with no closing ]
+ *     - imbalanced $(...) expression
+ *     - [...] and *(...) not nested (eg, [a$(b|]c), *(a[b|c]d))
+ */
+/*XXX
+- if no magic,
+       if dest given, copy to dst
+       return ?
+- if magic && (no globbing || syntax error)
+       debunk to dst
+       return ?
+- return ?
+*/
+int
+has_globbing(xp, xpe)
+       const char *xp, *xpe;
+{
+       const unsigned char *p = (const unsigned char *) xp;
+       const unsigned char *pe = (const unsigned char *) xpe;
+       int c;
+       int nest = 0, bnest = 0;
+       int saw_glob = 0;
+       int in_bracket = 0; /* inside [...] */
+
+       for (; p < pe; p++) {
+               if (!ISMAGIC(*p))
+                       continue;
+               if ((c = *++p) == '*' || c == '?')
+                       saw_glob = 1;
+               else if (c == '[') {
+                       if (!in_bracket) {
+                               saw_glob = 1;
+                               in_bracket = 1;
+                               if (ISMAGIC(p[1]) && p[2] == NOT)
+                                       p += 2;
+                               if (ISMAGIC(p[1]) && p[2] == ']')
+                                       p += 2;
+                       }
+                       /* XXX Do we need to check ranges here? POSIX Q */
+               } else if (c == ']') {
+                       if (in_bracket) {
+                               if (bnest)              /* [a*(b]) */
+                                       return 0;
+                               in_bracket = 0;
+                       }
+               } else if ((c & 0x80) && strchr("*+?@! ", c & 0x7f)) {
+                       saw_glob = 1;
+                       if (in_bracket)
+                               bnest++;
+                       else
+                               nest++;
+               } else if (c == '|') {
+                       if (in_bracket && !bnest)       /* *(a[foo|bar]) */
+                               return 0;
+               } else if (c == /*(*/ ')') {
+                       if (in_bracket) {
+                               if (!bnest--)           /* *(a[b)c] */
+                                       return 0;
+                       } else if (nest)
+                               nest--;
+               }
+               /* else must be a MAGIC-MAGIC, or MAGIC-!, MAGIC--, MAGIC-]
+                        MAGIC-{, MAGIC-,, MAGIC-} */
+       }
+       return saw_glob && !in_bracket && !nest;
+}
+
+/* Function must return either 0 or 1 (assumed by code for 0x80|'!') */
+static int
+do_gmatch(s, se, p, pe, isfile)
+       const unsigned char *s, *p;
+       const unsigned char *se, *pe;
+       int isfile;
+{
+       register int sc, pc;
+       const unsigned char *prest, *psub, *pnext;
+       const unsigned char *srest;
+
+       if (s == NULL || p == NULL)
+               return 0;
+       while (p < pe) {
+               pc = *p++;
+               sc = s < se ? *s : '\0';
+               s++;
+               if (isfile) {
+                       sc = FILECHCONV((unsigned char)sc);
+                       pc = FILECHCONV((unsigned char)pc);
+               }
+               if (!ISMAGIC(pc)) {
+                       if (sc != pc)
+                               return 0;
+                       continue;
+               }
+               switch (*p++) {
+                 case '[':
+                       if (sc == 0 || (p = cclass(p, sc)) == NULL)
+                               return 0;
+                       break;
+
+                 case '?':
+                       if (sc == 0)
+                               return 0;
+                       break;
+
+                 case '*':
+                       if (p == pe)
+                               return 1;
+                       s--;
+                       do {
+                               if (do_gmatch(s, se, p, pe, isfile))
+                                       return 1;
+                       } while (s++ < se);
+                       return 0;
+
+                 /*
+                  * [*+?@!](pattern|pattern|..)
+                  *
+                  * Not ifdef'd KSH as this is needed for ${..%..}, etc.
+                  */
+                 case 0x80|'+': /* matches one or more times */
+                 case 0x80|'*': /* matches zero or more times */
+                       if (!(prest = pat_scan(p, pe, 0)))
+                               return 0;
+                       s--;
+                       /* take care of zero matches */
+                       if (p[-1] == (0x80 | '*')
+                           && do_gmatch(s, se, prest, pe, isfile))
+                               return 1;
+                       for (psub = p; ; psub = pnext) {
+                               pnext = pat_scan(psub, pe, 1);
+                               for (srest = s; srest <= se; srest++) {
+                                       if (do_gmatch(s, srest,
+                                               psub, pnext - 2, isfile)
+                                           && (do_gmatch(srest, se,
+                                                         prest, pe, isfile)
+                                               || (s != srest
+                                                   && do_gmatch(srest, se,
+                                                       p - 2, pe, isfile))))
+                                               return 1;
+                               }
+                               if (pnext == prest)
+                                       break;
+                       }
+                       return 0;
+
+                 case 0x80|'?': /* matches zero or once */
+                 case 0x80|'@': /* matches one of the patterns */
+                 case 0x80|' ': /* simile for @ */
+                       if (!(prest = pat_scan(p, pe, 0)))
+                               return 0;
+                       s--;
+                       /* Take care of zero matches */
+                       if (p[-1] == (0x80 | '?')
+                           && do_gmatch(s, se, prest, pe, isfile))
+                               return 1;
+                       for (psub = p; ; psub = pnext) {
+                               pnext = pat_scan(psub, pe, 1);
+                               srest = prest == pe ? se : s;
+                               for (; srest <= se; srest++) {
+                                       if (do_gmatch(s, srest,
+                                                     psub, pnext - 2, isfile)
+                                           && do_gmatch(srest, se,
+                                                        prest, pe, isfile))
+                                               return 1;
+                               }
+                               if (pnext == prest)
+                                       break;
+                       }
+                       return 0;
+
+                 case 0x80|'!': /* matches none of the patterns */
+                       if (!(prest = pat_scan(p, pe, 0)))
+                               return 0;
+                       s--;
+                       for (srest = s; srest <= se; srest++) {
+                               int matched = 0;
+
+                               for (psub = p; ; psub = pnext) {
+                                       pnext = pat_scan(psub, pe, 1);
+                                       if (do_gmatch(s, srest,
+                                                     psub, pnext - 2, isfile))
+                                       {
+                                               matched = 1;
+                                               break;
+                                       }
+                                       if (pnext == prest)
+                                               break;
+                               }
+                               if (!matched && do_gmatch(srest, se,
+                                                         prest, pe, isfile))
+                                       return 1;
+                       }
+                       return 0;
+
+                 default:
+                       if (sc != p[-1])
+                               return 0;
+                       break;
+               }
+       }
+       return s == se;
+}
+
+static const unsigned char *
+cclass(p, sub)
+       const unsigned char *p;
+       register int sub;
+{
+       register int c, d, not, found = 0;
+       const unsigned char *orig_p = p;
+
+       if ((not = (ISMAGIC(*p) && *++p == NOT)))
+               p++;
+       do {
+               c = *p++;
+               if (ISMAGIC(c)) {
+                       c = *p++;
+                       if ((c & 0x80) && !ISMAGIC(c)) {
+                               c &= 0x7f;/* extended pattern matching: *+?@! */
+                               /* XXX the ( char isn't handled as part of [] */
+                               if (c == ' ') /* simile for @: plain (..) */
+                                       c = '(' /*)*/;
+                       }
+               }
+               if (c == '\0')
+                       /* No closing ] - act as if the opening [ was quoted */
+                       return sub == '[' ? orig_p : NULL;
+               if (ISMAGIC(p[0]) && p[1] == '-'
+                   && (!ISMAGIC(p[2]) || p[3] != ']'))
+               {
+                       p += 2; /* MAGIC- */
+                       d = *p++;
+                       if (ISMAGIC(d)) {
+                               d = *p++;
+                               if ((d & 0x80) && !ISMAGIC(d))
+                                       d &= 0x7f;
+                       }
+                       /* POSIX says this is an invalid expression */
+                       if (c > d)
+                               return NULL;
+               } else
+                       d = c;
+               if (c == sub || (c <= sub && sub <= d))
+                       found = 1;
+       } while (!(ISMAGIC(p[0]) && p[1] == ']'));
+
+       return (found != not) ? p+2 : NULL;
+}
+
+/* Look for next ) or | (if match_sep) in *(foo|bar) pattern */
+const unsigned char *
+pat_scan(p, pe, match_sep)
+       const unsigned char *p;
+       const unsigned char *pe;
+       int match_sep;
+{
+       int nest = 0;
+
+       for (; p < pe; p++) {
+               if (!ISMAGIC(*p))
+                       continue;
+               if ((*++p == /*(*/ ')' && nest-- == 0)
+                   || (*p == '|' && match_sep && nest == 0))
+                       return ++p;
+               if ((*p & 0x80) && strchr("*+?@! ", *p & 0x7f))
+                       nest++;
+       }
+       return (const unsigned char *) 0;
+}
+
+
+/* -------- qsort.c -------- */
+
+/*
+ * quick sort of array of generic pointers to objects.
+ */
+static void qsort1 ARGS((void **base, void **lim, int (*f)(void *, void *)));
+
+void
+qsortp(base, n, f)
+       void **base;                            /* base address */
+       size_t n;                               /* elements */
+       int (*f) ARGS((void *, void *));        /* compare function */
+{
+       qsort1(base, base + n, f);
+}
+
+#define        swap2(a, b)     {\
+       register void *t; t = *(a); *(a) = *(b); *(b) = t;\
+}
+#define        swap3(a, b, c)  {\
+       register void *t; t = *(a); *(a) = *(c); *(c) = *(b); *(b) = t;\
+}
+
+static void
+qsort1(base, lim, f)
+       void **base, **lim;
+       int (*f) ARGS((void *, void *));
+{
+       register void **i, **j;
+       register void **lptr, **hptr;
+       size_t n;
+       int c;
+
+  top:
+       n = (lim - base) / 2;
+       if (n == 0)
+               return;
+       hptr = lptr = base+n;
+       i = base;
+       j = lim - 1;
+
+       for (;;) {
+               if (i < lptr) {
+                       if ((c = (*f)(*i, *lptr)) == 0) {
+                               lptr--;
+                               swap2(i, lptr);
+                               continue;
+                       }
+                       if (c < 0) {
+                               i += 1;
+                               continue;
+                       }
+               }
+
+         begin:
+               if (j > hptr) {
+                       if ((c = (*f)(*hptr, *j)) == 0) {
+                               hptr++;
+                               swap2(hptr, j);
+                               goto begin;
+                       }
+                       if (c > 0) {
+                               if (i == lptr) {
+                                       hptr++;
+                                       swap3(i, hptr, j);
+                                       i = lptr += 1;
+                                       goto begin;
+                               }
+                               swap2(i, j);
+                               j -= 1;
+                               i += 1;
+                               continue;
+                       }
+                       j -= 1;
+                       goto begin;
+               }
+
+               if (i == lptr) {
+                       if (lptr-base >= lim-hptr) {
+                               qsort1(hptr+1, lim, f);
+                               lim = lptr;
+                       } else {
+                               qsort1(base, lptr, f);
+                               base = hptr+1;
+                       }
+                       goto top;
+               }
+
+               lptr -= 1;
+               swap3(j, lptr, i);
+               j = hptr -= 1;
+       }
+}
+
+int
+xstrcmp(p1, p2)
+       void *p1, *p2;
+{
+       return (strcmp((char *)p1, (char *)p2));
+}
+
+/* Initialize a Getopt structure */
+void
+ksh_getopt_reset(go, flags)
+       Getopt *go;
+       int flags;
+{
+       go->optind = 1;
+       go->optarg = (char *) 0;
+       go->p = 0;
+       go->flags = flags;
+       go->info = 0;
+       go->buf[1] = '\0';
+}
+
+
+/* getopt() used for shell built-in commands, the getopts command, and
+ * command line options.
+ * A leading ':' in options means don't print errors, instead return '?'
+ * or ':' and set go->optarg to the offending option character.
+ * If GF_ERROR is set (and option doesn't start with :), errors result in
+ * a call to bi_errorf().
+ *
+ * Non-standard features:
+ *     - ';' is like ':' in options, except the argument is optional
+ *       (if it isn't present, optarg is set to 0).
+ *       Used for 'set -o'.
+ *     - ',' is like ':' in options, except the argument always immediately
+ *       follows the option character (optarg is set to the null string if
+ *       the option is missing).
+ *       Used for 'read -u2', 'print -u2' and fc -40.
+ *     - '#' is like ':' in options, expect that the argument is optional
+ *       and must start with a digit.  If the argument doesn't start with a
+ *       digit, it is assumed to be missing and normal option processing
+ *       continues (optarg is set to 0 if the option is missing).
+ *       Used for 'typeset -LZ4'.
+ *     - accepts +c as well as -c IF the GF_PLUSOPT flag is present.  If an
+ *       option starting with + is accepted, the GI_PLUS flag will be set
+ *       in go->info.
+ */
+int
+ksh_getopt(argv, go, options)
+       char **argv;
+       Getopt *go;
+       const char *options;
+{
+       char c;
+       char *o;
+
+       if (go->p == 0 || (c = argv[go->optind - 1][go->p]) == '\0') {
+               char *arg = argv[go->optind], flag = arg ? *arg : '\0';
+
+               go->p = 1;
+               if (flag == '-' && arg[1] == '-' && arg[2] == '\0') {
+                       go->optind++;
+                       go->p = 0;
+                       go->info |= GI_MINUSMINUS;
+                       return EOF;
+               }
+               if (arg == (char *) 0
+                   || ((flag != '-' ) /* neither a - nor a + (if + allowed) */
+                       && (!(go->flags & GF_PLUSOPT) || flag != '+'))
+                   || (c = arg[1]) == '\0')
+               {
+                       go->p = 0;
+                       return EOF;
+               }
+               go->optind++;
+               go->info &= ~(GI_MINUS|GI_PLUS);
+               go->info |= flag == '-' ? GI_MINUS : GI_PLUS;
+       }
+       go->p++;
+       if (c == '?' || c == ':' || c == ';' || c == ',' || c == '#'
+           || !(o = strchr(options, c)))
+       {
+               if (options[0] == ':') {
+                       go->buf[0] = c;
+                       go->optarg = go->buf;
+               } else {
+                       warningf(TRUE, "%s%s-%c: unknown option",
+                               (go->flags & GF_NONAME) ? "" : argv[0],
+                               (go->flags & GF_NONAME) ? "" : ": ", c);
+                       if (go->flags & GF_ERROR)
+                               bi_errorf("%s", null);
+               }
+               return '?';
+       }
+       /* : means argument must be present, may be part of option argument
+        *   or the next argument
+        * ; same as : but argument may be missing
+        * , means argument is part of option argument, and may be null.
+        */
+       if (*++o == ':' || *o == ';') {
+               if (argv[go->optind - 1][go->p])
+                       go->optarg = argv[go->optind - 1] + go->p;
+               else if (argv[go->optind])
+                       go->optarg = argv[go->optind++];
+               else if (*o == ';')
+                       go->optarg = (char *) 0;
+               else {
+                       if (options[0] == ':') {
+                               go->buf[0] = c;
+                               go->optarg = go->buf;
+                               return ':';
+                       }
+                       warningf(TRUE, "%s%s-`%c' requires argument",
+                               (go->flags & GF_NONAME) ? "" : argv[0],
+                               (go->flags & GF_NONAME) ? "" : ": ", c);
+                       if (go->flags & GF_ERROR)
+                               bi_errorf("%s", null);
+                       return '?';
+               }
+               go->p = 0;
+       } else if (*o == ',') {
+               /* argument is attached to option character, even if null */
+               go->optarg = argv[go->optind - 1] + go->p;
+               go->p = 0;
+       } else if (*o == '#') {
+               /* argument is optional and may be attached or unattached
+                * but must start with a digit.  optarg is set to 0 if the
+                * argument is missing.
+                */
+               if (argv[go->optind - 1][go->p]) {
+                       if (digit(argv[go->optind - 1][go->p])) {
+                               go->optarg = argv[go->optind - 1] + go->p;
+                               go->p = 0;
+                       } else
+                               go->optarg = (char *) 0;
+               } else {
+                       if (argv[go->optind] && digit(argv[go->optind][0])) {
+                               go->optarg = argv[go->optind++];
+                               go->p = 0;
+                       } else
+                               go->optarg = (char *) 0;
+               }
+       }
+       return c;
+}
+
+/* print variable/alias value using necessary quotes
+ * (POSIX says they should be suitable for re-entry...)
+ * No trailing newline is printed.
+ */
+void
+print_value_quoted(s)
+       const char *s;
+{
+       const char *p;
+       int inquote = 0;
+
+       /* Test if any quotes are needed */
+       for (p = s; *p; p++)
+               if (ctype(*p, C_QUOTE))
+                       break;
+       if (!*p) {
+               shprintf("%s", s);
+               return;
+       }
+       for (p = s; *p; p++) {
+               if (*p == '\'') {
+                       shprintf("%s", "'\\'" + 1 - inquote);
+                       inquote = 0;
+               } else {
+                       if (!inquote) {
+                               shprintf("'");
+                               inquote = 1;
+                       }
+                       shf_putc(*p, shl_stdout);
+               }
+       }
+       if (inquote)
+               shprintf("'");
+}
+
+/* Print things in columns and rows - func() is called to format the ith
+ * element
+ */
+void
+print_columns(shf, n, func, arg, max_width, prefcol)
+       struct shf *shf;
+       int n;
+       char *(*func) ARGS((void *, int, char *, int));
+       void *arg;
+       int max_width;
+       int prefcol;
+{
+       char *str = (char *) alloc(max_width + 1, ATEMP);
+       int i;
+       int r, c;
+       int rows, cols;
+       int nspace;
+
+       /* max_width + 1 for the space.  Note that no space
+        * is printed after the last column to avoid problems
+        * with terminals that have auto-wrap.
+        */
+       cols = x_cols / (max_width + 1);
+       if (!cols)
+               cols = 1;
+       rows = (n + cols - 1) / cols;
+       if (prefcol && n && cols > rows) {
+               int tmp = rows;
+
+               rows = cols;
+               cols = tmp;
+               if (rows > n)
+                       rows = n;
+       }
+
+       nspace = (x_cols - max_width * cols) / cols;
+       if (nspace <= 0)
+               nspace = 1;
+       for (r = 0; r < rows; r++) {
+               for (c = 0; c < cols; c++) {
+                       i = c * rows + r;
+                       if (i < n) {
+                               shf_fprintf(shf, "%-*s",
+                                       max_width,
+                                       (*func)(arg, i, str, max_width + 1));
+                               if (c + 1 < cols)
+                                       shf_fprintf(shf, "%*s", nspace, null);
+                       }
+               }
+               shf_putchar('\n', shf);
+       }
+       afree(str, ATEMP);
+}
+
+/* Strip any nul bytes from buf - returns new length (nbytes - # of nuls) */
+int
+strip_nuls(buf, nbytes)
+       char *buf;
+       int nbytes;
+{
+       char *dst;
+
+       /* nbytes check because some systems (older freebsd's) have a buggy
+        * memchr()
+        */
+       if (nbytes && (dst = memchr(buf, '\0', nbytes))) {
+               char *end = buf + nbytes;
+               char *p, *q;
+
+               for (p = dst; p < end; p = q) {
+                       /* skip a block of nulls */
+                       while (++p < end && *p == '\0')
+                               ;
+                       /* find end of non-null block */
+                       if (!(q = memchr(p, '\0', end - p)))
+                               q = end;
+                       memmove(dst, p, q - p);
+                       dst += q - p;
+               }
+               *dst = '\0';
+               return dst - buf;
+       }
+       return nbytes;
+}
+
+/* Copy at most dsize-1 bytes from src to dst, ensuring dst is null terminated.
+ * Returns dst.
+ */
+char *
+str_zcpy(dst, src, dsize)
+       char *dst;
+       const char *src;
+       int dsize;
+{
+       if (dsize > 0) {
+               int len = strlen(src);
+
+               if (len >= dsize)
+                       len = dsize - 1;
+               memcpy(dst, src, len);
+               dst[len] = '\0';
+       }
+       return dst;
+}
+
+/* Like read(2), but if read fails due to non-blocking flag, resets flag
+ * and restarts read.
+ */
+int
+blocking_read(fd, buf, nbytes)
+       int fd;
+       char *buf;
+       int nbytes;
+{
+       int ret;
+       int tried_reset = 0;
+
+       while ((ret = read(fd, buf, nbytes)) < 0) {
+               if (!tried_reset && (errno == EAGAIN
+#ifdef EWOULDBLOCK
+                                    || errno == EWOULDBLOCK
+#endif /* EWOULDBLOCK */
+                                   ))
+               {
+                       int oerrno = errno;
+                       if (reset_nonblock(fd) > 0) {
+                               tried_reset = 1;
+                               continue;
+                       }
+                       errno = oerrno;
+               }
+               break;
+       }
+       return ret;
+}
+
+/* Reset the non-blocking flag on the specified file descriptor.
+ * Returns -1 if there was an error, 0 if non-blocking wasn't set,
+ * 1 if it was.
+ */
+int
+reset_nonblock(fd)
+       int fd;
+{
+       int flags;
+       int blocking_flags;
+
+       if ((flags = fcntl(fd, F_GETFL, 0)) < 0)
+               return -1;
+       /* With luck, the C compiler will reduce this to a constant */
+       blocking_flags = 0;
+#ifdef O_NONBLOCK
+       blocking_flags |= O_NONBLOCK;
+#endif /* O_NONBLOCK */
+#ifdef O_NDELAY
+       blocking_flags |= O_NDELAY;
+#else /* O_NDELAY */
+# ifndef O_NONBLOCK
+       blocking_flags |= FNDELAY; /* hope this exists... */
+# endif /* O_NONBLOCK */
+#endif /* O_NDELAY */
+       if (!(flags & blocking_flags))
+               return 0;
+       flags &= ~blocking_flags;
+       if (fcntl(fd, F_SETFL, flags) < 0)
+               return -1;
+       return 1;
+}
+
+
+#ifdef HAVE_SYS_PARAM_H
+# include <sys/param.h>
+#endif /* HAVE_SYS_PARAM_H */
+#ifndef MAXPATHLEN
+# define MAXPATHLEN PATH
+#endif /* MAXPATHLEN */
+
+#ifdef HPUX_GETWD_BUG
+# include "ksh_dir.h"
+
+/*
+ * Work around bug in hpux 10.x C library - getwd/getcwd dump core
+ * if current directory is not readable.  Done in macro 'cause code
+ * is needed in GETWD and GETCWD cases.
+ */
+# define HPUX_GETWD_BUG_CODE \
+       { \
+           DIR *d = ksh_opendir("."); \
+           if (!d) \
+               return (char *) 0; \
+           closedir(d); \
+       }
+#else /* HPUX_GETWD_BUG */
+# define HPUX_GETWD_BUG_CODE
+#endif /* HPUX_GETWD_BUG */
+
+/* Like getcwd(), except bsize is ignored if buf is 0 (MAXPATHLEN is used) */
+char *
+ksh_get_wd(buf, bsize)
+       char *buf;
+       int bsize;
+{
+#ifdef HAVE_GETCWD
+       char *b;
+       char *ret;
+
+       /* Before memory allocated */
+       HPUX_GETWD_BUG_CODE
+
+       /* Assume getcwd() available */
+       if (!buf) {
+               bsize = MAXPATHLEN;
+               b = alloc(MAXPATHLEN + 1, ATEMP);
+       } else
+               b = buf;
+
+       ret = getcwd(b, bsize);
+
+       if (!buf) {
+               if (ret)
+                       ret = aresize(b, strlen(b) + 1, ATEMP);
+               else
+                       afree(b, ATEMP);
+       }
+
+       return ret;
+#else /* HAVE_GETCWD */
+       extern char *getwd ARGS((char *));
+       char *b;
+       int len;
+
+       /* Before memory allocated */
+       HPUX_GETWD_BUG_CODE
+
+       if (buf && bsize > MAXPATHLEN)
+               b = buf;
+       else
+               b = alloc(MAXPATHLEN + 1, ATEMP);
+       if (!getcwd(b, MAXPATHLEN)) {
+               errno = EACCES;
+               if (b != buf)
+                       afree(b, ATEMP);
+               return (char *) 0;
+       }
+       len = strlen(b) + 1;
+       if (!buf)
+               b = aresize(b, len, ATEMP);
+       else if (buf != b) {
+               if (len > bsize) {
+                       errno = ERANGE;
+                       return (char *) 0;
+               }
+               memcpy(buf, b, len);
+               afree(b, ATEMP);
+               b = buf;
+       }
+
+       return b;
+#endif /* HAVE_GETCWD */
+}
diff --git a/bin/ksh/mkman b/bin/ksh/mkman
new file mode 100755 (executable)
index 0000000..1959281
--- /dev/null
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+: ${AWK:=awk}
+
+verbose=no
+
+if [ X"$1" = X-v ] ; then
+    verbose=yes
+    shift
+fi
+if [ $# != 2 ] ; then
+    echo "usage: $0 [-v] which-shell ksh.Man-file" 1>&2
+    exit 1;
+fi
+shell=$1
+man=$2
+
+case $shell in
+ sh) which=0;;
+ ksh) which=1;;
+ *)
+    echo "$0: bad shell option (must be sh or ksh)" 1>&2
+    exit 1
+    ;;
+esac
+if [ ! -r "$man" ] ; then
+    echo "$0: can't read $man file" 1>&2
+    exit 1;
+fi
+
+
+#
+# Now generate the appropriate man page...
+#
+[ $verbose = yes ] && echo "$0: Generating $which man page (0=sh,1=ksh)..." 1>&2
+
+${AWK} 'BEGIN { ksh = '$which'; pr = 1 }
+    /^\.sh\(/ { pr = ksh - 1; next }
+    /^\.sh\)/ { pr = 1; next }
+    /^\.ksh\(/ { pr = ksh; next }
+    /^\.ksh\)/ { pr = 1; next }
+    { if (pr) print $0 } ' < $man
+
+[ $verbose = yes ] && echo "$0: All done" 1>&2
+
+exit 0
diff --git a/bin/ksh/path.c b/bin/ksh/path.c
new file mode 100644 (file)
index 0000000..31e59b4
--- /dev/null
@@ -0,0 +1,318 @@
+/*     $NetBSD: path.c,v 1.8 2009/04/25 05:11:37 lukem Exp $   */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: path.c,v 1.8 2009/04/25 05:11:37 lukem Exp $");
+#endif
+
+
+#include "sh.h"
+#include "ksh_stat.h"
+
+/*
+ *     Contains a routine to search a : separated list of
+ *     paths (a la CDPATH) and make appropriate file names.
+ *     Also contains a routine to simplify .'s and ..'s out of
+ *     a path name.
+ *
+ *     Larry Bouzane (larry@cs.mun.ca)
+ */
+
+#ifdef S_ISLNK
+static char    *do_phys_path ARGS((XString *, char *, const char *));
+#endif /* S_ISLNK */
+
+/*
+ *     Makes a filename into result using the following algorithm.
+ *     - make result NULL
+ *     - if file starts with '/', append file to result & set cdpathp to NULL
+ *     - if file starts with ./ or ../ append cwd and file to result
+ *       and set cdpathp to NULL
+ *     - if the first element of cdpathp doesnt start with a '/' xx or '.' xx
+ *       then cwd is appended to result.
+ *     - the first element of cdpathp is appended to result
+ *     - file is appended to result
+ *     - cdpathp is set to the start of the next element in cdpathp (or NULL
+ *       if there are no more elements.
+ *     The return value indicates whether a non-null element from cdpathp
+ *     was appended to result.
+ */
+int
+make_path(cwd, file, cdpathp, xsp, phys_pathp)
+       const char *cwd;
+       const char *file;
+       char    **cdpathp;      /* & of : separated list */
+       XString *xsp;
+       int     *phys_pathp;
+{
+       int     rval = 0;
+       int     use_cdpath = 1;
+       char    *plist;
+       int     len;
+       int     plen = 0;
+       char    *xp = Xstring(*xsp, xp);
+
+       if (!file)
+               file = null;
+
+       if (!ISRELPATH(file)) {
+               *phys_pathp = 0;
+               use_cdpath = 0;
+       } else {
+               if (file[0] == '.') {
+                       char c = file[1];
+
+                       if (c == '.')
+                               c = file[2];
+                       if (ISDIRSEP(c) || c == '\0')
+                               use_cdpath = 0;
+               }
+
+               plist = *cdpathp;
+               if (!plist)
+                       use_cdpath = 0;
+               else if (use_cdpath) {
+                       char *pend;
+
+                       for (pend = plist; *pend && *pend != PATHSEP; pend++)
+                               ;
+                       plen = pend - plist;
+                       *cdpathp = *pend ? ++pend : (char *) 0;
+               }
+
+               if ((use_cdpath == 0 || !plen || ISRELPATH(plist))
+                   && (cwd && *cwd))
+               {
+                       len = strlen(cwd);
+                       XcheckN(*xsp, xp, len);
+                       memcpy(xp, cwd, len);
+                       xp += len;
+                       if (!ISDIRSEP(cwd[len - 1]))
+                               Xput(*xsp, xp, DIRSEP);
+               }
+               *phys_pathp = Xlength(*xsp, xp);
+               if (use_cdpath && plen) {
+                       XcheckN(*xsp, xp, plen);
+                       memcpy(xp, plist, plen);
+                       xp += plen;
+                       if (!ISDIRSEP(plist[plen - 1]))
+                               Xput(*xsp, xp, DIRSEP);
+                       rval = 1;
+               }
+       }
+
+       len = strlen(file) + 1;
+       XcheckN(*xsp, xp, len);
+       memcpy(xp, file, len);
+
+       if (!use_cdpath)
+               *cdpathp = (char *) 0;
+
+       return rval;
+}
+
+/*
+ * Simplify pathnames containing "." and ".." entries.
+ * ie, simplify_path("/a/b/c/./../d/..") returns "/a/b"
+ */
+void
+simplify_path(pathx)
+       char    *pathx;
+{
+       char    *cur;
+       char    *t;
+       int     isrooted;
+       char    *very_start = pathx;
+       char    *start;
+
+       if (!*pathx)
+               return;
+
+       if ((isrooted = ISROOTEDPATH(pathx)))
+               very_start++;
+#if defined (OS2) || defined (__CYGWIN__)
+       if (pathx[0] && pathx[1] == ':')        /* skip a: */
+               very_start += 2;
+#endif /* OS2 || __CYGWIN__ */
+
+       /* Before                       After
+        *  /foo/                       /foo
+        *  /foo/../../bar              /bar
+        *  /foo/./blah/..              /foo
+        *  .                           .
+        *  ..                          ..
+        *  ./foo                       foo
+        *  foo/../../../bar            ../../bar
+        * OS2 and CYGWIN:
+        *  a:/foo/../..                a:/
+        *  a:.                         a:
+        *  a:..                        a:..
+        *  a:foo/../../blah            a:../blah
+        */
+
+#ifdef __CYGWIN__
+       /* preserve leading double-slash on pathnames (for UNC paths) */
+       if (pathx[0] && ISDIRSEP(pathx[0]) && pathx[1] && ISDIRSEP(pathx[1]))
+               very_start++;
+#endif /* __CYGWIN__ */
+
+       for (cur = t = start = very_start; ; ) {
+               /* treat multiple '/'s as one '/' */
+               while (ISDIRSEP(*t))
+                       t++;
+
+               if (*t == '\0') {
+                       if (cur == pathx)
+                               /* convert empty path to dot */
+                               *cur++ = '.';
+                       *cur = '\0';
+                       break;
+               }
+
+               if (t[0] == '.') {
+                       if (!t[1] || ISDIRSEP(t[1])) {
+                               t += 1;
+                               continue;
+                       } else if (t[1] == '.' && (!t[2] || ISDIRSEP(t[2]))) {
+                               if (!isrooted && cur == start) {
+                                       if (cur != very_start)
+                                               *cur++ = DIRSEP;
+                                       *cur++ = '.';
+                                       *cur++ = '.';
+                                       start = cur;
+                               } else if (cur != start)
+                                       while (--cur > start && !ISDIRSEP(*cur))
+                                               ;
+                               t += 2;
+                               continue;
+                       }
+               }
+
+               if (cur != very_start)
+                       *cur++ = DIRSEP;
+
+               /* find/copy next component of pathname */
+               while (*t && !ISDIRSEP(*t))
+                       *cur++ = *t++;
+       }
+}
+
+
+void
+set_current_wd(pathx)
+       char *pathx;
+{
+       int len;
+       char *p = pathx;
+
+       if (!p && !(p = ksh_get_wd((char *) 0, 0)))
+               p = null;
+
+       len = strlen(p) + 1;
+
+       if (len > current_wd_size)
+               current_wd = aresize(current_wd, current_wd_size = len, APERM);
+       memcpy(current_wd, p, len);
+       if (p != pathx && p != null)
+               afree(p, ATEMP);
+}
+
+#ifdef S_ISLNK
+char *
+get_phys_path(pathx)
+       const char *pathx;
+{
+       XString xs;
+       char *xp;
+
+       Xinit(xs, xp, strlen(pathx) + 1, ATEMP);
+
+       xp = do_phys_path(&xs, xp, pathx);
+
+       if (!xp)
+               return (char *) 0;
+
+       if (Xlength(xs, xp) == 0)
+               Xput(xs, xp, DIRSEP);
+       Xput(xs, xp, '\0');
+
+       return Xclose(xs, xp);
+}
+
+static char *
+do_phys_path(xsp, xp, pathx)
+       XString *xsp;
+       char *xp;
+       const char *pathx;
+{
+       const char *p, *q;
+       int len, llen;
+       int savepos;
+       char lbuf[PATH];
+
+       Xcheck(*xsp, xp);
+       for (p = pathx; p; p = q) {
+               while (ISDIRSEP(*p))
+                       p++;
+               if (!*p)
+                       break;
+               len = (q = ksh_strchr_dirsep(p)) ? q - p : (int)strlen(p);
+               if (len == 1 && p[0] == '.')
+                       continue;
+               if (len == 2 && p[0] == '.' && p[1] == '.') {
+                       while (xp > Xstring(*xsp, xp)) {
+                               xp--;
+                               if (ISDIRSEP(*xp))
+                                       break;
+                       }
+                       continue;
+               }
+
+               savepos = Xsavepos(*xsp, xp);
+               Xput(*xsp, xp, DIRSEP);
+               XcheckN(*xsp, xp, len + 1);
+               memcpy(xp, p, len);
+               xp += len;
+               *xp = '\0';
+
+               llen = readlink(Xstring(*xsp, xp), lbuf, sizeof(lbuf) - 1);
+               if (llen < 0) {
+                       /* EINVAL means it wasn't a symlink... */
+                       if (errno != EINVAL)
+                               return (char *) 0;
+                       continue;
+               }
+               lbuf[llen] = '\0';
+
+               /* If absolute path, start from scratch.. */
+               xp = ISABSPATH(lbuf) ? Xstring(*xsp, xp)
+                                    : Xrestpos(*xsp, xp, savepos);
+               if (!(xp = do_phys_path(xsp, xp, lbuf)))
+                       return (char *) 0;
+       }
+       return xp;
+}
+#endif /* S_ISLNK */
+
+#ifdef TEST
+
+main(argc, argv)
+{
+       int     rv;
+       char    *cp, cdpath[256], pwd[256], file[256], result[256];
+
+       printf("enter CDPATH: "); gets(cdpath);
+       printf("enter PWD: "); gets(pwd);
+       while (1) {
+               if (printf("Enter file: "), gets(file) == 0)
+                       return 0;
+               cp = cdpath;
+               do {
+                       rv = make_path(pwd, file, &cp, result, sizeof(result));
+                       printf("make_path returns (%d), \"%s\" ", rv, result);
+                       simplify_path(result);
+                       printf("(simpifies to \"%s\")\n", result);
+               } while (cp);
+       }
+}
+#endif /* TEST */
diff --git a/bin/ksh/proto.h b/bin/ksh/proto.h
new file mode 100644 (file)
index 0000000..3129b7c
--- /dev/null
@@ -0,0 +1,298 @@
+/*     $NetBSD: proto.h,v 1.7 2005/06/26 19:09:00 christos Exp $       */
+
+/*
+ * prototypes for PD-KSH
+ * originally generated using "cproto.c 3.5 92/04/11 19:28:01 cthuang "
+ * $Id: proto.h,v 1.7 2005/06/26 19:09:00 christos Exp $
+ */
+
+/* alloc.c */
+Area * ainit           ARGS((Area *));
+void   afreeall        ARGS((Area *));
+void * alloc           ARGS((size_t, Area *));
+void * aresize         ARGS((void *, size_t, Area *));
+void   afree           ARGS((void *, Area *));
+/* c_ksh.c */
+int    c_hash          ARGS((char **));
+int    c_cd            ARGS((char **));
+int    c_pwd           ARGS((char **));
+int    c_print         ARGS((char **));
+int    c_whence        ARGS((char **));
+int    c_command       ARGS((char **));
+int    c_typeset       ARGS((char **));
+int    c_alias         ARGS((char **));
+int    c_unalias       ARGS((char **));
+int    c_let           ARGS((char **));
+int    c_jobs          ARGS((char **));
+int    c_fgbg          ARGS((char **));
+int    c_kill          ARGS((char **));
+void   getopts_reset   ARGS((int));
+int    c_getopts       ARGS((char **));
+int    c_bind          ARGS((char **));
+/* c_sh.c */
+int    c_label         ARGS((char **));
+int    c_shift         ARGS((char **));
+int    c_umask         ARGS((char **));
+int    c_dot           ARGS((char **));
+int    c_wait          ARGS((char **));
+int    c_read          ARGS((char **));
+int    c_eval          ARGS((char **));
+int    c_trap          ARGS((char **));
+int    c_brkcont       ARGS((char **));
+int    c_exitreturn    ARGS((char **));
+int    c_set           ARGS((char **));
+int    c_unset         ARGS((char **));
+int    c_ulimit        ARGS((char **));
+int    c_times         ARGS((char **));
+int    timex           ARGS((struct op *, int));
+void   timex_hook      ARGS((struct op *, char ** volatile *));
+int    c_exec          ARGS((char **));
+int    c_builtin       ARGS((char **));
+/* c_test.c */
+int    c_test          ARGS((char **));
+/* edit.c: most prototypes in edit.h */
+void   x_init          ARGS((void));
+int    x_read          ARGS((char *, size_t));
+void   set_editmode    ARGS((const char *));
+/* emacs.c: most prototypes in edit.h */
+int    x_bind          ARGS((const char *, const char *, int, int));
+/* eval.c */
+char * substitute      ARGS((const char *, int));
+char **        eval            ARGS((char **, int));
+char * evalstr         ARGS((char *, int));
+char * evalonestr      ARGS((char *, int));
+char   *debunk         ARGS((char *, const char *, size_t));
+void   expand          ARGS((char *, XPtrV *, int));
+int glob_str           ARGS((char *, XPtrV *, int));
+/* exec.c */
+int    fd_clexec       ARGS((int));
+int    execute         ARGS((struct op * volatile, volatile int));
+int    shcomexec       ARGS((char **));
+struct tbl * findfunc  ARGS((const char *, unsigned int, int));
+int    define          ARGS((const char *, struct op *));
+void   builtin         ARGS((const char *, int (*)(char **)));
+struct tbl *   findcom ARGS((const char *, int));
+void   flushcom        ARGS((int all));
+char * search          ARGS((const char *, const char *, int, int *));
+int    search_access   ARGS((const char *, int, int *));
+int    pr_menu         ARGS((char *const *));
+int    pr_list         ARGS((char *const *));
+/* expr.c */
+int    evaluate        ARGS((const char *, long *, int));
+int    v_evaluate      ARGS((struct tbl *, const char *, volatile int));
+/* history.c */
+void   init_histvec    ARGS((void));
+void   hist_init       ARGS((Source *));
+void   hist_finish     ARGS((void));
+void   histsave        ARGS((int, const char *, int));
+#ifdef HISTORY
+int    c_fc            ARGS((char **));
+void   sethistsize     ARGS((int));
+void   sethistfile     ARGS((const char *));
+# ifdef EASY_HISTORY
+void   histappend      ARGS((const char *, int));
+# endif
+char **        histpos         ARGS((void));
+int    histN           ARGS((void));
+int    histnum         ARGS((int));
+int    findhist        ARGS((int, int, const char *, int));
+#endif /* HISTORY */
+/* io.c */
+void   errorf          ARGS((const char *, ...))
+                               GCC_FUNC_ATTR2(noreturn, format(printf, 1, 2));
+void   warningf        ARGS((int, const char *, ...))
+                               GCC_FUNC_ATTR(format(printf, 2, 3));
+void   bi_errorf       ARGS((const char *, ...))
+                               GCC_FUNC_ATTR(format(printf, 1, 2));
+void   internal_errorf ARGS((int, const char *, ...))
+                               GCC_FUNC_ATTR(format(printf, 2, 3));
+void   error_prefix    ARGS((int));
+void   shellf          ARGS((const char *, ...))
+                               GCC_FUNC_ATTR(format(printf, 1, 2));
+void   shprintf        ARGS((const char *, ...))
+                               GCC_FUNC_ATTR(format(printf, 1, 2));
+#ifdef KSH_DEBUG
+void   kshdebug_init_  ARGS((void));
+void   kshdebug_printf_ ARGS((const char *, ...))
+                               GCC_FUNC_ATTR(format(printf, 1, 2));
+void   kshdebug_dump_  ARGS((const char *, const void *, int));
+#endif /* KSH_DEBUG */
+int    can_seek        ARGS((int));
+void   initio          ARGS((void));
+int    ksh_dup2        ARGS((int, int, int));
+int    savefd          ARGS((int, int));
+void   restfd          ARGS((int, int));
+void   openpipe        ARGS((int *));
+void   closepipe       ARGS((int *));
+int    check_fd        ARGS((char *, int, const char **));
+#ifdef KSH
+void   coproc_init     ARGS((void));
+void   coproc_read_close ARGS((int));
+void   coproc_readw_close ARGS((int));
+void   coproc_write_close ARGS((int));
+int    coproc_getfd    ARGS((int, const char **));
+void   coproc_cleanup  ARGS((int));
+#endif /* KSH */
+struct temp *maketemp  ARGS((Area *, Temp_type, struct temp **));
+/* jobs.c */
+void   j_init          ARGS((int));
+void   j_exit          ARGS((void));
+void   j_change        ARGS((void));
+int    exchild         ARGS((struct op *, int, int));
+void   startlast       ARGS((void));
+int    waitlast        ARGS((void));
+int    waitfor         ARGS((const char *, int *));
+int    j_kill          ARGS((const char *, int));
+int    j_resume        ARGS((const char *, int));
+int    j_jobs          ARGS((const char *, int, int));
+void   j_notify        ARGS((void));
+pid_t  j_async         ARGS((void));
+int    j_stopped_running       ARGS((void));
+/* lex.c */
+int    yylex           ARGS((int));
+void   yyerror         ARGS((const char *, ...))
+                               GCC_FUNC_ATTR2(noreturn, format(printf, 1, 2));
+Source * pushs         ARGS((int, Area *));
+void   set_prompt      ARGS((int, Source *));
+void   pprompt         ARGS((const char *, int));
+/* mail.c */
+#ifdef KSH
+void   mcheck          ARGS((void));
+void   mcset           ARGS((long));
+void   mbset           ARGS((char *));
+void   mpset           ARGS((char *));
+#endif /* KSH */
+/* main.c */
+int    include         ARGS((const char *, int, char **, int));
+int    command         ARGS((const char *));
+int    shell           ARGS((Source *volatile, int volatile));
+void   unwind          ARGS((int)) GCC_FUNC_ATTR(noreturn);
+void   newenv          ARGS((int));
+void   quitenv         ARGS((void));
+void   cleanup_parents_env ARGS((void));
+void   cleanup_proc_env ARGS((void));
+void   aerror          ARGS((Area *, const char *))
+                               GCC_FUNC_ATTR(noreturn);
+/* misc.c */
+void   setctypes       ARGS((const char *, int));
+void   initctypes      ARGS((void));
+char * ulton           ARGS((unsigned long, int));
+char * str_save        ARGS((const char *, Area *));
+char * str_nsave       ARGS((const char *, int, Area *));
+int    option          ARGS((const char *));
+char * getoptions      ARGS((void));
+void   change_flag     ARGS((enum sh_flag, int, int));
+int    parse_args      ARGS((char **v, int what, int *));
+int    getn            ARGS((const char *, int *));
+int    bi_getn         ARGS((const char *, int *));
+int    gmatch          ARGS((const char *, const char *, int));
+int    has_globbing    ARGS((const char *, const char *));
+const unsigned char *pat_scan ARGS((const unsigned char *,
+                               const unsigned char *, int));
+void   qsortp          ARGS((void **, size_t, int (*)(void *, void *)));
+int    xstrcmp         ARGS((void *, void *));
+void   ksh_getopt_reset ARGS((Getopt *, int));
+int    ksh_getopt      ARGS((char **, Getopt *, const char *));
+void   print_value_quoted ARGS((const char *));
+void   print_columns   ARGS((struct shf *, int,
+                             char *(*)(void *, int, char *, int),
+                             void *, int, int));
+int    strip_nuls      ARGS((char *, int));
+char   *str_zcpy       ARGS((char *, const char *, int));
+int    blocking_read   ARGS((int, char *, int));
+int    reset_nonblock  ARGS((int));
+char   *ksh_get_wd     ARGS((char *, int));
+/* path.c */
+int    make_path       ARGS((const char *, const char *,
+                             char **, XString *, int *));
+void   simplify_path   ARGS((char *));
+char   *get_phys_path  ARGS((const char *));
+void   set_current_wd  ARGS((char *));
+/* syn.c */
+void   initkeywords    ARGS((void));
+struct op * compile    ARGS((Source *));
+/* table.c */
+unsigned int   hash    ARGS((const char *));
+void   tinit           ARGS((struct table *, Area *, int));
+struct tbl *   tsearch ARGS((struct table *, const char *, unsigned int));
+struct tbl *   tenter  ARGS((struct table *, const char *, unsigned int));
+void   tdelete         ARGS((struct tbl *));
+void   twalk           ARGS((struct tstate *, struct table *));
+struct tbl *   tnext   ARGS((struct tstate *));
+struct tbl **  tsort   ARGS((struct table *));
+/* trace.c */
+/* trap.c */
+void   inittraps       ARGS((void));
+#ifdef KSH
+void   alarm_init      ARGS((void));
+#endif /* KSH */
+Trap * gettrap         ARGS((const char *, int));
+RETSIGTYPE trapsig     ARGS((int));
+void   intrcheck       ARGS((void));
+int    fatal_trap_check ARGS((void));
+int    trap_pending    ARGS((void));
+void   runtraps        ARGS((int));
+void   runtrap         ARGS((Trap *));
+void   cleartraps      ARGS((void));
+void   restoresigs     ARGS((void));
+void   settrap         ARGS((Trap *, char *));
+int    block_pipe      ARGS((void));
+void   restore_pipe    ARGS((int));
+int    setsig          ARGS((Trap *, handler_t, int));
+void   setexecsig      ARGS((Trap *, int));
+/* tree.c */
+int    fptreef         ARGS((struct shf *, int, const char *, ...));
+char * snptreef        ARGS((char *, int, const char *, ...));
+struct op *    tcopy   ARGS((struct op *, Area *));
+char * wdcopy          ARGS((const char *, Area *));
+char * wdscan          ARGS((const char *, int));
+char * wdstrip         ARGS((const char *));
+void   tfree           ARGS((struct op *, Area *));
+/* var.c */
+void   newblock        ARGS((void));
+void   popblock        ARGS((void));
+void   initvar         ARGS((void));
+struct tbl *   global  ARGS((const char *));
+struct tbl *   local   ARGS((const char *, bool_t));
+char * str_val         ARGS((struct tbl *));
+long   intval          ARGS((struct tbl *));
+int    setstr          ARGS((struct tbl *, const char *, int));
+struct tbl *setint_v   ARGS((struct tbl *, struct tbl *));
+void   setint          ARGS((struct tbl *, long));
+int    getint          ARGS((struct tbl *, long *));
+struct tbl *   typeset ARGS((const char *, Tflag, Tflag, int, int));
+void   unset           ARGS((struct tbl *, int));
+char  * skip_varname   ARGS((const char *, int));
+char   *skip_wdvarname ARGS((const char *, int));
+int    is_wdvarname    ARGS((const char *, int));
+int    is_wdvarassign  ARGS((const char *));
+char **        makenv          ARGS((void));
+void   change_random   ARGS((void));
+int    array_ref_len   ARGS((const char *));
+char * arrayname       ARGS((const char *));
+void    set_array      ARGS((const char *, int, char **));
+/* version.c */
+/* vi.c: see edit.h */
+
+
+/* Hack to avoid billions of compile warnings on SunOS 4.1.x */
+#if defined(MUN) && defined(sun) && !defined(__svr4__)
+extern void bcopy ARGS((const void *, void *, size_t));
+extern intclose ARGS((FILE *));
+extern intprintf ARGS((FILE *, const char *, ...));
+extern intread ARGS((void *, int, int, FILE *));
+extern int ioctl ARGS((int, int, void *));
+extern int killpg ARGS((int, int));
+extern int nice ARGS((int));
+extern int readlink ARGS((const char *, char *, int));
+extern int setpgrp ARGS((int, int));
+extern int strcasecmp ARGS((const char *, const char *));
+extern int tolower ARGS((int));
+extern int toupper ARGS((int));
+/*  Include files aren't included yet */
+extern int getrlimit ARGS(( /* int, struct rlimit * */ ));
+extern int getrusage ARGS(( /* int, struct rusage * */ ));
+extern int gettimeofday ARGS(( /* struct timeval *, struct timezone * */ ));
+extern int setrlimit ARGS(( /* int, struct rlimit * */ ));
+extern int lstat ARGS(( /* const char *, struct stat * */ ));
+#endif
diff --git a/bin/ksh/sh.h b/bin/ksh/sh.h
new file mode 100644 (file)
index 0000000..83c3f70
--- /dev/null
@@ -0,0 +1,735 @@
+/*     $NetBSD: sh.h,v 1.7 2005/06/26 19:09:00 christos Exp $  */
+
+/*
+ * Public Domain Bourne/Korn shell
+ */
+
+/* $Id: sh.h,v 1.7 2005/06/26 19:09:00 christos Exp $ */
+
+#include "config.h"    /* system and option configuration info */
+
+#ifdef HAVE_PROTOTYPES
+# define       ARGS(args)      args    /* prototype declaration */
+#else
+# define       ARGS(args)      ()      /* K&R declaration */
+#endif
+
+/* Start of common headers */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <setjmp.h>
+#ifdef HAVE_STDDEF_H
+# include <stddef.h>
+#endif
+
+#ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+#else
+/* just a useful subset of what stdlib.h would have */
+extern char * getenv  ARGS((const char *));
+extern void * malloc  ARGS((size_t));
+extern void * realloc ARGS((void *, size_t));
+extern int    free    ARGS((void *));
+extern int    exit    ARGS((int));
+extern int    rand    ARGS((void));
+extern void   srand   ARGS((unsigned int));
+extern int    atoi    ARGS((const char *));
+#endif /* HAVE_STDLIB_H */
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#else
+/* just a useful subset of what unistd.h would have */
+extern int access ARGS((const char *, int));
+extern int open ARGS((const char *, int, ...));
+extern int creat ARGS((const char *, mode_t));
+extern int read ARGS((int, char *, unsigned));
+extern int write ARGS((int, const char *, unsigned));
+extern off_t lseek ARGS((int, off_t, int));
+extern int close ARGS((int));
+extern int pipe ARGS((int []));
+extern int dup2 ARGS((int, int));
+extern int unlink ARGS((const char *));
+extern int fork ARGS((void));
+extern int execve ARGS((const char *, char * const[], char * const[]));
+extern int chdir ARGS((const char *));
+extern int kill ARGS((pid_t, int));
+extern char *getcwd(); /* no ARGS here - differs on different machines */
+extern int geteuid ARGS((void));
+extern int readlink ARGS((const char *, char *, int));
+extern int getegid ARGS((void));
+extern int getpid ARGS((void));
+extern int getppid ARGS((void));
+extern unsigned int sleep ARGS((unsigned int));
+extern int isatty ARGS((int));
+# ifdef POSIX_PGRP
+extern int getpgrp ARGS((void));
+extern int setpgid ARGS((pid_t, pid_t));
+# endif /* POSIX_PGRP */
+# ifdef BSD_PGRP
+extern int getpgrp ARGS((pid_t));
+extern int setpgrp ARGS((pid_t, pid_t));
+# endif /* BSD_PGRP */
+# ifdef SYSV_PGRP
+extern int getpgrp ARGS((void));
+extern int setpgrp ARGS((void));
+# endif /* SYSV_PGRP */
+#endif /* HAVE_UNISTD_H */
+
+#ifdef HAVE_STRING_H
+# include <string.h>
+#else
+# include <strings.h>
+# define strchr index
+# define strrchr rindex
+#endif /* HAVE_STRING_H */
+#ifndef HAVE_STRSTR
+char *strstr ARGS((const char *s, const char *p));
+#endif /* HAVE_STRSTR */
+#ifndef HAVE_STRCASECMP
+int strcasecmp ARGS((const char *s1, const char *s2));
+int strncasecmp ARGS((const char *s1, const char *s2, int n));
+#endif /* HAVE_STRCASECMP */
+
+#ifdef HAVE_MEMORY_H
+# include <memory.h>
+#endif
+#ifndef HAVE_MEMSET
+# define memcpy(d, s, n)       bcopy(s, d, n)
+# define memcmp(s1, s2, n)     bcmp(s1, s2, n)
+void *memset ARGS((void *d, int c, size_t n));
+#endif /* HAVE_MEMSET */
+#ifndef HAVE_MEMMOVE
+# ifdef HAVE_BCOPY
+#  define memmove(d, s, n)     bcopy(s, d, n)
+# else
+void *memmove ARGS((void *d, const void *s, size_t n));
+# endif
+#endif /* HAVE_MEMMOVE */
+
+#ifdef HAVE_PROTOTYPES
+# include <stdarg.h>
+# define SH_VA_START(va, argn) va_start(va, argn)
+#else
+# include <varargs.h>
+# define SH_VA_START(va, argn) va_start(va)
+#endif /* HAVE_PROTOTYPES */
+
+#include <errno.h>
+
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#else
+# include <sys/file.h>
+#endif /* HAVE_FCNTL_H */
+#ifndef O_ACCMODE
+# define O_ACCMODE     (O_RDONLY|O_WRONLY|O_RDWR)
+#endif /* !O_ACCMODE */
+
+#ifndef F_OK   /* access() arguments */
+# define F_OK 0
+# define X_OK 1
+# define W_OK 2
+# define R_OK 4
+#endif /* !F_OK */
+
+#ifndef SEEK_SET
+# ifdef L_SET
+#  define SEEK_SET L_SET
+#  define SEEK_CUR L_INCR
+#  define SEEK_END L_XTND
+# else /* L_SET */
+#  define SEEK_SET 0
+#  define SEEK_CUR 1
+#  define SEEK_END 2
+# endif /* L_SET */
+#endif /* !SEEK_SET */
+
+/* Some machines (eg, FreeBSD 1.1.5) define CLK_TCK in limits.h
+ * (ksh_limval.h assumes limits has been included, if available)
+ */
+#ifdef HAVE_LIMITS_H
+# include <limits.h>
+#endif /* HAVE_LIMITS_H */
+
+#include <signal.h>
+#ifdef NSIG
+# define SIGNALS       NSIG
+#else
+# ifdef        _MINIX
+#  define SIGNALS      (_NSIG+1) /* _NSIG is # of signals used, excluding 0. */
+# else
+#  ifdef _SIGMAX       /* QNX */
+#   define SIGNALS     _SIGMAX
+#  else /* _SIGMAX */
+#   define SIGNALS     32
+#  endif /* _SIGMAX */
+# endif        /* _MINIX */
+#endif /* NSIG */
+#ifndef SIGCHLD
+# define SIGCHLD SIGCLD
+#endif
+/* struct sigaction.sa_flags is set to KSH_SA_FLAGS.  Used to ensure
+ * system calls are interrupted
+ */
+#ifdef SA_INTERRUPT
+# define KSH_SA_FLAGS  SA_INTERRUPT
+#else /* SA_INTERRUPT */
+# define KSH_SA_FLAGS  0
+#endif /* SA_INTERRUPT */
+
+typedef        RETSIGTYPE (*handler_t) ARGS((int));    /* signal handler */
+
+#ifdef USE_FAKE_SIGACT
+# include "sigact.h"                   /* use sjg's fake sigaction() */
+#endif
+
+#ifdef HAVE_PATHS_H
+# include <paths.h>
+#endif /* HAVE_PATHS_H */
+#ifdef _PATH_DEFPATH
+# define DEFAULT__PATH _PATH_DEFPATH
+#else /* _PATH_DEFPATH */
+# define DEFAULT__PATH DEFAULT_PATH
+#endif /* _PATH_DEFPATH */
+
+#ifndef offsetof
+# define offsetof(type,id) ((size_t)&((type*)NULL)->id)
+#endif
+
+#ifndef HAVE_KILLPG
+# define killpg(p, s)  kill(-(p), (s))
+#endif /* !HAVE_KILLPG */
+
+/* Special cases for execve(2) */
+#ifdef OS2
+extern int ksh_execve(char *cmd, char **args, char **env, int flags);
+#else /* OS2 */
+# if defined(OS_ISC) && defined(_POSIX_SOURCE)
+/* Kludge for ISC 3.2 (and other versions?) so programs will run correctly.  */
+#  define ksh_execve(p, av, ev, flags) \
+                               do { \
+                                       __setostype(0); \
+                                       execve(p, av, ev); \
+                                       __setostype(1); \
+                               } while (0)
+# else /* OS_ISC && _POSIX */
+#  define ksh_execve(p, av, ev, flags) execve(p, av, ev)
+# endif /* OS_ISC && _POSIX */
+#endif /* OS2 */
+
+/* this is a hang-over from older versions of the os2 port */
+#define ksh_dupbase(fd, base) fcntl(fd, F_DUPFD, base)
+
+#ifdef HAVE_SIGSETJMP
+# define ksh_sigsetjmp(env,sm) sigsetjmp((env), (sm))
+# define ksh_siglongjmp(env,v) siglongjmp((env), (v))
+# define ksh_jmp_buf           sigjmp_buf
+#else /* HAVE_SIGSETJMP */
+# ifdef HAVE__SETJMP
+#  define ksh_sigsetjmp(env,sm)        _setjmp(env)
+#  define ksh_siglongjmp(env,v)        _longjmp((env), (v))
+# else /* HAVE__SETJMP */
+#  define ksh_sigsetjmp(env,sm)        setjmp(env)
+#  define ksh_siglongjmp(env,v)        longjmp((env), (v))
+# endif /* HAVE__SETJMP */
+# define ksh_jmp_buf           jmp_buf
+#endif /* HAVE_SIGSETJMP */
+
+#ifndef HAVE_DUP2
+extern int dup2 ARGS((int, int));
+#endif /* !HAVE_DUP2 */
+
+/* Find a integer type that is at least 32 bits (or die) - SIZEOF_* defined
+ * by autoconf (assumes an 8 bit byte, but I'm not concerned).
+ * NOTE: INT32 may end up being more than 32 bits.
+ */
+#if SIZEOF_INT >= 4
+# define INT32 int
+#else /* SIZEOF_INT */
+# if SIZEOF_LONG >= 4
+#  define INT32        long
+# else /* SIZEOF_LONG */
+   #error cannot find 32 bit type...
+# endif /* SIZEOF_LONG */
+#endif /* SIZEOF_INT */
+
+/* end of common headers */
+
+/* Stop gcc and lint from complaining about possibly uninitialized variables */
+#if defined(__GNUC__) || defined(lint)
+# define UNINITIALIZED(var)    var = 0
+#else
+# define UNINITIALIZED(var)    var
+#endif /* GNUC || lint */
+
+/* some useful #defines */
+#ifdef EXTERN
+# define I__(i) = i
+#else
+# define I__(i)
+# define EXTERN extern
+# define EXTERN_DEFINED
+#endif
+
+#ifdef OS2
+# define inDOS() (!(_emx_env & 0x200))
+#endif
+
+#ifndef EXECSHELL
+/* shell to exec scripts (see also $SHELL initialization in main.c) */
+# ifdef OS2
+#  define EXECSHELL    (inDOS() ? "c:\\command.com" : "c:\\os2\\cmd.exe")
+#  define EXECSHELL_STR        (inDOS() ? "COMSPEC" : "OS2_SHELL")
+# else /* OS2 */
+#  define EXECSHELL    "/bin/sh"
+#  define EXECSHELL_STR        "EXECSHELL"
+# endif /* OS2 */
+#endif
+
+/* ISABSPATH() means path is fully and completely specified,
+ * ISROOTEDPATH() means a .. as the first component is a no-op,
+ * ISRELPATH() means $PWD can be tacked on to get an absolute path.
+ *
+ * OS          Path            ISABSPATH       ISROOTEDPATH    ISRELPATH
+ * unix                /foo            yes             yes             no
+ * unix                foo             no              no              yes
+ * unix                ../foo          no              no              yes
+ * os2+cyg     a:/foo          yes             yes             no
+ * os2+cyg     a:foo           no              no              no
+ * os2+cyg     /foo            no              yes             no
+ * os2+cyg     foo             no              no              yes
+ * os2+cyg     ../foo          no              no              yes
+ * cyg                 //foo           yes             yes             no
+ */
+#ifdef OS2
+# define PATHSEP        ';'
+# define DIRSEP         '/'    /* even though \ is native */
+# define DIRSEPSTR      "\\"
+# define ISDIRSEP(c)    ((c) == '\\' || (c) == '/')
+# define ISABSPATH(s)  (((s)[0] && (s)[1] == ':' && ISDIRSEP((s)[2])))
+# define ISROOTEDPATH(s) (ISDIRSEP((s)[0]) || ISABSPATH(s))
+# define ISRELPATH(s)  (!(s)[0] || ((s)[1] != ':' && !ISDIRSEP((s)[0])))
+# define FILECHCONV(c) (isascii(c) && isupper(c) ? tolower(c) : c)
+# define FILECMP(s1, s2) stricmp(s1, s2)
+# define FILENCMP(s1, s2, n) strnicmp(s1, s2, n)
+extern char *ksh_strchr_dirsep(const char *path);
+extern char *ksh_strrchr_dirsep(const char *path);
+# define chdir          _chdir2
+# define getcwd         _getcwd2
+#else
+# define PATHSEP        ':'
+# define DIRSEP         '/'
+# define DIRSEPSTR      "/"
+# define ISDIRSEP(c)    ((c) == '/')
+#ifdef __CYGWIN__
+#  define ISABSPATH(s) \
+       (((s)[0] && (s)[1] == ':' && ISDIRSEP((s)[2])) || ISDIRSEP((s)[0]))
+#  define ISRELPATH(s) (!(s)[0] || ((s)[1] != ':' && !ISDIRSEP((s)[0])))
+#else /* __CYGWIN__ */
+# define ISABSPATH(s)  ISDIRSEP((s)[0])
+# define ISRELPATH(s)  (!ISABSPATH(s))
+#endif /* __CYGWIN__ */
+# define ISROOTEDPATH(s) ISABSPATH(s)
+# define FILECHCONV(c) c
+# define FILECMP(s1, s2) strcmp(s1, s2)
+# define FILENCMP(s1, s2, n) strncmp(s1, s2, n)
+# define ksh_strchr_dirsep(p)   strchr(p, DIRSEP)
+# define ksh_strrchr_dirsep(p)  strrchr(p, DIRSEP)
+#endif
+
+typedef int bool_t;
+#define        FALSE   0
+#define        TRUE    1
+
+#define        NELEM(a) (sizeof(a) / sizeof((a)[0]))
+#define        sizeofN(type, n) (sizeof(type) * (n))
+#define        BIT(i)  (1<<(i))        /* define bit in flag */
+
+/* Table flag type - needs > 16 and < 32 bits */
+typedef INT32 Tflag;
+
+#define        NUFILE  32              /* Number of user-accessible files */
+#define        FDBASE  10              /* First file usable by Shell */
+
+/* you're not going to run setuid shell scripts, are you? */
+#define        eaccess(path, mode)     access(path, mode)
+
+/* Make MAGIC a char that might be printed to make bugs more obvious, but
+ * not a char that is used often.  Also, can't use the high bit as it causes
+ * portability problems (calling strchr(x, 0x80|'x') is error prone).
+ */
+#define        MAGIC           (7)     /* prefix for *?[!{,} during expand */
+#define ISMAGIC(c)     ((unsigned char)(c) == MAGIC)
+#define        NOT             '!'     /* might use ^ (ie, [!...] vs [^..]) */
+
+#define        LINE    1024            /* input line size */
+#define        PATH    1024            /* pathname size (todo: PATH_MAX/pathconf()) */
+#define ARRAYMAX 1023          /* max array index */
+
+EXTERN const char *kshname;    /* $0 */
+EXTERN pid_t   kshpid;         /* $$, shell pid */
+EXTERN pid_t   procpid;        /* pid of executing process */
+EXTERN uid_t   ksheuid;        /* effective uid of shell */
+EXTERN int     exstat;         /* exit status */
+EXTERN int     subst_exstat;   /* exit status of last $(..)/`..` */
+EXTERN const char *safe_prompt; /* safe prompt if PS1 substitution fails */
+
+/*
+ * Area-based allocation built on malloc/free
+ */
+typedef struct Area {
+       struct link *freelist;  /* free list */
+} Area;
+
+EXTERN Area    aperm;          /* permanent object space */
+#define        APERM   &aperm
+#define        ATEMP   &e->area
+
+#ifdef MEM_DEBUG
+# include "chmem.h" /* a debugging front end for malloc et. al. */
+#endif /* MEM_DEBUG */
+
+#ifdef KSH_DEBUG
+# define kshdebug_init()       kshdebug_init_()
+# define kshdebug_printf(a)    kshdebug_printf_ a
+# define kshdebug_dump(a)      kshdebug_dump_ a
+#else /* KSH_DEBUG */
+# define kshdebug_init()
+# define kshdebug_printf(a)
+# define kshdebug_dump(a)
+#endif /* KSH_DEBUG */
+
+/*
+ * parsing & execution environment
+ */
+EXTERN struct env {
+       short   type;                   /* environment type - see below */
+       short   flags;                  /* EF_* */
+       Area    area;                   /* temporary allocation area */
+       struct  block *loc;             /* local variables and functions */
+       short  *savefd;                 /* original redirected fd's */
+       struct  env *oenv;              /* link to previous environment */
+       ksh_jmp_buf jbuf;               /* long jump back to env creator */
+       struct temp *temps;             /* temp files */
+} *e;
+
+/* struct env.type values */
+#define        E_NONE  0               /* dummy environment */
+#define        E_PARSE 1               /* parsing command # */
+#define        E_FUNC  2               /* executing function # */
+#define        E_INCL  3               /* including a file via . # */
+#define        E_EXEC  4               /* executing command tree */
+#define        E_LOOP  5               /* executing for/while # */
+#define        E_ERRH  6               /* general error handler # */
+/* # indicates env has valid jbuf (see unwind()) */
+
+/* struct env.flag values */
+#define EF_FUNC_PARSE  BIT(0)  /* function being parsed */
+#define EF_BRKCONT_PASS        BIT(1)  /* set if E_LOOP must pass break/continue on */
+#define EF_FAKE_SIGDIE BIT(2)  /* hack to get info from unwind to quitenv */
+
+/* Do breaks/continues stop at env type e? */
+#define STOP_BRKCONT(t)        ((t) == E_NONE || (t) == E_PARSE \
+                        || (t) == E_FUNC || (t) == E_INCL)
+/* Do returns stop at env type e? */
+#define STOP_RETURN(t) ((t) == E_FUNC || (t) == E_INCL)
+
+/* values for ksh_siglongjmp(e->jbuf, 0) */
+#define LRETURN        1               /* return statement */
+#define        LEXIT   2               /* exit statement */
+#define LERROR 3               /* errorf() called */
+#define LLEAVE 4               /* untrappable exit/error */
+#define LINTR  5               /* ^C noticed */
+#define        LBREAK  6               /* break statement */
+#define        LCONTIN 7               /* continue statement */
+#define LSHELL 8               /* return to interactive shell() */
+#define LAEXPR 9               /* error in arithmetic expression */
+
+/* option processing */
+#define OF_CMDLINE     0x01    /* command line */
+#define OF_SET         0x02    /* set builtin */
+#define OF_SPECIAL     0x04    /* a special variable changing */
+#define OF_INTERNAL    0x08    /* set internally by shell */
+#define OF_ANY         (OF_CMDLINE | OF_SET | OF_SPECIAL | OF_INTERNAL)
+
+struct option {
+    const char *name;  /* long name of option */
+    char       c;      /* character flag (if any) */
+    short      flags;  /* OF_* */
+};
+extern const struct option goptions[];
+
+/*
+ * flags (the order of these enums MUST match the order in misc.c(options[]))
+ */
+enum sh_flag {
+       FEXPORT = 0,    /* -a: export all */
+#ifdef BRACE_EXPAND
+       FBRACEEXPAND,   /* enable {} globbing */
+#endif
+       FBGNICE,        /* bgnice */
+       FCOMMAND,       /* -c: (invocation) execute specified command */
+#ifdef EMACS
+       FEMACS,         /* emacs command editing */
+       FEMACSUSEMETA,  /* use 8th bit as meta */
+#endif
+       FERREXIT,       /* -e: quit on error */
+#ifdef EMACS
+       FGMACS,         /* gmacs command editing */
+#endif
+       FIGNOREEOF,     /* eof does not exit */
+       FTALKING,       /* -i: interactive */
+       FKEYWORD,       /* -k: name=value anywhere */
+       FLOGIN,         /* -l: a login shell */
+       FMARKDIRS,      /* mark dirs with / in file name completion */
+       FMONITOR,       /* -m: job control monitoring */
+       FNOCLOBBER,     /* -C: don't overwrite existing files */
+       FNOEXEC,        /* -n: don't execute any commands */
+       FNOGLOB,        /* -f: don't do file globbing */
+       FNOHUP,         /* -H: don't kill running jobs when login shell exits */
+       FNOLOG,         /* don't save functions in history (ignored) */
+#ifdef JOBS
+       FNOTIFY,        /* -b: asynchronous job completion notification */
+#endif
+       FNOUNSET,       /* -u: using an unset var is an error */
+       FPHYSICAL,      /* -o physical: don't do logical cd's/pwd's */
+       FPOSIX,         /* -o posix: be posixly correct */
+       FPRIVILEGED,    /* -p: use suid_profile */
+       FRESTRICTED,    /* -r: restricted shell */
+       FSTDIN,         /* -s: (invocation) parse stdin */
+       FTRACKALL,      /* -h: create tracked aliases for all commands */
+       FVERBOSE,       /* -v: echo input */
+#ifdef VI
+       FVI,            /* vi command editing */
+       FVIRAW,         /* always read in raw mode (ignored) */
+       FVISHOW8,       /* display chars with 8th bit set as is (versus M-) */
+       FVITABCOMPLETE, /* enable tab as file name completion char */
+       FVIESCCOMPLETE, /* enable ESC as file name completion in command mode */
+#endif
+       FXTRACE,        /* -x: execution trace */
+       FTALKING_I,     /* (internal): initial shell was interactive */
+       FNFLAGS /* (place holder: how many flags are there) */
+};
+
+#define Flag(f)        (shell_flags[(int) (f)])
+
+EXTERN char shell_flags [FNFLAGS];
+
+EXTERN char    null [] I__("");        /* null value for variable */
+EXTERN char    space [] I__(" ");
+EXTERN char    newline [] I__("\n");
+EXTERN char    slash [] I__("/");
+
+enum temp_type {
+    TT_HEREDOC_EXP,    /* expanded heredoc */
+    TT_HIST_EDIT       /* temp file used for history editing (fc -e) */
+};
+typedef enum temp_type Temp_type;
+/* temp/heredoc files.  The file is removed when the struct is freed. */
+struct temp {
+       struct temp     *next;
+       struct shf      *shf;
+       int             pid;            /* pid of process parsed here-doc */
+       Temp_type       type;
+       char            *name;
+};
+
+/*
+ * stdio and our IO routines
+ */
+
+#define shl_spare      (&shf_iob[0])   /* for c_read()/c_print() */
+#define shl_stdout     (&shf_iob[1])
+#define shl_out                (&shf_iob[2])
+EXTERN int shl_stdout_ok;
+
+/*
+ * trap handlers
+ */
+typedef struct trap {
+       int     signal;         /* signal number */
+       const char *name;       /* short name */
+       const char *mess;       /* descriptive name */
+       char   *trap;           /* trap command */
+       int     volatile set;   /* trap pending */
+       int     flags;          /* TF_* */
+       handler_t cursig;       /* current handler (valid if TF_ORIG_* set) */
+       handler_t shtrap;       /* shell signal handler */
+} Trap;
+
+/* values for Trap.flags */
+#define TF_SHELL_USES  BIT(0)  /* shell uses signal, user can't change */
+#define TF_USER_SET    BIT(1)  /* user has (tried to) set trap */
+#define TF_ORIG_IGN    BIT(2)  /* original action was SIG_IGN */
+#define TF_ORIG_DFL    BIT(3)  /* original action was SIG_DFL */
+#define TF_EXEC_IGN    BIT(4)  /* restore SIG_IGN just before exec */
+#define TF_EXEC_DFL    BIT(5)  /* restore SIG_DFL just before exec */
+#define TF_DFL_INTR    BIT(6)  /* when received, default action is LINTR */
+#define TF_TTY_INTR    BIT(7)  /* tty generated signal (see j_waitj) */
+#define TF_CHANGED     BIT(8)  /* used by runtrap() to detect trap changes */
+#define TF_FATAL       BIT(9)  /* causes termination if not trapped */
+
+/* values for setsig()/setexecsig() flags argument */
+#define SS_RESTORE_MASK        0x3     /* how to restore a signal before an exec() */
+#define SS_RESTORE_CURR        0       /* leave current handler in place */
+#define SS_RESTORE_ORIG        1       /* restore original handler */
+#define SS_RESTORE_DFL 2       /* restore to SIG_DFL */
+#define SS_RESTORE_IGN 3       /* restore to SIG_IGN */
+#define SS_FORCE       BIT(3)  /* set signal even if original signal ignored */
+#define SS_USER                BIT(4)  /* user is doing the set (ie, trap command) */
+#define SS_SHTRAP      BIT(5)  /* trap for internal use (CHLD,ALRM,WINCH) */
+
+#define SIGEXIT_       0       /* for trap EXIT */
+#define SIGERR_                SIGNALS /* for trap ERR */
+
+EXTERN int volatile trap;      /* traps pending? */
+EXTERN int volatile intrsig;   /* pending trap interrupts executing command */
+EXTERN int volatile fatal_trap;/* received a fatal signal */
+#ifndef FROM_TRAP_C
+/* Kludge to avoid bogus re-declaration of sigtraps[] error on AIX 3.2.5 */
+extern Trap    sigtraps[SIGNALS+1];
+#endif /* !FROM_TRAP_C */
+
+#ifdef KSH
+/*
+ * TMOUT support
+ */
+/* values for ksh_tmout_state */
+enum tmout_enum {
+               TMOUT_EXECUTING = 0,    /* executing commands */
+               TMOUT_READING,          /* waiting for input */
+               TMOUT_LEAVING           /* have timed out */
+       };
+EXTERN unsigned int ksh_tmout;
+EXTERN enum tmout_enum ksh_tmout_state I__(TMOUT_EXECUTING);
+#endif /* KSH */
+
+/* For "You have stopped jobs" message */
+EXTERN int really_exit;
+
+/*
+ * fast character classes
+ */
+#define        C_ALPHA  BIT(0)         /* a-z_A-Z */
+#define        C_DIGIT  BIT(1)         /* 0-9 */
+#define        C_LEX1   BIT(2)         /* \0 \t\n|&;<>() */
+#define        C_VAR1   BIT(3)         /* *@#!$-? */
+#define        C_IFSWS  BIT(4)         /* \t \n (IFS white space) */
+#define        C_SUBOP1 BIT(5)         /* "=-+?" */
+#define        C_SUBOP2 BIT(6)         /* "#%" */
+#define        C_IFS    BIT(7)         /* $IFS */
+#define        C_QUOTE  BIT(8)         /*  \n\t"#$&'()*;<>?[\`| (needing quoting) */
+
+extern short ctypes [];
+
+#define        ctype(c, t)     !!(ctypes[(unsigned char)(c)]&(t))
+#define        letter(c)       ctype(c, C_ALPHA)
+#define        digit(c)        ctype(c, C_DIGIT)
+#define        letnum(c)       ctype(c, C_ALPHA|C_DIGIT)
+
+EXTERN int ifs0 I__(' ');      /* for "$*" */
+
+/* Argument parsing for built-in commands and getopts command */
+
+/* Values for Getopt.flags */
+#define GF_ERROR       BIT(0)  /* call errorf() if there is an error */
+#define GF_PLUSOPT     BIT(1)  /* allow +c as an option */
+#define GF_NONAME      BIT(2)  /* don't print argv[0] in errors */
+
+/* Values for Getopt.info */
+#define GI_MINUS       BIT(0)  /* an option started with -... */
+#define GI_PLUS                BIT(1)  /* an option started with +... */
+#define GI_MINUSMINUS  BIT(2)  /* arguments were ended with -- */
+
+typedef struct {
+       int             optind;
+       int             uoptind;/* what user sees in $OPTIND */
+       char            *optarg;
+       int             flags;  /* see GF_* */
+       int             info;   /* see GI_* */
+       unsigned int    p;      /* 0 or index into argv[optind - 1] */
+       char            buf[2]; /* for bad option OPTARG value */
+} Getopt;
+
+EXTERN Getopt builtin_opt;     /* for shell builtin commands */
+EXTERN Getopt user_opt;                /* parsing state for getopts builtin command */
+
+#ifdef KSH
+/* This for co-processes */
+
+typedef INT32 Coproc_id; /* something that won't (realisticly) wrap */
+struct coproc {
+       int     read;           /* pipe from co-process's stdout */
+       int     readw;          /* other side of read (saved temporarily) */
+       int     write;          /* pipe to co-process's stdin */
+       Coproc_id id;           /* id of current output pipe */
+       int     njobs;          /* number of live jobs using output pipe */
+       void    *job;           /* 0 or job of co-process using input pipe */
+};
+EXTERN struct coproc coproc;
+#endif /* KSH */
+
+/* Used in jobs.c and by coprocess stuff in exec.c */
+#ifdef JOB_SIGS
+EXTERN sigset_t                sm_default, sm_sigchld;
+#endif /* JOB_SIGS */
+
+extern char ksh_version[];
+
+/* name of called builtin function (used by error functions) */
+EXTERN char    *builtin_argv0;
+EXTERN Tflag   builtin_flag;   /* flags of called builtin (SPEC_BI, etc.) */
+
+/* current working directory, and size of memory allocated for same */
+EXTERN char    *current_wd;
+EXTERN int     current_wd_size;
+
+#ifdef EDIT
+/* Minimum required space to work with on a line - if the prompt leaves less
+ * space than this on a line, the prompt is truncated.
+ */
+# define MIN_EDIT_SPACE        7
+/* Minimum allowed value for x_cols: 2 for prompt, 3 for " < " at end of line
+ */
+# define MIN_COLS      (2 + MIN_EDIT_SPACE + 3)
+EXTERN int     x_cols I__(80); /* tty columns */
+#else
+# define x_cols 80             /* for pr_menu(exec.c) */
+#endif
+
+/* These to avoid bracket matching problems */
+#define OPAREN '('
+#define CPAREN ')'
+#define OBRACK '['
+#define CBRACK ']'
+#define OBRACE '{'
+#define CBRACE '}'
+
+/* Determine the location of the system (common) profile */
+#ifndef KSH_SYSTEM_PROFILE
+# ifdef __NeXT
+#  define KSH_SYSTEM_PROFILE "/etc/profile.std"
+# else /* __NeXT */
+#  define KSH_SYSTEM_PROFILE "/etc/profile"
+# endif /* __NeXT */
+#endif /* KSH_SYSTEM_PROFILE */
+
+/* Used by v_evaluate() and setstr() to control action when error occurs */
+#define KSH_UNWIND_ERROR       0       /* unwind the stack (longjmp) */
+#define KSH_RETURN_ERROR       1       /* return 1/0 for success/failure */
+
+#include "shf.h"
+#include "table.h"
+#include "tree.h"
+#include "expand.h"
+#include "lex.h"
+#include "proto.h"
+
+/* be sure not to interfere with anyone else's idea about EXTERN */
+#ifdef EXTERN_DEFINED
+# undef EXTERN_DEFINED
+# undef EXTERN
+#endif
+#undef I__
diff --git a/bin/ksh/shf.c b/bin/ksh/shf.c
new file mode 100644 (file)
index 0000000..9913b5a
--- /dev/null
@@ -0,0 +1,1302 @@
+/*     $NetBSD: shf.c,v 1.7 2005/06/26 19:09:00 christos Exp $ */
+
+/*
+ *  Shell file I/O routines
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: shf.c,v 1.7 2005/06/26 19:09:00 christos Exp $");
+#endif
+
+
+#include "sh.h"
+#include "ksh_stat.h"
+#include "ksh_limval.h"
+
+
+/* flags to shf_emptybuf() */
+#define EB_READSW      0x01    /* about to switch to reading */
+#define EB_GROW                0x02    /* grow buffer if necessary (STRING+DYNAMIC) */
+
+/*
+ * Replacement stdio routines.  Stdio is too flakey on too many machines
+ * to be useful when you have multiple processes using the same underlying
+ * file descriptors.
+ */
+
+static int     shf_fillbuf     ARGS((struct shf *shf));
+static int     shf_emptybuf    ARGS((struct shf *shf, int flags));
+
+/* Open a file.  First three args are for open(), last arg is flags for
+ * this package.  Returns NULL if file could not be opened, or if a dup
+ * fails.
+ */
+struct shf *
+shf_open(name, oflags, mode, sflags)
+       const char *name;
+       int oflags;
+       int mode;
+       int sflags;
+{
+       struct shf *shf;
+       int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
+       int fd;
+
+       /* Done before open so if alloca fails, fd won't be lost. */
+       shf = (struct shf *) alloc(sizeof(struct shf) + bsize, ATEMP);
+       shf->areap = ATEMP;
+       shf->buf = (unsigned char *) &shf[1];
+       shf->bsize = bsize;
+       shf->flags = SHF_ALLOCS;
+       /* Rest filled in by reopen. */
+
+       fd = open(name, oflags, mode);
+       if (fd < 0) {
+               afree(shf, shf->areap);
+               return NULL;
+       }
+       if ((sflags & SHF_MAPHI) && fd < FDBASE) {
+               int nfd;
+
+               nfd = ksh_dupbase(fd, FDBASE);
+               close(fd);
+               if (nfd < 0) {
+                       afree(shf, shf->areap);
+                       return NULL;
+               }
+               fd = nfd;
+       }
+       sflags &= ~SHF_ACCMODE;
+       sflags |= (oflags & O_ACCMODE) == O_RDONLY ? SHF_RD
+                 : ((oflags & O_ACCMODE) == O_WRONLY ? SHF_WR
+                    : SHF_RDWR);
+
+       return shf_reopen(fd, sflags, shf);
+}
+
+/* Set up the shf structure for a file descriptor.  Doesn't fail. */
+struct shf *
+shf_fdopen(fd, sflags, shf)
+       int fd;
+       int sflags;
+       struct shf *shf;
+{
+       int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
+
+       /* use fcntl() to figure out correct read/write flags */
+       if (sflags & SHF_GETFL) {
+               int flags = fcntl(fd, F_GETFL, 0);
+
+               if (flags < 0)
+                       /* will get an error on first read/write */
+                       sflags |= SHF_RDWR;
+               else
+                       switch (flags & O_ACCMODE) {
+                       case O_RDONLY: sflags |= SHF_RD; break;
+                       case O_WRONLY: sflags |= SHF_WR; break;
+                       case O_RDWR: sflags |= SHF_RDWR; break;
+                       }
+       }
+
+       if (!(sflags & (SHF_RD | SHF_WR)))
+               internal_errorf(1, "shf_fdopen: missing read/write");
+
+       if (shf) {
+               if (bsize) {
+                       shf->buf = (unsigned char *) alloc(bsize, ATEMP);
+                       sflags |= SHF_ALLOCB;
+               } else
+                       shf->buf = (unsigned char *) 0;
+       } else {
+               shf = (struct shf *) alloc(sizeof(struct shf) + bsize, ATEMP);
+               shf->buf = (unsigned char *) &shf[1];
+               sflags |= SHF_ALLOCS;
+       }
+       shf->areap = ATEMP;
+       shf->fd = fd;
+       shf->rp = shf->wp = shf->buf;
+       shf->rnleft = 0;
+       shf->rbsize = bsize;
+       shf->wnleft = 0; /* force call to shf_emptybuf() */
+       shf->wbsize = sflags & SHF_UNBUF ? 0 : bsize;
+       shf->flags = sflags;
+       shf->errno_ = 0;
+       shf->bsize = bsize;
+       if (sflags & SHF_CLEXEC)
+               fd_clexec(fd);
+       return shf;
+}
+
+/* Set up an existing shf (and buffer) to use the given fd */
+struct shf *
+shf_reopen(fd, sflags, shf)
+       int fd;
+       int sflags;
+       struct shf *shf;
+{
+       int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
+
+       /* use fcntl() to figure out correct read/write flags */
+       if (sflags & SHF_GETFL) {
+               int flags = fcntl(fd, F_GETFL, 0);
+
+               if (flags < 0)
+                       /* will get an error on first read/write */
+                       sflags |= SHF_RDWR;
+               else
+                       switch (flags & O_ACCMODE) {
+                       case O_RDONLY: sflags |= SHF_RD; break;
+                       case O_WRONLY: sflags |= SHF_WR; break;
+                       case O_RDWR: sflags |= SHF_RDWR; break;
+                       }
+       }
+
+       if (!(sflags & (SHF_RD | SHF_WR)))
+               internal_errorf(1, "shf_reopen: missing read/write");
+       if (!shf || !shf->buf || shf->bsize < bsize)
+               internal_errorf(1, "shf_reopen: bad shf/buf/bsize");
+
+       /* assumes shf->buf and shf->bsize already set up */
+       shf->fd = fd;
+       shf->rp = shf->wp = shf->buf;
+       shf->rnleft = 0;
+       shf->rbsize = bsize;
+       shf->wnleft = 0; /* force call to shf_emptybuf() */
+       shf->wbsize = sflags & SHF_UNBUF ? 0 : bsize;
+       shf->flags = (shf->flags & (SHF_ALLOCS | SHF_ALLOCB)) | sflags;
+       shf->errno_ = 0;
+       if (sflags & SHF_CLEXEC)
+               fd_clexec(fd);
+       return shf;
+}
+
+/* Open a string for reading or writing.  If reading, bsize is the number
+ * of bytes that can be read.  If writing, bsize is the maximum number of
+ * bytes that can be written.  If shf is not null, it is filled in and
+ * returned, if it is null, shf is allocated.  If writing and buf is null
+ * and SHF_DYNAMIC is set, the buffer is allocated (if bsize > 0, it is
+ * used for the initial size).  Doesn't fail.
+ * When writing, a byte is reserved for a trailing null - see shf_sclose().
+ */
+struct shf *
+shf_sopen(buf, bsize, sflags, shf)
+       char *buf;
+       int bsize;
+       int sflags;
+       struct shf *shf;
+{
+       /* can't have a read+write string */
+       if (!(sflags & (SHF_RD | SHF_WR))
+           || (sflags & (SHF_RD | SHF_WR)) == (SHF_RD | SHF_WR))
+               internal_errorf(1, "shf_sopen: flags 0x%x", sflags);
+
+       if (!shf) {
+               shf = (struct shf *) alloc(sizeof(struct shf), ATEMP);
+               sflags |= SHF_ALLOCS;
+       }
+       shf->areap = ATEMP;
+       if (!buf && (sflags & SHF_WR) && (sflags & SHF_DYNAMIC)) {
+               if (bsize <= 0)
+                       bsize = 64;
+               sflags |= SHF_ALLOCB;
+               buf = alloc(bsize, shf->areap);
+       }
+       shf->fd = -1;
+       shf->buf = shf->rp = shf->wp = (unsigned char *) buf;
+       shf->rnleft = bsize;
+       shf->rbsize = bsize;
+       shf->wnleft = bsize - 1;        /* space for a '\0' */
+       shf->wbsize = bsize;
+       shf->flags = sflags | SHF_STRING;
+       shf->errno_ = 0;
+       shf->bsize = bsize;
+
+       return shf;
+}
+
+/* Flush and close file descriptor, free the shf structure */
+int
+shf_close(shf)
+       struct shf *shf;
+{
+       int ret = 0;
+
+       if (shf->fd >= 0) {
+               ret = shf_flush(shf);
+               if (close(shf->fd) < 0)
+                       ret = EOF;
+       }
+       if (shf->flags & SHF_ALLOCS)
+               afree(shf, shf->areap);
+       else if (shf->flags & SHF_ALLOCB)
+               afree(shf->buf, shf->areap);
+
+       return ret;
+}
+
+/* Flush and close file descriptor, don't free file structure */
+int
+shf_fdclose(shf)
+       struct shf *shf;
+{
+       int ret = 0;
+
+       if (shf->fd >= 0) {
+               ret = shf_flush(shf);
+               if (close(shf->fd) < 0)
+                       ret = EOF;
+               shf->rnleft = 0;
+               shf->rp = shf->buf;
+               shf->wnleft = 0;
+               shf->fd = -1;
+       }
+
+       return ret;
+}
+
+/* Close a string - if it was opened for writing, it is null terminated;
+ * returns a pointer to the string and frees shf if it was allocated
+ * (does not free string if it was allocated).
+ */
+char *
+shf_sclose(shf)
+       struct shf *shf;
+{
+       unsigned char *s = shf->buf;
+
+       /* null terminate */
+       if (shf->flags & SHF_WR) {
+               shf->wnleft++;
+               shf_putc('\0', shf);
+       }
+       if (shf->flags & SHF_ALLOCS)
+               afree(shf, shf->areap);
+       return (char *) s;
+}
+
+/* Flush and free file structure, don't close file descriptor */
+int
+shf_finish(shf)
+       struct shf *shf;
+{
+       int ret = 0;
+
+       if (shf->fd >= 0)
+               ret = shf_flush(shf);
+       if (shf->flags & SHF_ALLOCS)
+               afree(shf, shf->areap);
+       else if (shf->flags & SHF_ALLOCB)
+               afree(shf->buf, shf->areap);
+
+       return ret;
+}
+
+/* Un-read what has been read but not examined, or write what has been
+ * buffered.  Returns 0 for success, EOF for (write) error.
+ */
+int
+shf_flush(shf)
+       struct shf *shf;
+{
+       if (shf->flags & SHF_STRING)
+               return (shf->flags & SHF_WR) ? EOF : 0;
+
+       if (shf->fd < 0)
+               internal_errorf(1, "shf_flush: no fd");
+
+       if (shf->flags & SHF_ERROR) {
+               errno = shf->errno_;
+               return EOF;
+       }
+
+       if (shf->flags & SHF_READING) {
+               shf->flags &= ~(SHF_EOF | SHF_READING);
+               if (shf->rnleft > 0) {
+                       lseek(shf->fd, (off_t) -shf->rnleft, 1);
+                       shf->rnleft = 0;
+                       shf->rp = shf->buf;
+               }
+               return 0;
+       } else if (shf->flags & SHF_WRITING)
+               return shf_emptybuf(shf, 0);
+
+       return 0;
+}
+
+/* Write out any buffered data.  If currently reading, flushes the read
+ * buffer.  Returns 0 for success, EOF for (write) error.
+ */
+static int
+shf_emptybuf(shf, flags)
+       struct shf *shf;
+       int flags;
+{
+       int ret = 0;
+
+       if (!(shf->flags & SHF_STRING) && shf->fd < 0)
+               internal_errorf(1, "shf_emptybuf: no fd");
+
+       if (shf->flags & SHF_ERROR) {
+               errno = shf->errno_;
+               return EOF;
+       }
+
+       if (shf->flags & SHF_READING) {
+               if (flags & EB_READSW) /* doesn't happen */
+                       return 0;
+               ret = shf_flush(shf);
+               shf->flags &= ~SHF_READING;
+       }
+       if (shf->flags & SHF_STRING) {
+               unsigned char   *nbuf;
+
+               /* Note that we assume SHF_ALLOCS is not set if SHF_ALLOCB
+                * is set... (changing the shf pointer could cause problems)
+                */
+               if (!(flags & EB_GROW) || !(shf->flags & SHF_DYNAMIC)
+                   || !(shf->flags & SHF_ALLOCB))
+                       return EOF;
+               /* allocate more space for buffer */
+               nbuf = (unsigned char *) aresize(shf->buf, shf->wbsize * 2,
+                                               shf->areap);
+               shf->rp = nbuf + (shf->rp - shf->buf);
+               shf->wp = nbuf + (shf->wp - shf->buf);
+               shf->rbsize += shf->wbsize;
+               shf->wnleft += shf->wbsize;
+               shf->wbsize *= 2;
+               shf->buf = nbuf;
+       } else {
+               if (shf->flags & SHF_WRITING) {
+                       int ntowrite = shf->wp - shf->buf;
+                       unsigned char *buf = shf->buf;
+                       int n;
+
+                       while (ntowrite > 0) {
+                               n = write(shf->fd, buf, ntowrite);
+                               if (n < 0) {
+                                       if (errno == EINTR
+                                           && !(shf->flags & SHF_INTERRUPT))
+                                               continue;
+                                       shf->flags |= SHF_ERROR;
+                                       shf->errno_ = errno;
+                                       shf->wnleft = 0;
+                                       if (buf != shf->buf) {
+                                               /* allow a second flush
+                                                * to work */
+                                               memmove(shf->buf, buf,
+                                                       ntowrite);
+                                               shf->wp = shf->buf + ntowrite;
+                                       }
+                                       return EOF;
+                               }
+                               buf += n;
+                               ntowrite -= n;
+                       }
+                       if (flags & EB_READSW) {
+                               shf->wp = shf->buf;
+                               shf->wnleft = 0;
+                               shf->flags &= ~SHF_WRITING;
+                               return 0;
+                       }
+               }
+               shf->wp = shf->buf;
+               shf->wnleft = shf->wbsize;
+       }
+       shf->flags |= SHF_WRITING;
+
+       return ret;
+}
+
+/* Fill up a read buffer.  Returns EOF for a read error, 0 otherwise. */
+static int
+shf_fillbuf(shf)
+       struct shf *shf;
+{
+       if (shf->flags & SHF_STRING)
+               return 0;
+
+       if (shf->fd < 0)
+               internal_errorf(1, "shf_fillbuf: no fd");
+
+       if (shf->flags & (SHF_EOF | SHF_ERROR)) {
+               if (shf->flags & SHF_ERROR)
+                       errno = shf->errno_;
+               return EOF;
+       }
+
+       if ((shf->flags & SHF_WRITING) && shf_emptybuf(shf, EB_READSW) == EOF)
+               return EOF;
+
+       shf->flags |= SHF_READING;
+
+       shf->rp = shf->buf;
+       while (1) {
+               shf->rnleft = blocking_read(shf->fd, (char *) shf->buf,
+                                           shf->rbsize);
+               if (shf->rnleft < 0 && errno == EINTR
+                   && !(shf->flags & SHF_INTERRUPT))
+                       continue;
+               break;
+       }
+       if (shf->rnleft <= 0) {
+               if (shf->rnleft < 0) {
+                       shf->flags |= SHF_ERROR;
+                       shf->errno_ = errno;
+                       shf->rnleft = 0;
+                       shf->rp = shf->buf;
+                       return EOF;
+               }
+               shf->flags |= SHF_EOF;
+       }
+       return 0;
+}
+
+/* Seek to a new position in the file.  If writing, flushes the buffer
+ * first.  If reading, optimizes small relative seeks that stay inside the
+ * buffer.  Returns 0 for success, EOF otherwise.
+ */
+int
+shf_seek(shf, where, from)
+       struct shf *shf;
+       off_t where;
+       int from;
+{
+       if (shf->fd < 0) {
+               errno = EINVAL;
+               return EOF;
+       }
+
+       if (shf->flags & SHF_ERROR) {
+               errno = shf->errno_;
+               return EOF;
+       }
+
+       if ((shf->flags & SHF_WRITING) && shf_emptybuf(shf, EB_READSW) == EOF)
+               return EOF;
+
+       if (shf->flags & SHF_READING) {
+               if (from == SEEK_CUR &&
+                               (where < 0 ?
+                                       -where >= shf->rbsize - shf->rnleft :
+                                       where < shf->rnleft)) {
+                       shf->rnleft -= where;
+                       shf->rp += where;
+                       return 0;
+               }
+               shf->rnleft = 0;
+               shf->rp = shf->buf;
+       }
+
+       shf->flags &= ~(SHF_EOF | SHF_READING | SHF_WRITING);
+
+       if (lseek(shf->fd, where, from) < 0) {
+               shf->errno_ = errno;
+               shf->flags |= SHF_ERROR;
+               return EOF;
+       }
+
+       return 0;
+}
+
+
+/* Read a buffer from shf.  Returns the number of bytes read into buf,
+ * if no bytes were read, returns 0 if end of file was seen, EOF if
+ * a read error occurred.
+ */
+int
+shf_read(buf, bsize, shf)
+       char *buf;
+       int bsize;
+       struct shf *shf;
+{
+       int orig_bsize = bsize;
+       int ncopy;
+
+       if (!(shf->flags & SHF_RD))
+               internal_errorf(1, "shf_read: flags %x", shf->flags);
+
+       if (bsize <= 0)
+               internal_errorf(1, "shf_read: bsize %d", bsize);
+
+       while (bsize > 0) {
+               if (shf->rnleft == 0
+                   && (shf_fillbuf(shf) == EOF || shf->rnleft == 0))
+                       break;
+               ncopy = shf->rnleft;
+               if (ncopy > bsize)
+                       ncopy = bsize;
+               memcpy(buf, shf->rp, ncopy);
+               buf += ncopy;
+               bsize -= ncopy;
+               shf->rp += ncopy;
+               shf->rnleft -= ncopy;
+       }
+       /* Note: fread(3S) returns 0 for errors - this doesn't */
+       return orig_bsize == bsize ? (shf_error(shf) ? EOF : 0)
+                                  : orig_bsize - bsize;
+}
+
+/* Read up to a newline or EOF.  The newline is put in buf; buf is always
+ * null terminated.  Returns NULL on read error or if nothing was read before
+ * end of file, returns a pointer to the null byte in buf otherwise.
+ */
+char *
+shf_getse(buf, bsize, shf)
+       char *buf;
+       int bsize;
+       struct shf *shf;
+{
+       unsigned char *end;
+       int ncopy;
+       char *orig_buf = buf;
+
+       if (!(shf->flags & SHF_RD))
+               internal_errorf(1, "shf_getse: flags %x", shf->flags);
+
+       if (bsize <= 0)
+               return (char *) 0;
+
+       --bsize;        /* save room for null */
+       do {
+               if (shf->rnleft == 0) {
+                       if (shf_fillbuf(shf) == EOF)
+                               return NULL;
+                       if (shf->rnleft == 0) {
+                               *buf = '\0';
+                               return buf == orig_buf ? NULL : buf;
+                       }
+               }
+               end = (unsigned char *) memchr((char *) shf->rp, '\n',
+                                            shf->rnleft);
+               ncopy = end ? end - shf->rp + 1 : shf->rnleft;
+               if (ncopy > bsize)
+                       ncopy = bsize;
+               memcpy(buf, (char *) shf->rp, ncopy);
+               shf->rp += ncopy;
+               shf->rnleft -= ncopy;
+               buf += ncopy;
+               bsize -= ncopy;
+#ifdef OS2
+               if (end && buf > orig_buf + 1 && buf[-2] == '\r') {
+                       buf--;
+                       bsize++;
+                       buf[-1] = '\n';
+               }
+#endif
+
+       } while (!end && bsize);
+       *buf = '\0';
+       return buf;
+}
+
+/* Returns the char read.  Returns EOF for error and end of file. */
+int
+shf_getchar(shf)
+       struct shf *shf;
+{
+       if (!(shf->flags & SHF_RD))
+               internal_errorf(1, "shf_getchar: flags %x", shf->flags);
+
+       if (shf->rnleft == 0 && (shf_fillbuf(shf) == EOF || shf->rnleft == 0))
+               return EOF;
+       --shf->rnleft;
+       return *shf->rp++;
+}
+
+/* Put a character back in the input stream.  Returns the character if
+ * successful, EOF if there is no room.
+ */
+int
+shf_ungetc(c, shf)
+       int c;
+       struct shf *shf;
+{
+       if (!(shf->flags & SHF_RD))
+               internal_errorf(1, "shf_ungetc: flags %x", shf->flags);
+
+       if ((shf->flags & SHF_ERROR) || c == EOF
+           || (shf->rp == shf->buf && shf->rnleft))
+               return EOF;
+
+       if ((shf->flags & SHF_WRITING) && shf_emptybuf(shf, EB_READSW) == EOF)
+               return EOF;
+
+       if (shf->rp == shf->buf)
+               shf->rp = shf->buf + shf->rbsize;
+       if (shf->flags & SHF_STRING) {
+               /* Can unget what was read, but not something different - we
+                * don't want to modify a string.
+                */
+               if (shf->rp[-1] != c)
+                       return EOF;
+               shf->flags &= ~SHF_EOF;
+               shf->rp--;
+               shf->rnleft++;
+               return c;
+       }
+       shf->flags &= ~SHF_EOF;
+       *--(shf->rp) = c;
+       shf->rnleft++;
+       return c;
+}
+
+/* Write a character.  Returns the character if successful, EOF if
+ * the char could not be written.
+ */
+int
+shf_putchar(c, shf)
+       int c;
+       struct shf *shf;
+{
+       if (!(shf->flags & SHF_WR))
+               internal_errorf(1, "shf_putchar: flags %x", shf->flags);
+
+       if (c == EOF)
+               return EOF;
+
+       if (shf->flags & SHF_UNBUF) {
+               char cc = c;
+               int n;
+
+               if (shf->fd < 0)
+                       internal_errorf(1, "shf_putchar: no fd");
+               if (shf->flags & SHF_ERROR) {
+                       errno = shf->errno_;
+                       return EOF;
+               }
+               while ((n = write(shf->fd, &cc, 1)) != 1)
+                       if (n < 0) {
+                               if (errno == EINTR
+                                   && !(shf->flags & SHF_INTERRUPT))
+                                       continue;
+                               shf->flags |= SHF_ERROR;
+                               shf->errno_ = errno;
+                               return EOF;
+                       }
+       } else {
+               /* Flush deals with strings and sticky errors */
+               if (shf->wnleft == 0 && shf_emptybuf(shf, EB_GROW) == EOF)
+                       return EOF;
+               shf->wnleft--;
+               *shf->wp++ = c;
+       }
+
+       return c;
+}
+
+/* Write a string.  Returns the length of the string if successful, EOF if
+ * the string could not be written.
+ */
+int
+shf_puts(s, shf)
+       const char *s;
+       struct shf *shf;
+{
+       if (!s)
+               return EOF;
+
+       return shf_write(s, strlen(s), shf);
+}
+
+/* Write a buffer.  Returns nbytes if successful, EOF if there is an error. */
+int
+shf_write(buf, nbytes, shf)
+       const char *buf;
+       int nbytes;
+       struct shf *shf;
+{
+       int orig_nbytes = nbytes;
+       int n;
+       int ncopy;
+
+       if (!(shf->flags & SHF_WR))
+               internal_errorf(1, "shf_write: flags %x", shf->flags);
+
+       if (nbytes < 0)
+               internal_errorf(1, "shf_write: nbytes %d", nbytes);
+
+       /* Don't buffer if buffer is empty and we're writting a large amount. */
+       if ((ncopy = shf->wnleft)
+           && (shf->wp != shf->buf || nbytes < shf->wnleft))
+       {
+               if (ncopy > nbytes)
+                       ncopy = nbytes;
+               memcpy(shf->wp, buf, ncopy);
+               nbytes -= ncopy;
+               buf += ncopy;
+               shf->wp += ncopy;
+               shf->wnleft -= ncopy;
+       }
+       if (nbytes > 0) {
+               /* Flush deals with strings and sticky errors */
+               if (shf_emptybuf(shf, EB_GROW) == EOF)
+                       return EOF;
+               if (nbytes > shf->wbsize) {
+                       ncopy = nbytes;
+                       if (shf->wbsize)
+                               ncopy -= nbytes % shf->wbsize;
+                       nbytes -= ncopy;
+                       while (ncopy > 0) {
+                               n = write(shf->fd, buf, ncopy);
+                               if (n < 0) {
+                                       if (errno == EINTR
+                                           && !(shf->flags & SHF_INTERRUPT))
+                                               continue;
+                                       shf->flags |= SHF_ERROR;
+                                       shf->errno_ = errno;
+                                       shf->wnleft = 0;
+                                       /* Note: fwrite(3S) returns 0 for
+                                        * errors - this doesn't */
+                                       return EOF;
+                               }
+                               buf += n;
+                               ncopy -= n;
+                       }
+               }
+               if (nbytes > 0) {
+                       memcpy(shf->wp, buf, nbytes);
+                       shf->wp += nbytes;
+                       shf->wnleft -= nbytes;
+               }
+       }
+
+       return orig_nbytes;
+}
+
+int
+#ifdef HAVE_PROTOTYPES
+shf_fprintf(struct shf *shf, const char *fmt, ...)
+#else
+shf_fprintf(shf, fmt, va_alist)
+       struct shf *shf;
+       const char *fmt;
+       va_dcl
+#endif
+{
+       va_list args;
+       int n;
+
+       SH_VA_START(args, fmt);
+       n = shf_vfprintf(shf, fmt, args);
+       va_end(args);
+
+       return n;
+}
+
+int
+#ifdef HAVE_PROTOTYPES
+shf_snprintf(char *buf, int bsize, const char *fmt, ...)
+#else
+shf_snprintf(buf, bsize, fmt, va_alist)
+       char *buf;
+       int bsize;
+       const char *fmt;
+       va_dcl
+#endif
+{
+       struct shf shf;
+       va_list args;
+       int n;
+
+       if (!buf || bsize <= 0)
+               internal_errorf(1, "shf_snprintf: buf %lx, bsize %d",
+                       (long) buf, bsize);
+
+       shf_sopen(buf, bsize, SHF_WR, &shf);
+       SH_VA_START(args, fmt);
+       n = shf_vfprintf(&shf, fmt, args);
+       va_end(args);
+       shf_sclose(&shf); /* null terminates */
+       return n;
+}
+
+char *
+#ifdef HAVE_PROTOTYPES
+shf_smprintf(const char *fmt, ...)
+#else
+shf_smprintf(fmt, va_alist)
+       char *fmt;
+       va_dcl
+#endif
+{
+       struct shf shf;
+       va_list args;
+
+       shf_sopen((char *) 0, 0, SHF_WR|SHF_DYNAMIC, &shf);
+       SH_VA_START(args, fmt);
+       shf_vfprintf(&shf, fmt, args);
+       va_end(args);
+       return shf_sclose(&shf); /* null terminates */
+}
+
+#undef FP                      /* if you want floating point stuff */
+
+#define BUF_SIZE       128
+#define FPBUF_SIZE     (DMAXEXP+16)/* this must be >
+                                *      MAX(DMAXEXP, log10(pow(2, DSIGNIF)))
+                                *    + ceil(log10(DMAXEXP)) + 8 (I think).
+                                * Since this is hard to express as a
+                                * constant, just use a large buffer.
+                                */
+
+/*
+ *     What kinda of machine we on?  Hopefully the C compiler will optimize
+ *  this out...
+ *
+ *     For shorts, we want sign extend for %d but not for %[oxu] - on 16 bit
+ *  machines it don't matter.  Assumes C compiler has converted shorts to
+ *  ints before pushing them.
+ */
+#define POP_INT(f, s, a) (((f) & FL_LONG) ?                            \
+                               va_arg((a), unsigned long)              \
+                           :                                           \
+                               (sizeof(int) < sizeof(long) ?           \
+                                       ((s) ?                          \
+                                               (long) va_arg((a), int) \
+                                           :                           \
+                                               va_arg((a), unsigned))  \
+                                   :                                   \
+                                       va_arg((a), unsigned)))
+
+#define ABIGNUM                32000   /* big numer that will fit in a short */
+#define LOG2_10                3.321928094887362347870319429   /* log base 2 of 10 */
+
+#define        FL_HASH         0x001   /* `#' seen */
+#define FL_PLUS                0x002   /* `+' seen */
+#define FL_RIGHT       0x004   /* `-' seen */
+#define FL_BLANK       0x008   /* ` ' seen */
+#define FL_SHORT       0x010   /* `h' seen */
+#define FL_LONG                0x020   /* `l' seen */
+#define FL_ZERO                0x040   /* `0' seen */
+#define FL_DOT         0x080   /* '.' seen */
+#define FL_UPPER       0x100   /* format character was uppercase */
+#define FL_NUMBER      0x200   /* a number was formated %[douxefg] */
+
+
+#ifdef FP
+#include <math.h>
+
+static double
+my_ceil(d)
+       double  d;
+{
+       double          i;
+
+       return d - modf(d, &i) + (d < 0 ? -1 : 1);
+}
+#endif /* FP */
+
+int
+shf_vfprintf(shf, fmt, args)
+       struct shf *shf;
+       const char *fmt;
+       va_list args;
+{
+       char            c, *s;
+       int             UNINITIALIZED(tmp);
+       int             field, precision;
+       int             len;
+       int             flags;
+       unsigned long   lnum;
+                                       /* %#o produces the longest output */
+       char            numbuf[(BITS(long) + 2) / 3 + 1];
+       /* this stuff for dealing with the buffer */
+       int             nwritten = 0;
+       static char nulls[] = "(null %s)";
+#ifdef FP
+       /* should be in <math.h>
+        *  extern double frexp();
+        */
+       extern char *ecvt();
+
+       double          fpnum;
+       int             expo, decpt;
+       char            style;
+       char            fpbuf[FPBUF_SIZE];
+#endif /* FP */
+
+       if (!fmt)
+               return 0;
+
+       while ((c = *fmt++)) {
+               if (c != '%') {
+                       shf_putc(c, shf);
+                       nwritten++;
+                       continue;
+               }
+               /*
+                *      This will accept flags/fields in any order - not
+                *  just the order specified in printf(3), but this is
+                *  the way _doprnt() seems to work (on bsd and sysV).
+                *  The only restriction is that the format character must
+                *  come last :-).
+                */
+               flags = field = precision = 0;
+               for ( ; (c = *fmt++) ; ) {
+                       switch (c) {
+                       case '#':
+                               flags |= FL_HASH;
+                               continue;
+
+                       case '+':
+                               flags |= FL_PLUS;
+                               continue;
+
+                       case '-':
+                               flags |= FL_RIGHT;
+                               continue;
+
+                       case ' ':
+                               flags |= FL_BLANK;
+                               continue;
+
+                       case '0':
+                               if (!(flags & FL_DOT))
+                                       flags |= FL_ZERO;
+                               continue;
+
+                       case '.':
+                               flags |= FL_DOT;
+                               precision = 0;
+                               continue;
+
+                       case '*':
+                               tmp = va_arg(args, int);
+                               if (flags & FL_DOT)
+                                       precision = tmp;
+                               else if ((field = tmp) < 0) {
+                                       field = -field;
+                                       flags |= FL_RIGHT;
+                               }
+                               continue;
+
+                       case 'l':
+                               flags |= FL_LONG;
+                               continue;
+
+                       case 'h':
+                               flags |= FL_SHORT;
+                               continue;
+                       }
+                       if (digit(c)) {
+                               tmp = c - '0';
+                               while (c = *fmt++, digit(c))
+                                       tmp = tmp * 10 + c - '0';
+                               --fmt;
+                               if (tmp < 0)            /* overflow? */
+                                       tmp = 0;
+                               if (flags & FL_DOT)
+                                       precision = tmp;
+                               else
+                                       field = tmp;
+                               continue;
+                       }
+                       break;
+               }
+
+               if (precision < 0)
+                       precision = 0;
+
+               if (!c)         /* nasty format */
+                       break;
+
+               if (c >= 'A' && c <= 'Z') {
+                       flags |= FL_UPPER;
+                       c = c - 'A' + 'a';
+               }
+
+               switch (c) {
+               case 'p': /* pointer */
+                       flags &= ~(FL_LONG | FL_SHORT);
+                       if (sizeof(char *) > sizeof(int))
+                               flags |= FL_LONG; /* hope it fits.. */
+                       /* aaahhh... */
+               case 'd':
+               case 'i':
+               case 'o':
+               case 'u':
+               case 'x':
+                       flags |= FL_NUMBER;
+                       s = &numbuf[sizeof(numbuf)];
+                       lnum = POP_INT(flags, c == 'd', args);
+                       switch (c) {
+                       case 'd':
+                       case 'i':
+                               if (0 > (long) lnum)
+                                       lnum = - (long) lnum, tmp = 1;
+                               else
+                                       tmp = 0;
+                               /* aaahhhh..... */
+
+                       case 'u':
+                               do {
+                                       *--s = lnum % 10 + '0';
+                                       lnum /= 10;
+                               } while (lnum);
+
+                               if (c != 'u') {
+                                       if (tmp)
+                                               *--s = '-';
+                                       else if (flags & FL_PLUS)
+                                               *--s = '+';
+                                       else if (flags & FL_BLANK)
+                                               *--s = ' ';
+                               }
+                               break;
+
+                       case 'o':
+                               do {
+                                       *--s = (lnum & 0x7) + '0';
+                                       lnum >>= 3;
+                               } while (lnum);
+
+                               if ((flags & FL_HASH) && *s != '0')
+                                       *--s = '0';
+                               break;
+
+                       case 'p':
+                       case 'x':
+                           {
+                               const char *digits = (flags & FL_UPPER) ?
+                                                 "0123456789ABCDEF"
+                                               : "0123456789abcdef";
+                               do {
+                                       *--s = digits[lnum & 0xf];
+                                       lnum >>= 4;
+                               } while (lnum);
+
+                               if (flags & FL_HASH) {
+                                       *--s = (flags & FL_UPPER) ? 'X' : 'x';
+                                       *--s = '0';
+                               }
+                           }
+                       }
+                       len = &numbuf[sizeof(numbuf)] - s;
+                       if (flags & FL_DOT) {
+                               if (precision > len) {
+                                       field = precision;
+                                       flags |= FL_ZERO;
+                               } else
+                                       precision = len; /* no loss */
+                       }
+                       break;
+
+#ifdef FP
+               case 'e':
+               case 'g':
+               case 'f':
+                   {
+                       char *p;
+
+                       /*
+                        *      This could probably be done better,
+                        *  but it seems to work.  Note that gcvt()
+                        *  is not used, as you cannot tell it to
+                        *  not strip the zeros.
+                        */
+                       flags |= FL_NUMBER;
+                       if (!(flags & FL_DOT))
+                               precision = 6;  /* default */
+                       /*
+                        *      Assumes doubles are pushed on
+                        *  the stack.  If this is not so, then
+                        *  FL_LONG/FL_SHORT should be checked.
+                        */
+                       fpnum = va_arg(args, double);
+                       s = fpbuf;
+                       style = c;
+                       /*
+                        *  This is the same as
+                        *      expo = ceil(log10(fpnum))
+                        *  but doesn't need -lm.  This is an
+                        *  approximation as expo is rounded up.
+                        */
+                       (void) frexp(fpnum, &expo);
+                       expo = my_ceil(expo / LOG2_10);
+
+                       if (expo < 0)
+                               expo = 0;
+
+                       p = ecvt(fpnum, precision + 1 + expo,
+                                &decpt, &tmp);
+                       if (c == 'g') {
+                               if (decpt < -4 || decpt > precision)
+                                       style = 'e';
+                               else
+                                       style = 'f';
+                               if (decpt > 0 && (precision -= decpt) < 0)
+                                       precision = 0;
+                       }
+                       if (tmp)
+                               *s++ = '-';
+                       else if (flags & FL_PLUS)
+                               *s++ = '+';
+                       else if (flags & FL_BLANK)
+                               *s++ = ' ';
+
+                       if (style == 'e')
+                               *s++ = *p++;
+                       else {
+                               if (decpt > 0) {
+                                       /* Overflow check - should
+                                        * never have this problem.
+                                        */
+                                       if (decpt >
+                                               &fpbuf[sizeof(fpbuf)]
+                                                       - s - 8)
+                                               decpt =
+                                                &fpbuf[sizeof(fpbuf)]
+                                                       - s - 8;
+                                       (void) memcpy(s, p, decpt);
+                                       s += decpt;
+                                       p += decpt;
+                               } else
+                                       *s++ = '0';
+                       }
+
+                       /* print the fraction? */
+                       if (precision > 0) {
+                               *s++ = '.';
+                               /* Overflow check - should
+                                * never have this problem.
+                                */
+                               if (precision > &fpbuf[sizeof(fpbuf)]
+                                                       - s - 7)
+                                       precision =
+                                               &fpbuf[sizeof(fpbuf)]
+                                               - s - 7;
+                               for (tmp = decpt;  tmp++ < 0 &&
+                                           precision > 0 ; precision--)
+                                       *s++ = '0';
+                               tmp = strlen(p);
+                               if (precision > tmp)
+                                       precision = tmp;
+                               /* Overflow check - should
+                                * never have this problem.
+                                */
+                               if (precision > &fpbuf[sizeof(fpbuf)]
+                                                       - s - 7)
+                                       precision =
+                                               &fpbuf[sizeof(fpbuf)]
+                                               - s - 7;
+                               (void) memcpy(s, p, precision);
+                               s += precision;
+                               /*
+                                *      `g' format strips trailing
+                                *  zeros after the decimal.
+                                */
+                               if (c == 'g' && !(flags & FL_HASH)) {
+                                       while (*--s == '0')
+                                               ;
+                                       if (*s != '.')
+                                               s++;
+                               }
+                       } else if (flags & FL_HASH)
+                               *s++ = '.';
+
+                       if (style == 'e') {
+                               *s++ = (flags & FL_UPPER) ? 'E' : 'e';
+                               if (--decpt >= 0)
+                                       *s++ = '+';
+                               else {
+                                       *s++ = '-';
+                                       decpt = -decpt;
+                               }
+                               p = &numbuf[sizeof(numbuf)];
+                               for (tmp = 0; tmp < 2 || decpt ; tmp++) {
+                                       *--p = '0' + decpt % 10;
+                                       decpt /= 10;
+                               }
+                               tmp = &numbuf[sizeof(numbuf)] - p;
+                               (void) memcpy(s, p, tmp);
+                               s += tmp;
+                       }
+
+                       len = s - fpbuf;
+                       s = fpbuf;
+                       precision = len;
+                       break;
+                   }
+#endif /* FP */
+
+               case 's':
+                       if (!(s = va_arg(args, char *)))
+                               s = nulls;
+                       len = strlen(s);
+                       break;
+
+               case 'c':
+                       flags &= ~FL_DOT;
+                       numbuf[0] = va_arg(args, int);
+                       s = numbuf;
+                       len = 1;
+                       break;
+
+               case '%':
+               default:
+                       numbuf[0] = c;
+                       s = numbuf;
+                       len = 1;
+                       break;
+               }
+
+               /*
+                *      At this point s should point to a string that is
+                *  to be formatted, and len should be the length of the
+                *  string.
+                */
+               if (!(flags & FL_DOT) || len < precision)
+                       precision = len;
+               if (field > precision) {
+                       field -= precision;
+                       if (!(flags & FL_RIGHT)) {
+                               field = -field;
+                               /* skip past sign or 0x when padding with 0 */
+                               if ((flags & FL_ZERO) && (flags & FL_NUMBER)) {
+                                       if (*s == '+' || *s == '-' || *s ==' ')
+                                       {
+                                               shf_putc(*s, shf);
+                                               s++;
+                                               precision--;
+                                               nwritten++;
+                                       } else if (*s == '0') {
+                                               shf_putc(*s, shf);
+                                               s++;
+                                               nwritten++;
+                                               if (--precision > 0 &&
+                                                       (*s | 0x20) == 'x')
+                                               {
+                                                       shf_putc(*s, shf);
+                                                       s++;
+                                                       precision--;
+                                                       nwritten++;
+                                               }
+                                       }
+                                       c = '0';
+                               } else
+                                       c = flags & FL_ZERO ? '0' : ' ';
+                               if (field < 0) {
+                                       nwritten += -field;
+                                       for ( ; field < 0 ; field++)
+                                               shf_putc(c, shf);
+                               }
+                       } else
+                               c = ' ';
+               } else
+                       field = 0;
+
+               if (precision > 0) {
+                       nwritten += precision;
+                       for ( ; precision-- > 0 ; s++)
+                               shf_putc(*s, shf);
+               }
+               if (field > 0) {
+                       nwritten += field;
+                       for ( ; field > 0 ; --field)
+                               shf_putc(c, shf);
+               }
+       }
+
+       return shf_error(shf) ? EOF : nwritten;
+}
diff --git a/bin/ksh/shf.h b/bin/ksh/shf.h
new file mode 100644 (file)
index 0000000..bc8af96
--- /dev/null
@@ -0,0 +1,87 @@
+/*     $NetBSD: shf.h,v 1.3 1999/10/20 15:10:00 hubertf Exp $  */
+
+#ifndef SHF_H
+# define SHF_H
+
+/*
+ * Shell file I/O routines
+ */
+/* $Id: shf.h,v 1.3 1999/10/20 15:10:00 hubertf Exp $ */
+
+#define SHF_BSIZE      512
+
+#define shf_fileno(shf)        ((shf)->fd)
+#define shf_setfileno(shf,nfd) ((shf)->fd = (nfd))
+#define shf_getc(shf) ((shf)->rnleft > 0 ? (shf)->rnleft--, *(shf)->rp++ : \
+                       shf_getchar(shf))
+#define shf_putc(c, shf)       ((shf)->wnleft == 0 ? shf_putchar((c), (shf)) \
+                                       : ((shf)->wnleft--, *(shf)->wp++ = (c)))
+#define shf_eof(shf)           ((shf)->flags & SHF_EOF)
+#define shf_error(shf)         ((shf)->flags & SHF_ERROR)
+#define shf_errno(shf)         ((shf)->errno_)
+#define shf_clearerr(shf)      ((shf)->flags &= ~(SHF_EOF | SHF_ERROR))
+
+/* Flags passed to shf_*open() */
+#define SHF_RD         0x0001
+#define SHF_WR         0x0002
+#define SHF_RDWR         (SHF_RD|SHF_WR)
+#define SHF_ACCMODE      0x0003        /* mask */
+#define SHF_GETFL      0x0004          /* use fcntl() to figure RD/WR flags */
+#define SHF_UNBUF      0x0008          /* unbuffered I/O */
+#define SHF_CLEXEC     0x0010          /* set close on exec flag */
+#define SHF_MAPHI      0x0020          /* make fd > FDBASE (and close orig)
+                                        * (shf_open() only) */
+#define SHF_DYNAMIC    0x0040          /* string: increase buffer as needed */
+#define SHF_INTERRUPT  0x0080          /* EINTR in read/write causes error */
+/* Flags used internally */
+#define SHF_STRING     0x0100          /* a string, not a file */
+#define SHF_ALLOCS     0x0200          /* shf and shf->buf were alloc()ed */
+#define SHF_ALLOCB     0x0400          /* shf->buf was alloc()ed */
+#define SHF_ERROR      0x0800          /* read()/write() error */
+#define SHF_EOF                0x1000          /* read eof (sticky) */
+#define SHF_READING    0x2000          /* currently reading: rnleft,rp valid */
+#define SHF_WRITING    0x4000          /* currently writing: wnleft,wp valid */
+
+
+struct shf {
+       int flags;              /* see SHF_* */
+       unsigned char *rp;      /* read: current position in buffer */
+       int rbsize;             /* size of buffer (1 if SHF_UNBUF) */
+       int rnleft;             /* read: how much data left in buffer */
+       unsigned char *wp;      /* write: current position in buffer */
+       int wbsize;             /* size of buffer (0 if SHF_UNBUF) */
+       int wnleft;             /* write: how much space left in buffer */
+       unsigned char *buf;     /* buffer */
+       int fd;                 /* file descriptor */
+       int errno_;             /* saved value of errno after error */
+       int bsize;              /* actual size of buf */
+       Area *areap;            /* area shf/buf were allocated in */
+};
+
+extern struct shf shf_iob[];
+
+struct shf *shf_open   ARGS((const char *name, int oflags, int mode,
+                             int sflags));
+struct shf *shf_fdopen ARGS((int fd, int sflags, struct shf *shf));
+struct shf *shf_reopen  ARGS((int fd, int sflags, struct shf *shf));
+struct shf *shf_sopen  ARGS((char *buf, int bsize, int sflags,
+                             struct shf *shf));
+int        shf_close   ARGS((struct shf *shf));
+int        shf_fdclose ARGS((struct shf *shf));
+char      *shf_sclose  ARGS((struct shf *shf));
+int        shf_finish  ARGS((struct shf *shf));
+int        shf_flush   ARGS((struct shf *shf));
+int        shf_seek    ARGS((struct shf *shf, off_t where, int from));
+int        shf_read    ARGS((char *buf, int bsize, struct shf *shf));
+char      *shf_getse   ARGS((char *buf, int bsize, struct shf *shf));
+int        shf_getchar ARGS((struct shf *shf));
+int        shf_ungetc  ARGS((int c, struct shf *shf));
+int        shf_putchar ARGS((int c, struct shf *shf));
+int        shf_puts    ARGS((const char *s, struct shf *shf));
+int        shf_write   ARGS((const char *buf, int nbytes, struct shf *shf));
+int        shf_fprintf ARGS((struct shf *shf, const char *fmt, ...));
+int        shf_snprintf ARGS((char *buf, int bsize, const char *fmt, ...));
+char       *shf_smprintf ARGS((const char *fmt, ...));
+int        shf_vfprintf ARGS((struct shf *, const char *fmt, va_list args));
+
+#endif /* SHF_H */
diff --git a/bin/ksh/sigact.c b/bin/ksh/sigact.c
new file mode 100644 (file)
index 0000000..80ff9a8
--- /dev/null
@@ -0,0 +1,486 @@
+/*     $NetBSD: sigact.c,v 1.4 2003/06/23 11:39:03 agc Exp $   */
+
+/* NAME:
+ *      sigact.c - fake sigaction(2)
+ *
+ * SYNOPSIS:
+ *      #include "sigact.h"
+ * 
+ *      int sigaction(int sig, struct sigaction *act, 
+ *                      struct sigaction *oact);
+ *      int sigaddset(sigset_t *mask, int sig);
+ *      int sigdelset(sigset_t *mask, int sig);
+ *      int sigemptyset(sigset_t *mask);
+ *      int sigfillset(sigset_t *mask);
+ *      int sigismember(sigset_t *mask, int sig);
+ *      int sigpending(sigset_t *set);
+ *      int sigprocmask(int how, sigset_t *set, sigset_t *oset);
+ *      int sigsuspend(sigset_t *mask);
+ *      
+ *      RETSIGTYPE (*Signal(int sig, RETSIGTYPE (*disp)(int)))(int);
+ *
+ * DESCRIPTION:
+ *      This is a fake sigaction implementation.  It uses 
+ *      sigsetmask(2) et al or sigset(2) and friends if 
+ *      available, otherwise it just uses signal(2).  If it 
+ *      thinks sigaction(2) really exists it compiles to "almost" 
+ *      nothing. 
+ *      
+ *      In any case it provides a Signal() function that is 
+ *      implemented in terms of sigaction().
+ *      If not using signal(2) as part of the underlying 
+ *      implementation (USE_SIGNAL or USE_SIGMASK), and 
+ *      NO_SIGNAL is not defined, it also provides a signal() 
+ *      function that calls Signal(). 
+ *
+ *      The need for all this mucking about is the problems 
+ *      caused by mixing various signal handling mechanisms in 
+ *      the one process.  This module allows for a consistent 
+ *      POSIX compliant interface to whatever is actually 
+ *      available. 
+ *      
+ *      sigaction() allows the caller to examine and/or set the 
+ *      action to be associated with a given signal. "act" and 
+ *      "oact" are pointers to 'sigaction structs':
+ *.nf
+ * 
+ *      struct sigaction 
+ *      {
+ *             RETSIGTYPE  (*sa_handler)();
+ *             sigset_t  sa_mask;
+ *             int       sa_flags;
+ *      };
+ *.fi
+ * 
+ *      RETSIGTYPE is normally 'void' in the POSIX implementation 
+ *      and for most current systems.  On some older UNIX 
+ *      systems, signal handlers do not return 'void', so  
+ *      this implementation keeps 'sa_handler' inline with the 
+ *      hosts normal signal handling conventions.
+ *      'sa_mask' controls which signals will be blocked while 
+ *      the selected signal handler is active.  It is not used 
+ *      in this implementation.
+ *      'sa_flags' controls various semantics such as whether 
+ *      system calls should be automagically restarted 
+ *      (SA_RESTART) etc.  It is not used in this 
+ *      implementation. 
+ *      Either "act" or "oact" may be NULL in which case the 
+ *      appropriate operation is skipped.
+ *      
+ *      sigaddset() adds "sig" to the sigset_t pointed to by "mask".
+ *      
+ *      sigdelset() removes "sig" from the sigset_t pointed to 
+ *      by "mask". 
+ *      
+ *      sigemptyset() makes the sigset_t pointed to by "mask" empty.
+ *      
+ *      sigfillset() makes the sigset_t pointed to by "mask" 
+ *      full ie. match all signals.
+ *      
+ *      sigismember() returns true if "sig" is found in "*mask".
+ *      
+ *      sigpending() is supposed to return "set" loaded with the 
+ *      set of signals that are blocked and pending for the 
+ *      calling process.  It does nothing in this impementation.
+ *      
+ *      sigprocmask() is used to examine and/or change the 
+ *      signal mask for the calling process.  Either "set" or 
+ *      "oset" may be NULL in which case the appropriate 
+ *      operation is skipped.  "how" may be one of SIG_BLOCK, 
+ *      SIG_UNBLOCK or SIG_SETMASK.  If this package is built 
+ *      with USE_SIGNAL, then this routine achieves nothing.
+ *      
+ *      sigsuspend() sets the signal mask to "*mask" and waits 
+ *      for a signal to be delivered after which the previous 
+ *      mask is restored.
+ *      
+ *      
+ * RETURN VALUE:
+ *      0==success, -1==failure
+ *
+ * BUGS:
+ *      Since we fake most of this, don't expect fancy usage to 
+ *      work.
+ *
+ * AUTHOR:
+ *      Simon J. Gerraty <sjg@zen.void.oz.au>
+ */     
+/* COPYRIGHT:
+ *      @(#)Copyright (c) 1992 Simon J. Gerraty
+ *
+ *      This is free software.  It comes with NO WARRANTY.
+ *      Permission to use, modify and distribute this source code
+ *      is granted subject to the following conditions.
+ *      1/ that that the above copyright notice and this notice 
+ *      are preserved in all copies and that due credit be given 
+ *      to the author.  
+ *      2/ that any changes to this code are clearly commented 
+ *      as such so that the author does get blamed for bugs 
+ *      other than his own.
+ *      
+ *      Please send copies of changes and bug-fixes to:
+ *      sjg@zen.void.oz.au
+ *
+ */
+/* Changes to sigact.c for pdksh, Michael Rendell <michael@cs.mun.ca>:
+ *     - sigsuspend(): pass *mask to bsd4.2 sigpause instead of mask.
+ *     - changed SIG_HDLR to RETSIGTYPE for use with GNU autoconf
+ *     - added and used RETSIGVAL
+ *     - include sh.h instead of signal.h (to get *_SIGNALS macros)
+ *     - changed if !SA_NOCLDSTOP ... to USE_FAKE_SIGACT to avoid confusion
+ *     - set the USE_* defines using the *_SIGNALS defines from autoconf
+ *     - sigaction(): if using BSD signals, use sigvec() (used to use
+ *       signal()) and set the SV_INTERRUPT flag (POSIX says syscalls
+ *       are interrupted and pdksh needs this behaviour).
+ *     - define IS_KSH before including anything; ifdef out routines
+ *       not used in ksh if IS_KSH is defined (same in sigact.h).
+ *     - use ARGS() instead of __P()
+ *     - sigaction(),sigsuspend(),Signal(),signal(): use handler_t typedef
+ *       instead of explicit type.
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: sigact.c,v 1.4 2003/06/23 11:39:03 agc Exp $");
+#endif
+
+
+/*
+    #include <signal.h>
+*/
+#define IS_KSH
+#include "sh.h"
+
+/*
+    #ifndef __P
+    # define   __P(p)  p
+    #endif
+*/
+
+
+/*
+ * some systems have a faulty sigaction() implementation!
+ * Allow us to bypass it.
+ * Or they may have installed sigact.h as signal.h which is why 
+ * we have SA_NOCLDSTOP defined.
+ */
+#ifdef USE_FAKE_SIGACT /* let autoconf decide.. */
+/* #if !defined(SA_NOCLDSTOP) || defined(_SIGACT_H) || defined(USE_SIGNAL) || defined(USE_SIGSET) || defined(USE_SIGMASK) */
+
+/* Let autoconf decide which to use */
+#ifdef BSD42_SIGNALS
+# define USE_SIGMASK
+#else
+# ifdef BSD41_SIGNALS
+#  define USE_SIGSET
+# else
+#  define USE_SIGNAL
+# endif
+#endif /* BSD42_SIGNALS */
+
+/*
+ * if we haven't been told,
+ * try and guess what we should implement with.
+ */
+#if !defined(USE_SIGSET) && !defined(USE_SIGMASK) && !defined(USE_SIGNAL)
+# if defined(sigmask) || defined(BSD) || defined(_BSD) && !defined(BSD41)
+#   define USE_SIGMASK
+# else
+#   ifndef NO_SIGSET
+#     define USE_SIGSET
+#   else
+#     define USE_SIGNAL
+#   endif
+# endif
+#endif
+/*
+ * if we still don't know, we're in trouble
+ */
+#if !defined(USE_SIGSET) && !defined(USE_SIGMASK) && !defined(USE_SIGNAL)
+error must know what to implement with
+#endif
+
+#include "sigact.h"
+
+/*
+ * in case signal() has been mapped to our Signal().
+ */
+#undef signal
+
+int
+sigaction(sig, act, oact)
+  int sig;
+  struct sigaction *act, *oact;
+{
+  handler_t oldh;
+
+  if (act)
+  {
+#ifdef USE_SIGSET
+    oldh = sigset(sig, act->sa_handler);
+#else
+# ifdef USE_SIGMASK
+    struct sigvec nsv,osv;
+
+    nsv.sv_handler = act->sa_handler;
+    nsv.sv_mask = 0;                   /* punt */
+    nsv.sv_flags = SV_INTERRUPT;       /* punt */
+    sigvec(sig, &nsv, &osv);
+    oldh = osv.sv_handler;
+# else /* USE_SIGMASK */
+    oldh = signal(sig, act->sa_handler);
+# endif /* USE_SIGMASK */
+#endif
+  }
+  else
+  {
+    if (oact)
+    {      
+#ifdef USE_SIGSET
+      oldh = sigset(sig, SIG_IGN);
+#else
+      oldh = signal(sig, SIG_IGN);
+#endif
+      if (oldh != SIG_IGN && oldh !=  SIG_ERR)
+      {
+#ifdef USE_SIGSET
+       (void) sigset(sig, oldh);
+#else
+       (void) signal(sig, oldh);
+#endif
+      }
+    }
+  }
+  if (oact)
+  {
+    oact->sa_handler = oldh;
+  }
+  return 0;                            /* hey we're faking it */
+}
+
+
+int
+sigaddset(mask, sig)
+  sigset_t *mask;
+  int sig;
+{
+  *mask |= sigmask(sig);
+  return 0;
+}
+
+
+#ifndef IS_KSH
+int
+sigdelset(mask, sig)
+  sigset_t *mask;
+  int sig;
+{
+  *mask &= ~(sigmask(sig));
+  return 0;
+}
+#endif /* IS_KSH */
+
+
+int
+sigemptyset(mask)
+  sigset_t *mask;
+{
+  *mask = 0;
+  return 0;
+}
+
+
+#ifndef IS_KSH
+int
+sigfillset(mask)
+  sigset_t *mask;
+{
+  *mask = ~0;
+  return 0;
+}
+#endif /* IS_KSH */
+
+
+#ifndef IS_KSH
+int
+sigismember(mask, sig)
+  sigset_t *mask;
+  int sig;
+{
+  return ((*mask) & sigmask(sig));
+}
+#endif /* IS_KSH */
+
+
+#ifndef IS_KSH
+int
+sigpending(set)
+  sigset_t *set;
+{
+  return 0;                            /* faking it! */
+}
+#endif /* IS_KSH */
+
+
+int
+sigprocmask(how, set, oset)
+  int how;
+  sigset_t *set, *oset;
+{
+#ifdef USE_SIGSET
+  register int i;
+#endif
+  static sigset_t sm;
+  static int once = 0;
+
+  if (!once)
+  {
+    /*
+     * initally we clear sm,
+     * there after, it represents the last
+     * thing we did.
+     */
+    once++;
+#ifdef USE_SIGMASK
+    sm = sigblock(0);
+#else
+    sm = 0;
+#endif
+  }
+  
+  if (oset)
+    *oset = sm;
+  if (set)
+  {
+    switch (how)
+    {
+    case SIG_BLOCK:
+      sm |= *set;
+      break;
+    case SIG_UNBLOCK:
+      sm &= ~(*set);
+      break;
+    case SIG_SETMASK:
+      sm = *set;
+      break;
+    }
+#ifdef USE_SIGMASK
+    (void) sigsetmask(sm);
+#else
+# ifdef USE_SIGSET
+    for (i = 1; i < NSIG; i++)
+    {
+      if (how == SIG_UNBLOCK)
+      {
+       if (*set & sigmask(i))
+         sigrelse(i);
+      }
+      else
+       if (sm & sigmask(i))
+       {
+         sighold(i);
+       }
+    }
+# endif
+#endif
+  }
+  return 0;
+}
+
+
+int
+sigsuspend(mask)
+  sigset_t *mask;
+{
+#ifdef USE_SIGMASK
+  sigpause(*mask);
+#else
+  register int i;
+
+# ifdef USE_SIGSET
+
+  for (i = 1; i < NSIG; i++)
+  {
+    if (*mask & sigmask(i))
+    {
+      /* not the same sigpause() as above! */
+      sigpause(i);                     
+      break;
+    }
+  }
+# else /* signal(2) only */
+  handler_t oldh;
+
+  /*
+   * make sure that signals in mask will not
+   * be ignored.
+   */
+  for (i = 1; i < NSIG; i++)
+  {
+    if (*mask & sigmask(i))
+    {
+      if ((oldh = signal(i, SIG_DFL)) !=  SIG_ERR &&
+         oldh != SIG_IGN &&
+         oldh != SIG_DFL)
+       (void) signal(i, oldh);         /* restore handler */
+    }
+  }
+  pause();                             /* wait for a signal */
+# endif
+#endif
+  return 0;
+}
+
+#endif /* USE_FAKE_SIGACT (was ! SA_NOCLDSTOP) */
+
+#if !defined(RETSIGTYPE)
+# define RETSIGTYPE void
+# define RETSIGVAL
+#endif
+#if !defined(SIG_ERR)
+# define SIG_ERR       (RETSIGTYPE (*)())-1
+#endif
+
+/*
+ * an implementation of signal() using sigaction().
+ */
+
+#ifndef IS_KSH
+handler_t Signal(sig, handler)
+  int sig;
+  handler_t handler;
+{
+  struct sigaction act, oact;
+
+  act.sa_handler = handler;
+  sigemptyset(&act.sa_mask);
+  act.sa_flags = 0;
+  if (sigaction(sig, &act, &oact) < 0)
+    return (SIG_ERR);
+  return (oact.sa_handler);
+}
+#endif /* IS_KSH */
+
+#ifndef IS_KSH
+#if !defined(USE_SIGNAL) && !defined(USE_SIGMASK) && !defined(NO_SIGNAL)
+/*
+ * ensure we avoid signal mayhem
+ */
+
+handler_t signal(sig, handler)
+  int sig;
+  handler_t handler;
+{
+  return (Signal(sig, handler));
+}
+#endif
+#endif /* IS_KSH */
+
+/* This lot (for GNU-Emacs) goes at the end of the file. */
+/* 
+ * Local Variables:
+ * version-control:t
+ * comment-column:40
+ * End:
+ */
diff --git a/bin/ksh/sigact.h b/bin/ksh/sigact.h
new file mode 100644 (file)
index 0000000..e1dce46
--- /dev/null
@@ -0,0 +1,125 @@
+/*     $NetBSD: sigact.h,v 1.3 2002/05/25 23:29:17 wiz Exp $   */
+
+/* NAME:
+ *      sigact.h - sigaction et al
+ *
+ * SYNOPSIS:
+ *      #include "sigact.h"
+ *
+ * DESCRIPTION:
+ *      This header is the interface to a fake sigaction(2) 
+ *      implementation. It provides a POSIX compliant interface 
+ *      to whatever signal handling mechanisms are available.
+ *      It also provides a Signal() function that is implemented 
+ *      in terms of sigaction().
+ *      If not using signal(2) as part of the underlying 
+ *      implementation (USE_SIGNAL or USE_SIGMASK), and 
+ *      NO_SIGNAL is not defined, it also provides a signal() 
+ *      function that calls Signal(). 
+ *      
+ * SEE ALSO:
+ *      sigact.c
+ */
+/*
+ * RCSid:
+ *      $NetBSD: sigact.h,v 1.3 2002/05/25 23:29:17 wiz Exp $
+ */
+/* Changes to sigact.h for pdksh, Michael Rendell <michael@cs.mun.ca>:
+ *     - changed SIG_HDLR to RETSIGTYPE for use with GNU autoconf
+ *     - added RETSIGVAL
+ *     - ifdef'd out ARGS(), volatile and const initializations
+ *     - ifdef'd out sigset_t definition - let autoconf handle it
+ *     - ifdef out routines not used in ksh if IS_KSH is defined
+ *       (same in sigact.c).
+ */
+#ifndef _SIGACT_H
+#define _SIGACT_H
+
+/*
+ * most modern systems use void for signal handlers but
+ * not all.
+ */
+#ifndef RETSIGTYPE
+# define RETSIGTYPE void
+# define RETSIGVAL
+#endif
+
+#if 0 /* ARGS(), volatile and const are already set up in config*.h -mhr */
+#undef ARGS
+#define ARGS(p) p
+#endif
+
+#ifndef IS_KSH
+handler_t Signal       ARGS((int sig, handler_t disp));
+#endif /* IS_KSH */
+
+/*
+ * if you want to install this header as signal.h,
+ * modify this to pick up the original signal.h
+ */
+#ifndef SIGKILL
+# include <signal.h>
+#endif
+  
+#ifndef SIG_ERR
+# define SIG_ERR  ((handler_t) -1)
+#endif
+#ifndef BADSIG
+# define BADSIG  SIG_ERR
+#endif
+    
+#ifndef SA_NOCLDSTOP
+/* we assume we need the fake sigaction */
+/* sa_flags */
+#define        SA_NOCLDSTOP    1               /* don't send SIGCHLD on child stop */
+#define SA_RESTART     2               /* re-start I/O */
+
+/* sigprocmask flags */
+#define        SIG_BLOCK       1
+#define        SIG_UNBLOCK     2
+#define        SIG_SETMASK     4
+
+#if 0 /* autoconf will define sigset_t if it isn't available */
+/*
+ * this is a bit untidy
+ */
+#if !defined(__sys_stdtypes_h)
+typedef unsigned int sigset_t;
+#endif
+#endif /* 0 */
+  
+/*
+ * POSIX sa_handler should return void, but since we are
+ * implementing in terms of something else, it may
+ * be appropriate to use the normal RETSIGTYPE return type
+ */
+struct sigaction
+{
+  handler_t    sa_handler;
+  sigset_t     sa_mask;
+  int          sa_flags;
+};
+
+
+int    sigaction       ARGS(( int sig, struct sigaction *act, struct sigaction *oact ));
+int    sigaddset       ARGS(( sigset_t *mask, int sig ));
+#ifndef IS_KSH
+int    sigdelset       ARGS(( sigset_t *mask, int sig ));
+#endif /* IS_KSH */
+int    sigemptyset     ARGS(( sigset_t *mask ));
+#ifndef IS_KSH
+int    sigfillset      ARGS(( sigset_t *mask ));
+int    sigismember     ARGS(( sigset_t *mask, int sig ));
+int    sigpending      ARGS(( sigset_t *set ));
+#endif /* IS_KSH */
+int    sigprocmask     ARGS(( int how, sigset_t *set, sigset_t *oset ));
+int    sigsuspend      ARGS(( sigset_t *mask ));
+       
+#ifndef sigmask
+# define sigmask(s)    (1<<((s)-1))    /* convert SIGnum to mask */
+#endif
+#if !defined(NSIG) && defined(_NSIG)
+# define NSIG _NSIG
+#endif
+#endif /* ! SA_NOCLDSTOP */
+#endif /* _SIGACT_H */
diff --git a/bin/ksh/siglist.in b/bin/ksh/siglist.in
new file mode 100644 (file)
index 0000000..8aa29a6
--- /dev/null
@@ -0,0 +1,56 @@
+#      $NetBSD: siglist.in,v 1.2 1997/01/12 19:12:17 tls Exp $
+#
+# List of signals used to initialize ksh's signal table (see trap.c
+# and siglist.sh).
+#
+# Note that if a system has multiple defines for the same signal
+# (eg, SIGABRT vs SIGIOT, SIGCHLD vs SIGCLD), only the first one
+# will be seen, so the order in this list is important.
+#
+    HUP    Hangup
+    INT    Interrupt
+    QUIT   Quit
+    ILL    Illegal instruction
+    TRAP   Trace trap
+# before IOT (ABRT is posix and ABRT is sometimes the same as IOT)
+    ABRT   Abort
+    IOT    IOT instruction
+    EMT    EMT trap
+    FPE    Floating point exception
+    KILL   Killed
+# before BUS (linux doesn't really have a BUS, but defines it to UNUSED)
+    UNUSED Unused
+    BUS    Bus error
+    SEGV   Memory fault
+    SYS    Bad system call
+    PIPE   Broken pipe
+    ALRM   Alarm clock
+    TERM   Terminated
+    STKFLT Stack fault
+    IO     I/O possible
+    XCPU   CPU time limit exceeded
+    XFSZ   File size limit exceeded
+    VTALRM Virtual timer expired
+    PROF   Profiling timer expired
+    WINCH  Window size change
+    LOST   File lock lost
+    USR1   User defined signal 1
+    USR2   User defined signal 2
+    PWR    Power-fail/Restart
+    POLL   Pollable event occurred
+    STOP   Stopped (signal)
+    TSTP   Stopped
+    CONT   Continued
+# before CLD (CHLD is posix and CHLD is sometimes the same as CLD)
+    CHLD   Child exited
+    CLD    Child exited
+    TTIN   Stopped (tty input)
+    TTOU   Stopped (tty output)
+    INFO   Information request
+    URG    Urgent I/O condition
+# Solaris (svr4?) signals
+    WAITING No runnable LWPs
+    LWP           Inter-LWP signal
+    FREEZE Checkpoint freeze
+    THAW   Checkpoint thaw
+    CANCEL Thread cancellation
diff --git a/bin/ksh/siglist.sh b/bin/ksh/siglist.sh
new file mode 100755 (executable)
index 0000000..e5d7084
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/sh
+#      $NetBSD: siglist.sh,v 1.9 2011/01/23 17:11:55 hauke Exp $
+#
+# Script to generate a sorted, complete list of signals, suitable
+# for inclusion in trap.c as array initializer.
+#
+
+set -e
+
+: ${AWK:=awk}
+: ${SED:=sed}
+
+in=tmpi$$.c
+out=tmpo$$.c
+ecode=1
+trapsigs='0 1 2 13 15'
+trap 'rm -f $in $out; trap 0; exit $ecode' $trapsigs
+
+CPP="${1-cc -E}"
+
+# The trap here to make up for a bug in bash (1.14.3(1)) that calls the trap
+(trap $trapsigs;
+ echo '#include "sh.h"';
+ echo '        { QwErTy SIGNALS , "DUMMY" , "hook for number of signals" },';
+ ${SED} -e '/^[         ]*#/d' -e 's/^[         ]*\([^         ][^     ]*\)[    ][      ]*\(.*[^       ]\)[    ]*$/#ifdef SIG\1\
+       { QwErTy .signal = SIG\1 , .name = "\1", .mess = "\2" },\
+#endif/') > $in
+$CPP $in  > $out
+${SED} -n 's/{ QwErTy/{/p' < $out | ${AWK} '{print NR, $0}' | sort -k 5n -k 1n |
+    ${SED} 's/^[0-9]* //' |
+    ${AWK} 'BEGIN { last=0; nsigs=0; }
+       {
+           if ($4 ~ /^[0-9][0-9]*$/ && $5 == ",") {
+               n = $4;
+               if (n > 0 && n != last) {
+                   while (++last < n) {
+                       printf "\t{ .signal = %d , .name = NULL, .mess = `Signal %d` } ,\n", last, last;
+                   }
+                   print;
+               }
+           }
+       }' |
+    tr '`' '"' | grep -v '"DUMMY"'
+ecode=0
diff --git a/bin/ksh/syn.c b/bin/ksh/syn.c
new file mode 100644 (file)
index 0000000..01cc8ed
--- /dev/null
@@ -0,0 +1,958 @@
+/*     $NetBSD: syn.c,v 1.9 2006/10/16 00:07:32 christos Exp $ */
+
+/*
+ * shell parser (C version)
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: syn.c,v 1.9 2006/10/16 00:07:32 christos Exp $");
+#endif
+
+
+#include "sh.h"
+#include "c_test.h"
+#include <assert.h>
+
+struct nesting_state {
+       int     start_token;    /* token than began nesting (eg, FOR) */
+       int     start_line;     /* line nesting began on */
+};
+
+static void    yyparse         ARGS((void));
+static struct op *pipeline     ARGS((int cf));
+static struct op *andor                ARGS((void));
+static struct op *c_list       ARGS((int multi));
+static struct ioword *synio    ARGS((int cf));
+static void    musthave        ARGS((int c, int cf));
+static struct op *nested       ARGS((int type, int smark, int emark));
+static struct op *get_command  ARGS((int cf));
+static struct op *dogroup      ARGS((void));
+static struct op *thenpart     ARGS((void));
+static struct op *elsepart     ARGS((void));
+static struct op *caselist     ARGS((void));
+static struct op *casepart     ARGS((int endtok));
+static struct op *function_body        ARGS((char *name, int ksh_func));
+static char ** wordlist        ARGS((void));
+static struct op *block                ARGS((int type, struct op *t1, struct op *t2,
+                                     char **wp));
+static struct op *newtp                ARGS((int type));
+static void    syntaxerr       ARGS((const char *what))
+                                               GCC_FUNC_ATTR(noreturn);
+static void    nesting_push ARGS((struct nesting_state *save, int tok));
+static void    nesting_pop ARGS((struct nesting_state *saved));
+static int     assign_command ARGS((char *s));
+static int     inalias ARGS((struct source *s));
+#ifdef KSH
+static int     dbtestp_isa ARGS((Test_env *te, Test_meta meta));
+static const char *dbtestp_getopnd ARGS((Test_env *te, Test_op op,
+                                       int do_eval));
+static int     dbtestp_eval ARGS((Test_env *te, Test_op op, const char *opnd1,
+                               const char *opnd2, int do_eval));
+static void    dbtestp_error ARGS((Test_env *te, int offset, const char *msg));
+#endif /* KSH */
+
+static struct  op      *outtree; /* yyparse output */
+
+static struct nesting_state nesting;   /* \n changed to ; */
+
+static int     reject;         /* token(cf) gets symbol again */
+static int     symbol;         /* yylex value */
+
+#define        REJECT  (reject = 1)
+#define        ACCEPT  (reject = 0)
+#define        token(cf) \
+       ((reject) ? (ACCEPT, symbol) : (symbol = yylex(cf)))
+#define        tpeek(cf) \
+       ((reject) ? (symbol) : (REJECT, symbol = yylex(cf)))
+
+static void
+yyparse()
+{
+       int c;
+
+       ACCEPT;
+
+       outtree = c_list(source->type == SSTRING);
+       c = tpeek(0);
+       if (c == 0 && !outtree)
+               outtree = newtp(TEOF);
+       else if (c != '\n' && c != 0)
+               syntaxerr((char *) 0);
+}
+
+static struct op *
+pipeline(cf)
+       int cf;
+{
+       register struct op *t, *p, *tl = NULL;
+
+       t = get_command(cf);
+       if (t != NULL) {
+               while (token(0) == '|') {
+                       if ((p = get_command(CONTIN)) == NULL)
+                               syntaxerr((char *) 0);
+                       if (tl == NULL)
+                               t = tl = block(TPIPE, t, p, NOWORDS);
+                       else
+                               tl = tl->right = block(TPIPE, tl->right, p, NOWORDS);
+               }
+               REJECT;
+       }
+       return (t);
+}
+
+static struct op *
+andor()
+{
+       register struct op *t, *p;
+       register int c;
+
+       t = pipeline(0);
+       if (t != NULL) {
+               while ((c = token(0)) == LOGAND || c == LOGOR) {
+                       if ((p = pipeline(CONTIN)) == NULL)
+                               syntaxerr((char *) 0);
+                       t = block(c == LOGAND? TAND: TOR, t, p, NOWORDS);
+               }
+               REJECT;
+       }
+       return (t);
+}
+
+static struct op *
+c_list(multi)
+       int multi;
+{
+       register struct op *t = NULL, *p, *tl = NULL;
+       register int c;
+       int have_sep;
+
+       while (1) {
+               p = andor();
+               /* Token has always been read/rejected at this point, so
+                * we don't worry about what flags to pass token()
+                */
+               c = token(0);
+               have_sep = 1;
+               if (c == '\n' && (multi || inalias(source))) {
+                       if (!p) /* ignore blank lines */
+                               continue;
+               } else if (!p)
+                       break;
+               else if (c == '&' || c == COPROC)
+                       p = block(c == '&' ? TASYNC : TCOPROC,
+                                 p, NOBLOCK, NOWORDS);
+               else if (c != ';')
+                       have_sep = 0;
+               if (!t)
+                       t = p;
+               else if (!tl)
+                       t = tl = block(TLIST, t, p, NOWORDS);
+               else
+                       tl = tl->right = block(TLIST, tl->right, p, NOWORDS);
+               if (!have_sep)
+                       break;
+       }
+       REJECT;
+       return t;
+}
+
+static struct ioword *
+synio(cf)
+       int cf;
+{
+       register struct ioword *iop;
+       int ishere;
+
+       if (tpeek(cf) != REDIR)
+               return NULL;
+       ACCEPT;
+       iop = yylval.iop;
+       ishere = (iop->flag&IOTYPE) == IOHERE;
+       musthave(LWORD, ishere ? HEREDELIM : 0);
+       if (ishere) {
+               iop->delim = yylval.cp;
+               if (*ident != 0) /* unquoted */
+                       iop->flag |= IOEVAL;
+               if (herep >= &heres[HERES])
+                       yyerror("too many <<'s\n");
+               *herep++ = iop;
+       } else
+               iop->name = yylval.cp;
+       return iop;
+}
+
+static void
+musthave(c, cf)
+       int c, cf;
+{
+       if ((token(cf)) != c)
+               syntaxerr((char *) 0);
+}
+
+static struct op *
+nested(type, smark, emark)
+       int type, smark, emark;
+{
+       register struct op *t;
+       struct nesting_state old_nesting;
+
+       nesting_push(&old_nesting, smark);
+       t = c_list(TRUE);
+       musthave(emark, KEYWORD|ALIAS);
+       nesting_pop(&old_nesting);
+       return (block(type, t, NOBLOCK, NOWORDS));
+}
+
+static struct op *
+get_command(cf)
+       int cf;
+{
+       register struct op *t;
+       register int c, iopn = 0, syniocf;
+       struct ioword *iop, **iops;
+       XPtrV args, vars;
+       struct nesting_state old_nesting;
+
+       iops = (struct ioword **) alloc(sizeofN(struct ioword *, NUFILE+1),
+                                       ATEMP);
+       XPinit(args, 16);
+       XPinit(vars, 16);
+
+       syniocf = KEYWORD|ALIAS;
+       switch (c = token(cf|KEYWORD|ALIAS|VARASN)) {
+         default:
+               REJECT;
+               afree((void*) iops, ATEMP);
+               XPfree(args);
+               XPfree(vars);
+               return NULL; /* empty line */
+
+         case LWORD:
+         case REDIR:
+               REJECT;
+               syniocf &= ~(KEYWORD|ALIAS);
+               t = newtp(TCOM);
+               t->lineno = source->line;
+               while (1) {
+                       cf = (t->u.evalflags ? ARRAYVAR : 0)
+                            | (XPsize(args) == 0 ? ALIAS|VARASN : CMDWORD);
+                       switch (tpeek(cf)) {
+                         case REDIR:
+                               if (iopn >= NUFILE)
+                                       yyerror("too many redirections\n");
+                               iops[iopn++] = synio(cf);
+                               break;
+
+                         case LWORD:
+                               ACCEPT;
+                               /* the iopn == 0 and XPsize(vars) == 0 are
+                                * dubious but at&t ksh acts this way
+                                */
+                               if (iopn == 0 && XPsize(vars) == 0
+                                   && XPsize(args) == 0
+                                   && assign_command(ident))
+                                       t->u.evalflags = DOVACHECK;
+                               if ((XPsize(args) == 0 || Flag(FKEYWORD))
+                                   && is_wdvarassign(yylval.cp))
+                                       XPput(vars, yylval.cp);
+                               else
+                                       XPput(args, yylval.cp);
+                               break;
+
+                         case '(':
+                               /* Check for "> foo (echo hi)", which at&t ksh
+                                * allows (not POSIX, but not disallowed)
+                                */
+                               afree(t, ATEMP);
+                               if (XPsize(args) == 0 && XPsize(vars) == 0) {
+                                       ACCEPT;
+                                       goto Subshell;
+                               }
+                               /* Must be a function */
+                               if (iopn != 0 || XPsize(args) != 1
+                                   || XPsize(vars) != 0)
+                                       syntaxerr((char *) 0);
+                               ACCEPT;
+                               /*(*/
+                               musthave(')', 0);
+                               t = function_body(XPptrv(args)[0], FALSE);
+                               goto Leave;
+
+                         default:
+                               goto Leave;
+                       }
+               }
+         Leave:
+               break;
+
+         Subshell:
+         case '(':
+               t = nested(TPAREN, '(', ')');
+               break;
+
+         case '{': /*}*/
+               t = nested(TBRACE, '{', '}');
+               break;
+
+#ifdef KSH
+         case MDPAREN:
+         {
+               static const char let_cmd[] = { CHAR, 'l', CHAR, 'e',
+                                               CHAR, 't', EOS };
+               /* Leave KEYWORD in syniocf (allow if (( 1 )) then ...) */
+               t = newtp(TCOM);
+               t->lineno = source->line;
+               ACCEPT;
+               XPput(args, wdcopy(let_cmd, ATEMP));
+               musthave(LWORD,LETEXPR);
+               XPput(args, yylval.cp);
+               break;
+         }
+#endif /* KSH */
+
+#ifdef KSH
+         case DBRACKET: /* [[ .. ]] */
+               /* Leave KEYWORD in syniocf (allow if [[ -n 1 ]] then ...) */
+               t = newtp(TDBRACKET);
+               ACCEPT;
+               {
+                       Test_env te;
+
+                       te.flags = TEF_DBRACKET;
+                       te.pos.av = &args;
+                       te.isa = dbtestp_isa;
+                       te.getopnd = dbtestp_getopnd;
+                       te.eval = dbtestp_eval;
+                       te.error = dbtestp_error;
+
+                       test_parse(&te);
+               }
+               break;
+#endif /* KSH */
+
+         case FOR:
+         case SELECT:
+               t = newtp((c == FOR) ? TFOR : TSELECT);
+               musthave(LWORD, ARRAYVAR);
+               if (!is_wdvarname(yylval.cp, TRUE))
+                       yyerror("%s: bad identifier\n",
+                               c == FOR ? "for" : "select");
+               t->str = str_save(ident, ATEMP);
+               nesting_push(&old_nesting, c);
+               t->vars = wordlist();
+               t->left = dogroup();
+               nesting_pop(&old_nesting);
+               break;
+
+         case WHILE:
+         case UNTIL:
+               nesting_push(&old_nesting, c);
+               t = newtp((c == WHILE) ? TWHILE : TUNTIL);
+               t->left = c_list(TRUE);
+               t->right = dogroup();
+               nesting_pop(&old_nesting);
+               break;
+
+         case CASE:
+               t = newtp(TCASE);
+               musthave(LWORD, 0);
+               t->str = yylval.cp;
+               nesting_push(&old_nesting, c);
+               t->left = caselist();
+               nesting_pop(&old_nesting);
+               break;
+
+         case IF:
+               nesting_push(&old_nesting, c);
+               t = newtp(TIF);
+               t->left = c_list(TRUE);
+               t->right = thenpart();
+               musthave(FI, KEYWORD|ALIAS);
+               nesting_pop(&old_nesting);
+               break;
+
+         case BANG:
+               syniocf &= ~(KEYWORD|ALIAS);
+               t = pipeline(0);
+               if (t == (struct op *) 0)
+                       syntaxerr((char *) 0);
+               t = block(TBANG, NOBLOCK, t, NOWORDS);
+               break;
+
+         case TIME:
+               syniocf &= ~(KEYWORD|ALIAS);
+               t = pipeline(0);
+               t = block(TTIME, t, NOBLOCK, NOWORDS);
+               break;
+
+         case FUNCTION:
+               musthave(LWORD, 0);
+               t = function_body(yylval.cp, TRUE);
+               break;
+       }
+
+       while ((iop = synio(syniocf)) != NULL) {
+               if (iopn >= NUFILE)
+                       yyerror("too many redirections\n");
+               iops[iopn++] = iop;
+       }
+
+       if (iopn == 0) {
+               afree((void*) iops, ATEMP);
+               t->ioact = NULL;
+       } else {
+               iops[iopn++] = NULL;
+               iops = (struct ioword **) aresize((void*) iops,
+                                       sizeofN(struct ioword *, iopn), ATEMP);
+               t->ioact = iops;
+       }
+
+       if (t->type == TCOM || t->type == TDBRACKET) {
+               XPput(args, NULL);
+               t->args = (char **) XPclose(args);
+               XPput(vars, NULL);
+               t->vars = (char **) XPclose(vars);
+       } else {
+               XPfree(args);
+               XPfree(vars);
+       }
+
+       return t;
+}
+
+static struct op *
+dogroup()
+{
+       register int c;
+       register struct op *list;
+
+       c = token(CONTIN|KEYWORD|ALIAS);
+       /* A {...} can be used instead of do...done for for/select loops
+        * but not for while/until loops - we don't need to check if it
+        * is a while loop because it would have been parsed as part of
+        * the conditional command list...
+        */
+       if (c == DO)
+               c = DONE;
+       else if (c == '{')
+               c = '}';
+       else
+               syntaxerr((char *) 0);
+       list = c_list(TRUE);
+       musthave(c, KEYWORD|ALIAS);
+       return list;
+}
+
+static struct op *
+thenpart()
+{
+       register struct op *t;
+
+       musthave(THEN, KEYWORD|ALIAS);
+       t = newtp(0);
+       t->left = c_list(TRUE);
+       if (t->left == NULL)
+               syntaxerr((char *) 0);
+       t->right = elsepart();
+       return (t);
+}
+
+static struct op *
+elsepart()
+{
+       register struct op *t;
+
+       switch (token(KEYWORD|ALIAS|VARASN)) {
+         case ELSE:
+               if ((t = c_list(TRUE)) == NULL)
+                       syntaxerr((char *) 0);
+               return (t);
+
+         case ELIF:
+               t = newtp(TELIF);
+               t->left = c_list(TRUE);
+               t->right = thenpart();
+               return (t);
+
+         default:
+               REJECT;
+       }
+       return NULL;
+}
+
+static struct op *
+caselist()
+{
+       register struct op *t, *tl;
+       int c;
+
+       c = token(CONTIN|KEYWORD|ALIAS);
+       /* A {...} can be used instead of in...esac for case statements */
+       if (c == IN)
+               c = ESAC;
+       else if (c == '{')
+               c = '}';
+       else
+               syntaxerr((char *) 0);
+       t = tl = NULL;
+       while ((tpeek(CONTIN|KEYWORD|ESACONLY)) != c) { /* no ALIAS here */
+               struct op *tc = casepart(c);
+               if (tl == NULL)
+                       t = tl = tc, tl->right = NULL;
+               else
+                       tl->right = tc, tl = tc;
+       }
+       musthave(c, KEYWORD|ALIAS);
+       return (t);
+}
+
+static struct op *
+casepart(endtok)
+       int endtok;
+{
+       register struct op *t;
+       register int c;
+       XPtrV ptns;
+
+       XPinit(ptns, 16);
+       t = newtp(TPAT);
+       c = token(CONTIN|KEYWORD); /* no ALIAS here */
+       if (c != '(')
+               REJECT;
+       do {
+               musthave(LWORD, 0);
+               XPput(ptns, yylval.cp);
+       } while ((c = token(0)) == '|');
+       REJECT;
+       XPput(ptns, NULL);
+       t->vars = (char **) XPclose(ptns);
+       musthave(')', 0);
+
+       t->left = c_list(TRUE);
+       /* Note: Posix requires the ;; */
+       if ((tpeek(CONTIN|KEYWORD|ALIAS)) != endtok)
+               musthave(BREAK, CONTIN|KEYWORD|ALIAS);
+       return (t);
+}
+
+static struct op *
+function_body(name, ksh_func)
+       char *name;
+       int ksh_func;   /* function foo { ... } vs foo() { .. } */
+{
+       char *sname, *p;
+       struct op *t;
+       int old_func_parse;
+
+       sname = wdstrip(name);
+       /* Check for valid characters in name.  posix and ksh93 say only
+        * allow [a-zA-Z_0-9] but this allows more as old pdksh's have
+        * allowed more (the following were never allowed:
+        *      nul space nl tab $ ' " \ ` ( ) & | ; = < >
+        *  C_QUOTE covers all but = and adds # [ ? *)
+        */
+       for (p = sname; *p; p++)
+               if (ctype(*p, C_QUOTE) || *p == '=')
+                       yyerror("%s: invalid function name\n", sname);
+
+       t = newtp(TFUNCT);
+       t->str = sname;
+       t->u.ksh_func = ksh_func;
+       t->lineno = source->line;
+
+       /* Note that POSIX allows only compound statements after foo(), sh and
+        * at&t ksh allow any command, go with the later since it shouldn't
+        * break anything.  However, for function foo, at&t ksh only accepts
+        * an open-brace.
+        */
+       if (ksh_func) {
+               musthave('{', CONTIN|KEYWORD|ALIAS); /* } */
+               REJECT;
+       }
+
+       old_func_parse = e->flags & EF_FUNC_PARSE;
+       e->flags |= EF_FUNC_PARSE;
+       if ((t->left = get_command(CONTIN)) == (struct op *) 0) {
+               /*
+                * Probably something like foo() followed by eof or ;.
+                * This is accepted by sh and ksh88.
+                * To make "typeset -f foo" work reliably (so its output can
+                * be used as input), we pretend there is a colon here.
+                */
+               t->left = newtp(TCOM);
+               t->left->args = (char **) alloc(sizeof(char *) * 2, ATEMP);
+               t->left->args[0] = alloc(sizeof(char) * 3, ATEMP);
+               t->left->args[0][0] = CHAR;
+               t->left->args[0][1] = ':';
+               t->left->args[0][2] = EOS;
+               t->left->args[1] = (char *) 0;
+               t->left->vars = (char **) alloc(sizeof(char *), ATEMP);
+               t->left->vars[0] = (char *) 0;
+               t->left->lineno = 1;
+       }
+       if (!old_func_parse)
+               e->flags &= ~EF_FUNC_PARSE;
+
+       return t;
+}
+
+static char **
+wordlist()
+{
+       register int c;
+       XPtrV args;
+
+       XPinit(args, 16);
+       /* Posix does not do alias expansion here... */
+       if ((c = token(CONTIN|KEYWORD|ALIAS)) != IN) {
+               if (c != ';') /* non-POSIX, but at&t ksh accepts a ; here */
+                       REJECT;
+               return NULL;
+       }
+       while ((c = token(0)) == LWORD)
+               XPput(args, yylval.cp);
+       if (c != '\n' && c != ';')
+               syntaxerr((char *) 0);
+       if (XPsize(args) == 0) {
+               XPfree(args);
+               return NULL;
+       } else {
+               XPput(args, NULL);
+               return (char **) XPclose(args);
+       }
+}
+
+/*
+ * supporting functions
+ */
+
+static struct op *
+block(type, t1, t2, wp)
+       int type;
+       struct op *t1, *t2;
+       char **wp;
+{
+       register struct op *t;
+
+       t = newtp(type);
+       t->left = t1;
+       t->right = t2;
+       t->vars = wp;
+       return (t);
+}
+
+const  struct tokeninfo {
+       const char *name;
+       short   val;
+       short   reserved;
+} tokentab[] = {
+       /* Reserved words */
+       { "if",         IF,     TRUE },
+       { "then",       THEN,   TRUE },
+       { "else",       ELSE,   TRUE },
+       { "elif",       ELIF,   TRUE },
+       { "fi",         FI,     TRUE },
+       { "case",       CASE,   TRUE },
+       { "esac",       ESAC,   TRUE },
+       { "for",        FOR,    TRUE },
+#ifdef KSH
+       { "select",     SELECT, TRUE },
+#endif /* KSH */
+       { "while",      WHILE,  TRUE },
+       { "until",      UNTIL,  TRUE },
+       { "do",         DO,     TRUE },
+       { "done",       DONE,   TRUE },
+       { "in",         IN,     TRUE },
+       { "function",   FUNCTION, TRUE },
+       { "time",       TIME,   TRUE },
+       { "{",          '{',    TRUE },
+       { "}",          '}',    TRUE },
+       { "!",          BANG,   TRUE },
+#ifdef KSH
+       { "[[",         DBRACKET, TRUE },
+#endif /* KSH */
+       /* Lexical tokens (0[EOF], LWORD and REDIR handled specially) */
+       { "&&",         LOGAND, FALSE },
+       { "||",         LOGOR,  FALSE },
+       { ";;",         BREAK,  FALSE },
+#ifdef KSH
+       { "((",         MDPAREN, FALSE },
+       { "|&",         COPROC, FALSE },
+#endif /* KSH */
+       /* and some special cases... */
+       { "newline",    '\n',   FALSE },
+       { .name = NULL }
+};
+
+void
+initkeywords()
+{
+       register struct tokeninfo const *tt;
+       register struct tbl *p;
+
+       tinit(&keywords, APERM, 32); /* must be 2^n (currently 20 keywords) */
+       for (tt = tokentab; tt->name; tt++) {
+               if (tt->reserved) {
+                       p = tenter(&keywords, tt->name, hash(tt->name));
+                       p->flag |= DEFINED|ISSET;
+                       p->type = CKEYWD;
+                       p->val.i = tt->val;
+               }
+       }
+}
+
+static void
+syntaxerr(what)
+       const char *what;
+{
+       char redir[6];  /* 2<<- is the longest redirection, I think */
+       const char *s;
+       struct tokeninfo const *tt;
+       int c;
+
+       if (!what)
+               what = "unexpected";
+       REJECT;
+       c = token(0);
+    Again:
+       switch (c) {
+       case 0:
+               if (nesting.start_token) {
+                       c = nesting.start_token;
+                       source->errline = nesting.start_line;
+                       what = "unmatched";
+                       goto Again;
+               }
+               /* don't quote the EOF */
+               yyerror("syntax error: unexpected EOF\n");
+               /*NOTREACHED*/
+
+       case LWORD:
+               s = snptreef((char *) 0, 32, "%S", yylval.cp);
+               break;
+
+       case REDIR:
+               s = snptreef(redir, sizeof(redir), "%R", yylval.iop);
+               break;
+
+       default:
+               for (tt = tokentab; tt->name; tt++)
+                       if (tt->val == c)
+                           break;
+               if (tt->name)
+                       s = tt->name;
+               else {
+                       if (c > 0 && c < 256) {
+                               redir[0] = c;
+                               redir[1] = '\0';
+                       } else
+                               shf_snprintf(redir, sizeof(redir),
+                                       "?%d", c);
+                       s = redir;
+               }
+       }
+       yyerror("syntax error: `%s' %s\n", s, what);
+}
+
+static void
+nesting_push(save, tok)
+       struct nesting_state *save;
+       int tok;
+{
+       *save = nesting;
+       nesting.start_token = tok;
+       nesting.start_line = source->line;
+}
+
+static void
+nesting_pop(saved)
+       struct nesting_state *saved;
+{
+       nesting = *saved;
+}
+
+static struct op *
+newtp(type)
+       int type;
+{
+       register struct op *t;
+
+       t = (struct op *) alloc(sizeof(*t), ATEMP);
+       t->type = type;
+       t->u.evalflags = 0;
+       t->args = t->vars = NULL;
+       t->ioact = NULL;
+       t->left = t->right = NULL;
+       t->str = NULL;
+       return (t);
+}
+
+struct op *
+compile(s)
+       Source *s;
+{
+       nesting.start_token = 0;
+       nesting.start_line = 0;
+       herep = heres;
+       source = s;
+       yyparse();
+       return outtree;
+}
+
+/* This kludge exists to take care of sh/at&t ksh oddity in which
+ * the arguments of alias/export/readonly/typeset have no field
+ * splitting, file globbing, or (normal) tilde expansion done.
+ * at&t ksh seems to do something similar to this since
+ *     $ touch a=a; typeset a=[ab]; echo "$a"
+ *     a=[ab]
+ *     $ x=typeset; $x a=[ab]; echo "$a"
+ *     a=a
+ *     $
+ */
+static int
+assign_command(s)
+       char *s;
+{
+       char c = *s;
+
+       if (Flag(FPOSIX) || !*s)
+               return 0;
+       return     (c == 'a' && strcmp(s, "alias") == 0)
+               || (c == 'e' && strcmp(s, "export") == 0)
+               || (c == 'r' && strcmp(s, "readonly") == 0)
+               || (c == 't' && strcmp(s, "typeset") == 0);
+}
+
+/* Check if we are in the middle of reading an alias */
+static int
+inalias(s)
+       struct source *s;
+{
+       for (; s && s->type == SALIAS; s = s->next)
+               if (!(s->flags & SF_ALIASEND))
+                       return 1;
+       return 0;
+}
+
+
+#ifdef KSH
+/* Order important - indexed by Test_meta values
+ * Note that ||, &&, ( and ) can't appear in as unquoted strings
+ * in normal shell input, so these can be interpreted unambiguously
+ * in the evaluation pass.
+ */
+static const char dbtest_or[] = { CHAR, '|', CHAR, '|', EOS };
+static const char dbtest_and[] = { CHAR, '&', CHAR, '&', EOS };
+static const char dbtest_not[] = { CHAR, '!', EOS };
+static const char dbtest_oparen[] = { CHAR, '(', EOS };
+static const char dbtest_cparen[] = { CHAR, ')', EOS };
+const char *const dbtest_tokens[] = {
+                       dbtest_or, dbtest_and, dbtest_not,
+                       dbtest_oparen, dbtest_cparen
+               };
+const char db_close[] = { CHAR, ']', CHAR, ']', EOS };
+const char db_lthan[] = { CHAR, '<', EOS };
+const char db_gthan[] = { CHAR, '>', EOS };
+
+/* Test if the current token is a whatever.  Accepts the current token if
+ * it is.  Returns 0 if it is not, non-zero if it is (in the case of
+ * TM_UNOP and TM_BINOP, the returned value is a Test_op).
+ */
+static int
+dbtestp_isa(te, meta)
+       Test_env *te;
+       Test_meta meta;
+{
+       int c = tpeek(ARRAYVAR | (meta == TM_BINOP ? 0 : CONTIN));
+       int uqword = 0;
+       char *save = (char *) 0;
+       int ret = 0;
+
+       /* unquoted word? */
+       uqword = c == LWORD && *ident;
+
+       if (meta == TM_OR)
+               ret = c == LOGOR;
+       else if (meta == TM_AND)
+               ret = c == LOGAND;
+       else if (meta == TM_NOT)
+               ret = uqword && strcmp(yylval.cp, dbtest_tokens[(int) TM_NOT]) == 0;
+       else if (meta == TM_OPAREN)
+               ret = c == '(' /*)*/;
+       else if (meta == TM_CPAREN)
+               ret = c == /*(*/ ')';
+       else if (meta == TM_UNOP || meta == TM_BINOP) {
+               if (meta == TM_BINOP && c == REDIR
+                   && (yylval.iop->flag == IOREAD
+                       || yylval.iop->flag == IOWRITE))
+               {
+                       ret = 1;
+                       save = wdcopy(yylval.iop->flag == IOREAD ?
+                               db_lthan : db_gthan, ATEMP);
+               } else if (uqword && (ret = (int) test_isop(te, meta, ident)))
+                       save = yylval.cp;
+       } else /* meta == TM_END */
+               ret = uqword && strcmp(yylval.cp, db_close) == 0;
+       if (ret) {
+               ACCEPT;
+               if (meta != TM_END) {
+                       if (!save) {
+                               assert(/* meta >= 0 && */
+                                   meta < sizeof(dbtest_tokens) /
+                                   sizeof(dbtest_tokens[0]));
+                               save = wdcopy(dbtest_tokens[(int) meta], ATEMP);
+                       }
+                       XPput(*te->pos.av, save);
+               }
+       }
+       return ret;
+}
+
+static const char *
+dbtestp_getopnd(te, op, do_eval)
+       Test_env *te;
+       Test_op op;
+       int do_eval;
+{
+       int c = tpeek(ARRAYVAR);
+
+       if (c != LWORD)
+               return (const char *) 0;
+
+       ACCEPT;
+       XPput(*te->pos.av, yylval.cp);
+
+       return null;
+}
+
+static int
+dbtestp_eval(te, op, opnd1, opnd2, do_eval)
+       Test_env *te;
+       Test_op op;
+       const char *opnd1;
+       const char *opnd2;
+       int do_eval;
+{
+       return 1;
+}
+
+static void
+dbtestp_error(te, offset, msg)
+       Test_env *te;
+       int offset;
+       const char *msg;
+{
+       te->flags |= TEF_ERROR;
+
+       if (offset < 0) {
+               REJECT;
+               /* Kludgy to say the least... */
+               symbol = LWORD;
+               yylval.cp = *(XPptrv(*te->pos.av) + XPsize(*te->pos.av)
+                               + offset);
+       }
+       syntaxerr(msg);
+}
+#endif /* KSH */
diff --git a/bin/ksh/table.c b/bin/ksh/table.c
new file mode 100644 (file)
index 0000000..025b56d
--- /dev/null
@@ -0,0 +1,247 @@
+/*     $NetBSD: table.c,v 1.4 2003/06/23 11:39:04 agc Exp $    */
+
+/*
+ * dynamic hashed associative table for commands and variables
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: table.c,v 1.4 2003/06/23 11:39:04 agc Exp $");
+#endif
+
+
+#include "sh.h"
+
+#define        INIT_TBLS       8       /* initial table size (power of 2) */
+
+static void     texpand     ARGS((struct table *tp, int nsize));
+static int      tnamecmp    ARGS((void *p1, void *p2));
+
+
+unsigned int
+hash(n)
+       register const char * n;
+{
+       register unsigned int h = 0;
+
+       while (*n != '\0')
+               h = 2*h + *n++;
+       return h * 32821;       /* scatter bits */
+}
+
+void
+tinit(tp, ap, tsize)
+       register struct table *tp;
+       register Area *ap;
+       int tsize;
+{
+       tp->areap = ap;
+       tp->tbls = NULL;
+       tp->size = tp->nfree = 0;
+       if (tsize)
+               texpand(tp, tsize);
+}
+
+static void
+texpand(tp, nsize)
+       register struct table *tp;
+       int nsize;
+{
+       register int i;
+       register struct tbl *tblp, **p;
+       register struct tbl **ntblp, **otblp = tp->tbls;
+       int osize = tp->size;
+
+       ntblp = (struct tbl**) alloc(sizeofN(struct tbl *, nsize), tp->areap);
+       for (i = 0; i < nsize; i++)
+               ntblp[i] = NULL;
+       tp->size = nsize;
+       tp->nfree = 8*nsize/10; /* table can get 80% full */
+       tp->tbls = ntblp;
+       if (otblp == NULL)
+               return;
+       for (i = 0; i < osize; i++) {
+               if ((tblp = otblp[i]) != NULL) {
+                       if ((tblp->flag&DEFINED)) {
+                               for (p = &ntblp[hash(tblp->name)
+                                         & (tp->size-1)];
+                                    *p != NULL; p--)
+                                       if (p == ntblp) /* wrap */
+                                               p += tp->size;
+                               *p = tblp;
+                               tp->nfree--;
+                       } else if (!(tblp->flag & FINUSE)) {
+                               afree((void*)tblp, tp->areap);
+                       }
+               }
+       }
+       afree((void*)otblp, tp->areap);
+}
+
+struct tbl *
+tsearch(tp, n, h)
+       register struct table *tp;      /* table */
+       register const char *n;         /* name to enter */
+       unsigned int h;                 /* hash(n) */
+{
+       register struct tbl **pp, *p;
+
+       if (tp->size == 0)
+               return NULL;
+
+       /* search for name in hashed table */
+       for (pp = &tp->tbls[h & (tp->size-1)]; (p = *pp) != NULL; pp--) {
+               if (*p->name == *n && strcmp(p->name, n) == 0
+                   && (p->flag&DEFINED))
+                       return p;
+               if (pp == tp->tbls) /* wrap */
+                       pp += tp->size;
+       }
+
+       return NULL;
+}
+
+struct tbl *
+tenter(tp, n, h)
+       register struct table *tp;      /* table */
+       register const char *n;         /* name to enter */
+       unsigned int h;                 /* hash(n) */
+{
+       register struct tbl **pp, *p;
+       register int len;
+
+       if (tp->size == 0)
+               texpand(tp, INIT_TBLS);
+  Search:
+       /* search for name in hashed table */
+       for (pp = &tp->tbls[h & (tp->size-1)]; (p = *pp) != NULL; pp--) {
+               if (*p->name == *n && strcmp(p->name, n) == 0)
+                       return p;       /* found */
+               if (pp == tp->tbls) /* wrap */
+                       pp += tp->size;
+       }
+
+       if (tp->nfree <= 0) {   /* too full */
+               texpand(tp, 2*tp->size);
+               goto Search;
+       }
+
+       /* create new tbl entry */
+       len = strlen(n) + 1;
+       p = (struct tbl *) alloc(offsetof(struct tbl, name[0]) + len,
+                                tp->areap);
+       p->flag = 0;
+       p->type = 0;
+       p->areap = tp->areap;
+       p->u2.field = 0;
+       p->u.array = (struct tbl *)0;
+       memcpy(p->name, n, len);
+
+       /* enter in tp->tbls */
+       tp->nfree--;
+       *pp = p;
+       return p;
+}
+
+void
+tdelete(p)
+       register struct tbl *p;
+{
+       p->flag = 0;
+}
+
+void
+twalk(ts, tp)
+       struct tstate *ts;
+       struct table *tp;
+{
+       ts->left = tp->size;
+       ts->next = tp->tbls;
+}
+
+struct tbl *
+tnext(ts)
+       struct tstate *ts;
+{
+       while (--ts->left >= 0) {
+               struct tbl *p = *ts->next++;
+               if (p != NULL && (p->flag&DEFINED))
+                       return p;
+       }
+       return NULL;
+}
+
+static int
+tnamecmp(p1, p2)
+       void *p1, *p2;
+{
+       return strcmp(((struct tbl *)p1)->name, ((struct tbl *)p2)->name);
+}
+
+struct tbl **
+tsort(tp)
+       register struct table *tp;
+{
+       register int i;
+       register struct tbl **p, **sp, **dp;
+
+       p = (struct tbl **)alloc(sizeofN(struct tbl *, tp->size+1), ATEMP);
+       sp = tp->tbls;          /* source */
+       dp = p;                 /* dest */
+       for (i = 0; i < tp->size; i++)
+               if ((*dp = *sp++) != NULL && (((*dp)->flag&DEFINED) ||
+                                             ((*dp)->flag&ARRAY)))
+                       dp++;
+       i = dp - p;
+       qsortp((void**)p, (size_t)i, tnamecmp);
+       p[i] = NULL;
+       return p;
+}
+
+#ifdef PERF_DEBUG /* performance debugging */
+
+void tprintinfo ARGS((struct table *tp));
+
+void
+tprintinfo(tp)
+       struct table *tp;
+{
+       struct tbl *te;
+       char *n;
+       unsigned int h;
+       int ncmp;
+       int totncmp = 0, maxncmp = 0;
+       int nentries = 0;
+       struct tstate ts;
+
+       shellf("table size %d, nfree %d\n", tp->size, tp->nfree);
+       shellf("    Ncmp name\n");
+       twalk(&ts, tp);
+       while ((te = tnext(&ts))) {
+               register struct tbl **pp, *p;
+
+               h = hash(n = te->name);
+               ncmp = 0;
+
+               /* taken from tsearch() and added counter */
+               for (pp = &tp->tbls[h & (tp->size-1)]; (p = *pp); pp--) {
+                       ncmp++;
+                       if (*p->name == *n && strcmp(p->name, n) == 0
+                           && (p->flag&DEFINED))
+                               break; /* return p; */
+                       if (pp == tp->tbls) /* wrap */
+                               pp += tp->size;
+               }
+               shellf("    %4d %s\n", ncmp, n);
+               totncmp += ncmp;
+               nentries++;
+               if (ncmp > maxncmp)
+                       maxncmp = ncmp;
+       }
+       if (nentries)
+               shellf("  %d entries, worst ncmp %d, avg ncmp %d.%02d\n",
+                       nentries, maxncmp,
+                       totncmp / nentries,
+                       (totncmp % nentries) * 100 / nentries);
+}
+#endif /* PERF_DEBUG */
diff --git a/bin/ksh/table.h b/bin/ksh/table.h
new file mode 100644 (file)
index 0000000..9dc42c8
--- /dev/null
@@ -0,0 +1,181 @@
+/* $NetBSD: table.h,v 1.3 1999/10/20 15:10:00 hubertf Exp $ */
+
+/*
+ * generic hashed associative table for commands and variables.
+ */
+
+struct table {
+       Area   *areap;          /* area to allocate entries */
+       short   size, nfree;    /* hash size (always 2^^n), free entries */
+       struct  tbl **tbls;     /* hashed table items */
+};
+
+struct tbl {                   /* table item */
+       Tflag   flag;           /* flags */
+       int     type;           /* command type (see below), base (if INTEGER),
+                                * or offset from val.s of value (if EXPORT) */
+       Area    *areap;         /* area to allocate from */
+       union {
+               char *s;        /* string */
+               long i;         /* integer */
+               int (*f) ARGS((char **));       /* int function */
+               struct op *t;   /* "function" tree */
+       } val;                  /* value */
+       int     index;          /* index for an array */
+       union {
+           int field;          /* field with for -L/-R/-Z */
+           int errno_;         /* CEXEC/CTALIAS */
+       } u2;
+       union {
+               struct tbl *array;      /* array values */
+               char *fpath;            /* temporary path to undef function */
+       } u;
+       char    name[4];        /* name -- variable length */
+};
+
+/* common flag bits */
+#define        ALLOC           BIT(0)  /* val.s has been allocated */
+#define        DEFINED         BIT(1)  /* is defined in block */
+#define        ISSET           BIT(2)  /* has value, vp->val.[si] */
+#define        EXPORT          BIT(3)  /* exported variable/function */
+#define        TRACE           BIT(4)  /* var: user flagged, func: execution tracing */
+/* (start non-common flags at 8) */
+/* flag bits used for variables */
+#define        SPECIAL         BIT(8)  /* PATH, IFS, SECONDS, etc */
+#define        INTEGER         BIT(9)  /* val.i contains integer value */
+#define        RDONLY          BIT(10) /* read-only variable */
+#define        LOCAL           BIT(11) /* for local typeset() */
+#define ARRAY          BIT(13) /* array */
+#define LJUST          BIT(14) /* left justify */
+#define RJUST          BIT(15) /* right justify */
+#define ZEROFIL                BIT(16) /* 0 filled if RJUSTIFY, strip 0s if LJUSTIFY */
+#define LCASEV         BIT(17) /* convert to lower case */
+#define UCASEV_AL      BIT(18)/* convert to upper case / autoload function */
+#define INT_U          BIT(19) /* unsigned integer */
+#define INT_L          BIT(20) /* long integer (no-op) */
+#define IMPORT         BIT(21) /* flag to typeset(): no arrays, must have = */
+#define LOCAL_COPY     BIT(22) /* with LOCAL - copy attrs from existing var */
+#define EXPRINEVAL     BIT(23) /* contents currently being evaluated */
+#define EXPRLVALUE     BIT(24) /* useable as lvalue (temp flag) */
+/* flag bits used for taliases/builtins/aliases/keywords/functions */
+#define KEEPASN                BIT(8)  /* keep command assignments (eg, var=x cmd) */
+#define FINUSE         BIT(9)  /* function being executed */
+#define FDELETE                BIT(10) /* function deleted while it was executing */
+#define FKSH           BIT(11) /* function defined with function x (vs x()) */
+#define SPEC_BI                BIT(12) /* a POSIX special builtin */
+#define REG_BI         BIT(13) /* a POSIX regular builtin */
+/* Attributes that can be set by the user (used to decide if an unset param
+ * should be repoted by set/typeset).  Does not include ARRAY or LOCAL.
+ */
+#define USERATTRIB     (EXPORT|INTEGER|RDONLY|LJUST|RJUST|ZEROFIL\
+                        |LCASEV|UCASEV_AL|INT_U|INT_L)
+
+/* command types */
+#define        CNONE   0               /* undefined */
+#define        CSHELL  1               /* built-in */
+#define        CFUNC   2               /* function */
+#define        CEXEC   4               /* executable command */
+#define        CALIAS  5               /* alias */
+#define        CKEYWD  6               /* keyword */
+#define CTALIAS        7               /* tracked alias */
+
+/* Flags for findcom()/comexec() */
+#define FC_SPECBI      BIT(0)  /* special builtin */
+#define FC_FUNC                BIT(1)  /* function builtin */
+#define FC_REGBI       BIT(2)  /* regular builtin */
+#define FC_UNREGBI     BIT(3)  /* un-regular builtin (!special,!regular) */
+#define FC_BI          (FC_SPECBI|FC_REGBI|FC_UNREGBI)
+#define FC_PATH                BIT(4)  /* do path search */
+#define FC_DEFPATH     BIT(5)  /* use default path in path search */
+
+
+#define AF_ARGV_ALLOC  0x1     /* argv[] array allocated */
+#define AF_ARGS_ALLOCED        0x2     /* argument strings allocated */
+#define AI_ARGV(a, i)  ((i) == 0 ? (a).argv[0] : (a).argv[(i) - (a).skip])
+#define AI_ARGC(a)     ((a).argc_ - (a).skip)
+
+/* Argument info.  Used for $#, $* for shell, functions, includes, etc. */
+struct arg_info {
+       int flags;      /* AF_* */
+       char **argv;
+       int argc_;
+       int skip;       /* first arg is argv[0], second is argv[1 + skip] */
+};
+
+/*
+ * activation record for function blocks
+ */
+struct block {
+       Area    area;           /* area to allocate things */
+       /*struct arg_info argi;*/
+       char    **argv;
+       int     argc;
+       int     flags;          /* see BF_* */
+       struct  table vars;     /* local variables */
+       struct  table funs;     /* local functions */
+       Getopt  getopts_state;
+#if 1
+       char *  error;          /* error handler */
+       char *  exit;           /* exit handler */
+#else
+       Trap    error, exit;
+#endif
+       struct  block *next;    /* enclosing block */
+};
+
+/* Values for struct block.flags */
+#define BF_DOGETOPTS   BIT(0)  /* save/restore getopts state */
+
+/*
+ * Used by twalk() and tnext() routines.
+ */
+struct tstate {
+       int left;
+       struct tbl **next;
+};
+
+
+EXTERN struct table taliases;  /* tracked aliases */
+EXTERN struct table builtins;  /* built-in commands */
+EXTERN struct table aliases;   /* aliases */
+EXTERN struct table keywords;  /* keywords */
+EXTERN struct table homedirs;  /* homedir() cache */
+
+struct builtin {
+       const char   *name;
+       int  (*func) ARGS((char **));
+};
+
+/* these really are externs! Look in table.c for them */
+extern const struct builtin shbuiltins [], kshbuiltins [];
+
+/* var spec values */
+#define        V_NONE                  0
+#define        V_PATH                  1
+#define        V_IFS                   2
+#define        V_SECONDS               3
+#define        V_OPTIND                4
+#define        V_MAIL                  5
+#define        V_MAILPATH              6
+#define        V_MAILCHECK             7
+#define        V_RANDOM                8
+#define V_HISTSIZE             9
+#define V_HISTFILE             10
+#define V_VISUAL               11
+#define V_EDITOR               12
+#define V_COLUMNS              13
+#define V_POSIXLY_CORRECT      14
+#define V_TMOUT                        15
+#define V_TMPDIR               16
+#define V_LINENO               17
+
+/* values for set_prompt() */
+#define PS1    0               /* command */
+#define PS2    1               /* command continuation */
+
+EXTERN char *path;             /* copy of either PATH or def_path */
+EXTERN const char *def_path;   /* path to use if PATH not set */
+EXTERN char *tmpdir;           /* TMPDIR value */
+EXTERN const char *prompt;
+EXTERN int cur_prompt;         /* PS1 or PS2 */
+EXTERN int current_lineno;     /* LINENO value */
diff --git a/bin/ksh/trap.c b/bin/ksh/trap.c
new file mode 100644 (file)
index 0000000..fa212aa
--- /dev/null
@@ -0,0 +1,460 @@
+/*     $NetBSD: trap.c,v 1.8 2006/10/16 00:07:32 christos Exp $        */
+
+/*
+ * signal handling
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: trap.c,v 1.8 2006/10/16 00:07:32 christos Exp $");
+#endif
+
+
+/* Kludge to avoid bogus re-declaration of sigtraps[] error on AIX 3.2.5 */
+#define FROM_TRAP_C
+#include "sh.h"
+
+/* Table is indexed by signal number
+ *
+ * The script siglist.sh generates siglist.out, which is a sorted, complete
+ * list of signals
+ */
+Trap sigtraps[SIGNALS+1] = {
+       { .signal = SIGEXIT_, .name = "EXIT", .mess = "Signal 0" },
+#include "siglist.out" /* generated by siglist.sh */
+       { .signal = SIGERR_,  .name = "ERR",  .mess = "Error handler" },
+    };
+
+static struct sigaction Sigact_ign, Sigact_trap;
+
+void
+inittraps()
+{
+#ifdef HAVE_SYS_SIGLIST
+# ifndef SYS_SIGLIST_DECLARED
+       extern char     *sys_siglist[];
+# endif
+       int     i;
+
+       /* Use system description, if available, for unknown signals... */
+       for (i = 0; i < NSIG; i++)
+               if (!sigtraps[i].name && sys_siglist[i] && sys_siglist[i][0])
+                       sigtraps[i].mess = sys_siglist[i];
+#endif /* HAVE_SYS_SIGLIST */
+
+       sigemptyset(&Sigact_ign.sa_mask);
+       Sigact_ign.sa_flags = KSH_SA_FLAGS;
+       Sigact_ign.sa_handler = SIG_IGN;
+       Sigact_trap = Sigact_ign;
+       Sigact_trap.sa_handler = trapsig;
+
+       sigtraps[SIGINT].flags |= TF_DFL_INTR | TF_TTY_INTR;
+       sigtraps[SIGQUIT].flags |= TF_DFL_INTR | TF_TTY_INTR;
+       sigtraps[SIGTERM].flags |= TF_DFL_INTR;/* not fatal for interactive */
+       sigtraps[SIGHUP].flags |= TF_FATAL;
+       sigtraps[SIGCHLD].flags |= TF_SHELL_USES;
+
+       /* these are always caught so we can clean up any temporary files. */
+       setsig(&sigtraps[SIGINT], trapsig, SS_RESTORE_ORIG);
+       setsig(&sigtraps[SIGQUIT], trapsig, SS_RESTORE_ORIG);
+       setsig(&sigtraps[SIGTERM], trapsig, SS_RESTORE_ORIG);
+       setsig(&sigtraps[SIGHUP], trapsig, SS_RESTORE_ORIG);
+}
+
+#ifdef KSH
+static RETSIGTYPE alarm_catcher ARGS((int sig));
+
+void
+alarm_init()
+{
+       sigtraps[SIGALRM].flags |= TF_SHELL_USES;
+       setsig(&sigtraps[SIGALRM], alarm_catcher,
+               SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP);
+}
+
+static RETSIGTYPE
+alarm_catcher(sig)
+       int sig;
+{
+       int errno_ = errno;
+
+       if (ksh_tmout_state == TMOUT_READING) {
+               int left = alarm(0);
+
+               if (left == 0) {
+                       ksh_tmout_state = TMOUT_LEAVING;
+                       intrsig = 1;
+               } else
+                       alarm(left);
+       }
+       errno = errno_;
+       return RETSIGVAL;
+}
+#endif /* KSH */
+
+Trap *
+gettrap(name, igncase)
+       const char *name;
+       int igncase;
+{
+       int i;
+       register Trap *p;
+
+       if (digit(*name)) {
+               int n;
+
+               if (getn(name, &n) && 0 <= n && n < SIGNALS)
+                       return &sigtraps[n];
+               return NULL;
+       }
+       for (p = sigtraps, i = SIGNALS+1; --i >= 0; p++)
+               if (p->name) {
+                       if (igncase) {
+                               if (p->name && (!strcasecmp(p->name, name) ||
+                                   (strlen(name) > 3 && !strncasecmp("SIG",
+                                   p->name, 3) &&
+                                   !strcasecmp(p->name, name + 3))))
+                                       return p;
+                       } else {
+                               if (p->name && (!strcmp(p->name, name) ||
+                                   (strlen(name) > 3 && !strncmp("SIG",
+                                   p->name, 3) && !strcmp(p->name, name + 3))))
+                                       return p;
+                       }
+               }
+       return NULL;
+}
+
+/*
+ * trap signal handler
+ */
+RETSIGTYPE
+trapsig(i)
+       int i;
+{
+       Trap *p = &sigtraps[i];
+       int errno_ = errno;
+
+       trap = p->set = 1;
+       if (p->flags & TF_DFL_INTR)
+               intrsig = 1;
+       if ((p->flags & TF_FATAL) && !p->trap) {
+               fatal_trap = 1;
+               intrsig = 1;
+       }
+       if (p->shtrap)
+               (*p->shtrap)(i);
+#ifdef V7_SIGNALS
+       if (sigtraps[i].cursig == trapsig) /* this for SIGCHLD,SIGALRM */
+               sigaction(i, &Sigact_trap, (struct sigaction *) 0);
+#endif /* V7_SIGNALS */
+       errno = errno_;
+       return RETSIGVAL;
+}
+
+/* called when we want to allow the user to ^C out of something - won't
+ * work if user has trapped SIGINT.
+ */
+void
+intrcheck()
+{
+       if (intrsig)
+               runtraps(TF_DFL_INTR|TF_FATAL);
+}
+
+/* called after EINTR to check if a signal with normally causes process
+ * termination has been received.
+ */
+int
+fatal_trap_check()
+{
+       int i;
+       Trap *p;
+
+       /* todo: should check if signal is fatal, not the TF_DFL_INTR flag */
+       for (p = sigtraps, i = SIGNALS+1; --i >= 0; p++)
+               if (p->set && (p->flags & (TF_DFL_INTR|TF_FATAL)))
+                       /* return value is used as an exit code */
+                       return 128 + p->signal;
+       return 0;
+}
+
+/* Returns the signal number of any pending traps: ie, a signal which has
+ * occurred for which a trap has been set or for which the TF_DFL_INTR flag
+ * is set.
+ */
+int
+trap_pending()
+{
+       int i;
+       Trap *p;
+
+       for (p = sigtraps, i = SIGNALS+1; --i >= 0; p++)
+               if (p->set && ((p->trap && p->trap[0])
+                              || ((p->flags & (TF_DFL_INTR|TF_FATAL))
+                                  && !p->trap)))
+                       return p->signal;
+       return 0;
+}
+
+/*
+ * run any pending traps.  If intr is set, only run traps that
+ * can interrupt commands.
+ */
+void
+runtraps(flag)
+       int flag;
+{
+       int i;
+       register Trap *p;
+
+#ifdef KSH
+       if (ksh_tmout_state == TMOUT_LEAVING) {
+               ksh_tmout_state = TMOUT_EXECUTING;
+               warningf(FALSE, "timed out waiting for input");
+               unwind(LEXIT);
+       } else
+               /* XXX: this means the alarm will have no effect if a trap
+                * is caught after the alarm() was started...not good.
+                */
+               ksh_tmout_state = TMOUT_EXECUTING;
+#endif /* KSH */
+       if (!flag)
+               trap = 0;
+       if (flag & TF_DFL_INTR)
+               intrsig = 0;
+       if (flag & TF_FATAL)
+               fatal_trap = 0;
+       for (p = sigtraps, i = SIGNALS+1; --i >= 0; p++)
+               if (p->set && (!flag
+                              || ((p->flags & flag) && p->trap == (char *) 0)))
+                       runtrap(p);
+}
+
+void
+runtrap(p)
+       Trap *p;
+{
+       int     i = p->signal;
+       char    *trapstr = p->trap;
+       int     oexstat;
+       int     UNINITIALIZED(old_changed);
+
+       p->set = 0;
+       if (trapstr == (char *) 0) { /* SIG_DFL */
+               if (p->flags & TF_FATAL) {
+                       /* eg, SIGHUP */
+                       exstat = 128 + i;
+                       unwind(LLEAVE);
+               }
+               if (p->flags & TF_DFL_INTR) {
+                       /* eg, SIGINT, SIGQUIT, SIGTERM, etc. */
+                       exstat = 128 + i;
+                       unwind(LINTR);
+               }
+               return;
+       }
+       if (trapstr[0] == '\0') /* SIG_IGN */
+               return;
+       if (i == SIGEXIT_ || i == SIGERR_) {    /* avoid recursion on these */
+               old_changed = p->flags & TF_CHANGED;
+               p->flags &= ~TF_CHANGED;
+               p->trap = (char *) 0;
+       }
+       oexstat = exstat;
+       /* Note: trapstr is fully parsed before anything is executed, thus
+        * no problem with afree(p->trap) in settrap() while still in use.
+        */
+       command(trapstr);
+       exstat = oexstat;
+       if (i == SIGEXIT_ || i == SIGERR_) {
+               if (p->flags & TF_CHANGED)
+                       /* don't clear TF_CHANGED */
+                       afree(trapstr, APERM);
+               else
+                       p->trap = trapstr;
+               p->flags |= old_changed;
+       }
+}
+
+/* clear pending traps and reset user's trap handlers; used after fork(2) */
+void
+cleartraps()
+{
+       int i;
+       Trap *p;
+
+       trap = 0;
+       intrsig = 0;
+       fatal_trap = 0;
+       for (i = SIGNALS+1, p = sigtraps; --i >= 0; p++) {
+               p->set = 0;
+               if ((p->flags & TF_USER_SET) && (p->trap && p->trap[0]))
+                       settrap(p, (char *) 0);
+       }
+}
+
+/* restore signals just before an exec(2) */
+void
+restoresigs()
+{
+       int i;
+       Trap *p;
+
+       for (i = SIGNALS+1, p = sigtraps; --i >= 0; p++)
+               if (p->flags & (TF_EXEC_IGN|TF_EXEC_DFL))
+                       setsig(p, (p->flags & TF_EXEC_IGN) ? SIG_IGN : SIG_DFL,
+                               SS_RESTORE_CURR|SS_FORCE);
+}
+
+void
+settrap(p, s)
+       Trap *p;
+       char *s;
+{
+       handler_t f;
+
+       if (p->trap)
+               afree(p->trap, APERM);
+       p->flags |= TF_CHANGED|TF_USER_SET;
+       if (s) {
+               p->trap = str_save(s, APERM);
+               f = s[0] ? trapsig : SIG_IGN;
+       } else {
+               p->trap = NULL;
+               f = SIG_DFL;
+       }
+       if ((p->flags & (TF_DFL_INTR|TF_FATAL)) && f == SIG_DFL)
+               f = trapsig;
+       else if (p->flags & TF_SHELL_USES) {
+               if (!(p->flags & TF_ORIG_IGN) || Flag(FTALKING)) {
+                       /* do what user wants at exec time */
+                       p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL);
+                       if (f == SIG_IGN)
+                               p->flags |= TF_EXEC_IGN;
+                       else
+                               p->flags |= TF_EXEC_DFL;
+               }
+               /* assumes handler already set to what shell wants it
+                * (normally trapsig, but could be j_sigchld() or SIG_IGN)
+                */
+               return;
+       }
+
+       /* todo: should we let user know signal is ignored? how? */
+       setsig(p, f, SS_RESTORE_CURR|SS_USER);
+}
+
+/* Called by c_print() when writing to a co-process to ensure SIGPIPE won't
+ * kill shell (unless user catches it and exits)
+ */
+int
+block_pipe()
+{
+       int restore_dfl = 0;
+       Trap *p = &sigtraps[SIGPIPE];
+
+       if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) {
+               setsig(p, SIG_IGN, SS_RESTORE_CURR);
+               if (p->flags & TF_ORIG_DFL)
+                       restore_dfl = 1;
+       } else if (p->cursig == SIG_DFL) {
+               setsig(p, SIG_IGN, SS_RESTORE_CURR);
+               restore_dfl = 1; /* restore to SIG_DFL */
+       }
+       return restore_dfl;
+}
+
+/* Called by c_print() to undo whatever block_pipe() did */
+void
+restore_pipe(restore_dfl)
+       int restore_dfl;
+{
+       if (restore_dfl)
+               setsig(&sigtraps[SIGPIPE], SIG_DFL, SS_RESTORE_CURR);
+}
+
+/* Set action for a signal.  Action may not be set if original
+ * action was SIG_IGN, depending on the value of flags and
+ * FTALKING.
+ */
+int
+setsig(p, f, flags)
+       Trap *p;
+       handler_t f;
+       int flags;
+{
+       struct sigaction sigact;
+
+       if (p->signal == SIGEXIT_ || p->signal == SIGERR_)
+               return 1;
+
+       /* First time setting this signal?  If so, get and note the current
+        * setting.
+        */
+       if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) {
+               sigaction(p->signal, &Sigact_ign, &sigact);
+               p->flags |= sigact.sa_handler == SIG_IGN ?
+                                       TF_ORIG_IGN : TF_ORIG_DFL;
+               p->cursig = SIG_IGN;
+       }
+
+       /* Generally, an ignored signal stays ignored, except if
+        *      - the user of an interactive shell wants to change it
+        *      - the shell wants for force a change
+        */
+       if ((p->flags & TF_ORIG_IGN) && !(flags & SS_FORCE)
+           && (!(flags & SS_USER) || !Flag(FTALKING)))
+               return 0;
+
+       setexecsig(p, flags & SS_RESTORE_MASK);
+
+       /* This is here 'cause there should be a way of clearing shtraps, but
+        * don't know if this is a sane way of doing it.  At the moment,
+        * all users of shtrap are lifetime users (SIGCHLD, SIGALRM, SIGWINCH).
+        */
+       if (!(flags & SS_USER))
+               p->shtrap = (handler_t) 0;
+       if (flags & SS_SHTRAP) {
+               p->shtrap = f;
+               f = trapsig;
+       }
+
+       if (p->cursig != f) {
+               p->cursig = f;
+               sigemptyset(&sigact.sa_mask);
+               sigact.sa_flags = KSH_SA_FLAGS;
+               sigact.sa_handler = f;
+               sigaction(p->signal, &sigact, (struct sigaction *) 0);
+       }
+
+       return 1;
+}
+
+/* control what signal is set to before an exec() */
+void
+setexecsig(p, restore)
+       Trap *p;
+       int restore;
+{
+       /* XXX debugging */
+       if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL)))
+               internal_errorf(1, "setexecsig: unset signal %d(%s)",
+                       p->signal, p->name);
+
+       /* restore original value for exec'd kids */
+       p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL);
+       switch (restore & SS_RESTORE_MASK) {
+         case SS_RESTORE_CURR: /* leave things as they currently are */
+               break;
+         case SS_RESTORE_ORIG:
+               p->flags |= p->flags & TF_ORIG_IGN ? TF_EXEC_IGN : TF_EXEC_DFL;
+               break;
+         case SS_RESTORE_DFL:
+               p->flags |= TF_EXEC_DFL;
+               break;
+         case SS_RESTORE_IGN:
+               p->flags |= TF_EXEC_IGN;
+               break;
+       }
+}
diff --git a/bin/ksh/tree.c b/bin/ksh/tree.c
new file mode 100644 (file)
index 0000000..9645022
--- /dev/null
@@ -0,0 +1,766 @@
+/*     $NetBSD: tree.c,v 1.6 2005/06/26 19:09:00 christos Exp $        */
+
+/*
+ * command tree climbing
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: tree.c,v 1.6 2005/06/26 19:09:00 christos Exp $");
+#endif
+
+
+#include "sh.h"
+
+#define INDENT 4
+
+#define tputc(c, shf)  shf_putchar(c, shf);
+static void    ptree ARGS((struct op *t, int indent, struct shf *f));
+static void    pioact ARGS((struct shf *f, int indent, struct ioword *iop));
+static void    tputC ARGS((int c, struct shf *shf));
+static void    tputS ARGS((char *wp, struct shf *shf));
+static void    vfptreef ARGS((struct shf *shf, int indent, const char *fmt, va_list va));
+static struct ioword **iocopy ARGS((struct ioword **iow, Area *ap));
+static void     iofree ARGS((struct ioword **iow, Area *ap));
+
+/*
+ * print a command tree
+ */
+
+static void
+ptree(t, indent, shf)
+       register struct op *t;
+       int indent;
+       register struct shf *shf;
+{
+       register char **w;
+       struct ioword **ioact;
+       struct op *t1;
+
+ Chain:
+       if (t == NULL)
+               return;
+       switch (t->type) {
+         case TCOM:
+               if (t->vars)
+                       for (w = t->vars; *w != NULL; )
+                               fptreef(shf, indent, "%S ", *w++);
+               else
+                       fptreef(shf, indent, "#no-vars# ");
+               if (t->args)
+                       for (w = t->args; *w != NULL; )
+                               fptreef(shf, indent, "%S ", *w++);
+               else
+                       fptreef(shf, indent, "#no-args# ");
+               break;
+         case TEXEC:
+#if 0 /* ?not useful - can't be called? */
+               /* Print original vars */
+               if (t->left->vars)
+                       for (w = t->left->vars; *w != NULL; )
+                               fptreef(shf, indent, "%S ", *w++);
+               else
+                       fptreef(shf, indent, "#no-vars# ");
+               /* Print expanded vars */
+               if (t->args)
+                       for (w = t->args; *w != NULL; )
+                               fptreef(shf, indent, "%s ", *w++);
+               else
+                       fptreef(shf, indent, "#no-args# ");
+               /* Print original io */
+               t = t->left;
+#else
+               t = t->left;
+               goto Chain;
+#endif
+         case TPAREN:
+               fptreef(shf, indent + 2, "( %T) ", t->left);
+               break;
+         case TPIPE:
+               fptreef(shf, indent, "%T| ", t->left);
+               t = t->right;
+               goto Chain;
+         case TLIST:
+               fptreef(shf, indent, "%T%;", t->left);
+               t = t->right;
+               goto Chain;
+         case TOR:
+         case TAND:
+               fptreef(shf, indent, "%T%s %T",
+                       t->left, (t->type==TOR) ? "||" : "&&", t->right);
+               break;
+         case TBANG:
+               fptreef(shf, indent, "! ");
+               t = t->right;
+               goto Chain;
+         case TDBRACKET:
+         {
+               int i;
+
+               fptreef(shf, indent, "[[");
+               for (i = 0; t->args[i]; i++)
+                       fptreef(shf, indent, " %S", t->args[i]);
+               fptreef(shf, indent, " ]] ");
+               break;
+         }
+#ifdef KSH
+         case TSELECT:
+               fptreef(shf, indent, "select %s ", t->str);
+               /* fall through */
+#endif /* KSH */
+         case TFOR:
+               if (t->type == TFOR)
+                       fptreef(shf, indent, "for %s ", t->str);
+               if (t->vars != NULL) {
+                       fptreef(shf, indent, "in ");
+                       for (w = t->vars; *w; )
+                               fptreef(shf, indent, "%S ", *w++);
+                       fptreef(shf, indent, "%;");
+               }
+               fptreef(shf, indent + INDENT, "do%N%T", t->left);
+               fptreef(shf, indent, "%;done ");
+               break;
+         case TCASE:
+               fptreef(shf, indent, "case %S in", t->str);
+               for (t1 = t->left; t1 != NULL; t1 = t1->right) {
+                       fptreef(shf, indent, "%N(");
+                       for (w = t1->vars; *w != NULL; w++)
+                               fptreef(shf, indent, "%S%c", *w,
+                                       (w[1] != NULL) ? '|' : ')');
+                       fptreef(shf, indent + INDENT, "%;%T%N;;", t1->left);
+               }
+               fptreef(shf, indent, "%Nesac ");
+               break;
+         case TIF:
+         case TELIF:
+               /* 3 == strlen("if ") */
+               fptreef(shf, indent + 3, "if %T", t->left);
+               for (;;) {
+                       t = t->right;
+                       if (t->left != NULL) {
+                               fptreef(shf, indent, "%;");
+                               fptreef(shf, indent + INDENT, "then%N%T",
+                                       t->left);
+                       }
+                       if (t->right == NULL || t->right->type != TELIF)
+                               break;
+                       t = t->right;
+                       fptreef(shf, indent, "%;");
+                       /* 5 == strlen("elif ") */
+                       fptreef(shf, indent + 5, "elif %T", t->left);
+               }
+               if (t->right != NULL) {
+                       fptreef(shf, indent, "%;");
+                       fptreef(shf, indent + INDENT, "else%;%T", t->right);
+               }
+               fptreef(shf, indent, "%;fi ");
+               break;
+         case TWHILE:
+         case TUNTIL:
+               /* 6 == strlen("while"/"until") */
+               fptreef(shf, indent + 6, "%s %T",
+                       (t->type==TWHILE) ? "while" : "until",
+                       t->left);
+               fptreef(shf, indent, "%;do");
+               fptreef(shf, indent + INDENT, "%;%T", t->right);
+               fptreef(shf, indent, "%;done ");
+               break;
+         case TBRACE:
+               fptreef(shf, indent + INDENT, "{%;%T", t->left);
+               fptreef(shf, indent, "%;} ");
+               break;
+         case TCOPROC:
+               fptreef(shf, indent, "%T|& ", t->left);
+               break;
+         case TASYNC:
+               fptreef(shf, indent, "%T& ", t->left);
+               break;
+         case TFUNCT:
+               fptreef(shf, indent,
+                       t->u.ksh_func ? "function %s %T" : "%s() %T",
+                               t->str, t->left);
+               break;
+         case TTIME:
+               fptreef(shf, indent, "time %T", t->left);
+               break;
+         default:
+               fptreef(shf, indent, "<botch>");
+               break;
+       }
+       if ((ioact = t->ioact) != NULL) {
+               int     need_nl = 0;
+
+               while (*ioact != NULL)
+                       pioact(shf, indent, *ioact++);
+               /* Print here documents after everything else... */
+               for (ioact = t->ioact; *ioact != NULL; ) {
+                       struct ioword *iop = *ioact++;
+
+                       /* heredoc is 0 when tracing (set -x) */
+                       if ((iop->flag & IOTYPE) == IOHERE && iop->heredoc) {
+                               tputc('\n', shf);
+                               shf_puts(iop->heredoc, shf);
+                               fptreef(shf, indent, "%s",
+                                       evalstr(iop->delim, 0));
+                               need_nl = 1;
+                       }
+               }
+               /* Last delimiter must be followed by a newline (this often
+                * leads to an extra blank line, but its not worth worrying
+                * about)
+                */
+               if (need_nl)
+                       tputc('\n', shf);
+       }
+}
+
+static void
+pioact(shf, indent, iop)
+       register struct shf *shf;
+       int indent;
+       register struct ioword *iop;
+{
+       int flag = iop->flag;
+       int type = flag & IOTYPE;
+       int expected;
+
+       expected = (type == IOREAD || type == IORDWR || type == IOHERE) ? 0
+                   : (type == IOCAT || type == IOWRITE) ? 1
+                   : (type == IODUP && (iop->unit == !(flag & IORDUP))) ?
+                       iop->unit
+                   : iop->unit + 1;
+       if (iop->unit != expected)
+               tputc('0' + iop->unit, shf);
+
+       switch (type) {
+       case IOREAD:
+               fptreef(shf, indent, "< ");
+               break;
+       case IOHERE:
+               if (flag&IOSKIP)
+                       fptreef(shf, indent, "<<- ");
+               else
+                       fptreef(shf, indent, "<< ");
+               break;
+       case IOCAT:
+               fptreef(shf, indent, ">> ");
+               break;
+       case IOWRITE:
+               if (flag&IOCLOB)
+                       fptreef(shf, indent, ">| ");
+               else
+                       fptreef(shf, indent, "> ");
+               break;
+       case IORDWR:
+               fptreef(shf, indent, "<> ");
+               break;
+       case IODUP:
+               if (flag & IORDUP)
+                       fptreef(shf, indent, "<&");
+               else
+                       fptreef(shf, indent, ">&");
+               break;
+       }
+       /* name/delim are 0 when printing syntax errors */
+       if (type == IOHERE) {
+               if (iop->delim)
+                       fptreef(shf, indent, "%S ", iop->delim);
+       } else if (iop->name)
+               fptreef(shf, indent, (iop->flag & IONAMEXP) ? "%s " : "%S ",
+                       iop->name);
+}
+
+
+/*
+ * variants of fputc, fputs for ptreef and snptreef
+ */
+
+static void
+tputC(c, shf)
+       register int c;
+       register struct shf *shf;
+{
+       if ((c&0x60) == 0) {            /* C0|C1 */
+               tputc((c&0x80) ? '$' : '^', shf);
+               tputc(((c&0x7F)|0x40), shf);
+       } else if ((c&0x7F) == 0x7F) {  /* DEL */
+               tputc((c&0x80) ? '$' : '^', shf);
+               tputc('?', shf);
+       } else
+               tputc(c, shf);
+}
+
+static void
+tputS(wp, shf)
+       register char *wp;
+       register struct shf *shf;
+{
+       register int c, quoted=0;
+
+       /* problems:
+        *      `...` -> $(...)
+        *      'foo' -> "foo"
+        * could change encoding to:
+        *      OQUOTE ["'] ... CQUOTE ["']
+        *      COMSUB [(`] ...\0       (handle $ ` \ and maybe " in `...` case)
+        */
+       while (1)
+               switch ((c = *wp++)) {
+                 case EOS:
+                       return;
+                 case CHAR:
+                       tputC(*wp++, shf);
+                       break;
+                 case QCHAR:
+                       c = *wp++;
+                       if (!quoted || (c == '"' || c == '`' || c == '$'))
+                               tputc('\\', shf);
+                       tputC(c, shf);
+                       break;
+                 case COMSUB:
+                       tputc('$', shf);
+                       tputc('(', shf);
+                       while (*wp != 0)
+                               tputC(*wp++, shf);
+                       tputc(')', shf);
+                       wp++;
+                       break;
+                 case EXPRSUB:
+                       tputc('$', shf);
+                       tputc('(', shf);
+                       tputc('(', shf);
+                       while (*wp != 0)
+                               tputC(*wp++, shf);
+                       tputc(')', shf);
+                       tputc(')', shf);
+                       wp++;
+                       break;
+                 case OQUOTE:
+                       quoted = 1;
+                       tputc('"', shf);
+                       break;
+                 case CQUOTE:
+                       quoted = 0;
+                       tputc('"', shf);
+                       break;
+                 case OSUBST:
+                       tputc('$', shf);
+                       if (*wp++ == '{')
+                               tputc('{', shf);
+                       while ((c = *wp++) != 0)
+                               tputC(c, shf);
+                       break;
+                 case CSUBST:
+                       if (*wp++ == '}')
+                               tputc('}', shf);
+                       break;
+#ifdef KSH
+                 case OPAT:
+                       tputc(*wp++, shf);
+                       tputc('(', shf);
+                       break;
+                 case SPAT:
+                       tputc('|', shf);
+                       break;
+                 case CPAT:
+                       tputc(')', shf);
+                       break;
+#endif /* KSH */
+               }
+}
+
+/*
+ * this is the _only_ way to reliably handle
+ * variable args with an ANSI compiler
+ */
+/* VARARGS */
+int
+#ifdef HAVE_PROTOTYPES
+fptreef(struct shf *shf, int indent, const char *fmt, ...)
+#else
+fptreef(shf, indent, fmt, va_alist)
+  struct shf *shf;
+  int indent;
+  const char *fmt;
+  va_dcl
+#endif
+{
+  va_list      va;
+
+  SH_VA_START(va, fmt);
+
+  vfptreef(shf, indent, fmt, va);
+  va_end(va);
+  return 0;
+}
+
+/* VARARGS */
+char *
+#ifdef HAVE_PROTOTYPES
+snptreef(char *s, int n, const char *fmt, ...)
+#else
+snptreef(s, n, fmt, va_alist)
+  char *s;
+  int n;
+  const char *fmt;
+  va_dcl
+#endif
+{
+  va_list va;
+  struct shf shf;
+
+  shf_sopen(s, n, SHF_WR | (s ? 0 : SHF_DYNAMIC), &shf);
+
+  SH_VA_START(va, fmt);
+  vfptreef(&shf, 0, fmt, va);
+  va_end(va);
+
+  return shf_sclose(&shf); /* null terminates */
+}
+
+static void
+vfptreef(shf, indent, fmt, va)
+       register struct shf *shf;
+       int indent;
+       const char *fmt;
+       register va_list va;
+{
+       register int c;
+
+       while ((c = *fmt++))
+           if (c == '%') {
+               register long n;
+               register char *p;
+               int neg;
+
+               switch ((c = *fmt++)) {
+                 case 'c':
+                       tputc(va_arg(va, int), shf);
+                       break;
+                 case 's':
+                       p = va_arg(va, char *);
+                       while (*p)
+                               tputc(*p++, shf);
+                       break;
+                 case 'S':     /* word */
+                       p = va_arg(va, char *);
+                       tputS(p, shf);
+                       break;
+                 case 'd': case 'u': /* decimal */
+                       n = (c == 'd') ? va_arg(va, int)
+                                      : va_arg(va, unsigned int);
+                       neg = c=='d' && n<0;
+                       p = ulton((neg) ? -n : n, 10);
+                       if (neg)
+                               *--p = '-';
+                       while (*p)
+                               tputc(*p++, shf);
+                       break;
+                 case 'T':     /* format tree */
+                       ptree(va_arg(va, struct op *), indent, shf);
+                       break;
+                 case ';':     /* newline or ; */
+                 case 'N':     /* newline or space */
+                       if (shf->flags & SHF_STRING) {
+                               if (c == ';')
+                                       tputc(';', shf);
+                               tputc(' ', shf);
+                       } else {
+                               int i;
+
+                               tputc('\n', shf);
+                               for (i = indent; i >= 8; i -= 8)
+                                       tputc('\t', shf);
+                               for (; i > 0; --i)
+                                       tputc(' ', shf);
+                       }
+                       break;
+                 case 'R':
+                       pioact(shf, indent, va_arg(va, struct ioword *));
+                       break;
+                 default:
+                       tputc(c, shf);
+                       break;
+               }
+           } else
+               tputc(c, shf);
+}
+
+/*
+ * copy tree (for function definition)
+ */
+
+struct op *
+tcopy(t, ap)
+       register struct op *t;
+       Area *ap;
+{
+       register struct op *r;
+       register char **tw, **rw;
+
+       if (t == NULL)
+               return NULL;
+
+       r = (struct op *) alloc(sizeof(struct op), ap);
+
+       r->type = t->type;
+       r->u.evalflags = t->u.evalflags;
+
+       r->str = t->type == TCASE ? wdcopy(t->str, ap) : str_save(t->str, ap);
+
+       if (t->vars == NULL)
+               r->vars = NULL;
+       else {
+               for (tw = t->vars; *tw++ != NULL; )
+                       ;
+               rw = r->vars = (char **)
+                       alloc((tw - t->vars + 1) * sizeof(*tw), ap);
+               for (tw = t->vars; *tw != NULL; )
+                       *rw++ = wdcopy(*tw++, ap);
+               *rw = NULL;
+       }
+
+       if (t->args == NULL)
+               r->args = NULL;
+       else {
+               for (tw = t->args; *tw++ != NULL; )
+                       ;
+               rw = r->args = (char **)
+                       alloc((tw - t->args + 1) * sizeof(*tw), ap);
+               for (tw = t->args; *tw != NULL; )
+                       *rw++ = wdcopy(*tw++, ap);
+               *rw = NULL;
+       }
+
+       r->ioact = (t->ioact == NULL) ? NULL : iocopy(t->ioact, ap);
+
+       r->left = tcopy(t->left, ap);
+       r->right = tcopy(t->right, ap);
+       r->lineno = t->lineno;
+
+       return r;
+}
+
+char *
+wdcopy(wp, ap)
+       const char *wp;
+       Area *ap;
+{
+       size_t len = wdscan(wp, EOS) - wp;
+       return memcpy(alloc(len, ap), wp, len);
+}
+
+/* return the position of prefix c in wp plus 1 */
+char *
+wdscan(wp, c)
+       register const char *wp;
+       register int c;
+{
+       register int nest = 0;
+
+       while (1)
+               switch (*wp++) {
+                 case EOS:
+                       return (char *) __UNCONST(wp);
+                 case CHAR:
+                 case QCHAR:
+                       wp++;
+                       break;
+                 case COMSUB:
+                 case EXPRSUB:
+                       while (*wp++ != 0)
+                               ;
+                       break;
+                 case OQUOTE:
+                 case CQUOTE:
+                       break;
+                 case OSUBST:
+                       nest++;
+                       while (*wp++ != '\0')
+                               ;
+                       break;
+                 case CSUBST:
+                       wp++;
+                       if (c == CSUBST && nest == 0)
+                               return (char *) __UNCONST(wp);
+                       nest--;
+                       break;
+#ifdef KSH
+                 case OPAT:
+                       nest++;
+                       wp++;
+                       break;
+                 case SPAT:
+                 case CPAT:
+                       if (c == wp[-1] && nest == 0)
+                               return (char *) __UNCONST(wp);
+                       if (wp[-1] == CPAT)
+                               nest--;
+                       break;
+#endif /* KSH */
+                 default:
+                       internal_errorf(0,
+                               "wdscan: unknown char 0x%x (carrying on)",
+                               wp[-1]);
+               }
+}
+
+/* return a copy of wp without any of the mark up characters and
+ * with quote characters (" ' \) stripped.
+ * (string is allocated from ATEMP)
+ */
+char *
+wdstrip(wp)
+       const char *wp;
+{
+       struct shf shf;
+       int c;
+
+       shf_sopen((char *) 0, 32, SHF_WR | SHF_DYNAMIC, &shf);
+
+       /* problems:
+        *      `...` -> $(...)
+        *      x${foo:-"hi"} -> x${foo:-hi}
+        *      x${foo:-'hi'} -> x${foo:-hi}
+        */
+       while (1)
+               switch ((c = *wp++)) {
+                 case EOS:
+                       return shf_sclose(&shf); /* null terminates */
+                 case CHAR:
+                 case QCHAR:
+                       shf_putchar(*wp++, &shf);
+                       break;
+                 case COMSUB:
+                       shf_putchar('$', &shf);
+                       shf_putchar('(', &shf);
+                       while (*wp != 0)
+                               shf_putchar(*wp++, &shf);
+                       shf_putchar(')', &shf);
+                       break;
+                 case EXPRSUB:
+                       shf_putchar('$', &shf);
+                       shf_putchar('(', &shf);
+                       shf_putchar('(', &shf);
+                       while (*wp != 0)
+                               shf_putchar(*wp++, &shf);
+                       shf_putchar(')', &shf);
+                       shf_putchar(')', &shf);
+                       break;
+                 case OQUOTE:
+                       break;
+                 case CQUOTE:
+                       break;
+                 case OSUBST:
+                       shf_putchar('$', &shf);
+                       if (*wp++ == '{')
+                           shf_putchar('{', &shf);
+                       while ((c = *wp++) != 0)
+                               shf_putchar(c, &shf);
+                       break;
+                 case CSUBST:
+                       if (*wp++ == '}')
+                               shf_putchar('}', &shf);
+                       break;
+#ifdef KSH
+                 case OPAT:
+                       shf_putchar(*wp++, &shf);
+                       shf_putchar('(', &shf);
+                       break;
+                 case SPAT:
+                       shf_putchar('|', &shf);
+                       break;
+                 case CPAT:
+                       shf_putchar(')', &shf);
+                       break;
+#endif /* KSH */
+               }
+}
+
+static struct ioword **
+iocopy(iow, ap)
+       register struct ioword **iow;
+       Area *ap;
+{
+       register struct ioword **ior;
+       register int i;
+
+       for (ior = iow; *ior++ != NULL; )
+               ;
+       ior = (struct ioword **) alloc((ior - iow + 1) * sizeof(*ior), ap);
+
+       for (i = 0; iow[i] != NULL; i++) {
+               register struct ioword *p, *q;
+
+               p = iow[i];
+               q = (struct ioword *) alloc(sizeof(*p), ap);
+               ior[i] = q;
+               *q = *p;
+               if (p->name != (char *) 0)
+                       q->name = wdcopy(p->name, ap);
+               if (p->delim != (char *) 0)
+                       q->delim = wdcopy(p->delim, ap);
+               if (p->heredoc != (char *) 0)
+                       q->heredoc = str_save(p->heredoc, ap);
+       }
+       ior[i] = NULL;
+
+       return ior;
+}
+
+/*
+ * free tree (for function definition)
+ */
+
+void
+tfree(t, ap)
+       register struct op *t;
+       Area *ap;
+{
+       register char **w;
+
+       if (t == NULL)
+               return;
+
+       if (t->str != NULL)
+               afree((void*)t->str, ap);
+
+       if (t->vars != NULL) {
+               for (w = t->vars; *w != NULL; w++)
+                       afree((void*)*w, ap);
+               afree((void*)t->vars, ap);
+       }
+
+       if (t->args != NULL) {
+               for (w = t->args; *w != NULL; w++)
+                       afree((void*)*w, ap);
+               afree((void*)t->args, ap);
+       }
+
+       if (t->ioact != NULL)
+               iofree(t->ioact, ap);
+
+       tfree(t->left, ap);
+       tfree(t->right, ap);
+
+       afree((void*)t, ap);
+}
+
+static void
+iofree(iow, ap)
+       struct ioword **iow;
+       Area *ap;
+{
+       register struct ioword **iop;
+       register struct ioword *p;
+
+       for (iop = iow; (p = *iop++) != NULL; ) {
+               if (p->name != NULL)
+                       afree((void*)p->name, ap);
+               if (p->delim != NULL)
+                       afree((void*)p->delim, ap);
+               if (p->heredoc != NULL)
+                       afree((void*)p->heredoc, ap);
+               afree((void*)p, ap);
+       }
+}
diff --git a/bin/ksh/tree.h b/bin/ksh/tree.h
new file mode 100644 (file)
index 0000000..b0aea1e
--- /dev/null
@@ -0,0 +1,142 @@
+/*     $NetBSD: tree.h,v 1.4 2004/07/07 19:20:09 mycroft Exp $ */
+
+/*
+ * command trees for compile/execute
+ */
+
+/* $Id: tree.h,v 1.4 2004/07/07 19:20:09 mycroft Exp $ */
+
+#define        NOBLOCK ((struct op *)NULL)
+#define        NOWORD  ((char *)NULL)
+#define        NOWORDS ((char **)NULL)
+
+/*
+ * Description of a command or an operation on commands.
+ */
+struct op {
+       short   type;                   /* operation type, see below */
+       union { /* WARNING: newtp(), tcopy() use evalflags = 0 to clear union */
+               short   evalflags;      /* TCOM: arg expansion eval() flags */
+               short   ksh_func;       /* TFUNC: function x (vs x()) */
+       } u;
+       char  **args;                   /* arguments to a command */
+       char  **vars;                   /* variable assignments */
+       struct ioword   **ioact;        /* IO actions (eg, < > >>) */
+       struct op *left, *right;        /* descendents */
+       char   *str;                    /* word for case; identifier for for,
+                                        * select, and functions;
+                                        * path to execute for TEXEC;
+                                        * time hook for TCOM.
+                                        */
+       int     lineno;                 /* TCOM/TFUNC: LINENO for this */
+};
+
+/* Tree.type values */
+#define        TEOF            0
+#define        TCOM            1       /* command */
+#define        TPAREN          2       /* (c-list) */
+#define        TPIPE           3       /* a | b */
+#define        TLIST           4       /* a ; b */
+#define        TOR             5       /* || */
+#define        TAND            6       /* && */
+#define TBANG          7       /* ! */
+#define TDBRACKET      8       /* [[ .. ]] */
+#define        TFOR            9
+#define TSELECT                10
+#define        TCASE           11
+#define        TIF             12
+#define        TWHILE          13
+#define        TUNTIL          14
+#define        TELIF           15
+#define        TPAT            16      /* pattern in case */
+#define        TBRACE          17      /* {c-list} */
+#define        TASYNC          18      /* c & */
+#define        TFUNCT          19      /* function name { command; } */
+#define        TTIME           20      /* time pipeline */
+#define        TEXEC           21      /* fork/exec eval'd TCOM */
+#define TCOPROC                22      /* coprocess |& */
+
+/*
+ * prefix codes for words in command tree
+ */
+#define        EOS     0               /* end of string */
+#define        CHAR    1               /* unquoted character */
+#define        QCHAR   2               /* quoted character */
+#define        COMSUB  3               /* $() substitution (0 terminated) */
+#define EXPRSUB        4               /* $(()) substitution (0 terminated) */
+#define        OQUOTE  5               /* opening " or ' */
+#define        CQUOTE  6               /* closing " or ' */
+#define        OSUBST  7               /* opening ${ subst (followed by { or X) */
+#define        CSUBST  8               /* closing } of above (followed by } or X) */
+#define OPAT   9               /* open pattern: *(, @(, etc. */
+#define SPAT   10              /* separate pattern: | */
+#define CPAT   11              /* close pattern: ) */
+
+/*
+ * IO redirection
+ */
+struct ioword {
+       int     unit;   /* unit affected */
+       int     flag;   /* action (below) */
+       char    *name;  /* file name (unused if heredoc) */
+       char    *delim; /* delimiter for <<,<<- */
+       char    *heredoc;/* content of heredoc */
+};
+
+/* ioword.flag - type of redirection */
+#define        IOTYPE  0xF             /* type: bits 0:3 */
+#define        IOREAD  0x1             /* < */
+#define        IOWRITE 0x2             /* > */
+#define        IORDWR  0x3             /* <>: todo */
+#define        IOHERE  0x4             /* << (here file) */
+#define        IOCAT   0x5             /* >> */
+#define        IODUP   0x6             /* <&/>& */
+#define        IOEVAL  BIT(4)          /* expand in << */
+#define        IOSKIP  BIT(5)          /* <<-, skip ^\t* */
+#define        IOCLOB  BIT(6)          /* >|, override -o noclobber */
+#define IORDUP BIT(7)          /* x<&y (as opposed to x>&y) */
+#define IONAMEXP BIT(8)                /* name has been expanded */
+
+/* execute/exchild flags */
+#define        XEXEC   BIT(0)          /* execute without forking */
+#define        XFORK   BIT(1)          /* fork before executing */
+#define        XBGND   BIT(2)          /* command & */
+#define        XPIPEI  BIT(3)          /* input is pipe */
+#define        XPIPEO  BIT(4)          /* output is pipe */
+#define        XPIPE   (XPIPEI|XPIPEO) /* member of pipe */
+#define        XXCOM   BIT(5)          /* `...` command */
+#define        XPCLOSE BIT(6)          /* exchild: close close_fd in parent */
+#define        XCCLOSE BIT(7)          /* exchild: close close_fd in child */
+#define XERROK BIT(8)          /* non-zero exit ok (for set -e) */
+#define XCOPROC BIT(9)         /* starting a co-process */
+#define XTIME  BIT(10)         /* timing TCOM command */
+#define XINTACT BIT(11)                /* OS2: proc started from interactive session */
+
+/*
+ * flags to control expansion of words (assumed by t->evalflags to fit
+ * in a short)
+ */
+#define        DOBLANK BIT(0)          /* perform blank interpretation */
+#define        DOGLOB  BIT(1)          /* expand [?* */
+#define        DOPAT   BIT(2)          /* quote *?[ */
+#define        DOTILDE BIT(3)          /* normal ~ expansion (first char) */
+#define DONTRUNCOMMAND BIT(4)  /* do not run $(command) things */
+#define DOASNTILDE BIT(5)      /* assignment ~ expansion (after =, :) */
+#define DOBRACE_ BIT(6)                /* used by expand(): do brace expansion */
+#define DOMAGIC_ BIT(7)                /* used by expand(): string contains MAGIC */
+#define DOTEMP_        BIT(8)          /* ditto : in word part of ${..[%#=?]..} */
+#define DOVACHECK BIT(9)       /* var assign check (for typeset, set, etc) */
+#define DOMARKDIRS BIT(10)     /* force markdirs behaviour */
+
+/*
+ * The arguments of [[ .. ]] expressions are kept in t->args[] and flags
+ * indicating how the arguments have been munged are kept in t->vars[].
+ * The contents of t->vars[] are stuffed strings (so they can be treated
+ * like all other t->vars[]) in which the second character is the one that
+ * is examined.  The DB_* defines are the values for these second characters.
+ */
+#define DB_NORM        1               /* normal argument */
+#define DB_OR  2               /* || -> -o conversion */
+#define DB_AND 3               /* && -> -a conversion */
+#define DB_BE  4               /* an inserted -BE */
+#define DB_PAT 5               /* a pattern argument */
diff --git a/bin/ksh/tty.c b/bin/ksh/tty.c
new file mode 100644 (file)
index 0000000..8a60d73
--- /dev/null
@@ -0,0 +1,187 @@
+/*     $NetBSD: tty.c,v 1.4 2003/06/23 11:39:06 agc Exp $      */
+
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: tty.c,v 1.4 2003/06/23 11:39:06 agc Exp $");
+#endif
+
+
+#include "sh.h"
+#include "ksh_stat.h"
+#define EXTERN
+#include "tty.h"
+#undef EXTERN
+
+int
+get_tty(fd, ts)
+       int fd;
+       TTY_state *ts;
+{
+       int ret;
+
+# ifdef HAVE_TERMIOS_H
+       ret = tcgetattr(fd, ts);
+# else /* HAVE_TERIOS_H */
+#  ifdef HAVE_TERMIO_H
+       ret = ioctl(fd, TCGETA, ts);
+#  else /* HAVE_TERMIO_H */
+       ret = ioctl(fd, TIOCGETP, &ts->sgttyb);
+#   ifdef TIOCGATC
+       if (ioctl(fd, TIOCGATC, &ts->lchars) < 0)
+               ret = -1;
+#   else
+       if (ioctl(fd, TIOCGETC, &ts->tchars) < 0)
+               ret = -1;
+#    ifdef TIOCGLTC
+       if (ioctl(fd, TIOCGLTC, &ts->ltchars) < 0)
+               ret = -1;
+#    endif /* TIOCGLTC */
+#   endif /* TIOCGATC */
+#  endif /* HAVE_TERMIO_H */
+# endif /* HAVE_TERIOS_H */
+       return ret;
+}
+
+int
+set_tty(fd, ts, flags)
+       int fd;
+       TTY_state *ts;
+       int flags;
+{
+       int ret = 0;
+
+# ifdef HAVE_TERMIOS_H
+       ret = tcsetattr(fd, TCSADRAIN, ts);
+# else /* HAVE_TERIOS_H */
+#  ifdef HAVE_TERMIO_H
+#   ifndef TCSETAW                             /* e.g. Cray-2 */
+               /* first wait for output to drain */
+#    ifdef TCSBRK
+               if (ioctl(tty_fd, TCSBRK, 1) < 0)
+                       ret = -1;
+#    else /* the following kludge is minimally intrusive, but sometimes fails */
+               if (flags & TF_WAIT)
+                       sleep((unsigned)1);     /* fake it */
+#    endif
+#   endif /* !TCSETAW */
+#   if defined(_BSD_SYSV) || !defined(TCSETAW)
+/* _BSD_SYSV must force TIOCSETN instead of TIOCSETP (preserve type-ahead) */
+               if (ioctl(tty_fd, TCSETA, ts) < 0)
+                       ret = -1;
+#   else
+               if (ioctl(tty_fd, TCSETAW, ts) < 0)
+                       ret = -1;
+#   endif
+#  else /* HAVE_TERMIO_H */
+#   if defined(__mips) && (defined(_SYSTYPE_BSD43) || defined(__SYSTYPE_BSD43))
+       /* Under RISC/os 5.00, bsd43 environment, after a tty driver
+        * generated interrupt (eg, INTR, TSTP), all output to tty is
+        * lost until a SETP is done (there must be a better way of
+        * doing this...).
+        */
+       if (flags & TF_MIPSKLUDGE)
+               ret = ioctl(fd, TIOCSETP, &ts->sgttyb);
+       else
+#   endif /* _SYSTYPE_BSD43 */
+           ret = ioctl(fd, TIOCSETN, &ts->sgttyb);
+#   ifdef TIOCGATC
+       if (ioctl(fd, TIOCSATC, &ts->lchars) < 0)
+               ret = -1;
+#   else
+       if (ioctl(fd, TIOCSETC, &ts->tchars) < 0)
+               ret = -1;
+#    ifdef TIOCGLTC
+       if (ioctl(fd, TIOCSLTC, &ts->ltchars) < 0)
+               ret = -1;
+#    endif /* TIOCGLTC */
+#   endif /* TIOCGATC */
+#  endif /* HAVE_TERMIO_H */
+# endif /* HAVE_TERIOS_H */
+       return ret;
+}
+
+
+/* Initialize tty_fd.  Used for saving/reseting tty modes upon
+ * foreground job completion and for setting up tty process group.
+ */
+void
+tty_init(init_ttystate)
+       int init_ttystate;
+{
+       int     do_close = 1;
+       int     tfd;
+       const char      *devtty = _PATH_TTY;
+
+       if (tty_fd >= 0) {
+               close(tty_fd);
+               tty_fd = -1;
+       }
+       tty_devtty = 1;
+
+       /* SCO can't job control on /dev/tty, so don't try... */
+#if !defined(__SCO__)
+       if ((tfd = open(devtty, O_RDWR, 0)) < 0) {
+#ifdef __NeXT
+               /* rlogin on NeXT boxes does not set up the controlling tty,
+                * so force it to be done here...
+                */
+               {
+                       extern char *ttyname ARGS((int));
+                       char *s = ttyname(isatty(2) ? 2 : 0);
+                       int fd;
+
+                       if (s && (fd = open(s, O_RDWR, 0)) >= 0) {
+                               close(fd);
+                               tfd = open(devtty, O_RDWR, 0);
+                       }
+               }
+#endif /* __NeXT */
+
+/* X11R5 xterm on mips doesn't set controlling tty properly - temporary hack */
+# if !defined(__mips) || !(defined(_SYSTYPE_BSD43) || defined(__SYSTYPE_BSD43))
+               if (tfd < 0) {
+                       tty_devtty = 0;
+                       warningf(FALSE,
+                               "No controlling tty (open %s: %s)",
+                               devtty, strerror(errno));
+               }
+# endif /* __mips  */
+       }
+#else /* !__SCO__ */
+       tfd = -1;
+#endif /* __SCO__ */
+
+       if (tfd < 0) {
+               do_close = 0;
+               if (isatty(0))
+                       tfd = 0;
+               else if (isatty(2))
+                       tfd = 2;
+               else {
+                       warningf(FALSE, "Can't find tty file descriptor");
+                       return;
+               }
+       }
+       if ((tty_fd = ksh_dupbase(tfd, FDBASE)) < 0) {
+               warningf(FALSE, "j_ttyinit: dup of tty fd failed: %s",
+                       strerror(errno));
+       } else if (fd_clexec(tty_fd) < 0) {
+               warningf(FALSE, "j_ttyinit: can't set close-on-exec flag: %s",
+                       strerror(errno));
+               close(tty_fd);
+               tty_fd = -1;
+       } else if (init_ttystate)
+               get_tty(tty_fd, &tty_state);
+       if (do_close)
+               close(tfd);
+}
+
+void
+tty_close()
+{
+       if (tty_fd >= 0) {
+               close(tty_fd);
+               tty_fd = -1;
+       }
+}
diff --git a/bin/ksh/tty.h b/bin/ksh/tty.h
new file mode 100644 (file)
index 0000000..08c6b7c
--- /dev/null
@@ -0,0 +1,110 @@
+/*     $NetBSD: tty.h,v 1.2 1997/01/12 19:12:25 tls Exp $      */
+
+/*
+       tty.h -- centralized definitions for a variety of terminal interfaces
+
+       created by DPK, Oct. 1986
+
+       Rearranged to work with autoconf, added TTY_state, get_tty/set_tty
+                                               Michael Rendell, May '94
+
+       last edit:      30-Jul-1987     D A Gwyn
+*/
+/* $NetBSD: tty.h,v 1.2 1997/01/12 19:12:25 tls Exp $ */
+
+/* some useful #defines */
+#ifdef EXTERN
+# define I__(i) = i
+#else
+# define I__(i)
+# define EXTERN extern
+# define EXTERN_DEFINED
+#endif
+
+/* Don't know of a system on which including sys/ioctl.h with termios.h
+ * causes problems.  If there is one, these lines need to be deleted and
+ * aclocal.m4 needs to have stuff un-commented.
+ */
+#ifdef SYS_IOCTL_WITH_TERMIOS
+# define SYS_IOCTL_WITH_TERMIOS
+#endif /* SYS_IOCTL_WITH_TERMIOS */
+#ifdef SYS_IOCTL_WITH_TERMIO
+# define SYS_IOCTL_WITH_TERMIO
+#endif /* SYS_IOCTL_WITH_TERMIO */
+
+#ifdef HAVE_TERMIOS_H
+# include <termios.h>
+# ifdef SYS_IOCTL_WITH_TERMIOS
+#  if !(defined(sun) && !defined(__svr4__)) /* too many warnings on sunos */
+    /* Need to include sys/ioctl.h on some systems to get the TIOCGWINSZ
+     * stuff (eg, digital unix).
+     */
+#   include <sys/ioctl.h>
+#  endif /* !(sun && !__svr4__) */
+# endif /* SYS_IOCTL_WITH_TERMIOS */
+typedef struct termios TTY_state;
+#else
+# ifdef HAVE_TERMIO_H
+#  include <termio.h>
+#  ifdef SYS_IOCTL_WITH_TERMIO
+#   include <sys/ioctl.h> /* see comment above in termios stuff */
+#  endif /* SYS_IOCTL_WITH_TERMIO */
+#  if _BSD_SYSV                        /* BRL UNIX System V emulation */
+#   ifndef NTTYDISC
+#    define    TIOCGETD        _IOR( 't', 0, int )
+#    define    TIOCSETD        _IOW( 't', 1, int )
+#    define    NTTYDISC        2
+#   endif
+#   ifndef TIOCSTI
+#    define    TIOCSTI         _IOW( 't', 114, char )
+#   endif
+#   ifndef TIOCSPGRP
+#    define    TIOCSPGRP       _IOW( 't', 118, int )
+#   endif
+#  endif /* _BSD_SYSV */
+typedef struct termio TTY_state;
+# else /* HAVE_TERMIO_H */
+/* Assume BSD tty stuff.  Uses TIOCGETP, TIOCSETN; uses TIOCGATC/TIOCSATC if
+ * available, otherwise it uses TIOCGETC/TIOCSETC (also uses TIOCGLTC/TIOCSLTC
+ * if available)
+ */
+#  ifdef _MINIX
+#   include <sgtty.h>
+#   define TIOCSETN    TIOCSETP
+#  else
+#   include <sys/ioctl.h>
+#  endif
+typedef struct {
+       struct sgttyb   sgttyb;
+#  ifdef TIOCGATC
+       struct lchars   lchars;
+#  else /* TIOCGATC */
+       struct tchars   tchars;
+#   ifdef TIOCGLTC
+       struct ltchars  ltchars;
+#   endif /* TIOCGLTC */
+#  endif /* TIOCGATC */
+} TTY_state;
+# endif /* HAVE_TERMIO_H */
+#endif /* HAVE_TERMIOS_H */
+
+/* Flags for set_tty() */
+#define TF_NONE                0x00
+#define TF_WAIT                0x01    /* drain output, even it requires sleep() */
+#define TF_MIPSKLUDGE  0x02    /* kludge to unwedge RISC/os 5.0 tty driver */
+
+EXTERN int             tty_fd I__(-1); /* dup'd tty file descriptor */
+EXTERN int             tty_devtty;     /* true if tty_fd is from /dev/tty */
+EXTERN TTY_state       tty_state;      /* saved tty state */
+
+extern int     get_tty ARGS((int fd, TTY_state *ts));
+extern int     set_tty ARGS((int fd, TTY_state *ts, int flags));
+extern void    tty_init ARGS((int init_ttystate));
+extern void    tty_close ARGS((void));
+
+/* be sure not to interfere with anyone else's idea about EXTERN */
+#ifdef EXTERN_DEFINED
+# undef EXTERN_DEFINED
+# undef EXTERN
+#endif
+#undef I__
diff --git a/bin/ksh/var.c b/bin/ksh/var.c
new file mode 100644 (file)
index 0000000..b98a5dd
--- /dev/null
@@ -0,0 +1,1260 @@
+/*     $NetBSD: var.c,v 1.17 2011/10/16 17:12:11 joerg Exp $   */
+
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: var.c,v 1.17 2011/10/16 17:12:11 joerg Exp $");
+#endif
+
+
+#include "sh.h"
+#include "ksh_time.h"
+#include "ksh_limval.h"
+#include "ksh_stat.h"
+#include <ctype.h>
+
+/*
+ * Variables
+ *
+ * WARNING: unreadable code, needs a rewrite
+ *
+ * if (flag&INTEGER), val.i contains integer value, and type contains base.
+ * otherwise, (val.s + type) contains string value.
+ * if (flag&EXPORT), val.s contains "name=value" for E-Z exporting.
+ */
+static struct tbl vtemp;
+static struct table specials;
+static char    *formatstr      ARGS((struct tbl *vp, const char *s));
+static void    export          ARGS((struct tbl *vp, const char *val));
+static int     special         ARGS((const char *name));
+static void    unspecial       ARGS((const char *name));
+static void    getspec         ARGS((struct tbl *vp));
+static void    setspec         ARGS((struct tbl *vp));
+static void    unsetspec       ARGS((struct tbl *vp));
+static struct tbl *arraysearch  ARGS((struct tbl *, int));
+
+/*
+ * create a new block for function calls and simple commands
+ * assume caller has allocated and set up e->loc
+ */
+void
+newblock()
+{
+       register struct block *l;
+       static char *const empty[] = {null};
+
+       l = (struct block *) alloc(sizeof(struct block), ATEMP);
+       l->flags = 0;
+       ainit(&l->area); /* todo: could use e->area (l->area => l->areap) */
+       if (!e->loc) {
+               l->argc = 0;
+               l->argv = (char **) __UNCONST(empty);
+       } else {
+               l->argc = e->loc->argc;
+               l->argv = e->loc->argv;
+       }
+       l->exit = l->error = NULL;
+       tinit(&l->vars, &l->area, 0);
+       tinit(&l->funs, &l->area, 0);
+       l->next = e->loc;
+       e->loc = l;
+}
+
+/*
+ * pop a block handling special variables
+ */
+void
+popblock()
+{
+       register struct block *l = e->loc;
+       register struct tbl *vp, **vpp = l->vars.tbls, *vq;
+       register int i;
+
+       e->loc = l->next;       /* pop block */
+       for (i = l->vars.size; --i >= 0; ) {
+               if ((vp = *vpp++) != NULL && (vp->flag&SPECIAL)) {
+                       if ((vq = global(vp->name))->flag & ISSET)
+                               setspec(vq);
+                       else
+                               unsetspec(vq);
+               }
+       }
+       if (l->flags & BF_DOGETOPTS)
+               user_opt = l->getopts_state;
+       afreeall(&l->area);
+       afree(l, ATEMP);
+}
+
+/* called by main() to initialize variable data structures */
+void
+initvar()
+{
+       static const struct {
+               const char *name;
+               int v;
+       } names[] = {
+                       { "COLUMNS",            V_COLUMNS },
+                       { "IFS",                V_IFS },
+                       { "OPTIND",             V_OPTIND },
+                       { "PATH",               V_PATH },
+                       { "POSIXLY_CORRECT",    V_POSIXLY_CORRECT },
+                       { "TMPDIR",             V_TMPDIR },
+#ifdef HISTORY
+                       { "HISTFILE",           V_HISTFILE },
+                       { "HISTSIZE",           V_HISTSIZE },
+#endif /* HISTORY */
+#ifdef EDIT
+                       { "EDITOR",             V_EDITOR },
+                       { "VISUAL",             V_VISUAL },
+#endif /* EDIT */
+#ifdef KSH
+                       { "MAIL",               V_MAIL },
+                       { "MAILCHECK",          V_MAILCHECK },
+                       { "MAILPATH",           V_MAILPATH },
+                       { "RANDOM",             V_RANDOM },
+                       { "SECONDS",            V_SECONDS },
+                       { "TMOUT",              V_TMOUT },
+#endif /* KSH */
+                       { "LINENO",             V_LINENO },
+                       { (char *) 0,   0 }
+               };
+       int i;
+       struct tbl *tp;
+
+       tinit(&specials, APERM, 32); /* must be 2^n (currently 17 specials) */
+       for (i = 0; names[i].name; i++) {
+               tp = tenter(&specials, names[i].name, hash(names[i].name));
+               tp->flag = DEFINED|ISSET;
+               tp->type = names[i].v;
+       }
+}
+
+/* Used to calculate an array index for global()/local().  Sets *arrayp to
+ * non-zero if this is an array, sets *valp to the array index, returns
+ * the basename of the array.
+ */
+const char *array_index_calc(const char *n, bool_t *arrayp, int *valp);
+
+const char *
+array_index_calc(n, arrayp, valp)
+       const char *n;
+       bool_t *arrayp;
+       int *valp;
+{
+       const char *p;
+       int len;
+
+       *arrayp = FALSE;
+       p = skip_varname(n, FALSE);
+       if (p != n && *p == '[' && (len = array_ref_len(p))) {
+               char *sub, *tmp;
+               long rval;
+
+               /* Calculate the value of the subscript */
+               *arrayp = TRUE;
+               tmp = str_nsave(p+1, len-2, ATEMP);
+               sub = substitute(tmp, 0);
+               afree(tmp, ATEMP);
+               n = str_nsave(n, p - n, ATEMP);
+               evaluate(sub, &rval, KSH_UNWIND_ERROR);
+               if (rval < 0 || rval > ARRAYMAX)
+                       errorf("%s: subscript out of range", n);
+               *valp = rval;
+               afree(sub, ATEMP);
+       }
+       return n;
+}
+
+/*
+ * Search for variable, if not found create globally.
+ */
+struct tbl *
+global(n)
+       register const char *n;
+{
+       register struct block *l = e->loc;
+       register struct tbl *vp;
+       register int c;
+       unsigned h;
+       bool_t   array;
+       int      val;
+
+       /* Check to see if this is an array */
+       n = array_index_calc(n, &array, &val);
+       h = hash(n);
+       c = n[0];
+       if (!letter(c)) {
+               if (array)
+                       errorf("bad substitution");
+               vp = &vtemp;
+               vp->flag = DEFINED;
+               vp->type = 0;
+               vp->areap = ATEMP;
+               *vp->name = c;
+               if (digit(c)) {
+                       for (c = 0; digit(*n); n++)
+                               c = c*10 + *n-'0';
+                       if (c <= l->argc)
+                               /* setstr can't fail here */
+                               setstr(vp, l->argv[c], KSH_RETURN_ERROR);
+                       vp->flag |= RDONLY;
+                       return vp;
+               }
+               vp->flag |= RDONLY;
+               if (n[1] != '\0')
+                       return vp;
+               vp->flag |= ISSET|INTEGER;
+               switch (c) {
+                 case '$':
+                       vp->val.i = kshpid;
+                       break;
+                 case '!':
+                       /* If no job, expand to nothing */
+                       if ((vp->val.i = j_async()) == 0)
+                               vp->flag &= ~(ISSET|INTEGER);
+                       break;
+                 case '?':
+                       vp->val.i = exstat;
+                       break;
+                 case '#':
+                       vp->val.i = l->argc;
+                       break;
+                 case '-':
+                       vp->flag &= ~INTEGER;
+                       vp->val.s = getoptions();
+                       break;
+                 default:
+                       vp->flag &= ~(ISSET|INTEGER);
+               }
+               return vp;
+       }
+       for (l = e->loc; ; l = l->next) {
+               vp = tsearch(&l->vars, n, h);
+               if (vp != NULL) {
+                       if (array)
+                               return arraysearch(vp, val);
+                       else
+                               return vp;
+               }
+               if (l->next == NULL)
+                       break;
+       }
+       vp = tenter(&l->vars, n, h);
+       if (array)
+               vp = arraysearch(vp, val);
+       vp->flag |= DEFINED;
+       if (special(n))
+               vp->flag |= SPECIAL;
+       return vp;
+}
+
+/*
+ * Search for local variable, if not found create locally.
+ */
+struct tbl *
+local(n, copy)
+       register const char *n;
+       bool_t copy;
+{
+       register struct block *l = e->loc;
+       register struct tbl *vp;
+       unsigned h;
+       bool_t   array;
+       int      val;
+
+       /* Check to see if this is an array */
+       n = array_index_calc(n, &array, &val);
+       h = hash(n);
+       if (!letter(*n)) {
+               vp = &vtemp;
+               vp->flag = DEFINED|RDONLY;
+               vp->type = 0;
+               vp->areap = ATEMP;
+               return vp;
+       }
+       vp = tenter(&l->vars, n, h);
+       if (copy && !(vp->flag & DEFINED)) {
+               struct block *ll = l;
+               struct tbl *vq = (struct tbl *) 0;
+
+               while ((ll = ll->next) && !(vq = tsearch(&ll->vars, n, h)))
+                       ;
+               if (vq) {
+                       vp->flag |= vq->flag & (EXPORT|INTEGER|RDONLY
+                                               |LJUST|RJUST|ZEROFIL
+                                               |LCASEV|UCASEV_AL|INT_U|INT_L);
+                       if (vq->flag & INTEGER)
+                               vp->type = vq->type;
+                       vp->u2.field = vq->u2.field;
+               }
+       }
+       if (array)
+               vp = arraysearch(vp, val);
+       vp->flag |= DEFINED;
+       if (special(n))
+               vp->flag |= SPECIAL;
+       return vp;
+}
+
+/* get variable string value */
+char *
+str_val(vp)
+       register struct tbl *vp;
+{
+       char *s;
+
+       if ((vp->flag&SPECIAL))
+               getspec(vp);
+       if (!(vp->flag&ISSET))
+               s = null;               /* special to dollar() */
+       else if (!(vp->flag&INTEGER))   /* string source */
+               s = vp->val.s + vp->type;
+       else {                          /* integer source */
+               /* worst case number length is when base=2, so use BITS(long) */
+                            /* minus base #     number    null */
+               static char strbuf[1 + 2 + 1 + BITS(long) + 1];
+               const char *digits = (vp->flag & UCASEV_AL) ?
+                                 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                               : "0123456789abcdefghijklmnopqrstuvwxyz";
+               register unsigned long n;
+               register int base;
+
+               s = strbuf + sizeof(strbuf);
+               if (vp->flag & INT_U)
+                       n = (unsigned long) vp->val.i;
+               else
+                       n = (vp->val.i < 0) ? -vp->val.i : vp->val.i;
+               base = (vp->type == 0) ? 10 : vp->type;
+
+               *--s = '\0';
+               do {
+                       *--s = digits[n % base];
+                       n /= base;
+               } while (n != 0);
+               if (base != 10) {
+                       *--s = '#';
+                       *--s = digits[base % 10];
+                       if (base >= 10)
+                               *--s = digits[base / 10];
+               }
+               if (!(vp->flag & INT_U) && vp->val.i < 0)
+                       *--s = '-';
+               if (vp->flag & (RJUST|LJUST)) { /* case already dealt with */
+                       s = formatstr(vp, s);
+                       (void)strlcpy(strbuf, s, sizeof(strbuf));
+                       afree(s, ATEMP);
+                       s = strbuf;
+               }
+       }
+       return s;
+}
+
+/* get variable integer value, with error checking */
+long
+intval(vp)
+       register struct tbl *vp;
+{
+       long num;
+       int base;
+
+       base = getint(vp, &num);
+       if (base == -1)
+               /* XXX check calls - is error here ok by POSIX? */
+               errorf("%s: bad number", str_val(vp));
+       return num;
+}
+
+/* set variable to string value */
+int
+setstr(vq, s, error_ok)
+       register struct tbl *vq;
+       const char *s;
+       int error_ok;
+{
+       char *fs = NULL;
+       int no_ro_check = error_ok & 0x4;
+       error_ok &= ~0x4;
+       if ((vq->flag & RDONLY) && !no_ro_check) {
+               warningf(TRUE, "%s: is read only", vq->name);
+               if (!error_ok)
+                       errorf("%s", null);
+               return 0;
+       }
+       if (!(vq->flag&INTEGER)) { /* string dest */
+               if ((vq->flag&ALLOC)) {
+                       /* debugging */
+                       if (s >= vq->val.s
+                           && s <= vq->val.s + strlen(vq->val.s))
+                               internal_errorf(TRUE,
+                                   "setstr: %s=%s: assigning to self",
+                                   vq->name, s);
+                       afree((void*)vq->val.s, vq->areap);
+               }
+               vq->flag &= ~(ISSET|ALLOC);
+               vq->type = 0;
+               if (s && (vq->flag & (UCASEV_AL|LCASEV|LJUST|RJUST)))
+                       s = fs = formatstr(vq, s);
+               if ((vq->flag&EXPORT))
+                       export(vq, s);
+               else {
+                       vq->val.s = str_save(s, vq->areap);
+                       vq->flag |= ALLOC;
+               }
+       } else                  /* integer dest */
+               if (!v_evaluate(vq, s, error_ok))
+                       return 0;
+       vq->flag |= ISSET;
+       if ((vq->flag&SPECIAL))
+               setspec(vq);
+       if (fs)
+               afree(fs, ATEMP);
+       return 1;
+}
+
+/* set variable to integer */
+void
+setint(vq, n)
+       register struct tbl *vq;
+       long n;
+{
+       if (!(vq->flag&INTEGER)) {
+               register struct tbl *vp = &vtemp;
+               vp->flag = (ISSET|INTEGER);
+               vp->type = 0;
+               vp->areap = ATEMP;
+               vp->val.i = n;
+               /* setstr can't fail here */
+               setstr(vq, str_val(vp), KSH_RETURN_ERROR);
+       } else
+               vq->val.i = n;
+       vq->flag |= ISSET;
+       if ((vq->flag&SPECIAL))
+               setspec(vq);
+}
+
+int
+getint(vp, nump)
+       struct tbl *vp;
+       long *nump;
+{
+       register char *s;
+       register int c;
+       int base, neg;
+       int have_base = 0;
+       long num;
+
+       if (vp->flag&SPECIAL)
+               getspec(vp);
+       /* XXX is it possible for ISSET to be set and val.s to be 0? */
+       if (!(vp->flag&ISSET) || (!(vp->flag&INTEGER) && vp->val.s == NULL))
+               return -1;
+       if (vp->flag&INTEGER) {
+               *nump = vp->val.i;
+               return vp->type;
+       }
+       s = vp->val.s + vp->type;
+       if (s == NULL)  /* redundant given initial test */
+               s = null;
+       base = 10;
+       num = 0;
+       neg = 0;
+       if (*s == '-') {
+               neg = 1;
+               s++;
+       }
+       if (s[0] == '0' && s[1] == 'x') {
+               base = 16;
+               have_base = 1;
+               s += 2;
+       }
+       for (c = (unsigned char)*s++; c ; c = (unsigned char)*s++) {
+               if (c == '#') {
+                       base = (int) num;
+                       if (have_base || base < 2 || base > 36)
+                               return -1;
+                       num = 0;
+                       have_base = 1;
+               } else if (letnum(c)) {
+                       if (isdigit(c))
+                               c -= '0';
+                       else if (islower(c))
+                               c -= 'a' - 10; /* todo: assumes ascii */
+                       else if (isupper(c))
+                               c -= 'A' - 10; /* todo: assumes ascii */
+                       else
+                               c = -1; /* _: force error */
+                       if (c < 0 || c >= base)
+                               return -1;
+                       num = num * base + c;
+               } else
+                       return -1;
+       }
+       if (neg)
+               num = -num;
+       *nump = num;
+       return base;
+}
+
+/* convert variable vq to integer variable, setting its value from vp
+ * (vq and vp may be the same)
+ */
+struct tbl *
+setint_v(vq, vp)
+       register struct tbl *vq, *vp;
+{
+       int base;
+       long num;
+
+       if ((base = getint(vp, &num)) == -1)
+               return NULL;
+       if (!(vq->flag & INTEGER) && (vq->flag & ALLOC)) {
+               vq->flag &= ~ALLOC;
+               afree(vq->val.s, vq->areap);
+       }
+       vq->val.i = num;
+       if (vq->type == 0) /* default base */
+               vq->type = base;
+       vq->flag |= ISSET|INTEGER;
+       if (vq->flag&SPECIAL)
+               setspec(vq);
+       return vq;
+}
+
+static char *
+formatstr(vp, s)
+       struct tbl *vp;
+       const char *s;
+{
+       int olen, nlen;
+       char *p, *q;
+
+       olen = strlen(s);
+
+       if (vp->flag & (RJUST|LJUST)) {
+               if (!vp->u2.field)      /* default field width */
+                       vp->u2.field = olen;
+               nlen = vp->u2.field;
+       } else
+               nlen = olen;
+
+       p = (char *) alloc(nlen + 1, ATEMP);
+       if (vp->flag & (RJUST|LJUST)) {
+               int slen;
+
+               if (vp->flag & RJUST) {
+                       const char *r = s + olen;
+                       /* strip trailing spaces (at&t ksh uses q[-1] == ' ') */
+                       while (r > s && isspace((unsigned char)r[-1]))
+                               --r;
+                       slen = r - s;
+                       if (slen > vp->u2.field) {
+                               s += slen - vp->u2.field;
+                               slen = vp->u2.field;
+                       }
+                       shf_snprintf(p, nlen + 1,
+                               ((vp->flag & ZEROFIL) && digit(*s)) ?
+                                         "%0*s%.*s" : "%*s%.*s",
+                               vp->u2.field - slen, null, slen, s);
+               } else {
+                       /* strip leading spaces/zeros */
+                       while (isspace((unsigned char)*s))
+                               s++;
+                       if (vp->flag & ZEROFIL)
+                               while (*s == '0')
+                                       s++;
+                       shf_snprintf(p, nlen + 1, "%-*.*s",
+                               vp->u2.field, vp->u2.field, s);
+               }
+       } else
+               memcpy(p, s, olen + 1);
+
+       if (vp->flag & UCASEV_AL) {
+               for (q = p; *q; q++)
+                       if (islower((unsigned char)*q))
+                               *q = toupper((unsigned char)*q);
+       } else if (vp->flag & LCASEV) {
+               for (q = p; *q; q++)
+                       if (isupper((unsigned char)*q))
+                               *q = tolower((unsigned char)*q);
+       }
+
+       return p;
+}
+
+/*
+ * make vp->val.s be "name=value" for quick exporting.
+ */
+static void
+export(vp, val)
+       register struct tbl *vp;
+       const char *val;
+{
+       register char *xp;
+       char *op = (vp->flag&ALLOC) ? vp->val.s : NULL;
+       int namelen = strlen(vp->name);
+       int vallen = strlen(val) + 1;
+
+       vp->flag |= ALLOC;
+       xp = (char*)alloc(namelen + 1 + vallen, vp->areap);
+       memcpy(vp->val.s = xp, vp->name, namelen);
+       xp += namelen;
+       *xp++ = '=';
+       vp->type = xp - vp->val.s; /* offset to value */
+       memcpy(xp, val, vallen);
+       if (op != NULL)
+               afree((void*)op, vp->areap);
+}
+
+/*
+ * lookup variable (according to (set&LOCAL)),
+ * set its attributes (INTEGER, RDONLY, EXPORT, TRACE, LJUST, RJUST, ZEROFIL,
+ * LCASEV, UCASEV_AL), and optionally set its value if an assignment.
+ */
+struct tbl *
+typeset(var, set, clr, field, base)
+       register const char *var;
+       Tflag clr, set;
+       int field, base;
+{
+       register struct tbl *vp;
+       struct tbl *vpbase, *t;
+       char *tvar;
+       const char *val;
+
+       /* check for valid variable name, search for value */
+       val = skip_varname(var, FALSE);
+       if (val == var)
+               return NULL;
+       if (*val == '[') {
+               int len;
+
+               len = array_ref_len(val);
+               if (len == 0)
+                       return NULL;
+               /* IMPORT is only used when the shell starts up and is
+                * setting up its environment.  Allow only simple array
+                * references at this time since parameter/command substitution
+                * is performed on the [expression], which would be a major
+                * security hole.
+                */
+               if (set & IMPORT) {
+                       int i;
+                       for (i = 1; i < len - 1; i++)
+                               if (!digit(val[i]))
+                                       return NULL;
+               }
+               val += len;
+       }
+       if (*val == '=')
+               tvar = str_nsave(var, val++ - var, ATEMP);
+       else {
+               /* Importing from original environment: must have an = */
+               if (set & IMPORT)
+                       return NULL;
+               tvar = (char *) __UNCONST(var);
+               val = NULL;
+       }
+
+       /* Prevent typeset from creating a local PATH/ENV/SHELL */
+       if (Flag(FRESTRICTED) && (strcmp(tvar, "PATH") == 0
+                                 || strcmp(tvar, "ENV") == 0
+                                 || strcmp(tvar, "SHELL") == 0))
+               errorf("%s: restricted", tvar);
+
+       vp = (set&LOCAL) ? local(tvar, (set & LOCAL_COPY) ? TRUE : FALSE)
+               : global(tvar);
+       set &= ~(LOCAL|LOCAL_COPY);
+
+       vpbase = (vp->flag & ARRAY) ? global(arrayname(var)) : vp;
+
+       /* only allow export flag to be set.  at&t ksh allows any attribute to
+        * be changed, which means it can be truncated or modified
+        * (-L/-R/-Z/-i).
+        */
+       if ((vpbase->flag&RDONLY)
+           && (val || clr || (set & ~EXPORT)))
+               /* XXX check calls - is error here ok by POSIX? */
+               errorf("%s: is read only", tvar);
+       if (val)
+               afree(tvar, ATEMP);
+
+       /* most calls are with set/clr == 0 */
+       if (set | clr) {
+               int ok = 1;
+               /* XXX if x[0] isn't set, there will be problems: need to have
+                * one copy of attributes for arrays...
+                */
+               for (t = vpbase; t; t = t->u.array) {
+                       int fake_assign;
+                       char UNINITIALIZED(*s);
+                       char UNINITIALIZED(*free_me);
+
+                       fake_assign = (t->flag & ISSET) && (!val || t != vp)
+                                     && ((set & (UCASEV_AL|LCASEV|LJUST|RJUST|ZEROFIL))
+                                         || ((t->flag & INTEGER) && (clr & INTEGER))
+                                         || (!(t->flag & INTEGER) && (set & INTEGER)));
+                       if (fake_assign) {
+                               if (t->flag & INTEGER) {
+                                       s = str_val(t);
+                                       free_me = (char *) 0;
+                               } else {
+                                       s = t->val.s + t->type;
+                                       free_me = (t->flag & ALLOC) ? t->val.s
+                                                                 : (char *) 0;
+                               }
+                               t->flag &= ~ALLOC;
+                       }
+                       if (!(t->flag & INTEGER) && (set & INTEGER)) {
+                               t->type = 0;
+                               t->flag &= ~ALLOC;
+                       }
+                       t->flag = (t->flag | set) & ~clr;
+                       /* Don't change base if assignment is to be done,
+                        * in case assignment fails.
+                        */
+                       if ((set & INTEGER) && base > 0 && (!val || t != vp))
+                               t->type = base;
+                       if (set & (LJUST|RJUST|ZEROFIL))
+                               t->u2.field = field;
+                       if (fake_assign) {
+                               if (!setstr(t, s, KSH_RETURN_ERROR)) {
+                                       /* Somewhat arbitrary action here:
+                                        * zap contents of variable, but keep
+                                        * the flag settings.
+                                        */
+                                       ok = 0;
+                                       if (t->flag & INTEGER)
+                                               t->flag &= ~ISSET;
+                                       else {
+                                               if (t->flag & ALLOC)
+                                                       afree((void*) t->val.s,
+                                                             t->areap);
+                                               t->flag &= ~(ISSET|ALLOC);
+                                               t->type = 0;
+                                       }
+                               }
+                               if (free_me)
+                                       afree((void *) free_me, t->areap);
+                       }
+               }
+               if (!ok)
+                   errorf("%s", null);
+       }
+
+       if (val != NULL) {
+               if (vp->flag&INTEGER) {
+                       /* do not zero base before assignment */
+                       setstr(vp, val, KSH_UNWIND_ERROR | 0x4);
+                       /* Done after assignment to override default */
+                       if (base > 0)
+                               vp->type = base;
+               } else
+                       /* setstr can't fail (readonly check already done) */
+                       setstr(vp, val, KSH_RETURN_ERROR | 0x4);
+       }
+
+       /* only x[0] is ever exported, so use vpbase */
+       if ((vpbase->flag&EXPORT) && !(vpbase->flag&INTEGER)
+           && vpbase->type == 0)
+               export(vpbase, (vpbase->flag&ISSET) ? vpbase->val.s : null);
+
+       return vp;
+}
+
+/* Unset a variable.  array_ref is set if there was an array reference in
+ * the name lookup (eg, x[2]).
+ */
+void
+unset(vp, array_ref)
+       register struct tbl *vp;
+       int array_ref;
+{
+       if (vp->flag & ALLOC)
+               afree((void*)vp->val.s, vp->areap);
+       if ((vp->flag & ARRAY) && !array_ref) {
+               struct tbl *a, *tmp;
+
+               /* Free up entire array */
+               for (a = vp->u.array; a; ) {
+                       tmp = a;
+                       a = a->u.array;
+                       if (tmp->flag & ALLOC)
+                               afree((void *) tmp->val.s, tmp->areap);
+                       afree(tmp, tmp->areap);
+               }
+               vp->u.array = (struct tbl *) 0;
+       }
+       /* If foo[0] is being unset, the remainder of the array is kept... */
+       vp->flag &= SPECIAL | (array_ref ? ARRAY|DEFINED : 0);
+       if (vp->flag & SPECIAL)
+               unsetspec(vp);  /* responsible for `unspecial'ing var */
+}
+
+/* return a pointer to the first char past a legal variable name (returns the
+ * argument if there is no legal name, returns * a pointer to the terminating
+ * null if whole string is legal).
+ */
+char *
+skip_varname(s, aok)
+       const char *s;
+       int aok;
+{
+       int alen;
+
+       if (s && letter(*s)) {
+               while (*++s && letnum(*s))
+                       ;
+               if (aok && *s == '[' && (alen = array_ref_len(s)))
+                       s += alen;
+       }
+       return (char *) __UNCONST(s);
+}
+
+/* Return a pointer to the first character past any legal variable name.  */
+char *
+skip_wdvarname(s, aok)
+       const char *s;
+       int aok;        /* skip array de-reference? */
+{
+       if (s[0] == CHAR && letter(s[1])) {
+               do
+                       s += 2;
+               while (s[0] == CHAR && letnum(s[1]));
+               if (aok && s[0] == CHAR && s[1] == '[') {
+                       /* skip possible array de-reference */
+                       const char *p = s;
+                       char c;
+                       int depth = 0;
+
+                       while (1) {
+                               if (p[0] != CHAR)
+                                       break;
+                               c = p[1];
+                               p += 2;
+                               if (c == '[')
+                                       depth++;
+                               else if (c == ']' && --depth == 0) {
+                                       s = p;
+                                       break;
+                               }
+                       }
+               }
+       }
+       return (char *) __UNCONST(s);
+}
+
+/* Check if coded string s is a variable name */
+int
+is_wdvarname(s, aok)
+       const char *s;
+       int aok;
+{
+       char *p = skip_wdvarname(s, aok);
+
+       return p != s && p[0] == EOS;
+}
+
+/* Check if coded string s is a variable assignment */
+int
+is_wdvarassign(s)
+       const char *s;
+{
+       char *p = skip_wdvarname(s, TRUE);
+
+       return p != s && p[0] == CHAR && p[1] == '=';
+}
+
+/*
+ * Make the exported environment from the exported names in the dictionary.
+ */
+char **
+makenv()
+{
+       struct block *l = e->loc;
+       XPtrV env;
+       register struct tbl *vp, **vpp;
+       register int i;
+
+       XPinit(env, 64);
+       for (l = e->loc; l != NULL; l = l->next)
+               for (vpp = l->vars.tbls, i = l->vars.size; --i >= 0; )
+                       if ((vp = *vpp++) != NULL
+                           && (vp->flag&(ISSET|EXPORT)) == (ISSET|EXPORT)) {
+                               register struct block *l2;
+                               register struct tbl *vp2;
+                               unsigned h = hash(vp->name);
+
+                               /* unexport any redefined instances */
+                               for (l2 = l->next; l2 != NULL; l2 = l2->next) {
+                                       vp2 = tsearch(&l2->vars, vp->name, h);
+                                       if (vp2 != NULL)
+                                               vp2->flag &= ~EXPORT;
+                               }
+                               if ((vp->flag&INTEGER)) {
+                                       /* integer to string */
+                                       char *val;
+                                       val = str_val(vp);
+                                       vp->flag &= ~(INTEGER|RDONLY);
+                                       /* setstr can't fail here */
+                                       setstr(vp, val, KSH_RETURN_ERROR);
+                               }
+                               XPput(env, vp->val.s);
+                       }
+       XPput(env, NULL);
+       return (char **) XPclose(env);
+}
+
+/*
+ * Called after a fork in parent to bump the random number generator.
+ * Done to ensure children will not get the same random number sequence
+ * if the parent doesn't use $RANDOM.
+ */
+void
+change_random()
+{
+    rand();
+}
+
+/*
+ * handle special variables with side effects - PATH, SECONDS.
+ */
+
+/* Test if name is a special parameter */
+static int
+special(name)
+       register const char * name;
+{
+       register struct tbl *tp;
+
+       tp = tsearch(&specials, name, hash(name));
+       return tp && (tp->flag & ISSET) ? tp->type : V_NONE;
+}
+
+/* Make a variable non-special */
+static void
+unspecial(name)
+       register const char * name;
+{
+       register struct tbl *tp;
+
+       tp = tsearch(&specials, name, hash(name));
+       if (tp)
+               tdelete(tp);
+}
+
+#ifdef KSH
+static time_t  seconds;                /* time SECONDS last set */
+#endif /* KSH */
+static int     user_lineno;            /* what user set $LINENO to */
+
+static void
+getspec(vp)
+       register struct tbl *vp;
+{
+       switch (special(vp->name)) {
+#ifdef KSH
+         case V_SECONDS:
+               vp->flag &= ~SPECIAL;
+               /* On start up the value of SECONDS is used before seconds
+                * has been set - don't do anything in this case
+                * (see initcoms[] in main.c).
+                */
+               if (vp->flag & ISSET)
+                       setint(vp, (long) (time((time_t *)0) - seconds));
+               vp->flag |= SPECIAL;
+               break;
+         case V_RANDOM:
+               vp->flag &= ~SPECIAL;
+               setint(vp, (long) (rand() & 0x7fff));
+               vp->flag |= SPECIAL;
+               break;
+#endif /* KSH */
+#ifdef HISTORY
+         case V_HISTSIZE:
+               vp->flag &= ~SPECIAL;
+               setint(vp, (long) histsize);
+               vp->flag |= SPECIAL;
+               break;
+#endif /* HISTORY */
+         case V_OPTIND:
+               vp->flag &= ~SPECIAL;
+               setint(vp, (long) user_opt.uoptind);
+               vp->flag |= SPECIAL;
+               break;
+         case V_LINENO:
+               vp->flag &= ~SPECIAL;
+               setint(vp, (long) current_lineno + user_lineno);
+               vp->flag |= SPECIAL;
+               break;
+       }
+}
+
+static void
+setspec(vp)
+       register struct tbl *vp;
+{
+       char *s;
+
+       switch (special(vp->name)) {
+         case V_PATH:
+               if (path)
+                       afree(path, APERM);
+               path = str_save(str_val(vp), APERM);
+               flushcom(1);    /* clear tracked aliases */
+               break;
+         case V_IFS:
+               setctypes(s = str_val(vp), C_IFS);
+               ifs0 = *s;
+               break;
+         case V_OPTIND:
+               vp->flag &= ~SPECIAL;
+               getopts_reset((int) intval(vp));
+               vp->flag |= SPECIAL;
+               break;
+         case V_POSIXLY_CORRECT:
+               change_flag(FPOSIX, OF_SPECIAL, 1);
+               break;
+         case V_TMPDIR:
+               if (tmpdir) {
+                       afree(tmpdir, APERM);
+                       tmpdir = (char *) 0;
+               }
+               /* Use tmpdir iff it is an absolute path, is writable and
+                * searchable and is a directory...
+                */
+               {
+                       struct stat statb;
+                       s = str_val(vp);
+                       if (ISABSPATH(s) && eaccess(s, W_OK|X_OK) == 0
+                           && stat(s, &statb) == 0 && S_ISDIR(statb.st_mode))
+                               tmpdir = str_save(s, APERM);
+               }
+               break;
+#ifdef HISTORY
+         case V_HISTSIZE:
+               vp->flag &= ~SPECIAL;
+               sethistsize((int) intval(vp));
+               vp->flag |= SPECIAL;
+               break;
+         case V_HISTFILE:
+               sethistfile(str_val(vp));
+               break;
+#endif /* HISTORY */
+#ifdef EDIT
+         case V_VISUAL:
+               set_editmode(str_val(vp));
+               break;
+         case V_EDITOR:
+               if (!(global("VISUAL")->flag & ISSET))
+                       set_editmode(str_val(vp));
+               break;
+         case V_COLUMNS:
+               if ((x_cols = intval(vp)) <= MIN_COLS)
+                       x_cols = MIN_COLS;
+               break;
+#endif /* EDIT */
+#ifdef KSH
+         case V_MAIL:
+               mbset(str_val(vp));
+               break;
+         case V_MAILPATH:
+               mpset(str_val(vp));
+               break;
+         case V_MAILCHECK:
+               vp->flag &= ~SPECIAL;
+               mcset(intval(vp));
+               vp->flag |= SPECIAL;
+               break;
+         case V_RANDOM:
+               vp->flag &= ~SPECIAL;
+               srand((unsigned int)intval(vp));
+               vp->flag |= SPECIAL;
+               break;
+         case V_SECONDS:
+               vp->flag &= ~SPECIAL;
+               seconds = time((time_t*) 0) - intval(vp);
+               vp->flag |= SPECIAL;
+               break;
+         case V_TMOUT:
+               /* at&t ksh seems to do this (only listen if integer) */
+               if (vp->flag & INTEGER)
+                       ksh_tmout = vp->val.i >= 0 ? vp->val.i : 0;
+               break;
+#endif /* KSH */
+         case V_LINENO:
+               vp->flag &= ~SPECIAL;
+               /* The -1 is because line numbering starts at 1. */
+               user_lineno = (unsigned int) intval(vp) - current_lineno - 1;
+               vp->flag |= SPECIAL;
+               break;
+       }
+}
+
+static void
+unsetspec(vp)
+       register struct tbl *vp;
+{
+       switch (special(vp->name)) {
+         case V_PATH:
+               if (path)
+                       afree(path, APERM);
+               path = str_save(def_path, APERM);
+               flushcom(1);    /* clear tracked aliases */
+               break;
+         case V_IFS:
+               setctypes(" \t\n", C_IFS);
+               ifs0 = ' ';
+               break;
+         case V_TMPDIR:
+               /* should not become unspecial */
+               if (tmpdir) {
+                       afree(tmpdir, APERM);
+                       tmpdir = (char *) 0;
+               }
+               break;
+#ifdef KSH
+         case V_MAIL:
+               mbset((char *) 0);
+               break;
+         case V_MAILPATH:
+               mpset((char *) 0);
+               break;
+#endif /* KSH */
+
+         case V_LINENO:
+#ifdef KSH
+         case V_MAILCHECK:     /* at&t ksh leaves previous value in place */
+         case V_RANDOM:
+         case V_SECONDS:
+         case V_TMOUT:         /* at&t ksh leaves previous value in place */
+#endif /* KSH */
+               unspecial(vp->name);
+               break;
+
+         /* at&t ksh man page says OPTIND, OPTARG and _ lose special meaning,
+          * but OPTARG does not (still set by getopts) and _ is also still
+          * set in various places.
+          * Don't know what at&t does for:
+          *            MAIL, MAILPATH, HISTSIZE, HISTFILE,
+          * Unsetting these in at&t ksh does not loose the `specialness':
+          *    no effect: IFS, COLUMNS, PATH, TMPDIR,
+          *            VISUAL, EDITOR,
+          * pdkshisms: no effect:
+          *            POSIXLY_CORRECT (use set +o posix instead)
+          */
+       }
+}
+
+/*
+ * Search for (and possibly create) a table entry starting with
+ * vp, indexed by val.
+ */
+static struct tbl *
+arraysearch(vp, val)
+       struct tbl *vp;
+       int val;
+{
+       struct tbl *prev, *curr, *new;
+       size_t namelen = strlen(vp->name) + 1;
+
+       vp->flag |= ARRAY|DEFINED;
+
+       /* The table entry is always [0] */
+       if (val == 0) {
+               vp->index = 0;
+               return vp;
+       }
+       prev = vp;
+       curr = vp->u.array;
+       while (curr && curr->index < val) {
+               prev = curr;
+               curr = curr->u.array;
+       }
+       if (curr && curr->index == val) {
+               if (curr->flag&ISSET)
+                       return curr;
+               else
+                       new = curr;
+       } else
+               new = (struct tbl *)alloc(sizeof(struct tbl) + namelen,
+                   vp->areap);
+       strlcpy(new->name, vp->name, namelen);
+       new->flag = vp->flag & ~(ALLOC|DEFINED|ISSET|SPECIAL);
+       new->type = vp->type;
+       new->areap = vp->areap;
+       new->u2.field = vp->u2.field;
+       new->index = val;
+       if (curr != new) {              /* not reusing old array entry */
+               prev->u.array = new;
+               new->u.array = curr;
+       }
+       return new;
+}
+
+/* Return the length of an array reference (eg, [1+2]) - cp is assumed
+ * to point to the open bracket.  Returns 0 if there is no matching closing
+ * bracket.
+ */
+int
+array_ref_len(cp)
+       const char *cp;
+{
+       const char *s = cp;
+       int c;
+       int depth = 0;
+
+       while ((c = *s++) && (c != ']' || --depth))
+               if (c == '[')
+                       depth++;
+       if (!c)
+               return 0;
+       return s - cp;
+}
+
+/*
+ * Make a copy of the base of an array name
+ */
+char *
+arrayname(str)
+       const char *str;
+{
+       const char *p;
+
+       if ((p = strchr(str, '[')) == 0)
+               /* Shouldn't happen, but why worry? */
+               return (char *) __UNCONST(str);
+
+       return str_nsave(str, p - str, ATEMP);
+}
+
+/* Set (or overwrite, if !reset) the array variable var to the values in vals.
+ */
+void
+set_array(var, reset, vals)
+       const char *var;
+       int reset;
+       char **vals;
+{
+       struct tbl *vp, *vq;
+       int i;
+
+       /* to get local array, use "typeset foo; set -A foo" */
+       vp = global(var);
+
+       /* Note: at&t ksh allows set -A but not set +A of a read-only var */
+       if ((vp->flag&RDONLY))
+               errorf("%s: is read only", var);
+       /* This code is quite non-optimal */
+       if (reset > 0)
+               /* trash existing values and attributes */
+               unset(vp, 0);
+       /* todo: would be nice for assignment to completely succeed or
+        * completely fail.  Only really effects integer arrays:
+        * evaluation of some of vals[] may fail...
+        */
+       for (i = 0; vals[i]; i++) {
+               vq = arraysearch(vp, i);
+               /* would be nice to deal with errors here... (see above) */
+               setstr(vq, vals[i], KSH_RETURN_ERROR);
+       }
+}
diff --git a/bin/ksh/version.c b/bin/ksh/version.c
new file mode 100644 (file)
index 0000000..3db43d0
--- /dev/null
@@ -0,0 +1,16 @@
+/*     $NetBSD: version.c,v 1.5 2005/06/26 19:09:00 christos Exp $     */
+
+/*
+ * value of $KSH_VERSION (or $SH_VERSION)
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: version.c,v 1.5 2005/06/26 19:09:00 christos Exp $");
+#endif
+
+
+#include "sh.h"
+
+char ksh_version [] =
+       "@(#)PD KSH v5.2.14 99/07/13.2";
diff --git a/bin/ksh/vi.c b/bin/ksh/vi.c
new file mode 100644 (file)
index 0000000..ed3d06f
--- /dev/null
@@ -0,0 +1,2203 @@
+/*     $NetBSD: vi.c,v 1.12 2011/06/22 03:56:17 mrg Exp $      */
+
+/*
+ *     vi command editing
+ *     written by John Rochester (initially for nsh)
+ *     bludgeoned to fit pdksh by Larry Bouzane, Jeff Sparkes & Eric Gisin
+ *
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: vi.c,v 1.12 2011/06/22 03:56:17 mrg Exp $");
+#endif
+
+#include "config.h"
+#ifdef VI
+
+#include "sh.h"
+#include <ctype.h>
+#include "ksh_stat.h"          /* completion */
+#include "edit.h"
+
+#define CMDLEN         1024
+#define Ctrl(c)                (c&0x1f)
+#define        is_wordch(c)    (letnum(c))
+
+struct edstate {
+       int     winleft;
+       char    *cbuf;
+       int     cbufsize;
+       int     linelen;
+       int     cursor;
+};
+
+
+static int     vi_hook ARGS((int));
+static void    vi_reset ARGS((char *, size_t));
+static int     nextstate ARGS((int));
+static int     vi_insert ARGS((int));
+static int     vi_cmd ARGS((int, const char *));
+static int     domove ARGS((int, const char *, int));
+static int     redo_insert ARGS((int));
+static void    yank_range ARGS((int, int));
+static int     bracktype ARGS((int));
+static void    save_cbuf ARGS((void));
+static void    restore_cbuf ARGS((void));
+static void    edit_reset ARGS((char *, size_t));
+static int     putbuf ARGS((const char *, int, int));
+static void    del_range ARGS((int, int));
+static int     findch ARGS((int, int, int, int));
+static int     forwword ARGS((int));
+static int     backword ARGS((int));
+static int     endword ARGS((int));
+static int     Forwword ARGS((int));
+static int     Backword ARGS((int));
+static int     Endword ARGS((int));
+static int     grabhist ARGS((int, int));
+static int     grabsearch ARGS((int, int, int, char *));
+static void    redraw_line ARGS((int));
+static void    refresh ARGS((int));
+static int     outofwin ARGS((void));
+static void    rewindow ARGS((void));
+static int     newcol ARGS((int, int));
+static void    display ARGS((char *, char *, int));
+static void    ed_mov_opt ARGS((int, char *));
+static int     expand_word ARGS((int));
+static int     complete_word ARGS((int, int));
+static int     print_expansions ARGS((struct edstate *, int));
+static int     char_len ARGS((int));
+static void    x_vi_zotc ARGS((int));
+static void    vi_pprompt ARGS((int));
+static void    vi_error ARGS((void));
+static void    vi_macro_reset ARGS((void));
+static int     x_vi_putbuf     ARGS((const char *, size_t));
+
+#define C_     0x1             /* a valid command that isn't a M_, E_, U_ */
+#define M_     0x2             /* movement command (h, l, etc.) */
+#define E_     0x4             /* extended command (c, d, y) */
+#define X_     0x8             /* long command (@, f, F, t, T, etc.) */
+#define U_     0x10            /* an UN-undoable command (that isn't a M_) */
+#define B_     0x20            /* bad command (^@) */
+#define Z_     0x40            /* repeat count defaults to 0 (not 1) */
+#define S_     0x80            /* search (/, ?) */
+
+#define is_bad(c)      (classify[(c)&0x7f]&B_)
+#define is_cmd(c)      (classify[(c)&0x7f]&(M_|E_|C_|U_))
+#define is_move(c)     (classify[(c)&0x7f]&M_)
+#define is_extend(c)   (classify[(c)&0x7f]&E_)
+#define is_long(c)     (classify[(c)&0x7f]&X_)
+#define is_undoable(c) (!(classify[(c)&0x7f]&U_))
+#define is_srch(c)     (classify[(c)&0x7f]&S_)
+#define is_zerocount(c)        (classify[(c)&0x7f]&Z_)
+
+const unsigned char    classify[128] = {
+   /*       0       1       2       3       4       5       6       7        */
+   /*   0   ^@     ^A      ^B      ^C      ^D      ^E      ^F      ^G        */
+           B_,     0,      0,      0,      0,      C_|U_,  C_|Z_,  0,
+   /*  01   ^H     ^I      ^J      ^K      ^L      ^M      ^N      ^O        */
+           M_,     C_|Z_,  0,      0,      C_|U_,  0,      C_,     0,
+   /*  02   ^P     ^Q      ^R      ^S      ^T      ^U      ^V      ^W        */
+           C_,     0,      C_|U_,  0,      0,      0,      C_,     0,
+   /*  03   ^X     ^Y      ^Z      ^[      ^\      ^]      ^^      ^_        */
+           C_,     0,      0,      C_|Z_,  0,      0,      0,      0,
+   /*  04  <space>  !       "       #       $       %       &       '        */
+           M_,     0,      0,      C_,     M_,     M_,     0,      0,
+   /*  05   (       )       *       +       ,       -       .       /        */
+           0,      0,      C_,     C_,     M_,     C_,     0,      C_|S_,
+   /*  06   0       1       2       3       4       5       6       7        */
+           M_,     0,      0,      0,      0,      0,      0,      0,
+   /*  07   8       9       :       ;       <       =       >       ?        */
+           0,      0,      0,      M_,     0,      C_,     0,      C_|S_,
+   /* 010   @       A       B       C       D       E       F       G        */
+           C_|X_,  C_,     M_,     C_,     C_,     M_,     M_|X_,  C_|U_|Z_,
+   /* 011   H       I       J       K       L       M       N       O        */
+           0,      C_,     0,      0,      0,      0,      C_|U_,  0,
+   /* 012   P       Q       R       S       T       U       V       W        */
+           C_,     0,      C_,     C_,     M_|X_,  C_,     0,      M_,
+   /* 013   X       Y       Z       [       \       ]       ^       _        */
+           C_,     C_|U_,  0,      0,      C_|Z_,  0,      M_,     C_|Z_,
+   /* 014   `       a       b       c       d       e       f       g        */
+           0,      C_,     M_,     E_,     E_,     M_,     M_|X_,  C_|Z_,
+   /* 015   h       i       j       k       l       m       n       o        */
+           M_,     C_,     C_|U_,  C_|U_,  M_,     0,      C_|U_,  0,
+   /* 016   p       q       r       s       t       u       v       w        */
+           C_,     0,      X_,     C_,     M_|X_,  C_|U_,  C_|U_|Z_,M_,
+   /* 017   x       y       z       {       |       }       ~      ^?        */
+           C_,     E_|U_,  0,      0,      M_|Z_,  0,      C_,     0
+};
+
+#define MAXVICMD       3
+#define SRCHLEN                40
+
+#define INSERT         1
+#define REPLACE                2
+
+#define VNORMAL                0               /* command, insert or replace mode */
+#define VARG1          1               /* digit prefix (first, eg, 5l) */
+#define VEXTCMD                2               /* cmd + movement (eg, cl) */
+#define VARG2          3               /* digit prefix (second, eg, 2c3l) */
+#define VXCH           4               /* f, F, t, T, @ */
+#define VFAIL          5               /* bad command */
+#define VCMD           6               /* single char command (eg, X) */
+#define VREDO          7               /* . */
+#define VLIT           8               /* ^V */
+#define VSEARCH                9               /* /, ? */
+#define VVERSION       10              /* <ESC> ^V */
+
+static char            undocbuf[CMDLEN];
+
+static struct edstate  *save_edstate ARGS((struct edstate *old));
+static void            restore_edstate ARGS((struct edstate *old, struct edstate *new));
+static void            free_edstate ARGS((struct edstate *old));
+
+static struct edstate  ebuf;
+static struct edstate  undobuf = { 0, undocbuf, CMDLEN, 0, 0 };
+
+static struct edstate  *es;                    /* current editor state */
+static struct edstate  *undo;
+
+static char    ibuf[CMDLEN];           /* input buffer */
+static int     first_insert;           /* set when starting in insert mode */
+static int     saved_inslen;           /* saved inslen for first insert */
+static int     inslen;                 /* length of input buffer */
+static int     srchlen;                /* length of current search pattern */
+static char    ybuf[CMDLEN];           /* yank buffer */
+static int     yanklen;                /* length of yank buffer */
+static int     fsavecmd = ' ';         /* last find command */
+static int     fsavech;                /* character to find */
+static char    lastcmd[MAXVICMD];      /* last non-move command */
+static int     lastac;                 /* argcnt for lastcmd */
+static int     lastsearch = ' ';       /* last search command */
+static char    srchpat[SRCHLEN];       /* last search pattern */
+static int     insert;                 /* non-zero in insert mode */
+static int     hnum;                   /* position in history */
+static int     ohnum;                  /* history line copied (after mod) */
+static int     hlast;                  /* 1 past last position in history */
+static int     modified;               /* buffer has been "modified" */
+static int     state;
+
+/* Information for keeping track of macros that are being expanded.
+ * The format of buf is the alias contents followed by a null byte followed
+ * by the name (letter) of the alias.  The end of the buffer is marked by
+ * a double null.  The name of the alias is stored so recursive macros can
+ * be detected.
+ */
+struct macro_state {
+    unsigned char      *p;     /* current position in buf */
+    unsigned char      *buf;   /* pointer to macro(s) being expanded */
+    int                        len;    /* how much data in buffer */
+};
+static struct macro_state macro;
+
+enum expand_mode { NONE, EXPAND, COMPLETE, PRINT };
+static enum expand_mode expanded = NONE;/* last input was expanded */
+
+int
+x_vi(buf, len)
+       char    *buf;
+       size_t  len;
+{
+       int     c;
+
+       vi_reset(buf, len > CMDLEN ? CMDLEN : len);
+       vi_pprompt(1);
+       x_flush();
+       while (1) {
+               if (macro.p) {
+                       c = *macro.p++;
+                       /* end of current macro? */
+                       if (!c) {
+                               /* more macros left to finish? */
+                               if (*macro.p++)
+                                       continue;
+                               /* must be the end of all the macros */
+                               vi_macro_reset();
+                               c = x_getc();
+                       }
+               } else {
+                       c = x_getc();
+               }
+               if (c == -1)
+                       break;
+               if (state != VLIT) {
+                       if (c == edchars.intr || c == edchars.quit) {
+                               /* pretend we got an interrupt */
+                               x_vi_zotc(c);
+                               x_flush();
+                               trapsig(c == edchars.intr ? SIGINT : SIGQUIT);
+                               x_mode(FALSE);
+                               unwind(LSHELL);
+                       } else if (c == edchars.eof && state != VVERSION) {
+                               if (es->linelen == 0) {
+                                       x_vi_zotc(edchars.eof);
+                                       c = -1;
+                                       break;
+                               }
+                               continue;
+                       }
+               }
+               if (vi_hook(c))
+                       break;
+               x_flush();
+       }
+
+       x_putc('\r'); x_putc('\n'); x_flush();
+
+       if (c == -1 || len <= (size_t)es->linelen)
+               return -1;
+
+       if (es->cbuf != buf)
+               memmove(buf, es->cbuf, es->linelen);
+
+       buf[es->linelen++] = '\n';
+
+       return es->linelen;
+}
+
+static int
+vi_hook(ch)
+       int             ch;
+{
+       static char     curcmd[MAXVICMD];
+       static char     locpat[SRCHLEN];
+       static int      cmdlen;
+       static int      argc1, argc2;
+
+       switch (state) {
+
+       case VNORMAL:
+               if (insert != 0) {
+                       if (ch == Ctrl('v')) {
+                               state = VLIT;
+                               ch = '^';
+                       }
+                       switch (vi_insert(ch)) {
+                       case -1:
+#ifdef OS2
+                               /* Arrow keys generate 0xe0X, where X is H.. */
+                               state = VCMD;
+                               argc1 = 1;
+                               switch (x_getc()) {
+                                 case 'H':
+                                       *curcmd='k';
+                                       break;
+                                 case 'K':
+                                       *curcmd='h';
+                                       break;
+                                 case 'P':
+                                       *curcmd='j';
+                                       break;
+                                 case 'M':
+                                       *curcmd='l';
+                                       break;
+                                 default:
+                                       vi_error();
+                                       state = VNORMAL;
+                               }
+                               break;
+#else /* OS2 */
+                               vi_error();
+                               state = VNORMAL;
+#endif /* OS2 */
+                               break;
+                       case 0:
+                               if (state == VLIT) {
+                                       es->cursor--;
+                                       refresh(0);
+                               } else
+                                       refresh(insert != 0);
+                               break;
+                       case 1:
+                               return 1;
+                       }
+               } else {
+                       if (ch == '\r' || ch == '\n')
+                               return 1;
+                       cmdlen = 0;
+                       argc1 = 0;
+                       if (ch >= '1' && ch <= '9') {
+                               argc1 = ch - '0';
+                               state = VARG1;
+                       } else {
+                               curcmd[cmdlen++] = ch;
+                               state = nextstate(ch);
+                               if (state == VSEARCH) {
+                                       save_cbuf();
+                                       es->cursor = 0;
+                                       es->linelen = 0;
+                                       if (ch == '/') {
+                                               if (putbuf("/", 1, 0) != 0) {
+                                                       return -1;
+                                               }
+                                       } else if (putbuf("?", 1, 0) != 0)
+                                                       return -1;
+                                       refresh(0);
+                               }
+                               if (state == VVERSION) {
+                                       save_cbuf();
+                                       es->cursor = 0;
+                                       es->linelen = 0;
+                                       putbuf(ksh_version + 4,
+                                               strlen(ksh_version + 4), 0);
+                                       refresh(0);
+                               }
+                       }
+               }
+               break;
+
+       case VLIT:
+               if (is_bad(ch)) {
+                       del_range(es->cursor, es->cursor + 1);
+                       vi_error();
+               } else
+                       es->cbuf[es->cursor++] = ch;
+               refresh(1);
+               state = VNORMAL;
+               break;
+
+       case VVERSION:
+               restore_cbuf();
+               state = VNORMAL;
+               refresh(0);
+               break;
+
+       case VARG1:
+               if (isdigit(ch))
+                       argc1 = argc1 * 10 + ch - '0';
+               else {
+                       curcmd[cmdlen++] = ch;
+                       state = nextstate(ch);
+               }
+               break;
+
+       case VEXTCMD:
+               argc2 = 0;
+               if (ch >= '1' && ch <= '9') {
+                       argc2 = ch - '0';
+                       state = VARG2;
+                       return 0;
+               } else {
+                       curcmd[cmdlen++] = ch;
+                       if (ch == curcmd[0])
+                               state = VCMD;
+                       else if (is_move(ch))
+                               state = nextstate(ch);
+                       else
+                               state = VFAIL;
+               }
+               break;
+
+       case VARG2:
+               if (isdigit(ch))
+                       argc2 = argc2 * 10 + ch - '0';
+               else {
+                       if (argc1 == 0)
+                               argc1 = argc2;
+                       else
+                               argc1 *= argc2;
+                       curcmd[cmdlen++] = ch;
+                       if (ch == curcmd[0])
+                               state = VCMD;
+                       else if (is_move(ch))
+                               state = nextstate(ch);
+                       else
+                               state = VFAIL;
+               }
+               break;
+
+       case VXCH:
+               if (ch == Ctrl('['))
+                       state = VNORMAL;
+               else {
+                       curcmd[cmdlen++] = ch;
+                       state = VCMD;
+               }
+               break;
+
+       case VSEARCH:
+               if (ch == '\r' || ch == '\n' /*|| ch == Ctrl('[')*/ ) {
+                       restore_cbuf();
+                       /* Repeat last search? */
+                       if (srchlen == 0) {
+                               if (!srchpat[0]) {
+                                       vi_error();
+                                       state = VNORMAL;
+                                       refresh(0);
+                                       return 0;
+                               }
+                       } else {
+                               locpat[srchlen] = '\0';
+                               (void) strlcpy(srchpat, locpat, sizeof srchpat);
+                       }
+                       state = VCMD;
+               } else if (ch == edchars.erase || ch == Ctrl('h')) {
+                       if (srchlen != 0) {
+                               srchlen--;
+                               es->linelen -= char_len((unsigned char) locpat[srchlen]);
+                               es->cursor = es->linelen;
+                               refresh(0);
+                               return 0;
+                       }
+                       restore_cbuf();
+                       state = VNORMAL;
+                       refresh(0);
+               } else if (ch == edchars.kill) {
+                       srchlen = 0;
+                       es->linelen = 1;
+                       es->cursor = 1;
+                       refresh(0);
+                       return 0;
+               } else if (ch == edchars.werase) {
+                       int i;
+                       int n = srchlen;
+
+                       while (n > 0 && isspace((unsigned char)locpat[n - 1]))
+                               n--;
+                       while (n > 0 && !isspace((unsigned char)locpat[n - 1]))
+                               n--;
+                       for (i = srchlen; --i >= n; )
+                               es->linelen -= char_len((unsigned char) locpat[i]);
+                       srchlen = n;
+                       es->cursor = es->linelen;
+                       refresh(0);
+                       return 0;
+               } else {
+                       if (srchlen == SRCHLEN - 1)
+                               vi_error();
+                       else {
+                               locpat[srchlen++] = ch;
+                               if ((ch & 0x80) && Flag(FVISHOW8)) {
+                                       if (es->linelen + 2 > es->cbufsize)
+                                               vi_error();
+                                       es->cbuf[es->linelen++] = 'M';
+                                       es->cbuf[es->linelen++] = '-';
+                                       ch &= 0x7f;
+                               }
+                               if (ch < ' ' || ch == 0x7f) {
+                                       if (es->linelen + 2 > es->cbufsize)
+                                               vi_error();
+                                       es->cbuf[es->linelen++] = '^';
+                                       es->cbuf[es->linelen++] = ch ^ '@';
+                               } else {
+                                       if (es->linelen >= es->cbufsize)
+                                               vi_error();
+                                       es->cbuf[es->linelen++] = ch;
+                               }
+                               es->cursor = es->linelen;
+                               refresh(0);
+                       }
+                       return 0;
+               }
+               break;
+       }
+
+       switch (state) {
+       case VCMD:
+               state = VNORMAL;
+               switch (vi_cmd(argc1, curcmd)) {
+               case -1:
+                       vi_error();
+                       refresh(0);
+                       break;
+               case 0:
+                       if (insert != 0)
+                               inslen = 0;
+                       refresh(insert != 0);
+                       break;
+               case 1:
+                       refresh(0);
+                       return 1;
+               case 2:
+                       /* back from a 'v' command - don't redraw the screen */
+                       return 1;
+               }
+               break;
+
+       case VREDO:
+               state = VNORMAL;
+               if (argc1 != 0)
+                       lastac = argc1;
+               switch (vi_cmd(lastac, lastcmd)) {
+               case -1:
+                       vi_error();
+                       refresh(0);
+                       break;
+               case 0:
+                       if (insert != 0) {
+                               if (lastcmd[0] == 's' || lastcmd[0] == 'c' ||
+                                               lastcmd[0] == 'C') {
+                                       if (redo_insert(1) != 0)
+                                               vi_error();
+                               } else {
+                                       if (redo_insert(lastac) != 0)
+                                               vi_error();
+                               }
+                       }
+                       refresh(0);
+                       break;
+               case 1:
+                       refresh(0);
+                       return 1;
+               case 2:
+                       /* back from a 'v' command - can't happen */
+                       break;
+               }
+               break;
+
+       case VFAIL:
+               state = VNORMAL;
+               vi_error();
+               break;
+       }
+       return 0;
+}
+
+static void
+vi_reset(buf, len)
+       char    *buf;
+       size_t  len;
+{
+       state = VNORMAL;
+       ohnum = hnum = hlast = histnum(-1) + 1;
+       insert = INSERT;
+       saved_inslen = inslen;
+       first_insert = 1;
+       inslen = 0;
+       modified = 1;
+       vi_macro_reset();
+       edit_reset(buf, len);
+}
+
+static int
+nextstate(ch)
+       int     ch;
+{
+       if (is_extend(ch))
+               return VEXTCMD;
+       else if (is_srch(ch))
+               return VSEARCH;
+       else if (is_long(ch))
+               return VXCH;
+       else if (ch == '.')
+               return VREDO;
+       else if (ch == Ctrl('v'))
+               return VVERSION;
+       else if (is_cmd(ch))
+               return VCMD;
+       else
+               return VFAIL;
+}
+
+static int
+vi_insert(ch)
+       int     ch;
+{
+       int     tcursor;
+
+       if (ch == edchars.erase || ch == Ctrl('h')) {
+               if (insert == REPLACE) {
+                       if (es->cursor == undo->cursor) {
+                               vi_error();
+                               return 0;
+                       }
+                       if (inslen > 0)
+                               inslen--;
+                       es->cursor--;
+                       if (es->cursor >= undo->linelen)
+                               es->linelen--;
+                       else
+                               es->cbuf[es->cursor] = undo->cbuf[es->cursor];
+               } else {
+                       if (es->cursor == 0) {
+                               /* x_putc(BEL); no annoying bell here */
+                               return 0;
+                       }
+                       if (inslen > 0)
+                               inslen--;
+                       es->cursor--;
+                       es->linelen--;
+                       memmove(&es->cbuf[es->cursor], &es->cbuf[es->cursor+1],
+                                       es->linelen - es->cursor + 1);
+               }
+               expanded = NONE;
+               return 0;
+       }
+       if (ch == edchars.kill) {
+               if (es->cursor != 0) {
+                       inslen = 0;
+                       memmove(es->cbuf, &es->cbuf[es->cursor],
+                                               es->linelen - es->cursor);
+                       es->linelen -= es->cursor;
+                       es->cursor = 0;
+               }
+               expanded = NONE;
+               return 0;
+       }
+       if (ch == edchars.werase) {
+               if (es->cursor != 0) {
+                       tcursor = Backword(1);
+                       memmove(&es->cbuf[tcursor], &es->cbuf[es->cursor],
+                                               es->linelen - es->cursor);
+                       es->linelen -= es->cursor - tcursor;
+                       if (inslen < es->cursor - tcursor)
+                               inslen = 0;
+                       else
+                               inslen -= es->cursor - tcursor;
+                       es->cursor = tcursor;
+               }
+               expanded = NONE;
+               return 0;
+       }
+       /* If any chars are entered before escape, trash the saved insert
+        * buffer (if user inserts & deletes char, ibuf gets trashed and
+        * we don't want to use it)
+        */
+       if (first_insert && ch != Ctrl('['))
+               saved_inslen = 0;
+       switch (ch) {
+
+#ifdef OS2
+       case 224:        /* function key prefix */
+#endif /* OS2 */
+       case '\0':
+               return -1;
+
+       case '\r':
+       case '\n':
+               return 1;
+
+       case Ctrl('['):
+               expanded = NONE;
+               if (first_insert) {
+                       first_insert = 0;
+                       if (inslen == 0) {
+                               inslen = saved_inslen;
+                               return redo_insert(0);
+                       }
+                       lastcmd[0] = 'a';
+                       lastac = 1;
+               }
+               if (lastcmd[0] == 's' || lastcmd[0] == 'c' ||
+                               lastcmd[0] == 'C')
+                       return redo_insert(0);
+               else
+                       return redo_insert(lastac - 1);
+
+       /* { Begin nonstandard vi commands */
+       case Ctrl('x'):
+               expand_word(0);
+               break;
+
+       case Ctrl('f'):
+               complete_word(0, 0);
+               break;
+
+       case Ctrl('e'):
+               print_expansions(es, 0);
+               break;
+
+       case Ctrl('i'):
+               if (Flag(FVITABCOMPLETE)) {
+                       complete_word(0, 0);
+                       break;
+               }
+               /* FALLTHROUGH */
+       /* End nonstandard vi commands } */
+
+       default:
+               if (es->linelen >= es->cbufsize - 1)
+                       return -1;
+               ibuf[inslen++] = ch;
+               if (insert == INSERT) {
+                       memmove(&es->cbuf[es->cursor+1], &es->cbuf[es->cursor],
+                                       es->linelen - es->cursor);
+                       es->linelen++;
+               }
+               es->cbuf[es->cursor++] = ch;
+               if (insert == REPLACE && es->cursor > es->linelen)
+                       es->linelen++;
+               expanded = NONE;
+       }
+       return 0;
+}
+
+static int
+vi_cmd(argcnt, cmd)
+       int             argcnt;
+       const char      *cmd;
+{
+       int             ncursor;
+       int             cur, c1, c2, c3 = 0;
+       int             any;
+       struct edstate  *t;
+
+       if (argcnt == 0 && !is_zerocount(*cmd))
+               argcnt = 1;
+
+       if (is_move(*cmd)) {
+               if ((cur = domove(argcnt, cmd, 0)) >= 0) {
+                       if (cur == es->linelen && cur != 0)
+                               cur--;
+                       es->cursor = cur;
+               } else
+                       return -1;
+       } else {
+               /* Don't save state in middle of macro.. */
+               if (is_undoable(*cmd) && !macro.p) {
+                       undo->winleft = es->winleft;
+                       memmove(undo->cbuf, es->cbuf, es->linelen);
+                       undo->linelen = es->linelen;
+                       undo->cursor = es->cursor;
+                       lastac = argcnt;
+                       memmove(lastcmd, cmd, MAXVICMD);
+               }
+               switch (*cmd) {
+
+               case Ctrl('l'):
+               case Ctrl('r'):
+                       redraw_line(1);
+                       break;
+
+               case '@':
+                       {
+                               static char alias[] = "_\0";
+                               struct tbl *ap;
+                               int     olen, nlen;
+                               char    *p, *nbuf;
+
+                               /* lookup letter in alias list... */
+                               alias[1] = cmd[1];
+                               ap = tsearch(&aliases, alias, hash(alias));
+                               if (!cmd[1] || !ap || !(ap->flag & ISSET))
+                                       return -1;
+                               /* check if this is a recursive call... */
+                               if ((p = (char *) macro.p))
+                                       while ((p = strchr(p, '\0')) && p[1])
+                                               if (*++p == cmd[1])
+                                                       return -1;
+                               /* insert alias into macro buffer */
+                               nlen = strlen(ap->val.s) + 1;
+                               olen = !macro.p ? 2
+                                       : macro.len - (macro.p - macro.buf);
+                               nbuf = alloc(nlen + 1 + olen, APERM);
+                               memcpy(nbuf, ap->val.s, nlen);
+                               nbuf[nlen++] = cmd[1];
+                               if (macro.p) {
+                                       memcpy(nbuf + nlen, macro.p, olen);
+                                       afree(macro.buf, APERM);
+                                       nlen += olen;
+                               } else {
+                                       nbuf[nlen++] = '\0';
+                                       nbuf[nlen++] = '\0';
+                               }
+                               macro.p = macro.buf = (unsigned char *) nbuf;
+                               macro.len = nlen;
+                       }
+                       break;
+
+               case 'a':
+                       modified = 1; hnum = hlast;
+                       if (es->linelen != 0)
+                               es->cursor++;
+                       insert = INSERT;
+                       break;
+
+               case 'A':
+                       modified = 1; hnum = hlast;
+                       del_range(0, 0);
+                       es->cursor = es->linelen;
+                       insert = INSERT;
+                       break;
+
+               case 'S':
+                       es->cursor = domove(1, "^", 1);
+                       del_range(es->cursor, es->linelen);
+                       modified = 1; hnum = hlast;
+                       insert = INSERT;
+                       break;
+
+               case 'Y':
+                       cmd = "y$";
+                       /* ahhhhhh... */
+               case 'c':
+               case 'd':
+               case 'y':
+                       if (*cmd == cmd[1]) {
+                               c1 = *cmd == 'c' ? domove(1, "^", 1) : 0;
+                               c2 = es->linelen;
+                       } else if (!is_move(cmd[1]))
+                               return -1;
+                       else {
+                               if ((ncursor = domove(argcnt, &cmd[1], 1)) < 0)
+                                       return -1;
+                               if (*cmd == 'c' &&
+                                               (cmd[1]=='w' || cmd[1]=='W') &&
+                                               !isspace((unsigned char)es->cbuf[es->cursor])) {
+                                       while (isspace((unsigned char)es->cbuf[--ncursor]))
+                                               ;
+                                       ncursor++;
+                               }
+                               if (ncursor > es->cursor) {
+                                       c1 = es->cursor;
+                                       c2 = ncursor;
+                               } else {
+                                       c1 = ncursor;
+                                       c2 = es->cursor;
+                                       if (cmd[1] == '%')
+                                               c2++;
+                               }
+                       }
+                       if (*cmd != 'c' && c1 != c2)
+                               yank_range(c1, c2);
+                       if (*cmd != 'y') {
+                               del_range(c1, c2);
+                               es->cursor = c1;
+                       }
+                       if (*cmd == 'c') {
+                               modified = 1; hnum = hlast;
+                               insert = INSERT;
+                       }
+                       break;
+
+               case 'p':
+                       modified = 1; hnum = hlast;
+                       if (es->linelen != 0)
+                               es->cursor++;
+                       while (putbuf(ybuf, yanklen, 0) == 0 && --argcnt > 0)
+                               ;
+                       if (es->cursor != 0)
+                               es->cursor--;
+                       if (argcnt != 0)
+                               return -1;
+                       break;
+
+               case 'P':
+                       modified = 1; hnum = hlast;
+                       any = 0;
+                       while (putbuf(ybuf, yanklen, 0) == 0 && --argcnt > 0)
+                               any = 1;
+                       if (any && es->cursor != 0)
+                               es->cursor--;
+                       if (argcnt != 0)
+                               return -1;
+                       break;
+
+               case 'C':
+                       modified = 1; hnum = hlast;
+                       del_range(es->cursor, es->linelen);
+                       insert = INSERT;
+                       break;
+
+               case 'D':
+                       yank_range(es->cursor, es->linelen);
+                       del_range(es->cursor, es->linelen);
+                       if (es->cursor != 0)
+                               es->cursor--;
+                       break;
+
+               case 'g':
+                       if (!argcnt)
+                               argcnt = hlast + 1;
+                       /* fall through */
+               case 'G':
+                       if (!argcnt)
+                               argcnt = 1;
+                       else
+                               argcnt = hlast - (source->line - argcnt);
+                       if (grabhist(modified, argcnt - 1) < 0)
+                               return -1;
+                       else {
+                               modified = 0;
+                               hnum = argcnt - 1;
+                       }
+                       break;
+
+               case 'i':
+                       modified = 1; hnum = hlast;
+                       insert = INSERT;
+                       break;
+
+               case 'I':
+                       modified = 1; hnum = hlast;
+                       es->cursor = domove(1, "^", 1);
+                       insert = INSERT;
+                       break;
+
+               case 'j':
+               case '+':
+               case Ctrl('n'):
+                       if (grabhist(modified, hnum + argcnt) < 0)
+                               return -1;
+                       else {
+                               modified = 0;
+                               hnum += argcnt;
+                       }
+                       break;
+
+               case 'k':
+               case '-':
+               case Ctrl('p'):
+                       if (grabhist(modified, hnum - argcnt) < 0)
+                               return -1;
+                       else {
+                               modified = 0;
+                               hnum -= argcnt;
+                       }
+                       break;
+
+               case 'r':
+                       if (es->linelen == 0)
+                               return -1;
+                       modified = 1; hnum = hlast;
+                       if (cmd[1] == 0)
+                               vi_error();
+                       else
+                               es->cbuf[es->cursor] = cmd[1];
+                       break;
+
+               case 'R':
+                       modified = 1; hnum = hlast;
+                       insert = REPLACE;
+                       break;
+
+               case 's':
+                       if (es->linelen == 0)
+                               return -1;
+                       modified = 1; hnum = hlast;
+                       if (es->cursor + argcnt > es->linelen)
+                               argcnt = es->linelen - es->cursor;
+                       del_range(es->cursor, es->cursor + argcnt);
+                       insert = INSERT;
+                       break;
+
+               case 'v':
+                       if (es->linelen == 0)
+                               return -1;
+                       if (!argcnt) {
+                               if (modified) {
+                                       es->cbuf[es->linelen] = '\0';
+                                       source->line++;
+                                       histsave(source->line, es->cbuf, 1);
+                               } else
+                                       argcnt = source->line + 1
+                                               - (hlast - hnum);
+                       }
+                       shf_snprintf(es->cbuf, es->cbufsize,
+                                       argcnt ? "%s %d" : "%s",
+                                       "fc -e ${VISUAL:-${EDITOR:-vi}} --",
+                                       argcnt);
+                       es->linelen = strlen(es->cbuf);
+                       return 2;
+
+               case 'x':
+                       if (es->linelen == 0)
+                               return -1;
+                       modified = 1; hnum = hlast;
+                       if (es->cursor + argcnt > es->linelen)
+                               argcnt = es->linelen - es->cursor;
+                       yank_range(es->cursor, es->cursor + argcnt);
+                       del_range(es->cursor, es->cursor + argcnt);
+                       break;
+
+               case 'X':
+                       if (es->cursor > 0) {
+                               modified = 1; hnum = hlast;
+                               if (es->cursor < argcnt)
+                                       argcnt = es->cursor;
+                               yank_range(es->cursor - argcnt, es->cursor);
+                               del_range(es->cursor - argcnt, es->cursor);
+                               es->cursor -= argcnt;
+                       } else
+                               return -1;
+                       break;
+
+               case 'u':
+                       t = es;
+                       es = undo;
+                       undo = t;
+                       break;
+
+               case 'U':
+                       if (!modified)
+                               return -1;
+                       if (grabhist(modified, ohnum) < 0)
+                               return -1;
+                       modified = 0;
+                       hnum = ohnum;
+                       break;
+
+               case '?':
+                       if (hnum == hlast)
+                               hnum = -1;
+                       /* ahhh */
+               case '/':
+                       c3 = 1;
+                       srchlen = 0;
+                       lastsearch = *cmd;
+                       /* fall through */
+               case 'n':
+               case 'N':
+                       if (lastsearch == ' ')
+                               return -1;
+                       if (lastsearch == '?')
+                               c1 = 1;
+                       else
+                               c1 = 0;
+                       if (*cmd == 'N')
+                               c1 = !c1;
+                       if ((c2 = grabsearch(modified, hnum,
+                                                       c1, srchpat)) < 0) {
+                               if (c3) {
+                                       restore_cbuf();
+                                       refresh(0);
+                               }
+                               return -1;
+                       } else {
+                               modified = 0;
+                               hnum = c2;
+                               ohnum = hnum;
+                       }
+                       break;
+               case '_': {
+                       int     inspace;
+                       char    *p, *sp;
+
+                       if (histnum(-1) < 0)
+                               return -1;
+                       p = *histpos();
+#define issp(c)                (isspace((unsigned char)(c)) || (c) == '\n')
+                       if (argcnt) {
+                               while (*p && issp(*p))
+                                       p++;
+                               while (*p && --argcnt) {
+                                       while (*p && !issp(*p))
+                                               p++;
+                                       while (*p && issp(*p))
+                                               p++;
+                               }
+                               if (!*p)
+                                       return -1;
+                               sp = p;
+                       } else {
+                               sp = p;
+                               inspace = 0;
+                               while (*p) {
+                                       if (issp(*p))
+                                               inspace = 1;
+                                       else if (inspace) {
+                                               inspace = 0;
+                                               sp = p;
+                                       }
+                                       p++;
+                               }
+                               p = sp;
+                       }
+                       modified = 1; hnum = hlast;
+                       if (es->cursor != es->linelen)
+                               es->cursor++;
+                       while (*p && !issp(*p)) {
+                               argcnt++;
+                               p++;
+                       }
+                       if (putbuf(space, 1, 0) != 0)
+                               argcnt = -1;
+                       else if (putbuf(sp, argcnt, 0) != 0)
+                               argcnt = -1;
+                       if (argcnt < 0) {
+                               if (es->cursor != 0)
+                                       es->cursor--;
+                               return -1;
+                       }
+                       insert = INSERT;
+                       }
+                       break;
+
+               case '~': {
+                       char    *p;
+                       int     i;
+
+                       if (es->linelen == 0)
+                               return -1;
+                       for (i = 0; i < argcnt; i++) {
+                               p = &es->cbuf[es->cursor];
+                               if (islower((unsigned char)*p)) {
+                                       modified = 1; hnum = hlast;
+                                       *p = toupper((unsigned char)*p);
+                               } else if (isupper((unsigned char)*p)) {
+                                       modified = 1; hnum = hlast;
+                                       *p = tolower((unsigned char)*p);
+                               }
+                               if (es->cursor < es->linelen - 1)
+                                       es->cursor++;
+                       }
+                       break;
+                       }
+
+               case '#':
+                   {
+                       int ret = x_do_comment(es->cbuf, es->cbufsize,
+                                           &es->linelen);
+                       if (ret >= 0)
+                               es->cursor = 0;
+                       return ret;
+                   }
+
+               case '=':                       /* at&t ksh */
+               case Ctrl('e'):                 /* Nonstandard vi/ksh */
+                       print_expansions(es, 1);
+                       break;
+
+
+               case Ctrl('i'):                 /* Nonstandard vi/ksh */
+                       if (!Flag(FVITABCOMPLETE))
+                               return -1;
+                       complete_word(1, argcnt);
+                       break;
+
+               case Ctrl('['):                 /* some annoying at&t ksh's */
+                       if (!Flag(FVIESCCOMPLETE))
+                               return -1;
+               case '\\':                      /* at&t ksh */
+               case Ctrl('f'):                 /* Nonstandard vi/ksh */
+                       complete_word(1, argcnt);
+                       break;
+
+
+               case '*':                       /* at&t ksh */
+               case Ctrl('x'):                 /* Nonstandard vi/ksh */
+                       expand_word(1);
+                       break;
+               }
+               if (insert == 0 && es->cursor != 0 && es->cursor >= es->linelen)
+                       es->cursor--;
+       }
+       return 0;
+}
+
+static int
+domove(argcnt, cmd, sub)
+       int     argcnt;
+       const char *cmd;
+       int     sub;
+{
+       int     bcount, UNINITIALIZED(i), t;
+       int     UNINITIALIZED(ncursor);
+
+       switch (*cmd) {
+
+       case 'b':
+               if (!sub && es->cursor == 0)
+                       return -1;
+               ncursor = backword(argcnt);
+               break;
+
+       case 'B':
+               if (!sub && es->cursor == 0)
+                       return -1;
+               ncursor = Backword(argcnt);
+               break;
+
+       case 'e':
+               if (!sub && es->cursor + 1 >= es->linelen)
+                       return -1;
+               ncursor = endword(argcnt);
+               if (sub && ncursor < es->linelen)
+                       ncursor++;
+               break;
+
+       case 'E':
+               if (!sub && es->cursor + 1 >= es->linelen)
+                       return -1;
+               ncursor = Endword(argcnt);
+               if (sub && ncursor < es->linelen)
+                       ncursor++;
+               break;
+
+       case 'f':
+       case 'F':
+       case 't':
+       case 'T':
+               fsavecmd = *cmd;
+               fsavech = cmd[1];
+               /* drop through */
+
+       case ',':
+       case ';':
+               if (fsavecmd == ' ')
+                       return -1;
+               i = fsavecmd == 'f' || fsavecmd == 'F';
+               t = fsavecmd > 'a';
+               if (*cmd == ',')
+                       t = !t;
+               if ((ncursor = findch(fsavech, argcnt, t, i)) < 0)
+                       return -1;
+               if (sub && t)
+                       ncursor++;
+               break;
+
+       case 'h':
+       case Ctrl('h'):
+               if (!sub && es->cursor == 0)
+                       return -1;
+               ncursor = es->cursor - argcnt;
+               if (ncursor < 0)
+                       ncursor = 0;
+               break;
+
+       case ' ':
+       case 'l':
+               if (!sub && es->cursor + 1 >= es->linelen)
+                       return -1;
+               if (es->linelen != 0) {
+                       ncursor = es->cursor + argcnt;
+                       if (ncursor > es->linelen)
+                               ncursor = es->linelen;
+               }
+               break;
+
+       case 'w':
+               if (!sub && es->cursor + 1 >= es->linelen)
+                       return -1;
+               ncursor = forwword(argcnt);
+               break;
+
+       case 'W':
+               if (!sub && es->cursor + 1 >= es->linelen)
+                       return -1;
+               ncursor = Forwword(argcnt);
+               break;
+
+       case '0':
+               ncursor = 0;
+               break;
+
+       case '^':
+               ncursor = 0;
+               while (ncursor < es->linelen - 1 && isspace((unsigned char)es->cbuf[ncursor]))
+                       ncursor++;
+               break;
+
+       case '|':
+               ncursor = argcnt;
+               if (ncursor > es->linelen)
+                       ncursor = es->linelen;
+               if (ncursor)
+                       ncursor--;
+               break;
+
+       case '$':
+               if (es->linelen != 0)
+                       ncursor = es->linelen;
+               else
+                       ncursor = 0;
+               break;
+
+       case '%':
+               ncursor = es->cursor;
+               while (ncursor < es->linelen &&
+                               (i = bracktype(es->cbuf[ncursor])) == 0)
+                       ncursor++;
+               if (ncursor == es->linelen)
+                       return -1;
+               bcount = 1;
+               do {
+                       if (i > 0) {
+                               if (++ncursor >= es->linelen)
+                                       return -1;
+                       } else {
+                               if (--ncursor < 0)
+                                       return -1;
+                       }
+                       t = bracktype(es->cbuf[ncursor]);
+                       if (t == i)
+                               bcount++;
+                       else if (t == -i)
+                               bcount--;
+               } while (bcount != 0);
+               if (sub && i > 0)
+                       ncursor++;
+               break;
+
+       default:
+               return -1;
+       }
+       return ncursor;
+}
+
+static int
+redo_insert(count)
+       int     count;
+{
+       while (count-- > 0)
+               if (putbuf(ibuf, inslen, insert==REPLACE) != 0)
+                       return -1;
+       if (es->cursor > 0)
+               es->cursor--;
+       insert = 0;
+       return 0;
+}
+
+static void
+yank_range(a, b)
+       int     a, b;
+{
+       yanklen = b - a;
+       if (yanklen != 0)
+               memmove(ybuf, &es->cbuf[a], yanklen);
+}
+
+static int
+bracktype(ch)
+       int     ch;
+{
+       switch (ch) {
+
+       case '(':
+               return 1;
+
+       case '[':
+               return 2;
+
+       case '{':
+               return 3;
+
+       case ')':
+               return -1;
+
+       case ']':
+               return -2;
+
+       case '}':
+               return -3;
+
+       default:
+               return 0;
+       }
+}
+
+/*
+ *     Non user interface editor routines below here
+ */
+
+static int     cur_col;                /* current column on line */
+static int     pwidth;                 /* width of prompt */
+static int     prompt_trunc;           /* how much of prompt to truncate */
+static int     prompt_skip;            /* how much of prompt to skip */
+static int     winwidth;               /* width of window */
+static char    *wbuf[2];               /* window buffers */
+static int     wbuf_len;               /* length of window buffers (x_cols-3)*/
+static int     win;                    /* window buffer in use */
+static char    morec;                  /* more character at right of window */
+static int     lastref;                /* argument to last refresh() */
+static char    holdbuf[CMDLEN];        /* place to hold last edit buffer */
+static int     holdlen;                /* length of holdbuf */
+
+static void
+save_cbuf()
+{
+       memmove(holdbuf, es->cbuf, es->linelen);
+       holdlen = es->linelen;
+       holdbuf[holdlen] = '\0';
+}
+
+static void
+restore_cbuf()
+{
+       es->cursor = 0;
+       es->linelen = holdlen;
+       memmove(es->cbuf, holdbuf, holdlen);
+}
+
+/* return a new edstate */
+static struct edstate *
+save_edstate(old)
+       struct edstate *old;
+{
+       struct edstate *new;
+
+       new = (struct edstate *)alloc(sizeof(struct edstate), APERM);
+       new->cbuf = alloc(old->cbufsize, APERM);
+       memcpy(new->cbuf, old->cbuf, old->linelen);
+       new->cbufsize = old->cbufsize;
+       new->linelen = old->linelen;
+       new->cursor = old->cursor;
+       new->winleft = old->winleft;
+       return new;
+}
+
+static void
+restore_edstate(new, old)
+       struct edstate *old, *new;
+{
+       memcpy(new->cbuf, old->cbuf, old->linelen);
+       new->linelen = old->linelen;
+       new->cursor = old->cursor;
+       new->winleft = old->winleft;
+       free_edstate(old);
+}
+
+static void
+free_edstate(old)
+       struct edstate *old;
+{
+       afree(old->cbuf, APERM);
+       afree((char *)old, APERM);
+}
+
+
+
+static void
+edit_reset(buf, len)
+       char    *buf;
+       size_t  len;
+{
+       const char *p;
+
+       es = &ebuf;
+       es->cbuf = buf;
+       es->cbufsize = len;
+       undo = &undobuf;
+       undo->cbufsize = len;
+
+       es->linelen = undo->linelen = 0;
+       es->cursor = undo->cursor = 0;
+       es->winleft = undo->winleft = 0;
+
+       cur_col = pwidth = promptlen(prompt, &p);
+       prompt_skip = p - prompt;
+       if (pwidth > x_cols - 3 - MIN_EDIT_SPACE) {
+               cur_col = x_cols - 3 - MIN_EDIT_SPACE;
+               prompt_trunc = pwidth - cur_col;
+               pwidth -= prompt_trunc;
+       } else
+               prompt_trunc = 0;
+       if (!wbuf_len || wbuf_len != x_cols - 3) {
+               wbuf_len = x_cols - 3;
+               wbuf[0] = aresize(wbuf[0], wbuf_len, APERM);
+               wbuf[1] = aresize(wbuf[1], wbuf_len, APERM);
+       }
+       (void) memset(wbuf[0], ' ', wbuf_len);
+       (void) memset(wbuf[1], ' ', wbuf_len);
+       winwidth = x_cols - pwidth - 3;
+       win = 0;
+       morec = ' ';
+       lastref = 1;
+       holdlen = 0;
+}
+
+/*
+ * this is used for calling x_escape() in complete_word()
+ */
+static int
+x_vi_putbuf(s, len)
+       const char *s;
+       size_t len;
+{
+       return putbuf(s, len, 0);
+}
+
+static int
+putbuf(buf, len, repl)
+       const char *buf;
+       int     len;
+       int     repl;
+{
+       if (len == 0)
+               return 0;
+       if (repl) {
+               if (es->cursor + len >= es->cbufsize)
+                       return -1;
+               if (es->cursor + len > es->linelen)
+                       es->linelen = es->cursor + len;
+       } else {
+               if (es->linelen + len >= es->cbufsize)
+                       return -1;
+               memmove(&es->cbuf[es->cursor + len], &es->cbuf[es->cursor],
+                       es->linelen - es->cursor);
+               es->linelen += len;
+       }
+       memmove(&es->cbuf[es->cursor], buf, len);
+       es->cursor += len;
+       return 0;
+}
+
+static void
+del_range(a, b)
+       int     a, b;
+{
+       if (es->linelen != b)
+               memmove(&es->cbuf[a], &es->cbuf[b], es->linelen - b);
+       es->linelen -= b - a;
+}
+
+static int
+findch(ch, cnt, forw, incl)
+       int     ch;
+       int     cnt;
+       int     forw;
+       int     incl;
+{
+       int     ncursor;
+
+       if (es->linelen == 0)
+               return -1;
+       ncursor = es->cursor;
+       while (cnt--) {
+               do {
+                       if (forw) {
+                               if (++ncursor == es->linelen)
+                                       return -1;
+                       } else {
+                               if (--ncursor < 0)
+                                       return -1;
+                       }
+               } while (es->cbuf[ncursor] != ch);
+       }
+       if (!incl) {
+               if (forw)
+                       ncursor--;
+               else
+                       ncursor++;
+       }
+       return ncursor;
+}
+
+static int
+forwword(argcnt)
+       int     argcnt;
+{
+       int     ncursor;
+
+       ncursor = es->cursor;
+       while (ncursor < es->linelen && argcnt--) {
+               if (is_wordch(es->cbuf[ncursor]))
+                       while (is_wordch(es->cbuf[ncursor]) &&
+                                       ncursor < es->linelen)
+                               ncursor++;
+               else if (!isspace((unsigned char)es->cbuf[ncursor]))
+                       while (!is_wordch(es->cbuf[ncursor]) &&
+                                       !isspace((unsigned char)es->cbuf[ncursor]) &&
+                                       ncursor < es->linelen)
+                               ncursor++;
+               while (isspace((unsigned char)es->cbuf[ncursor]) && ncursor < es->linelen)
+                       ncursor++;
+       }
+       return ncursor;
+}
+
+static int
+backword(argcnt)
+       int     argcnt;
+{
+       int     ncursor;
+
+       ncursor = es->cursor;
+       while (ncursor > 0 && argcnt--) {
+               while (--ncursor > 0 && isspace((unsigned char)es->cbuf[ncursor]))
+                       ;
+               if (ncursor > 0) {
+                       if (is_wordch(es->cbuf[ncursor]))
+                               while (--ncursor >= 0 &&
+                                  is_wordch(es->cbuf[ncursor]))
+                                       ;
+                       else
+                               while (--ncursor >= 0 &&
+                                  !is_wordch(es->cbuf[ncursor]) &&
+                                  !isspace((unsigned char)es->cbuf[ncursor]))
+                                       ;
+                       ncursor++;
+               }
+       }
+       return ncursor;
+}
+
+static int
+endword(argcnt)
+       int     argcnt;
+{
+       int     ncursor;
+
+       ncursor = es->cursor;
+       while (ncursor < es->linelen && argcnt--) {
+               while (++ncursor < es->linelen - 1 &&
+                               isspace((unsigned char)es->cbuf[ncursor]))
+                       ;
+               if (ncursor < es->linelen - 1) {
+                       if (is_wordch(es->cbuf[ncursor]))
+                               while (++ncursor < es->linelen &&
+                                         is_wordch(es->cbuf[ncursor]))
+                                       ;
+                       else
+                               while (++ncursor < es->linelen &&
+                                  !is_wordch(es->cbuf[ncursor]) &&
+                                  !isspace((unsigned char)es->cbuf[ncursor]))
+                                       ;
+                       ncursor--;
+               }
+       }
+       return ncursor;
+}
+
+static int
+Forwword(argcnt)
+       int     argcnt;
+{
+       int     ncursor;
+
+       ncursor = es->cursor;
+       while (ncursor < es->linelen && argcnt--) {
+               while (!isspace((unsigned char)es->cbuf[ncursor]) && ncursor < es->linelen)
+                       ncursor++;
+               while (isspace((unsigned char)es->cbuf[ncursor]) && ncursor < es->linelen)
+                       ncursor++;
+       }
+       return ncursor;
+}
+
+static int
+Backword(argcnt)
+       int     argcnt;
+{
+       int     ncursor;
+
+       ncursor = es->cursor;
+       while (ncursor > 0 && argcnt--) {
+               while (--ncursor >= 0 && isspace((unsigned char)es->cbuf[ncursor]))
+                       ;
+               while (ncursor >= 0 && !isspace((unsigned char)es->cbuf[ncursor]))
+                       ncursor--;
+               ncursor++;
+       }
+       return ncursor;
+}
+
+static int
+Endword(argcnt)
+       int     argcnt;
+{
+       int     ncursor;
+
+       ncursor = es->cursor;
+       while (ncursor < es->linelen - 1 && argcnt--) {
+               while (++ncursor < es->linelen - 1 &&
+                               isspace((unsigned char)es->cbuf[ncursor]))
+                       ;
+               if (ncursor < es->linelen - 1) {
+                       while (++ncursor < es->linelen &&
+                                       !isspace((unsigned char)es->cbuf[ncursor]))
+                               ;
+                       ncursor--;
+               }
+       }
+       return ncursor;
+}
+
+static int
+grabhist(save, n)
+       int     save;
+       int     n;
+{
+       char    *hptr;
+
+       if (n < 0 || n > hlast)
+               return -1;
+       if (n == hlast) {
+               restore_cbuf();
+               ohnum = n;
+               return 0;
+       }
+       (void) histnum(n);
+       if ((hptr = *histpos()) == NULL) {
+               internal_errorf(0, "grabhist: bad history array");
+               return -1;
+       }
+       if (save)
+               save_cbuf();
+       if ((es->linelen = strlen(hptr)) >= es->cbufsize)
+               es->linelen = es->cbufsize - 1;
+       memmove(es->cbuf, hptr, es->linelen);
+       es->cursor = 0;
+       ohnum = n;
+       return 0;
+}
+
+static int
+grabsearch(save, start, fwd, pat)
+       int     save, start, fwd;
+       char    *pat;
+{
+       char    *hptr;
+       int     hist;
+       int     anchored;
+
+       if ((start == 0 && fwd == 0) || (start >= hlast-1 && fwd == 1))
+               return -1;
+       if (fwd)
+               start++;
+       else
+               start--;
+       anchored = *pat == '^' ? (++pat, 1) : 0;
+       if ((hist = findhist(start, fwd, pat, anchored)) < 0) {
+               /* if (start != 0 && fwd && match(holdbuf, pat) >= 0) { */
+               /* XXX should FILECMP be strncmp? */
+               if (start != 0 && fwd && FILECMP(holdbuf, pat) >= 0) {
+                       restore_cbuf();
+                       return 0;
+               } else
+                       return -1;
+       }
+       if (save)
+               save_cbuf();
+       histnum(hist);
+       hptr = *histpos();
+       if ((es->linelen = strlen(hptr)) >= es->cbufsize)
+               es->linelen = es->cbufsize - 1;
+       memmove(es->cbuf, hptr, es->linelen);
+       es->cursor = 0;
+       return hist;
+}
+
+static void
+redraw_line(newlinex)
+       int newlinex;
+{
+       (void) memset(wbuf[win], ' ', wbuf_len);
+       if (newlinex) {
+               x_putc('\r');
+               x_putc('\n');
+       }
+       vi_pprompt(0);
+       cur_col = pwidth;
+       morec = ' ';
+}
+
+static void
+refresh(leftside)
+       int             leftside;
+{
+       if (leftside < 0)
+               leftside = lastref;
+       else
+               lastref = leftside;
+       if (outofwin())
+               rewindow();
+       display(wbuf[1 - win], wbuf[win], leftside);
+       win = 1 - win;
+}
+
+static int
+outofwin()
+{
+       int     cur, col;
+
+       if (es->cursor < es->winleft)
+               return 1;
+       col = 0;
+       cur = es->winleft;
+       while (cur < es->cursor)
+               col = newcol((unsigned char) es->cbuf[cur++], col);
+       if (col >= winwidth)
+               return 1;
+       return 0;
+}
+
+static void
+rewindow()
+{
+       register int    tcur, tcol;
+       int             holdcur1, holdcol1;
+       int             holdcur2, holdcol2;
+
+       holdcur1 = holdcur2 = tcur = 0;
+       holdcol1 = holdcol2 = tcol = 0;
+       while (tcur < es->cursor) {
+               if (tcol - holdcol2 > winwidth / 2) {
+                       holdcur1 = holdcur2;
+                       holdcol1 = holdcol2;
+                       holdcur2 = tcur;
+                       holdcol2 = tcol;
+               }
+               tcol = newcol((unsigned char) es->cbuf[tcur++], tcol);
+       }
+       while (tcol - holdcol1 > winwidth / 2)
+               holdcol1 = newcol((unsigned char) es->cbuf[holdcur1++],
+                                 holdcol1);
+       es->winleft = holdcur1;
+}
+
+static int
+newcol(ch, col)
+       int     ch, col;
+{
+       if (ch == '\t')
+               return (col | 7) + 1;
+       return col + char_len(ch);
+}
+
+static void
+display(wb1, wb2, leftside)
+       char    *wb1, *wb2;
+       int     leftside;
+{
+       unsigned char ch;
+       char    *twb1, *twb2, mc;
+       int     cur, col, cnt;
+       int     UNINITIALIZED(ncol);
+       int     moreright;
+
+       col = 0;
+       cur = es->winleft;
+       moreright = 0;
+       twb1 = wb1;
+       while (col < winwidth && cur < es->linelen) {
+               if (cur == es->cursor && leftside)
+                       ncol = col + pwidth;
+               if ((ch = es->cbuf[cur]) == '\t') {
+                       do {
+                               *twb1++ = ' ';
+                       } while (++col < winwidth && (col & 7) != 0);
+               } else {
+                       if ((ch & 0x80) && Flag(FVISHOW8)) {
+                               *twb1++ = 'M';
+                               if (++col < winwidth) {
+                                       *twb1++ = '-';
+                                       col++;
+                               }
+                               ch &= 0x7f;
+                       }
+                       if (col < winwidth) {
+                               if (ch < ' ' || ch == 0x7f) {
+                                       *twb1++ = '^';
+                                       if (++col < winwidth) {
+                                               *twb1++ = ch ^ '@';
+                                               col++;
+                                       }
+                               } else {
+                                       *twb1++ = ch;
+                                       col++;
+                               }
+                       }
+               }
+               if (cur == es->cursor && !leftside)
+                       ncol = col + pwidth - 1;
+               cur++;
+       }
+       if (cur == es->cursor)
+               ncol = col + pwidth;
+       if (col < winwidth) {
+               while (col < winwidth) {
+                       *twb1++ = ' ';
+                       col++;
+               }
+       } else
+               moreright++;
+       *twb1 = ' ';
+
+       col = pwidth;
+       cnt = winwidth;
+       twb1 = wb1;
+       twb2 = wb2;
+       while (cnt--) {
+               if (*twb1 != *twb2) {
+                       if (cur_col != col)
+                               ed_mov_opt(col, wb1);
+                       x_putc(*twb1);
+                       cur_col++;
+               }
+               twb1++;
+               twb2++;
+               col++;
+       }
+       if (es->winleft > 0 && moreright)
+               /* POSIX says to use * for this but that is a globbing
+                * character and may confuse people; + is more innocuous
+                */
+               mc = '+';
+       else if (es->winleft > 0)
+               mc = '<';
+       else if (moreright)
+               mc = '>';
+       else
+               mc = ' ';
+       if (mc != morec) {
+               ed_mov_opt(pwidth + winwidth + 1, wb1);
+               x_putc(mc);
+               cur_col++;
+               morec = mc;
+       }
+       if (cur_col != ncol)
+               ed_mov_opt(ncol, wb1);
+}
+
+static void
+ed_mov_opt(col, wb)
+       int     col;
+       char    *wb;
+{
+       if (col < cur_col) {
+               if (col + 1 < cur_col - col) {
+                       x_putc('\r');
+                       vi_pprompt(0);
+                       cur_col = pwidth;
+                       while (cur_col++ < col)
+                               x_putc(*wb++);
+               } else {
+                       while (cur_col-- > col)
+                               x_putc('\b');
+               }
+       } else {
+               wb = &wb[cur_col - pwidth];
+               while (cur_col++ < col)
+                       x_putc(*wb++);
+       }
+       cur_col = col;
+}
+
+
+/* replace word with all expansions (ie, expand word*) */
+static int
+expand_word(commandx)
+       int commandx;
+{
+       static struct edstate *buf;
+       int rval = 0;
+       int nwords;
+       int start, end;
+       char **words;
+       int i;
+
+       /* Undo previous expansion */
+       if (commandx == 0 && expanded == EXPAND && buf) {
+               restore_edstate(es, buf);
+               buf = 0;
+               expanded = NONE;
+               return 0;
+       }
+       if (buf) {
+               free_edstate(buf);
+               buf = 0;
+       }
+
+       nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH,
+               es->cbuf, es->linelen, es->cursor,
+               &start, &end, &words, (int *) 0);
+       if (nwords == 0) {
+               vi_error();
+               return -1;
+       }
+
+       buf = save_edstate(es);
+       expanded = EXPAND;
+       del_range(start, end);
+       es->cursor = start;
+       for (i = 0; i < nwords; ) {
+               if (x_escape(words[i], strlen(words[i]), x_vi_putbuf) != 0) {
+                       rval = -1;
+                       break;
+               }
+               if (++i < nwords && putbuf(space, 1, 0) != 0) {
+                       rval = -1;
+                       break;
+               }
+       }
+       i = buf->cursor - end;
+       if (rval == 0 && i > 0)
+               es->cursor += i;
+       modified = 1; hnum = hlast;
+       insert = INSERT;
+       lastac = 0;
+       refresh(0);
+       return rval;
+}
+
+static int
+complete_word(commandx, count)
+       int commandx;
+       int count;
+{
+       static struct edstate *buf;
+       int rval = 0;
+       int nwords;
+       int start, end;
+       char **words;
+       char *match;
+       int match_len;
+       int is_unique;
+       int is_command;
+
+       /* Undo previous completion */
+       if (commandx == 0 && expanded == COMPLETE && buf) {
+               print_expansions(buf, 0);
+               expanded = PRINT;
+               return 0;
+       }
+       if (commandx == 0 && expanded == PRINT && buf) {
+               restore_edstate(es, buf);
+               buf = 0;
+               expanded = NONE;
+               return 0;
+       }
+       if (buf) {
+               free_edstate(buf);
+               buf = 0;
+       }
+
+       /* XCF_FULLPATH for count 'cause the menu printed by print_expansions()
+        * was done this way.
+        */
+       nwords = x_cf_glob(XCF_COMMAND_FILE | (count ? XCF_FULLPATH : 0),
+               es->cbuf, es->linelen, es->cursor,
+               &start, &end, &words, &is_command);
+       if (nwords == 0) {
+               vi_error();
+               return -1;
+       }
+       if (count) {
+               int i;
+
+               count--;
+               if (count >= nwords) {
+                       vi_error();
+                       x_print_expansions(nwords, words, is_command);
+                       x_free_words(nwords, words);
+                       redraw_line(0);
+                       return -1;
+               }
+               /*
+                * Expand the count'th word to its basename
+                */
+               if (is_command) {
+                       match = words[count]
+                               + x_basename(words[count], (char *) 0);
+                       /* If more than one possible match, use full path */
+                       for (i = 0; i < nwords; i++)
+                               if (i != count &&
+                                   FILECMP(words[i]
+                                           + x_basename(words[i], (char *) 0),
+                                           match) == 0)
+                               {
+                                       match = words[count];
+                                       break;
+                               }
+               } else
+                       match = words[count];
+               match_len = strlen(match);
+               is_unique = 1;
+               /* expanded = PRINT;    next call undo */
+       } else {
+               match = words[0];
+               match_len = x_longest_prefix(nwords, words);
+               expanded = COMPLETE;    /* next call will list completions */
+               is_unique = nwords == 1;
+       }
+
+       buf = save_edstate(es);
+       del_range(start, end);
+       es->cursor = start;
+
+       /* escape all shell-sensitive characters and put the result into
+        * command buffer */
+       rval = x_escape(match, match_len, x_vi_putbuf);
+
+       if (rval == 0 && is_unique) {
+               /* If exact match, don't undo.  Allows directory completions
+                * to be used (ie, complete the next portion of the path).
+                */
+               expanded = NONE;
+
+               /* If not a directory, add a space to the end... */
+               if (match_len > 0 && !ISDIRSEP(match[match_len - 1]))
+                       rval = putbuf(space, 1, 0);
+       }
+       x_free_words(nwords, words);
+
+       modified = 1; hnum = hlast;
+       insert = INSERT;
+       lastac = 0;      /* prevent this from being redone... */
+       refresh(0);
+
+       return rval;
+}
+
+static int
+print_expansions(ex, commandx)
+       struct edstate *ex;
+       int     commandx;
+{
+       int nwords;
+       int start, end;
+       char **words;
+       int is_command;
+
+       nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH,
+               ex->cbuf, ex->linelen, ex->cursor,
+               &start, &end, &words, &is_command);
+       if (nwords == 0) {
+               vi_error();
+               return -1;
+       }
+       x_print_expansions(nwords, words, is_command);
+       x_free_words(nwords, words);
+       redraw_line(0);
+       return 0;
+}
+
+/* How long is char when displayed (not counting tabs) */
+static int
+char_len(c)
+       int c;
+{
+       int len = 1;
+
+       if ((c & 0x80) && Flag(FVISHOW8)) {
+               len += 2;
+               c &= 0x7f;
+       }
+       if (c < ' ' || c == 0x7f)
+               len++;
+       return len;
+}
+
+/* Similar to x_zotc(emacs.c), but no tab weirdness */
+static void
+x_vi_zotc(c)
+       int c;
+{
+       if (Flag(FVISHOW8) && (c & 0x80)) {
+               x_puts("M-");
+               c &= 0x7f;
+       }
+       if (c < ' ' || c == 0x7f) {
+               x_putc('^');
+               c ^= '@';
+       }
+       x_putc(c);
+}
+
+static void
+vi_pprompt(full)
+       int full;
+{
+       pprompt(prompt + (full ? 0 : prompt_skip), prompt_trunc);
+}
+
+static void
+vi_error()
+{
+       /* Beem out of any macros as soon as an error occurs */
+       vi_macro_reset();
+       x_putc(BEL);
+       x_flush();
+}
+
+static void
+vi_macro_reset()
+{
+       if (macro.p) {
+               afree(macro.buf, APERM);
+               memset((char *) &macro, 0, sizeof(macro));
+       }
+}
+
+#endif /* VI */
index 8fb7c768566d4447aeb9aa5a683a62cb6d71d7f9..40de49f87fb5d2b083699c95715016dab97c294b 100644 (file)
@@ -17,6 +17,7 @@
 ./bin/halt                             minix-sys
 ./bin/intr                             minix-sys
 ./bin/kill                             minix-sys
+./bin/ksh                              minix-sys
 ./bin/ln                               minix-sys
 ./bin/loadkeys                         minix-sys
 ./bin/ls                               minix-sys
 ./usr/man/man1/jobs.1                  minix-sys
 ./usr/man/man1/join.1                  minix-sys
 ./usr/man/man1/kill.1                  minix-sys
+./usr/man/man1/ksh.1                   minix-sys
 ./usr/man/man1/last.1                  minix-sys
 ./usr/man/man1/ldd.1                   minix-sys
 ./usr/man/man1/ld.elf_so.1             minix-sys
index 5913b9b96d7b76b7f43ab75e09f2b83f57773434..e12acc4726e78d519c6a0ce7f0f47a62e6f15097 100644 (file)
@@ -1,3 +1,9 @@
+20130312:
+       /bin/ksh was imported and needs to be added to your /etc/shells.
+       Run this command to add it:
+
+       # echo "/bin/ksh" >> /etc/shells
+
 20130306:
        For people building ARM images, the procedure has changed
        a bit. You need a full FS to boot now. In short, you need
index c2cdaa0f27c1a739461d409399833dc655e505ad..61f2c1551a834db95082207736d157501f1a759a 100644 (file)
@@ -1 +1,2 @@
 /bin/sh
+/bin/ksh
index 01121a7c60fc6279db9cb649c52729b97a04f31c..4abea3f2d09208bb54627ed3fffe05985d863666 100644 (file)
@@ -12,6 +12,7 @@
 2012/01/16 18:47:57,bin/ed
 2012/10/17 12:00:00,bin/expr
 2012/10/17 12:00:00,bin/kill
+2013/03/12 12:00:00,bin/ksh
 2012/10/17 12:00:00,bin/ln
 2012/10/17 12:00:00,bin/ls
 2012/10/17 12:00:00,bin/Makefile