summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Ankarström <john@ankarstrom.se>2021-06-07 14:12:11 +0200
committerJohn Ankarström <john@ankarstrom.se>2021-06-07 14:12:11 +0200
commite2294b61e15781ca784a611e8ca7dabe132ebc6d (patch)
treec2a8cc44582534f52a76b1b8e64e6f83ffa501da
downloadksh-e2294b61e15781ca784a611e8ca7dabe132ebc6d.tar.gz
First commit (NetBSD 9.1)
-rw-r--r--CVS/Entries48
-rw-r--r--CVS/Repository1
-rw-r--r--CVS/Root1
-rw-r--r--CVS/Tag1
-rw-r--r--CVS/Template16
-rw-r--r--Makefile55
-rw-r--r--alloc.c127
-rw-r--r--c_ksh.c1457
-rw-r--r--c_sh.c907
-rw-r--r--c_test.c642
-rw-r--r--c_test.h55
-rw-r--r--c_ulimit.c290
-rw-r--r--conf-end.h31
-rw-r--r--config.h192
-rw-r--r--edit.c1052
-rw-r--r--edit.h89
-rwxr-xr-xemacs-gen.sh47
-rw-r--r--emacs.c2174
-rw-r--r--eval.c1384
-rw-r--r--exec.c1553
-rw-r--r--expand.h91
-rw-r--r--expr.c608
-rw-r--r--history.c1207
-rw-r--r--io.c503
-rw-r--r--jobs.c1775
-rw-r--r--ksh.Man3621
-rw-r--r--ksh_dir.h27
-rw-r--r--ksh_limval.h25
-rw-r--r--lex.c1386
-rw-r--r--lex.h133
-rw-r--r--mail.c203
-rw-r--r--main.c792
-rw-r--r--misc.c1342
-rw-r--r--mkman46
-rw-r--r--path.c303
-rw-r--r--proto.h277
-rw-r--r--sh.h533
-rw-r--r--shf.c1274
-rw-r--r--shf.h87
-rw-r--r--siglist.in56
-rwxr-xr-xsiglist.sh46
-rw-r--r--syn.c958
-rw-r--r--table.c247
-rw-r--r--table.h181
-rw-r--r--trap.c454
-rw-r--r--tree.c750
-rw-r--r--tree.h141
-rw-r--r--tty.c153
-rw-r--r--tty.h110
-rw-r--r--var.c1260
-rw-r--r--version.c16
-rw-r--r--vi.c2179
52 files changed, 30906 insertions, 0 deletions
diff --git a/CVS/Entries b/CVS/Entries
new file mode 100644
index 0000000..6e7a2ab
--- /dev/null
+++ b/CVS/Entries
@@ -0,0 +1,48 @@
+/Makefile/1.35/Mon Feb 4 04:05:15 2019//Tnetbsd-9-1-RELEASE
+/alloc.c/1.10/Wed Dec 12 22:55:42 2007//Tnetbsd-9-1-RELEASE
+/c_ksh.c/1.29/Sun Jun 3 12:18:29 2018//Tnetbsd-9-1-RELEASE
+/c_sh.c/1.24/Tue May 8 16:37:59 2018//Tnetbsd-9-1-RELEASE
+/c_test.c/1.9/Fri Jun 30 04:41:19 2017//Tnetbsd-9-1-RELEASE
+/c_test.h/1.3/Wed Jul 7 19:20:09 2004//Tnetbsd-9-1-RELEASE
+/c_ulimit.c/1.16/Fri Jun 30 03:43:57 2017//Tnetbsd-9-1-RELEASE
+/conf-end.h/1.7/Fri Jun 30 02:38:09 2017//Tnetbsd-9-1-RELEASE
+/config.h/1.53/Fri Jun 30 04:22:22 2017//Tnetbsd-9-1-RELEASE
+/edit.c/1.35/Sun Jun 3 12:18:29 2018//Tnetbsd-9-1-RELEASE
+/edit.h/1.5/Sat Jul 1 23:12:08 2017//Tnetbsd-9-1-RELEASE
+/emacs-gen.sh/1.4/Sat Oct 25 22:18:15 2008//Tnetbsd-9-1-RELEASE
+/emacs.c/1.38/Tue May 8 16:37:59 2018//Tnetbsd-9-1-RELEASE
+/eval.c/1.25/Tue Jun 12 14:13:55 2018//Tnetbsd-9-1-RELEASE
+/exec.c/1.28/Sun Jun 3 12:18:29 2018//Tnetbsd-9-1-RELEASE
+/expand.h/1.7/Tue May 8 16:37:59 2018//Tnetbsd-9-1-RELEASE
+/expr.c/1.12/Tue May 8 16:37:59 2018//Tnetbsd-9-1-RELEASE
+/history.c/1.19/Tue May 8 16:37:59 2018//Tnetbsd-9-1-RELEASE
+/io.c/1.18/Tue May 8 16:37:59 2018//Tnetbsd-9-1-RELEASE
+/jobs.c/1.19/Fri Jun 30 04:41:19 2017//Tnetbsd-9-1-RELEASE
+/ksh.Man/1.26/Sun Aug 26 22:52:34 2018//Tnetbsd-9-1-RELEASE
+/ksh_dir.h/1.2/Sun Jan 12 19:11:59 1997//Tnetbsd-9-1-RELEASE
+/ksh_limval.h/1.2/Sun Jan 12 19:11:59 1997//Tnetbsd-9-1-RELEASE
+/lex.c/1.23/Tue May 8 16:37:59 2018//Tnetbsd-9-1-RELEASE
+/lex.h/1.7/Sun Sep 11 22:16:00 2005//Tnetbsd-9-1-RELEASE
+/mail.c/1.9/Tue May 8 16:37:59 2018//Tnetbsd-9-1-RELEASE
+/main.c/1.23/Tue May 8 16:37:59 2018//Tnetbsd-9-1-RELEASE
+/misc.c/1.24/Tue May 8 16:37:59 2018//Tnetbsd-9-1-RELEASE
+/mkman/1.3/Sun Oct 19 22:10:04 2008//Tnetbsd-9-1-RELEASE
+/path.c/1.13/Fri Jun 30 03:56:12 2017//Tnetbsd-9-1-RELEASE
+/proto.h/1.13/Sun Jun 3 12:18:29 2018//Tnetbsd-9-1-RELEASE
+/sh.h/1.35/Fri Jun 30 04:44:46 2017//Tnetbsd-9-1-RELEASE
+/shf.c/1.13/Fri Jun 30 03:56:12 2017//Tnetbsd-9-1-RELEASE
+/shf.h/1.3/Wed Oct 20 15:10:00 1999//Tnetbsd-9-1-RELEASE
+/siglist.in/1.2/Sun Jan 12 19:12:17 1997//Tnetbsd-9-1-RELEASE
+/siglist.sh/1.12/Thu Mar 17 13:59:02 2016//Tnetbsd-9-1-RELEASE
+/syn.c/1.11/Tue May 8 16:37:59 2018//Tnetbsd-9-1-RELEASE
+/table.c/1.8/Sun Jun 3 12:18:29 2018//Tnetbsd-9-1-RELEASE
+/table.h/1.4/Sun Jun 3 12:18:29 2018//Tnetbsd-9-1-RELEASE
+/trap.c/1.14/Tue May 8 16:37:59 2018//Tnetbsd-9-1-RELEASE
+/tree.c/1.9/Tue May 8 16:37:59 2018//Tnetbsd-9-1-RELEASE
+/tree.h/1.7/Thu Jun 22 14:20:46 2017//Tnetbsd-9-1-RELEASE
+/tty.c/1.9/Fri Jun 30 04:41:19 2017//Tnetbsd-9-1-RELEASE
+/tty.h/1.2/Sun Jan 12 19:12:25 1997//Tnetbsd-9-1-RELEASE
+/var.c/1.24/Tue May 8 16:37:59 2018//Tnetbsd-9-1-RELEASE
+/version.c/1.5/Sun Jun 26 19:09:00 2005//Tnetbsd-9-1-RELEASE
+/vi.c/1.20/Tue May 8 16:37:59 2018//Tnetbsd-9-1-RELEASE
+D
diff --git a/CVS/Repository b/CVS/Repository
new file mode 100644
index 0000000..70f719a
--- /dev/null
+++ b/CVS/Repository
@@ -0,0 +1 @@
+src/bin/ksh
diff --git a/CVS/Root b/CVS/Root
new file mode 100644
index 0000000..1accf80
--- /dev/null
+++ b/CVS/Root
@@ -0,0 +1 @@
+anoncvs@anoncvs.netbsd.org:/cvsroot
diff --git a/CVS/Tag b/CVS/Tag
new file mode 100644
index 0000000..b593710
--- /dev/null
+++ b/CVS/Tag
@@ -0,0 +1 @@
+Nnetbsd-9-1-RELEASE
diff --git a/CVS/Template b/CVS/Template
new file mode 100644
index 0000000..07e5af9
--- /dev/null
+++ b/CVS/Template
@@ -0,0 +1,16 @@
+CVS: ----------------------------------------------------------------------
+CVS: CVSROOT cvs.NetBSD.org:/cvsroot
+CVS: please use "PR category/123" to have the commitmsg appended to PR 123
+CVS:
+CVS: Please evaluate your changes and consider the following.
+CVS: Abort checkin if you answer no.
+CVS: => For all changes:
+CVS: Do the changed files compile?
+CVS: Has the change been tested?
+CVS: => If you are not completely familiar with the changed components:
+CVS: Has the change been posted for review?
+CVS: Have you allowed enough time for feedback?
+CVS: => If the change is major:
+CVS: => If the change adds files to, or removes files from $DESTDIR:
+CVS: => If you are changing a library or kernel interface:
+CVS: Have you successfully run "./build.sh release"?
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..892f59b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,55 @@
+# $NetBSD: Makefile,v 1.35 2019/02/04 04:05:15 mrg Exp $
+
+WARNS=3
+CWARNFLAGS.clang+= -Wno-error=cast-qual
+
+.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 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
+
+.if defined(HAVE_GCC) && ${HAVE_GCC} == 7 && ${ACTIVE_CC} == "gcc"
+COPTS+= -Wno-error=implicit-fallthrough
+.endif
+
+.include <bsd.prog.mk>
diff --git a/alloc.c b/alloc.c
new file mode 100644
index 0000000..31f2330
--- /dev/null
+++ b/alloc.c
@@ -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/c_ksh.c b/c_ksh.c
new file mode 100644
index 0000000..41e1f4e
--- /dev/null
+++ b/c_ksh.c
@@ -0,0 +1,1457 @@
+/* $NetBSD: c_ksh.c,v 1.29 2018/06/03 12:18:29 kamil Exp $ */
+
+/*
+ * built-in Korn commands: c_*
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: c_ksh.c,v 1.29 2018/06/03 12:18:29 kamil Exp $");
+#endif
+
+#include <sys/stat.h>
+#include <ctype.h>
+
+#include "sh.h"
+
+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))) {
+ pwd = (char *) 0;
+ } else
+#ifdef S_ISLNK
+ if (!physical || !(pwd = get_phys_path(Xstring(xs, xp))))
+#endif /* S_ISLNK */
+ pwd = Xstring(xs, xp);
+
+ /* Set PWD */
+ if (pwd) {
+ char *ptmp = pwd;
+ 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 */
+ 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;
+ const char *options = "Rnprsu,";
+ 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;
+ 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) {
+ int c;
+ s = *wp;
+ while ((c = *s++) != '\0') {
+ Xcheck(xs, xp);
+ if ((flags & PO_EXPAND) && c == '\\') {
+ int i;
+
+ switch ((c = *s++)) {
+ /* Oddly enough, \007 seems more portable than
+ * \a (due to 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 = mytsearch(&keywords, id, hash(id));
+ if (!tp && !pflag) {
+ tp = mytsearch(&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 = mytsearch(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;
+{
+ struct table *t = &aliases;
+ 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 = mytsearch(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 (ksh_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;
+ 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/c_sh.c b/c_sh.c
new file mode 100644
index 0000000..adfe3f2
--- /dev/null
+++ b/c_sh.c
@@ -0,0 +1,907 @@
+/* $NetBSD: c_sh.c,v 1.24 2018/05/08 16:37:59 kamil Exp $ */
+
+/*
+ * built-in Bourne commands
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: c_sh.c,v 1.24 2018/05/08 16:37:59 kamil Exp $");
+#endif
+
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/times.h>
+#include <time.h>
+
+#include "sh.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;
+{
+ struct block *l = e->loc;
+ 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;
+{
+ int i;
+ 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;
+{
+ int c = 0;
+ int expandv = 1, history = 0;
+ int expanding;
+ int ecode = 0;
+ 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'
+ )
+ 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;
+{
+ 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;
+ 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);
+ }
+#else
+ __USE(anydfl);
+#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;
+ 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;
+{
+ 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&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;
+
+ 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 = 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 = 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 */
+ int i;
+ 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},
+ {NULL, NULL}
+};
diff --git a/c_test.c b/c_test.c
new file mode 100644
index 0000000..b5c67da
--- /dev/null
+++ b/c_test.c
@@ -0,0 +1,642 @@
+/* $NetBSD: c_test.c,v 1.9 2017/06/30 04:41:19 kamil 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.9 2017/06/30 04:41:19 kamil Exp $");
+#endif
+
+#include <sys/stat.h>
+
+#include "sh.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;
+}
+
+static int
+test_stat(pathx, statb)
+ const char *pathx;
+ struct stat *statb;
+{
+ 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;
+
+ 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/c_test.h b/c_test.h
new file mode 100644
index 0000000..f1182f3
--- /dev/null
+++ b/c_test.h
@@ -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/c_ulimit.c b/c_ulimit.c
new file mode 100644
index 0000000..7445af8
--- /dev/null
+++ b/c_ulimit.c
@@ -0,0 +1,290 @@
+/* $NetBSD: c_ulimit.c,v 1.16 2017/06/30 03:43:57 kamil 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.16 2017/06/30 03:43:57 kamil Exp $");
+#endif
+
+#include <sys/time.h>
+#include <time.h>
+
+#include "sh.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 */
+# 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) {
+ if (getrlimit(l->gcmd, &limit) == -1) {
+ bi_errorf("can't get limit: %s",
+ strerror(errno));
+ return 1;
+ }
+ 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) {
+ if (getrlimit(l->gcmd, &limit) == -1) {
+ bi_errorf("can't get limit: %s", strerror(errno));
+ return 1;
+ }
+ 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/conf-end.h b/conf-end.h
new file mode 100644
index 0000000..fcc720e
--- /dev/null
+++ b/conf-end.h
@@ -0,0 +1,31 @@
+/* $NetBSD: conf-end.h,v 1.7 2017/06/30 02:38:09 kamil Exp $ */
+
+/*
+ * End of configuration stuff for PD ksh.
+ *
+ * RCSid: $NetBSD: conf-end.h,v 1.7 2017/06/30 02:38:09 kamil 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 defined(HISTORY) && (!defined(COMPLEX_HISTORY) || !defined(HAVE_FLOCK))
+# undef COMPLEX_HISTORY
+# define EASY_HISTORY /* sjg's trivial history file */
+#endif
+
+#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/config.h b/config.h
new file mode 100644
index 0000000..3afc432
--- /dev/null
+++ b/config.h
@@ -0,0 +1,192 @@
+/* $NetBSD: config.h,v 1.53 2017/06/30 04:22:22 kamil Exp $ */
+
+/* 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 MINIX. */
+/* #undef _MINIX */
+
+/* Define if the system does not provide POSIX.1 features except
+ with this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define if you need to in order for stat and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Define as the return type of signal handlers (int or void). */
+#define RETSIGTYPE void
+
+/* Define if `sys_siglist' is declared by <signal.h>. */
+#define SYS_SIGLIST_DECLARED 1
+
+/* Define as the return value of signal handlers (0 or ). */
+#define RETSIGVAL
+
+/* 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 */
+#define POSIX_PGRP 1
+
+/* 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 */
+/* #undef NO_PGRP */
+
+/* Define if C compiler groks __attribute__((...)) (const, noreturn, format) */
+#define HAVE_GCC_FUNC_ATTR 1
+
+/* 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 */
+#define HAVE_SYS_SIGLIST 1
+
+/* Define if you have a sane <termios.h> header file */
+#define HAVE_TERMIOS_H 1
+
+/* Define if you have a sane <termio.h> header file */
+/* #undef HAVE_TERMIO_H */
+
+/* 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 */
+
+/* Default PATH */
+#ifdef RESCUEDIR
+#define DEFAULT_PATH RESCUEDIR ":/bin:/usr/bin:/sbin:/usr/sbin"
+#else
+#define DEFAULT_PATH "/bin:/usr/bin:/sbin:/usr/sbin"
+#endif
+
+/* Include ksh features? */
+#define KSH 1
+
+/* Include emacs editing? */
+#define EMACS 1
+
+/* Include vi editing? */
+#define VI 1
+
+/* Include job control? */
+#define JOBS 1
+
+/* Include brace-expansion? */
+#define BRACE_EXPAND 1
+
+/* Include any history? */
+#define HISTORY 1
+
+/* Include complex history? */
+/* #undef COMPLEX_HISTORY */
+
+/* Strict POSIX behaviour? */
+#define POSIXLY_CORRECT 1
+
+/* Specify default $ENV? */
+#define DEFAULT_ENV "$HOME/.kshrc"
+
+/* Include shl(1) support? */
+/* #undef SWTCH */
+
+/* Include game-of-life? */
+/* #undef SILLY */
+
+/* Define if you have the _setjmp function. */
+#define HAVE__SETJMP
+
+/* Define if you have the confstr function. */
+#define HAVE_CONFSTR 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. */
+#define HAVE_GETGROUPS
+
+/* Define if you have the getpagesize function. */
+#define HAVE_GETPAGESIZE 1
+
+/* Define if you have the getrusage function. */
+#define HAVE_GETRUSAGE
+
+/* Define if you have the getwd function. */
+#define HAVE_GETWD 1
+
+/* 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. */
+#define HAVE_SETRLIMIT 1
+
+/* Define if you have the sigsetjmp function. */
+#define HAVE_SIGSETJMP 1
+
+/* Define if you have the strerror function. */
+#define HAVE_STRERROR 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
+
+/* Define if you have the valloc function. */
+#define HAVE_VALLOC 1
+
+/* Define if you have the <dirent.h> header file. */
+#define HAVE_DIRENT_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 <sys/dir.h> header file. */
+#define 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 <ulimit.h> header file. */
+#define HAVE_ULIMIT_H
+
+/* 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/edit.c b/edit.c
new file mode 100644
index 0000000..e915ca6
--- /dev/null
+++ b/edit.c
@@ -0,0 +1,1052 @@
+/* $NetBSD: edit.c,v 1.35 2018/06/03 12:18:29 kamil Exp $ */
+
+/*
+ * Command line editing - common code
+ *
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: edit.c,v 1.35 2018/06/03 12:18:29 kamil Exp $");
+#endif
+
+#include <stdbool.h>
+
+#include "config.h"
+#ifdef EDIT
+
+#include "sh.h"
+#include "tty.h"
+#define EXTERN
+#include "edit.h"
+#undef EXTERN
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <ctype.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()
+{
+ 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;
+}
+
+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
+x_mode(bool onoff)
+{
+ static bool x_cur_mode;
+ bool 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);
+
+ /* 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 *));
+
+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 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 (ksh_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/edit.h b/edit.h
new file mode 100644
index 0000000..14ffc27
--- /dev/null
+++ b/edit.h
@@ -0,0 +1,89 @@
+/* $NetBSD: edit.h,v 1.5 2017/07/01 23:12:08 joerg Exp $ */
+
+/* NAME:
+ * edit.h - globals for edit modes
+ *
+ * DESCRIPTION:
+ * This header defines various global edit objects.
+ *
+ * SEE ALSO:
+ *
+ *
+ * RCSid:
+ * $NetBSD: edit.h,v 1.5 2017/07/01 23:12:08 joerg Exp $
+ *
+ */
+
+#include <stdbool.h>
+
+/* 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 x_mode(bool 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/emacs-gen.sh b/emacs-gen.sh
new file mode 100755
index 0000000..3a2de8a
--- /dev/null
+++ b/emacs-gen.sh
@@ -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/emacs.c b/emacs.c
new file mode 100644
index 0000000..90febcf
--- /dev/null
+++ b/emacs.c
@@ -0,0 +1,2174 @@
+/* $NetBSD: emacs.c,v 1.38 2018/05/08 16:37:59 kamil 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.38 2018/05/08 16:37:59 kamil Exp $");
+#endif
+
+#include "config.h"
+#ifdef EMACS
+
+#include <sys/stat.h>
+#include <ctype.h>
+#include <locale.h>
+#include <stdbool.h>
+
+#include "sh.h"
+#include "ksh_dir.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 */
+
+# define CHARMASK 0xFF /* 8-bit character mask */
+# define X_NTABS 3 /* normal, meta1, meta2 */
+#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 },
+ { 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' },
+ /* 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;
+ 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;
+ 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;
+ 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)
+ 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;
+{
+ int i;
+ i = x_size(c);
+ while (i--)
+ x_e_putc('\b');
+}
+
+static int
+x_size_str(cp)
+ char *cp;
+{
+ 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)
+ char *str;
+{
+ 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)
+ 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 */
+ 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;
+{
+ 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;
+}
+
+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 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++;
+ 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];
+ char *p = buf;
+
+ 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));
+
+ 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;
+ 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;
+ 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];
+ 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;
+{
+ 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;
+{
+ 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()
+{
+ char *rcp;
+ 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/eval.c b/eval.c
new file mode 100644
index 0000000..1b53b89
--- /dev/null
+++ b/eval.c
@@ -0,0 +1,1384 @@
+/* $NetBSD: eval.c,v 1.25 2018/06/12 14:13:55 kamil Exp $ */
+
+/*
+ * Expansion - quoting, separation, substitution, globbing
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: eval.c,v 1.25 2018/06/12 14:13:55 kamil Exp $");
+#endif
+
+#include <sys/stat.h>
+#include <stdint.h>
+#include <pwd.h>
+
+#include "sh.h"
+#include "ksh_dir.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 ksh_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)
+ char **ap;
+ int f;
+{
+ XPtrV w;
+
+ if (*ap == NULL)
+ return ap;
+ XPinit(w, 32);
+ XPput(w, NULL); /* space for shell name */
+ while (*ap != NULL)
+ expand(*ap++, &w, f);
+ XPput(w, NULL);
+ return (char **) XPclose(w) + 1;
+}
+
+/*
+ * 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)
+ 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 */
+ XPtrV *wp; /* output words */
+ int f; /* DO* flags */
+{
+ int UNINITIALIZED(c);
+ int type; /* expansion type */
+ int quote = 0; /* quoted */
+ XString ds; /* destination string */
+ 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;
+ unsigned int 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++ = (char)('@' + 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)
+ ksh_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;
+ vp = global(arrayname(sp));
+ if (vp->flag & (ISSET|ARRAY))
+ zero_ok = 1;
+ for (; vp; vp = vp->u.array)
+ if (vp->flag & ISSET) {
+ 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)
+ Expand *xp;
+ char *cp;
+{
+ Source *s, *sold;
+ 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) {
+ 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)
+ char *str;
+ char *pat;
+ int how;
+{
+ char *end = strchr(str, 0);
+ 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 */
+}
+
+/*
+ * ksh_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
+ksh_glob(cp, wp, markdirs)
+ char *cp;
+ 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 */
+ XPtrV *wp; /* output list */
+ int check; /* GF_* flags */
+{
+ 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';
+ }
+ }
+# define KLUDGE_VAL 0
+ 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;
+{
+ struct tbl *ap;
+
+ ap = tenter(&homedirs, name, hash(name));
+ if (!(ap->flag & ISSET)) {
+ 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;
+ }
+ 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)
+ ksh_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/exec.c b/exec.c
new file mode 100644
index 0000000..721f301
--- /dev/null
+++ b/exec.c
@@ -0,0 +1,1553 @@
+/* $NetBSD: exec.c,v 1.28 2018/06/03 12:18:29 kamil Exp $ */
+
+/*
+ * execute command tree
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: exec.c,v 1.28 2018/06/03 12:18:29 kamil Exp $");
+#endif
+
+#include <sys/stat.h>
+#include <ctype.h>
+#include <stdbool.h>
+
+#include "sh.h"
+#include "c_test.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(char **, bool);
+#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 */
+
+/*
+ * 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 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:
+ {
+ sigset_t omask;
+
+ /* 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*/
+ }
+
+ /* 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;
+ }
+
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ e->type = E_EXEC; /* no more need for error handler */
+
+ /* 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 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();
+ execve(t->str, t->args, ap);
+ 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;
+ char **ap;
+ int volatile flags;
+{
+ int i;
+ int leave = LLEAVE;
+ volatile int rv = 0;
+ char *cp;
+ 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)
+ struct op *tp;
+ 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;
+ *tp->args = shellv;
+
+ execve(tp->args[0], tp->args, ap);
+
+ /* report both the program that was run and the bogus shell */
+ errorf("%s: %s: %s", tp->str, shellv, strerror(errno));
+}
+
+int
+shcomexec(wp)
+ char **wp;
+{
+ struct tbl *tp;
+
+ tp = mytsearch(&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 = mytsearch(&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 */
+ mytdelete(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 **));
+{
+ 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) ? mytsearch(&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 = mytsearch(&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 (ksh_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 */
+{
+ 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;
+}
+
+/*
+ * 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;
+
+ 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);
+
+ 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)
+ return Xclose(xs, xp + namelen);
+ 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)
+ struct ioword *iop;
+ struct tbl *tp;
+{
+ 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);
+ }
+ 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(char **ap, bool 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/expand.h b/expand.h
new file mode 100644
index 0000000..e9b984b
--- /dev/null
+++ b/expand.h
@@ -0,0 +1,91 @@
+/* $NetBSD: expand.h,v 1.7 2018/05/08 16:37:59 kamil Exp $ */
+
+/*
+ * Expanding strings
+ */
+/* $Id: expand.h,v 1.7 2018/05/08 16:37:59 kamil Exp $ */
+
+#define X_EXTRA 8 /* this many extra bytes in X string */
+
+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; \
+ __USE(xp); \
+ } 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 { \
+ 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/expr.c b/expr.c
new file mode 100644
index 0000000..5d85b52
--- /dev/null
+++ b/expr.c
@@ -0,0 +1,608 @@
+/* $NetBSD: expr.c,v 1.12 2018/05/08 16:37:59 kamil 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.12 2018/05/08 16:37:59 kamil Exp $");
+#endif
+
+
+#include "sh.h"
+#include <ctype.h>
+#include <stdbool.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(Expr_state *, enum token, struct tbl *, bool);
+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(Expr_state *es, enum token op, struct tbl *vasn, bool 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()
+{
+ 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/history.c b/history.c
new file mode 100644
index 0000000..127c882
--- /dev/null
+++ b/history.c
@@ -0,0 +1,1207 @@
+/* $NetBSD: history.c,v 1.19 2018/05/08 16:37:59 kamil 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.19 2018/05/08 16:37:59 kamil Exp $");
+#endif
+
+#include <sys/stat.h>
+
+#include "sh.h"
+
+#ifdef HISTORY
+# ifdef EASY_HISTORY
+
+# ifndef HISTFILE
+# define HISTFILE ".pdksh_history"
+# 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) */
+{
+ 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;
+ FILE *fh;
+ int i;
+ 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;
+
+ if ((fd = open(hname, O_WRONLY | O_CREAT | O_TRUNC | O_EXLOCK, 0600)) != -1) {
+ /* Remove anything written before we got the lock */
+ ftruncate(fd, 0);
+ if ((fh = fdopen(fd, "w")) != NULL) {
+ for (i = 0; hp + i <= histptr && hp[i]; i++)
+ fprintf(fh, "%s%c", hp[i], '\0');
+ 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;
+{
+ 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)
+ unsigned char *base;
+ 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;
+{
+ int lines = 0;
+ 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;
+ unsigned char *base;
+ 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;
+{
+ 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/io.c b/io.c
new file mode 100644
index 0000000..86febf6
--- /dev/null
+++ b/io.c
@@ -0,0 +1,503 @@
+/* $NetBSD: io.c,v 1.18 2018/05/08 16:37:59 kamil Exp $ */
+
+/*
+ * shell buffered IO and formatted output
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: io.c,v 1.18 2018/05/08 16:37:59 kamil Exp $");
+#endif
+
+#include <sys/stat.h>
+#include <ctype.h>
+#include "sh.h"
+
+static int initio_done;
+
+/*
+ * formatted output functions
+ */
+
+
+/* A shell error occurred (eg, syntax error, etc.) */
+void
+errorf(const char *fmt, ...)
+{
+ va_list va;
+
+ shl_stdout_ok = 0; /* debugging: note that stdout not valid */
+ exstat = 1;
+ if (*fmt) {
+ error_prefix(true);
+ 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
+warningf(int fileline, const char *fmt, ...)
+{
+ va_list va;
+
+ error_prefix(fileline);
+ 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
+bi_errorf(const char *fmt, ...)
+{
+ 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);
+ 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
+internal_errorf(int jump, const char *fmt, ...)
+{
+ va_list va;
+
+ error_prefix(true);
+ shf_fprintf(shl_out, "internal error: ");
+ 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
+shellf(const char *fmt, ...)
+{
+ va_list va;
+
+ if (!initio_done) /* shl_out may not be set up yet... */
+ return;
+ va_start(va, fmt);
+ shf_vfprintf(shl_out, fmt, va);
+ va_end(va);
+ shf_flush(shl_out);
+}
+
+/* printf to shl_stdout (stdout) */
+void
+shprintf(const char *fmt, ...)
+{
+ va_list va;
+
+ if (!shl_stdout_ok)
+ internal_errorf(1, "shl_stdout not valid");
+ 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
+kshdebug_printf_(const char *fmt, ...)
+{
+ va_list va;
+
+ if (!kshdebug_shf)
+ return;
+ 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");
+
+ 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)
+ 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)
+ 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;
+
+ /* 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;
+ }
+ 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/jobs.c b/jobs.c
new file mode 100644
index 0000000..c8cc781
--- /dev/null
+++ b/jobs.c
@@ -0,0 +1,1775 @@
+/* $NetBSD: jobs.c,v 1.19 2017/06/30 04:41:19 kamil 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:
+ * - 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.19 2017/06/30 04:41:19 kamil Exp $");
+#endif
+
+#include <sys/stat.h>
+#include <sys/times.h>
+#include <sys/wait.h>
+
+#include "sh.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;
+ int 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 */
+ int_least32_t 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 int_least32_t njobs; /* # of jobs started */
+static int child_max; /* CHILD_MAX */
+
+
+/* held_sigchld is set if sigchld occurs before a job is completely started */
+static int held_sigchld;
+
+#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 */
+
+ 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);
+
+#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;
+ sigset_t omask;
+ 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));
+
+ /* no SIGCHLD's while messing with job and process lists */
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+ p = new_proc();
+ p->next = (Proc *) 0;
+ p->state = PRUNNING;
+ 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 */
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ 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 */
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ 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;
+ 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");
+ }
+
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+
+ return rv;
+}
+
+/* start the last job: only used for `command` jobs */
+void
+startlast()
+{
+ sigset_t omask;
+
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+ 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);
+ }
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+}
+
+/* wait for last job: only used for `command` jobs */
+int
+waitlast()
+{
+ int rv;
+ Job *j;
+ sigset_t omask;
+
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+ j = last_job;
+ if (!j || !(j->flags & JF_STARTED)) {
+ if (!j)
+ warningf(true, "waitlast: no last job");
+ else
+ internal_errorf(0, "waitlast: not started");
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ return 125; /* not so arbitrary, non-zero value */
+ }
+
+ rv = j_waitj(j, JW_NONE, "jw:waitlast");
+
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+
+ 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;
+ sigset_t omask;
+
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+ *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) {
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ return -1;
+ }
+ } else if ((j = j_lookup(cp, &ecode))) {
+ /* don't report normal job completion */
+ flags &= ~JW_ASYNCNOTIFY;
+ if (j->ppid != procpid) {
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ return -1;
+ }
+ } else {
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ 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");
+
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+
+ 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;
+ 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) { /* 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;
+ }
+ }
+
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+
+ 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;
+ 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;
+ sigset_t omask;
+
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+ if (nflag < 0) { /* kludge: print zombies */
+ nflag = 0;
+ zflag = 1;
+ }
+ if (cp) {
+ int ecode;
+
+ 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;
+ }
+ } 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");
+ }
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ return 0;
+}
+
+/* list jobs for top-level notification */
+void
+j_notify()
+{
+ Job *j, *tmp;
+ sigset_t omask;
+
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+ 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);
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+}
+
+/* Return pid of last process in last asynchronous job */
+pid_t
+j_async()
+{
+ sigset_t omask;
+
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+ if (async_job)
+ async_job->flags |= JF_KNOWN;
+
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+
+ 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 */
+ if (held_sigchld) {
+ held_sigchld = 0;
+ /* Don't call j_sigchld() as it may remove job... */
+ kill(procpid, SIGCHLD);
+ }
+}
+
+/*
+ * 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))
+ {
+ sigsuspend(&sm_default);
+ 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) {
+ int status;
+
+ 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;
+ int status;
+ struct tms t0, t1;
+
+ /* 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;
+ }
+
+ times(&t0);
+ do {
+ pid = waitpid(-1, &status, (WNOHANG|WUNTRACED));
+
+ if (pid <= 0) /* return if would block (0) ... */
+ break; /* ... or no children or interrupted (-1) */
+
+ 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 */
+ }
+ while (1);
+
+ 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;
+ int 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 (WCOREDUMP(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 && p->status == 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/ksh.Man b/ksh.Man
new file mode 100644
index 0000000..69ea1f0
--- /dev/null
+++ b/ksh.Man
@@ -0,0 +1,3621 @@
+'\" t
+.\" $NetBSD: ksh.Man,v 1.26 2018/08/26 22:52:34 sevan 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 26, 2018" "" "User commands"
+.ksh)
+.sh(
+.TH SH 1 "August 26, 2018" "" "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\(+-abCefhiklmnprsuvxX\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 [-abcdfHlmnprsStvw] [value]
+.IP "\fBulimit\fP [\fB\-abcdfHlmnprsStvw\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\-r\fP
+Impose a limit of \fIn\fP threads that can be run by the user at any one
+time.
+.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.
+(Not supported on NetBSD)
+.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/ksh_dir.h b/ksh_dir.h
new file mode 100644
index 0000000..b0b3db6
--- /dev/null
+++ b/ksh_dir.h
@@ -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/ksh_limval.h b/ksh_limval.h
new file mode 100644
index 0000000..42e38bb
--- /dev/null
+++ b/ksh_limval.h
@@ -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/lex.c b/lex.c
new file mode 100644
index 0000000..5d39fcd
--- /dev/null
+++ b/lex.c
@@ -0,0 +1,1386 @@
+/* $NetBSD: lex.c,v 1.23 2018/05/08 16:37:59 kamil Exp $ */
+
+/*
+ * lexical analysis and source input
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: lex.c,v 1.23 2018/05/08 16:37:59 kamil 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;
+ int c, state;
+ XString ws; /* expandable output word */
+ 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();
+ 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 = mytsearch(&keywords, ident, h))
+ && (!(cf & ESACONLY) || p->val.i == ESAC || p->val.i == '}'))
+ {
+ afree(yylval.cp, ATEMP);
+ return p->val.i;
+ }
+ if ((cf & ALIAS) && (p = mytsearch(&aliases, ident, h))
+ && (p->flag & ISSET))
+ {
+ 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()
+{
+ 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;
+{
+ 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
+yyerror(const char *fmt, ...)
+{
+ 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);
+ 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;
+{
+ 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__()
+{
+ Source *s = source;
+ 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/lex.h b/lex.h
new file mode 100644
index 0000000..99c06da
--- /dev/null
+++ b/lex.h
@@ -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/mail.c b/mail.c
new file mode 100644
index 0000000..3404ed5
--- /dev/null
+++ b/mail.c
@@ -0,0 +1,203 @@
+/* $NetBSD: mail.c,v 1.9 2018/05/08 16:37:59 kamil 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.9 2018/05/08 16:37:59 kamil Exp $");
+#endif
+
+#include "config.h"
+
+#ifdef KSH
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include "sh.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()
+{
+ 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)
+ 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)
+ char *mptoparse;
+{
+ mbox_t *mbp;
+ 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)
+mbox_t *mlist;
+{
+ 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;
+ 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/main.c b/main.c
new file mode 100644
index 0000000..41d4d80
--- /dev/null
+++ b/main.c
@@ -0,0 +1,792 @@
+/* $NetBSD: main.c,v 1.23 2018/05/08 16:37:59 kamil Exp $ */
+
+/*
+ * startup, main loop, environments and error handling
+ */
+#include <sys/cdefs.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <locale.h>
+#include <time.h>
+
+#ifndef lint
+__RCSID("$NetBSD: main.c,v 1.23 2018/05/08 16:37:59 kamil Exp $");
+#endif
+
+
+#define EXTERN /* define EXTERNs in sh.h */
+
+#include "sh.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[])
+{
+ int i;
+ int argi;
+ Source *s;
+ struct block *l;
+ int restricted, errexit;
+ char **wp;
+ struct env env;
+ pid_t ppid;
+
+ /* 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);
+ s->file = argv[argi++];
+ 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)) {
+ include(KSH_SYSTEM_PROFILE, 0, (char **) 0, 1);
+ if (!Flag(FPRIVILEGED))
+ include(substitute("$HOME/.profile", 0), 0,
+ (char **) 0, 1);
+ }
+
+ 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);
+ else if (Flag(FTALKING))
+ include(substitute("$HOME/kshrc.ksh", 0), 0,
+ (char **) 0, 1);
+ }
+
+ 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;
+{
+ 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;
+{
+ 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;
+{
+ 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()
+{
+ struct env *ep = e;
+ 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);
+ }
+ }
+ }
+ 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;
+{
+ for (; tp != NULL; tp = tp->next)
+ if (tp->pid == procpid) {
+ unlink(tp->name);
+ }
+}
+
+/* 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/misc.c b/misc.c
new file mode 100644
index 0000000..393da1c
--- /dev/null
+++ b/misc.c
@@ -0,0 +1,1342 @@
+/* $NetBSD: misc.c,v 1.24 2018/05/08 16:37:59 kamil Exp $ */
+
+/*
+ * Miscellaneous functions
+ */
+#include <sys/cdefs.h>
+
+#ifndef lint
+__RCSID("$NetBSD: misc.c,v 1.24 2018/05/08 16:37:59 kamil Exp $");
+#endif
+
+
+#include "sh.h"
+#include <ctype.h> /* for FILECHCONV */
+#include <limits.h>
+
+#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)
+ const char *s;
+ int t;
+{
+ 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()
+{
+ 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)
+ unsigned long n;
+ int base;
+{
+ 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)
+ 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)
+ 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];
+ 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) {
+ seteuid(ksheuid = getuid());
+ setuid(ksheuid);
+ setegid(getgid());
+ setgid(getgid());
+ } 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 */
+