summaryrefslogtreecommitdiff
path: root/utils/rcsh
diff options
context:
space:
mode:
Diffstat (limited to 'utils/rcsh')
-rw-r--r--utils/rcsh/Nt.c549
-rw-r--r--utils/rcsh/code.c486
-rw-r--r--utils/rcsh/exec.c802
-rw-r--r--utils/rcsh/glob.c286
-rw-r--r--utils/rcsh/here.c145
-rw-r--r--utils/rcsh/io.c238
-rw-r--r--utils/rcsh/lex.c398
-rw-r--r--utils/rcsh/main.c228
-rw-r--r--utils/rcsh/mkfile53
-rw-r--r--utils/rcsh/pcmd.c110
-rw-r--r--utils/rcsh/pfnc.c70
-rw-r--r--utils/rcsh/rc.h295
-rw-r--r--utils/rcsh/rcmain29
-rw-r--r--utils/rcsh/rcpath13
-rw-r--r--utils/rcsh/simple.c528
-rw-r--r--utils/rcsh/syn.y90
-rw-r--r--utils/rcsh/trap.c42
-rw-r--r--utils/rcsh/tree.c140
-rw-r--r--utils/rcsh/var.c240
-rw-r--r--utils/rcsh/word.c173
20 files changed, 4915 insertions, 0 deletions
diff --git a/utils/rcsh/Nt.c b/utils/rcsh/Nt.c
new file mode 100644
index 00000000..dc6ad8a9
--- /dev/null
+++ b/utils/rcsh/Nt.c
@@ -0,0 +1,549 @@
+#include "rc.h"
+#include <windows.h>
+
+enum {
+ Nchild = 100,
+};
+
+typedef struct Child Child;
+
+struct Child {
+ int pid;
+ HANDLE handle;
+};
+
+static Child child[Nchild];
+
+static void
+winerror(void)
+{
+ int e, r;
+ char buf[100], *p, *q;
+
+ e = GetLastError();
+
+ r = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
+ 0, e, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ buf, sizeof(buf), 0);
+
+ if(r == 0)
+ snprint(buf, sizeof(buf), "windows error %d", e);
+
+ for(p=q=buf; *p; p++) {
+ if(*p == '\r')
+ continue;
+ if(*p == '\n')
+ *q++ = ' ';
+ else
+ *q++ = *p;
+ }
+ *q = 0;
+ errstr(buf, sizeof buf);
+}
+
+static int
+badentry(char *filename)
+{
+ if(*filename == 0)
+ return 1;
+ if(filename[0] == '.'){
+ if(filename[1] == 0)
+ return 1;
+ if(filename[1] == '.' && filename[2] == 0)
+ return 1;
+ }
+ return 0;
+}
+
+Direntry*
+readdirect(char *path)
+{
+ long n;
+ HANDLE h;
+ Direntry *d;
+ char fullpath[MAX_PATH];
+ WIN32_FIND_DATA data;
+
+ snprint(fullpath, MAX_PATH, "%s\\*.*", path);
+
+ h = FindFirstFile(fullpath, &data);
+ if(h == INVALID_HANDLE_VALUE)
+ return 0;
+
+ n = 0;
+ d = 0;
+ for(;;){
+ if(!badentry(data.cFileName)){
+ d = realloc(d, (n+2)*sizeof(Direntry));
+ if(d == 0){
+ werrstr("memory allocation");
+ return 0;
+ }
+ d[n].name = malloc(strlen(data.cFileName)+1);
+ if(d[n].name == 0){
+ werrstr("memory allocation");
+ return 0;
+ }
+ strcpy(d[n].name, data.cFileName);
+ if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ d[n].isdir = 1;
+ else
+ d[n].isdir = 0;
+ n++;
+ }
+ if(FindNextFile(h, &data) == 0)
+ break;
+ }
+ FindClose(h);
+ if(d){
+ d[n].name = 0;
+ d[n].isdir = 0;
+ }
+ return d;
+}
+
+void
+fatal(char *fmt, ...)
+{
+ char buf[512];
+ va_list arg;
+
+ va_start(arg, fmt);
+ vseprint(buf, buf+sizeof(buf), fmt, arg);
+ va_end(arg);
+
+ fprint(2, "rc: %s\n", buf);
+ _exits(buf);
+}
+
+static int
+tas(int *p)
+{
+ int v;
+
+ _asm {
+ mov eax, p
+ mov ebx, 1
+ xchg ebx, [eax]
+ mov v, ebx
+ }
+
+ return v;
+}
+
+static void
+lock(Lock *lk)
+{
+ int i;
+
+ /* easy case */
+ if(!tas(&lk->val))
+ return;
+
+ /* for muli processor machines */
+ for(i=0; i<100; i++)
+ if(!tas(&lk->val))
+ return;
+
+ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
+ for(;;) {
+ for(i=0; i<10000; i++) {
+ Sleep(0);
+ if(!tas(&lk->val)) {
+ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
+ return;
+ }
+ }
+ }
+}
+
+static void
+unlock(Lock *lk)
+{
+ lk->val = 0;
+}
+
+int
+refinc(Ref *r)
+{
+ int i;
+
+ lock(&r->lk);
+ i = r->ref;
+ r->ref++;
+ unlock(&r->lk);
+ return i;
+}
+
+int
+refdec(Ref *r)
+{
+ int i;
+
+ lock(&r->lk);
+ r->ref--;
+ i = r->ref;
+ unlock(&r->lk);
+
+ return i;
+}
+
+/*
+ * windows quoting rules - I think
+ * Words are seperated by space or tab
+ * Words containing a space or tab can be quoted using "
+ * 2N backslashes + " ==> N backslashes and end quote
+ * 2N+1 backslashes + " ==> N backslashes + literal "
+ * N backslashes not followed by " ==> N backslashes
+ */
+static char *
+dblquote(char *cmd, char *s)
+{
+ int nb;
+ char *p;
+
+ for(p=s; *p; p++)
+ if(*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || *p == '"')
+ break;
+
+ if(*p == 0){ /* easy case */
+ strcpy(cmd, s);
+ return cmd+(p-s);
+ }
+
+ *cmd++ = '"';
+ for(;;) {
+ for(nb=0; *s=='\\'; nb++)
+ *cmd++ = *s++;
+
+ if(*s == 0) { /* trailing backslashes -> 2N */
+ while(nb-- > 0)
+ *cmd++ = '\\';
+ break;
+ }
+
+ if(*s == '"') { /* literal quote -> 2N+1 backslashes */
+ while(nb-- > 0)
+ *cmd++ = '\\';
+ *cmd++ = '\\'; /* escape the quote */
+ }
+ *cmd++ = *s++;
+ }
+
+ *cmd++ = '"';
+ *cmd = 0;
+
+ return cmd;
+}
+
+static char *
+proccmd(char **argv)
+{
+ int i, n;
+ char *cmd, *p;
+
+ /* conservatively calculate length of command;
+ * backslash expansion can cause growth in dblquote().
+ */
+ for(i=0,n=0; argv[i]; i++) {
+ n += 2*strlen(argv[i]);
+ }
+ n++;
+
+ cmd = malloc(n);
+ for(i=0,p=cmd; argv[i]; i++) {
+ p = dblquote(p, argv[i]);
+ *p++ = ' ';
+ }
+ if(p != cmd)
+ p--;
+ *p = 0;
+
+ return cmd;
+}
+
+static char *
+exportenv(char **e)
+{
+ int i, j, n;
+ char *buf;
+
+ if(e == 0 || *e == 0)
+ return 0;
+
+ buf = 0;
+ n = 0;
+ for(i = 0; *e; e++, i++) {
+ j = strlen(*e)+1;
+ buf = realloc(buf, n+j);
+ strcpy(buf+n, *e);
+ n += j;
+ }
+ /* final null */
+ buf = realloc(buf, n+1);
+ buf[n] = 0;
+
+ return buf;
+}
+
+static int
+setpath(char *path, char *file)
+{
+ char *p, *last, tmp[MAX_PATH+1];
+ int n;
+
+ if(strlen(file) >= MAX_PATH){
+ werrstr("file name too long");
+ return -1;
+ }
+ strcpy(tmp, file);
+
+ for(p=tmp; *p; p++) {
+ if(*p == '/')
+ *p = '\\';
+ }
+
+ if(tmp[0] != 0 && tmp[1] == ':') {
+ if(tmp[2] == 0) {
+ tmp[2] = '\\';
+ tmp[3] = 0;
+ } else if(tmp[2] != '\\') {
+ /* don't allow c:foo - only c:\foo */
+ werrstr("illegal file name");
+ return -1;
+ }
+ }
+
+ path[0] = 0;
+ n = GetFullPathName(tmp, MAX_PATH, path, &last);
+ if(n >= MAX_PATH) {
+ werrstr("file name too long");
+ return -1;
+ }
+ if(n == 0 && tmp[0] == '\\' && tmp[1] == '\\' && tmp[2] != 0) {
+ strcpy(path, tmp);
+ return -1;
+ }
+
+ if(n == 0) {
+ werrstr("bad file name");
+ return -1;
+ }
+
+ for(p=path; *p; p++) {
+ if(*p < 32 || *p == '*' || *p == '?') {
+ werrstr("file not found");
+ return -1;
+ }
+ }
+
+ /* get rid of trailling \ */
+ if(path[n-1] == '\\') {
+ if(n <= 2) {
+ werrstr("illegal file name");
+ return -1;
+ }
+ path[n-1] = 0;
+ n--;
+ }
+
+ if(path[1] == ':' && path[2] == 0) {
+ path[2] = '\\';
+ path[3] = '.';
+ path[4] = 0;
+ return -1;
+ }
+
+ if(path[0] != '\\' || path[1] != '\\')
+ return 0;
+
+ for(p=path+2,n=0; *p; p++)
+ if(*p == '\\')
+ n++;
+ if(n == 0)
+ return -1;
+ if(n == 1)
+ return -1;
+ return 0;
+}
+
+
+static int
+execpath(char *path, char *file)
+{
+ int n;
+
+ if(setpath(path, file) < 0)
+ return 0;
+
+ n = strlen(path)-4;
+ if(path[n] == '.') {
+ if(GetFileAttributes(path) != -1)
+ return 1;
+ }
+ strncat(path, ".exe", MAX_PATH);
+ path[MAX_PATH-1] = 0;
+ if(GetFileAttributes(path) != -1)
+ return 1;
+ return 0;
+}
+
+static HANDLE
+fdexport(int fd)
+{
+ HANDLE h, r;
+
+ if(fd < 0)
+ return INVALID_HANDLE_VALUE;
+
+ h = (HANDLE)_get_osfhandle(fd);
+ if(h < 0)
+ return INVALID_HANDLE_VALUE;
+
+ if(!DuplicateHandle(GetCurrentProcess(), h,
+ GetCurrentProcess(), &r, DUPLICATE_SAME_ACCESS,
+ 1, DUPLICATE_SAME_ACCESS))
+ return INVALID_HANDLE_VALUE;
+ return r;
+}
+
+static int
+addchild(int pid, HANDLE handle)
+{
+ int i;
+
+ for(i=0; i<Nchild; i++) {
+ if(child[i].handle == 0) {
+ child[i].handle = handle;
+ child[i].pid = pid;
+ return 1;
+ }
+ }
+ werrstr("child table full");
+ return 0;
+}
+
+int
+procwait(uint pid)
+{
+ HANDLE h;
+ int i, exit;
+
+ if(pid == 0)
+ return 0;
+
+ h = 0;
+ for(i = 0; i < Nchild; i++){
+ if(child[i].pid == pid){
+ h = child[i].handle;
+ child[i].pid = 0;
+ child[i].handle = 0;
+ break;
+ }
+ }
+
+ if(h == 0){ /* we don't know about this one - let the system try to find it */
+ h = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
+ if(h == 0)
+ return 0; /* can't find it */
+ }
+
+ if(WaitForSingleObject(h, INFINITE) == WAIT_FAILED) {
+ winerror();
+ fatal("procwait: ");
+ }
+
+ if(!GetExitCodeProcess(h, &exit)) {
+ winerror();
+ exit = 1;
+ }
+
+ CloseHandle(h);
+ return exit;
+}
+
+uint
+proc(char **argv, int stdin, int stdout, int stderr)
+{
+ char *p, *arg0, *q, buf[MAX_PATH], path[MAX_PATH], *cmd, *eb;
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+ int r, found, full;
+ extern char **_environ;
+ Word *w;
+
+ arg0 = argv[0];
+ if(arg0 == 0) {
+ werrstr("null argv[0]");
+ return 0;
+ }
+
+ full = arg0[0] == '\\' || arg0[0] == '/' || arg0[0] == '.';
+ found = execpath(path, arg0);
+
+ if(!found && !full) {
+ w = vlook("path")->val;
+ if(w)
+ p = w->word;
+ else
+ p = getenv("path");
+ for(; p && *p; p = q){
+ q = strchr(p, ';');
+ if(q)
+ *q = 0;
+ snprint(buf, sizeof(buf), "%s/%s", p, arg0);
+ if(q)
+ *q++ = ';';
+ found = execpath(path, buf);
+ if(found)
+ break;
+ }
+ }
+
+ if(!found) {
+ werrstr("file not found");
+ return 0;
+ }
+
+ memset(&si, 0, sizeof(si));
+ si.cb = sizeof(si);
+ si.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
+ si.wShowWindow = SW_SHOW;
+ si.hStdInput = fdexport(stdin);
+ si.hStdOutput = fdexport(stdout);
+ si.hStdError = fdexport(stderr);
+
+ eb = exportenv(_environ);
+
+ cmd = proccmd(argv);
+
+ r = CreateProcess(path, cmd, 0, 0, 1, 0, eb, 0, &si, &pi);
+
+ /* allow child to run */
+ Sleep(0);
+
+ free(cmd);
+ free(eb);
+
+ CloseHandle(si.hStdInput);
+ CloseHandle(si.hStdOutput);
+ CloseHandle(si.hStdError);
+
+ if(!r) {
+ winerror();
+ return 0;
+ }
+
+ CloseHandle(pi.hThread);
+
+ if(addchild(pi.dwProcessId, pi.hProcess) == 0)
+ return 0;
+
+ return pi.dwProcessId;
+}
+
+int
+pipe(int *fd)
+{
+ return _pipe(fd, 0, _O_BINARY);
+}
diff --git a/utils/rcsh/code.c b/utils/rcsh/code.c
new file mode 100644
index 00000000..06aea6ff
--- /dev/null
+++ b/utils/rcsh/code.c
@@ -0,0 +1,486 @@
+#include "rc.h"
+#include "y.tab.h"
+
+#define c0 t->child[0]
+#define c1 t->child[1]
+#define c2 t->child[2]
+#define emitf(x) ((codep!=ncode || morecode()), codebuf[codep].f=(x), codep++)
+#define emiti(x) ((codep!=ncode || morecode()), codebuf[codep].i=(x), codep++)
+#define emits(x) ((codep!=ncode || morecode()), codebuf[codep].s=(x), codep++)
+
+void stuffdot(int);
+char *fnstr(Tree*);
+void outcode(Tree*, int);
+void codeswitch(Tree*, int);
+int iscase(Tree*);
+Code *codecopy(Code*);
+void codefree(Code*);
+
+int codep, ncode;
+Code *codebuf;
+
+int
+morecode(void)
+{
+ ncode+=100;
+ codebuf=realloc(codebuf, ncode*sizeof codebuf[0]);
+ if(codebuf==0)
+ panic("Can't realloc %d bytes in morecode!",
+ ncode*sizeof codebuf[0]);
+ return 0; /* not used */
+}
+
+void
+stuffdot(int a)
+{
+ if(a<0 || codep<=a) panic("Bad address %d in stuffdot", a);
+ codebuf[a].i=codep;
+}
+
+int
+compile(Tree *t)
+{
+ ncode=100;
+ codebuf=malloc(ncode*sizeof codebuf[0]);
+ codep=0;
+ emiti(0); /* reference count */
+ outcode(t, flag['e']?1:0);
+ if(nerror){
+ free(codebuf);
+ return 0;
+ }
+/* readhere(); */
+ emitf(Xreturn);
+ emitf(0);
+ return 1;
+}
+
+void
+cleanhere(char *f)
+{
+ emitf(Xdelhere);
+ emits(strdup(f));
+}
+
+char *
+fnstr(Tree *t)
+{
+ Io *f=openstr();
+ char *v;
+ extern char nl;
+ char svnl=nl;
+
+ nl=';';
+ pfmt(f, "%t", t);
+ nl=svnl;
+ v=f->strp;
+ f->strp=0;
+ closeio(f);
+ return v;
+}
+
+void
+outcode(Tree *t, int eflag)
+{
+ int p, q;
+ Tree *tt;
+
+ if(t==0)
+ return;
+ if(t->type != NOT && t->type != ';')
+ runq->iflast=0;
+ switch(t->type){
+ default:
+ pfmt(err, "bad type %d in outcode\n", t->type);
+ break;
+ case '$':
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xdol);
+ break;
+ case '"':
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xqdol);
+ break;
+ case SUB:
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xmark);
+ outcode(c1, eflag);
+ emitf(Xsub);
+ break;
+ case '&':
+ emitf(Xasync);
+ emits(fnstr(c0));
+/*
+ p=emiti(0);
+ outcode(c0, eflag);
+ emitf(Xexit);
+ stuffdot(p);
+*/
+ break;
+ case ';':
+ outcode(c0, eflag);
+ outcode(c1, eflag);
+ break;
+ case '^':
+ emitf(Xmark);
+ outcode(c1, eflag);
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xconc);
+ break;
+ case '`':
+ emitf(Xbackq);
+ emits(fnstr(c0));
+/*
+ p=emiti(0);
+ outcode(c0, 0);
+ emitf(Xexit);
+ stuffdot(p);
+*/
+ break;
+ case ANDAND:
+ outcode(c0, 0);
+ emitf(Xtrue);
+ p=emiti(0);
+ outcode(c1, eflag);
+ stuffdot(p);
+ break;
+ case ARGLIST:
+ outcode(c1, eflag);
+ outcode(c0, eflag);
+ break;
+ case BANG:
+ outcode(c0, eflag);
+ emitf(Xbang);
+ break;
+ case PCMD:
+ case BRACE:
+ outcode(c0, eflag);
+ break;
+ case COUNT:
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xcount);
+ break;
+ case FN:
+ emitf(Xmark);
+ outcode(c0, eflag);
+ if(c1){
+ emitf(Xfn);
+ p=emiti(0);
+ emits(fnstr(c1));
+ outcode(c1, eflag);
+ emitf(Xunlocal); /* get rid of $* */
+ emitf(Xreturn);
+ stuffdot(p);
+ }
+ else
+ emitf(Xdelfn);
+ break;
+ case IF:
+ outcode(c0, 0);
+ emitf(Xif);
+ p=emiti(0);
+ outcode(c1, eflag);
+ emitf(Xwastrue);
+ stuffdot(p);
+ break;
+ case NOT:
+ if(!runq->iflast) yyerror("`if not' does not follow `if(...)'");
+ emitf(Xifnot);
+ p=emiti(0);
+ outcode(c0, eflag);
+ stuffdot(p);
+ break;
+ case OROR:
+ outcode(c0, 0);
+ emitf(Xfalse);
+ p=emiti(0);
+ outcode(c1, eflag);
+ stuffdot(p);
+ break;
+ case PAREN:
+ outcode(c0, eflag);
+ break;
+ case SIMPLE:
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xsimple);
+ if(eflag) emitf(Xeflag);
+ break;
+ case SUBSHELL:
+ emitf(Xsubshell);
+ emits(fnstr(c0));
+/*
+ p=emiti(0);
+ outcode(c0, eflag);
+ emitf(Xexit);
+ stuffdot(p);
+*/
+ if(eflag) emitf(Xeflag);
+ break;
+ case SWITCH:
+ codeswitch(t, eflag);
+ break;
+ case TWIDDLE:
+ emitf(Xmark);
+ outcode(c1, eflag);
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xmatch);
+ if(eflag) emitf(Xeflag);
+ break;
+ case WHILE:
+ q=codep;
+ emitf(Xsettrue);
+ outcode(c0, 0);
+ emitf(Xtrue);
+ p=emiti(0);
+ outcode(c1, eflag);
+ emitf(Xjump);
+ emiti(q);
+ stuffdot(p);
+ break;
+ case WORDS:
+ outcode(c1, eflag);
+ outcode(c0, eflag);
+ break;
+ case FOR:
+ emitf(Xmark);
+ if(c1){
+ outcode(c1, eflag);
+ emitf(Xglob);
+ }
+ else{
+ emitf(Xmark);
+ emitf(Xword);
+ emits(strdup("*"));
+ emitf(Xdol);
+ }
+ emitf(Xmark); /* dummy value for Xlocal */
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xlocal);
+ p=emitf(Xfor);
+ q=emiti(0);
+ outcode(c2, eflag);
+ emitf(Xjump);
+ emiti(p);
+ stuffdot(q);
+ emitf(Xunlocal);
+ break;
+ case WORD:
+ emitf(Xword);
+ emits(strdup(t->str));
+ break;
+ case DUP:
+ if(t->rtype==DUPFD) {
+ emitf(Xdup);
+ emiti(t->fd0);
+ emiti(t->fd1);
+ } else { /* t->rtype == CLOSE */
+ emitf(Xclose);
+ emiti(t->fd0);
+ }
+ outcode(c1, eflag);
+ emitf(Xpopredir);
+ break;
+/*
+ case PIPEFD:
+ emitf(Xpipefd);
+ emiti(t->rtype);
+ p=emiti(0);
+ outcode(c0, eflag);
+ emitf(Xexit);
+ stuffdot(p);
+ break;
+*/
+ case REDIR:
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xglob);
+ switch(t->rtype){
+ case APPEND:
+ emitf(Xappend);
+ break;
+ case WRITE:
+ emitf(Xwrite);
+ break;
+ case READ:
+ case HERE:
+ emitf(Xread);
+ break;
+ }
+ emiti(t->fd0);
+ outcode(c1, eflag);
+ emitf(Xpopredir);
+ break;
+ case '=':
+ tt=t;
+ for(;t && t->type=='=';t=c2)
+ ;
+ if(t){
+ for(t=tt;t->type=='=';t=c2){
+ emitf(Xmark);
+ outcode(c1, eflag);
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xlocal);
+ }
+ t=tt;
+ outcode(c2, eflag);
+ for(;t->type=='=';t=c2)
+ emitf(Xunlocal);
+ }
+ else{
+ for(t=tt;t;t=c2){
+ emitf(Xmark);
+ outcode(c1, eflag);
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xassign);
+ }
+ }
+ t=tt; /* so tests below will work */
+ break;
+ case PIPE:
+ emitf(Xpipe);
+ emiti(t->fd0);
+ emiti(t->fd1);
+ emits(fnstr(c0));
+ q=emiti(0);
+/*
+ outcode(c0, eflag);
+ emitf(Xexit);
+ stuffdot(p);
+*/
+ outcode(c1, eflag);
+ emitf(Xreturn);
+ stuffdot(q);
+ emitf(Xpipewait);
+ break;
+ }
+ if(t->type!=NOT && t->type!=';')
+ runq->iflast=t->type==IF;
+ else if(c0) runq->iflast=c0->type==IF;
+}
+
+/*
+ * switch code looks like this:
+ * Xmark
+ * (get switch value)
+ * Xjump 1f
+ * out: Xjump leave
+ * 1: Xmark
+ * (get case values)
+ * Xcase 1f
+ * (commands)
+ * Xjump out
+ * 1: Xmark
+ * (get case values)
+ * Xcase 1f
+ * (commands)
+ * Xjump out
+ * 1:
+ * leave:
+ * Xpopm
+ */
+void
+codeswitch(Tree *t, int eflag)
+{
+ int leave; /* patch jump address to leave switch */
+ int out; /* jump here to leave switch */
+ int nextcase; /* patch jump address to next case */
+ Tree *tt;
+ if(c1->child[0]->type!=';'
+ || !iscase(c1->child[0]->child[0])){
+ yyerror("case missing in switch");
+ return;
+ }
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xjump);
+ nextcase=emiti(0);
+ out=emitf(Xjump);
+ leave=emiti(0);
+ stuffdot(nextcase);
+ t=c1->child[0];
+ while(t->type==';'){
+ tt=c1;
+ emitf(Xmark);
+ for(t=c0->child[0];t->type==ARGLIST;t=c0) outcode(c1, eflag);
+ emitf(Xcase);
+ nextcase=emiti(0);
+ t=tt;
+ for(;;){
+ if(t->type==';'){
+ if(iscase(c0)) break;
+ outcode(c0, eflag);
+ t=c1;
+ }
+ else{
+ outcode(t, eflag);
+ break;
+ }
+ }
+ emitf(Xjump);
+ emiti(out);
+ stuffdot(nextcase);
+ }
+ stuffdot(leave);
+ emitf(Xpopm);
+}
+
+int
+iscase(Tree *t)
+{
+ if(t->type!=SIMPLE)
+ return 0;
+ do
+ t=c0;
+ while(t->type==ARGLIST);
+
+ return t->type==WORD && !t->quoted && strcmp(t->str, "case")==0;
+}
+
+Code *
+codecopy(Code *cp)
+{
+ cp[0].i++;
+ return cp;
+}
+
+void
+codefree(Code *cp)
+{
+ Code *p;
+
+ if(--cp[0].i!=0)
+ return;
+
+ for(p=cp+1;p->f;){
+ if(p->f==Xappend || p->f==Xclose || p->f==Xread || p->f==Xwrite
+ || p->f==Xasync || p->f==Xcase || p->f==Xfalse
+ || p->f==Xfor || p->f==Xjump
+ || p->f==Xsubshell || p->f==Xtrue)
+ p+=2;
+ else if(p->f==Xdup || p->f==Xpipefd)
+ p+=3;
+ else if(p->f==Xpipe) {
+ free(p[3].s);
+ p+=5;
+ } else if(p->f==Xword || p->f==Xdelhere || p->f==Xbackq) {
+ free(p[1].s);
+ p+=2;
+ } else if(p->f==Xfn){
+ free(p[2].s);
+ p+=3;
+ } else
+ p++;
+ }
+
+ free(cp);
+}
diff --git a/utils/rcsh/exec.c b/utils/rcsh/exec.c
new file mode 100644
index 00000000..66112d80
--- /dev/null
+++ b/utils/rcsh/exec.c
@@ -0,0 +1,802 @@
+#include "rc.h"
+
+extern char *argv0;
+
+int ifnot;
+int eflagok;
+
+/*
+ * Opcode routines
+ * Arguments on stack (...)
+ * Arguments in line [...]
+ * Code in line with jump around {...}
+ *
+ * Xappend(file)[fd] open file to append
+ * Xassign(name, val) assign val to name
+ * Xasync{... Xexit} make thread for {}, no wait
+ * Xbackq{... Xreturn} make thread for {}, push stdout
+ * Xbang complement condition
+ * Xcase(pat, value){...} exec code on match, leave (value) on
+ * stack
+ * Xclose[i] close file descriptor
+ * Xconc(left, right) concatenate, push results
+ * Xcount(name) push var count
+ * Xdelfn(name) delete function definition
+ * Xdeltraps(names) delete named traps
+ * Xdol(name) get variable value
+ * Xqdol(name) concatenate variable components
+ * Xdup[i j] dup file descriptor
+ * Xexit rc exits with status
+ * Xfalse{...} execute {} if false
+ * Xfn(name){... Xreturn} define function
+ * Xfor(var, list){... Xreturn} for loop
+ * Xjump[addr] goto
+ * Xlocal(name, val) create local variable, assign value
+ * Xmark mark stack
+ * Xmatch(pat, str) match pattern, set status
+ * Xpipe[i j]{... Xreturn}{... Xreturn} construct a pipe between 2 new threads,
+ * wait for both
+ * Xpipefd[type]{... Xreturn} connect {} to pipe (input or output,
+ * depending on type), push /dev/fd/??
+ * Xpopm(value) pop value from stack
+ * Xread(file)[fd] open file to read
+ * Xsettraps(names){... Xreturn} define trap functions
+ * Xshowtraps print trap list
+ * Xsimple(args) run command and wait
+ * Xreturn kill thread
+ * Xsubshell{... Xexit} execute {} in a subshell and wait
+ * Xtrue{...} execute {} if true
+ * Xunlocal delete local variable
+ * Xword[string] push string
+ * Xwrite(file)[fd] open file to write
+ */
+
+static char **
+rcargv(char *s)
+{
+ char *flags;
+
+ if(flag['e'])
+ flags = "-Se";
+ else
+ flags = "-S";
+ return procargv(argv0, flags, "-c", s, vlook("*")->val);
+}
+
+void
+Xappend(void)
+{
+ char *file;
+ int f;
+
+ switch(count(runq->argv->words)){
+ default: Xerror(">> requires singleton"); return;
+ case 0: Xerror(">> requires file"); return;
+ case 1: break;
+ }
+ file=runq->argv->words->word;
+ if((f=open(file, 1))<0 && (f=create(file, 1, 0666))<0){
+ Xperror(file);
+ return;
+ }
+ seek(f, 0L, 2);
+ pushredir(ROPEN, f, runq->code[runq->pc].i);
+ runq->pc++;
+ poplist();
+}
+
+void
+Xassign(void)
+{
+ Var *v;
+
+ if(count(runq->argv->words)!=1){
+ Xerror("variable name not singleton!");
+ return;
+ }
+ deglob(runq->argv->words->word);
+ v=vlook(runq->argv->words->word);
+ poplist();
+ globlist();
+ freewords(v->val);
+ v->val=runq->argv->words;
+ v->changed=1;
+ runq->argv->words=0;
+ poplist();
+}
+
+void
+Xasync(void)
+{
+ uint pid;
+ char buf[20], **argv;
+
+ updenv();
+
+ argv = rcargv(runq->code[runq->pc].s);
+ pid = proc(argv, -1, 1, 2);
+ free(argv);
+
+ if(pid == 0) {
+ Xerror("proc failed");
+ return;
+ }
+
+ runq->pc++;
+ sprint(buf, "%d", pid);
+ setvar("apid", newword(buf, (Word *)0));
+}
+
+void
+Xbackq(void)
+{
+ char wd[8193], **argv;
+ int c;
+ char *s, *ewd=&wd[8192], *stop;
+ Io *f;
+ Var *ifs=vlook("ifs");
+ Word *v, *nextv;
+ int pfd[2];
+ int pid;
+
+ stop = ifs->val?ifs->val->word:"";
+ if(pipe(pfd)<0){
+ Xerror("can't make pipe");
+ return;
+ }
+
+ updenv();
+
+ argv = rcargv(runq->code[runq->pc].s);
+ pid = proc(argv, -1, pfd[1], 2);
+ free(argv);
+
+ close(pfd[1]);
+
+ if(pid == 0) {
+ Xerror("proc failed");
+ close(pfd[0]);
+ return;
+ }
+
+ f = openfd(pfd[0]);
+ s = wd;
+ v = 0;
+ while((c=rchr(f))!=EOF){
+ if(strchr(stop, c) || s==ewd){
+ if(s!=wd){
+ *s='\0';
+ v=newword(wd, v);
+ s=wd;
+ }
+ }
+ else *s++=c;
+ }
+ if(s!=wd){
+ *s='\0';
+ v=newword(wd, v);
+ }
+ closeio(f);
+ waitfor(pid);
+ /* v points to reversed arglist -- reverse it onto argv */
+ while(v){
+ nextv=v->next;
+ v->next=runq->argv->words;
+ runq->argv->words=v;
+ v=nextv;
+ }
+ runq->pc++;
+}
+
+void
+Xbang(void)
+{
+ setstatus(truestatus()?"false":"");
+}
+
+void
+Xcase(void)
+{
+ Word *p;
+ char *s;
+ int ok=0;
+
+ s=list2str(runq->argv->next->words);
+ for(p=runq->argv->words;p;p=p->next){
+ if(match(s, p->word, '\0')){
+ ok=1;
+ break;
+ }
+ }
+ free(s);
+ if(ok)
+ runq->pc++;
+ else
+ runq->pc=runq->code[runq->pc].i;
+ poplist();
+}
+
+void
+Xclose(void)
+{
+ pushredir(RCLOSE, runq->code[runq->pc].i, 0);
+ runq->pc++;
+}
+
+void
+Xconc(void)
+{
+ Word *lp=runq->argv->words;
+ Word *rp=runq->argv->next->words;
+ Word *vp=runq->argv->next->next->words;
+ int lc=count(lp), rc=count(rp);
+
+ if(lc!=0 || rc!=0){
+ if(lc==0 || rc==0){
+ Xerror("null list in concatenation");
+ return;
+ }
+ if(lc!=1 && rc!=1 && lc!=rc){
+ Xerror("mismatched list lengths in concatenation");
+ return;
+ }
+ vp=conclist(lp, rp, vp);
+ }
+ poplist();
+ poplist();
+ runq->argv->words=vp;
+}
+
+void
+Xcount(void)
+{
+ Word *a;
+ char *s, *t;
+ int n;
+ char num[12];
+
+ if(count(runq->argv->words)!=1){
+ Xerror("variable name not singleton!");
+ return;
+ }
+ s=runq->argv->words->word;
+ deglob(s);
+ n=0;
+ for(t=s;'0'<=*t && *t<='9';t++) n=n*10+*t-'0';
+ if(n==0 || *t){
+ a=vlook(s)->val;
+ sprint(num, "%d", count(a));
+ }
+ else{
+ a=vlook("*")->val;
+ sprint(num, "%d", a && 1<=n && n<=count(a)?1:0);
+ }
+ poplist();
+ pushword(num);
+}
+
+void
+Xdelfn(void)
+{
+ Var *v;
+ Word *a;
+
+ for(a=runq->argv->words;a;a=a->next){
+ v=gvlook(a->word);
+ if(v->fn)
+ codefree(v->fn);
+ v->fn=0;
+ v->fnchanged=1;
+ }
+ poplist();
+}
+
+void
+Xdelhere(void)
+{
+ Var *v;
+ Word *a;
+
+ for(a=runq->argv->words;a;a=a->next){
+ v=gvlook(a->word);
+ if(v->fn) codefree(v->fn);
+ v->fn=0;
+ v->fnchanged=1;
+ }
+ poplist();
+}
+
+void
+Xdol(void)
+{
+ Word *a, *star;
+ char *s, *t;
+ int n;
+
+ if(count(runq->argv->words)!=1){
+ Xerror("variable name not singleton!");
+ return;
+ }
+ s=runq->argv->words->word;
+ deglob(s);
+ n=0;
+ for(t=s;'0'<=*t && *t<='9';t++) n=n*10+*t-'0';
+ a=runq->argv->next->words;
+ if(n==0 || *t)
+ a=copywords(vlook(s)->val, a);
+ else{
+ star=vlook("*")->val;
+ if(star && 1<=n && n<=count(star)){
+ while(--n) star=star->next;
+ a=newword(star->word, a);
+ }
+ }
+ poplist();
+ runq->argv->words=a;
+}
+
+void
+Xdup(void)
+{
+ pushredir(RDUP, runq->code[runq->pc].i, runq->code[runq->pc+1].i);
+ runq->pc+=2;
+}
+
+void
+Xeflag(void)
+{
+ if(eflagok && !truestatus())
+ Xexit();
+}
+
+void
+Xexit(void)
+{
+ Var *trapreq;
+ Word *starval;
+ char *c;
+ static int beenhere=0;
+
+ if(truestatus())
+ c = "";
+ else
+ c = getstatus();
+
+ if(flag['S'] || beenhere)
+ exits(c);
+
+ trapreq=vlook("sigexit");
+ if(trapreq->fn){
+ beenhere=1;
+ --runq->pc;
+ starval=vlook("*")->val;
+ start(trapreq->fn, trapreq->pc, (Var*)0);
+ runq->local=newvar(strdup("*"), runq->local);
+ runq->local->val=copywords(starval, (Word*)0);
+ runq->local->changed=1;
+ runq->redir=runq->startredir=0;
+ }
+
+ exits(c);
+}
+
+void
+Xfalse(void)
+{
+ if(truestatus())
+ runq->pc=runq->code[runq->pc].i;
+ else
+ runq->pc++;
+}
+
+void
+Xfor(void)
+{
+ if(runq->argv->words==0) {
+ poplist();
+ runq->pc=runq->code[runq->pc].i;
+ } else {
+ freelist(runq->local->val);
+ runq->local->val=runq->argv->words;
+ runq->local->changed=1;
+ runq->argv->words=runq->argv->words->next;
+ runq->local->val->next=0;
+ runq->pc++;
+ }
+}
+
+void
+Xfn(void)
+{
+ Var *v;
+ Word *a;
+ int end;
+
+ end=runq->code[runq->pc].i;
+ for(a=runq->argv->words;a;a=a->next){
+ v=gvlook(a->word);
+ if(v->fn)
+ codefree(v->fn);
+ v->fn=codecopy(runq->code);
+ v->pc=runq->pc+2;
+ v->fnchanged=1;
+ }
+ runq->pc=end;
+ poplist();
+}
+
+void
+Xglob(void)
+{
+ globlist();
+}
+
+void
+Xif(void)
+{
+ ifnot=1;
+ if(truestatus()) runq->pc++;
+ else runq->pc=runq->code[runq->pc].i;
+}
+
+void
+Xifnot(void)
+{
+ if(ifnot)
+ runq->pc++;
+ else
+ runq->pc=runq->code[runq->pc].i;
+}
+
+void
+Xjump(void)
+{
+ runq->pc=runq->code[runq->pc].i;
+}
+
+
+void
+Xlocal(void)
+{
+ if(count(runq->argv->words)!=1){
+ Xerror("variable name must be singleton\n");
+ return;
+ }
+ deglob(runq->argv->words->word);
+ runq->local=newvar(strdup(runq->argv->words->word), runq->local);
+ runq->local->val=copywords(runq->argv->next->words, 0);
+ runq->local->changed=1;
+ poplist();
+ poplist();
+}
+
+
+void
+Xmark(void)
+{
+ pushlist();
+}
+
+void
+Xmatch(void)
+{
+ Word *p;
+ char *subject;
+
+ subject=list2str(runq->argv->words);
+ setstatus("no match");
+ for(p=runq->argv->next->words;p;p=p->next) {
+ if(match(subject, p->word, '\0')){
+ setstatus("");
+ break;
+ }
+ }
+ free(subject);
+ poplist();
+ poplist();
+}
+
+void
+Xpipe(void)
+{
+ Thread *p=runq;
+ int pc=p->pc, pid;
+ int lfd=p->code[pc].i;
+ int rfd=p->code[pc+1].i;
+ int pfd[2];
+ char **argv;
+
+ if(pipe(pfd)<0){
+ Xperror("can't get pipe");
+ return;
+ }
+
+ updenv();
+
+ argv = rcargv(runq->code[pc+2].s);
+ pid = proc(argv, 0, pfd[1], 2);
+ free(argv);
+ close(pfd[1]);
+
+ if(pid == 0) {
+ Xerror("proc failed");
+ close(pfd[0]);
+ return;
+ }
+
+ start(p->code, pc+4, runq->local);
+ pushredir(ROPEN, pfd[0], rfd);
+ p->pc=p->code[pc+3].i;
+ p->pid=pid;
+}
+
+void
+Xpipefd(void)
+{
+ fatal("Xpipefd");
+}
+
+void
+Xpipewait(void)
+{
+ char status[NSTATUS+1];
+ if(runq->pid==-1)
+ setstatus(concstatus(runq->status, getstatus()));
+ else{
+ strncpy(status, getstatus(), NSTATUS);
+ status[NSTATUS]='\0';
+ waitfor(runq->pid);
+ runq->pid=-1;
+ setstatus(concstatus(getstatus(), status));
+ }
+}
+
+void
+Xpopm(void)
+{
+ poplist();
+}
+
+void
+Xpopredir(void)
+{
+ Redir *rp=runq->redir;
+
+ if(rp==0)
+ panic("turfredir null!", 0);
+ runq->redir=rp->next;
+ if(rp->type==ROPEN)
+ close(rp->from);
+ free((char *)rp);
+}
+
+void
+Xqdol(void)
+{
+ Word *a, *p;
+ char *s;
+ int n;
+
+ if(count(runq->argv->words)!=1){
+ Xerror("variable name not singleton!");
+ return;
+ }
+ s=runq->argv->words->word;
+ deglob(s);
+ a=vlook(s)->val;
+ poplist();
+ n=count(a);
+ if(n==0){
+ pushword("");
+ return;
+ }
+ for(p=a;p;p=p->next) n+=strlen(p->word);
+ s=malloc(n);
+ if(a){
+ strcpy(s, a->word);
+ for(p=a->next;p;p=p->next){
+ strcat(s, " ");
+ strcat(s, p->word);
+ }
+ }
+ else
+ s[0]='\0';
+ pushword(s);
+ free(s);
+}
+
+void
+Xrdcmds(void)
+{
+ Thread *p=runq;
+ Word *prompt;
+
+ flush(err);
+ nerror=0;
+ if(flag['s'] && !truestatus())
+ pfmt(err, "status=%v\n", vlook("status")->val);
+ if(runq->iflag){
+ prompt=vlook("prompt")->val;
+ if(prompt)
+ promptstr=prompt->word;
+ else
+ promptstr="% ";
+ }
+ interrupted=0;
+ if(yyparse()) {
+ if(!p->iflag || p->eof /* && !Eintr() */) {
+ if(p->cmdfile)
+ free(p->cmdfile);
+ closeio(p->cmdfd);
+ Xreturn(); /* should this be omitted? */
+ } else {
+ if(interrupted){
+ pchr(err, '\n');
+ p->eof=0;
+ }
+ --p->pc; /* go back for next command */
+ }
+ } else {
+ --p->pc; /* re-execute Xrdcmds after codebuf runs */
+ start(codebuf, 1, runq->local);
+ }
+ freenodes();
+}
+
+void
+Xread(void)
+{
+ char *file;
+ int f;
+
+ switch(count(runq->argv->words)){
+ default: Xerror("< requires singleton\n"); return;
+ case 0: Xerror("< requires file\n"); return;
+ case 1: break;
+ }
+ file=runq->argv->words->word;
+ if((f=open(file, 0))<0){
+ Xperror(file);
+ return;
+ }
+ pushredir(ROPEN, f, runq->code[runq->pc].i);
+ runq->pc++;
+ poplist();
+}
+
+void
+Xreturn(void)
+{
+ Thread *p=runq;
+
+ turfredir();
+ while(p->argv)
+ poplist();
+ codefree(p->code);
+ runq=p->ret;
+ free(p);
+ if(runq==0)
+ exits(truestatus()?"":getstatus());
+}
+
+void
+Xsettrue(void)
+{
+ setstatus("");
+}
+
+
+void
+Xsub(void)
+{
+ Word *a, *v;
+ char *s;
+ if(count(runq->argv->next->words)!=1){
+ Xerror("variable name not singleton!");
+ return;
+ }
+ s=runq->argv->next->words->word;
+ deglob(s);
+ a=runq->argv->next->next->words;
+ v=vlook(s)->val;
+ a=subwords(v, count(v), runq->argv->words, a);
+ poplist();
+ poplist();
+ runq->argv->words=a;
+}
+
+void
+Xsubshell(void)
+{
+ char **argv;
+ uint pid;
+
+ updenv();
+
+ argv = rcargv(runq->code[runq->pc].s);
+ pid = proc(argv, -1, 1, 2);
+ free(argv);
+
+ if(pid == 0) {
+ Xerror("proc failed");
+ return;
+ }
+
+ waitfor(pid);
+ runq->pc++;
+}
+
+void
+Xtrue(void)
+{
+ if(truestatus())
+ runq->pc++;
+ else
+ runq->pc=runq->code[runq->pc].i;
+}
+
+void
+Xunlocal(void)
+{
+ Var *v=runq->local, *hid;
+
+ if(v==0)
+ panic("Xunlocal: no locals!", 0);
+ runq->local=v->next;
+ hid=vlook(v->name);
+ hid->changed=1;
+ free(v->name);
+ freewords(v->val);
+ free(v);
+}
+
+void
+Xwastrue(void)
+{
+ ifnot=0;
+}
+
+void
+Xwrite(void)
+{
+ char *file;
+ int f;
+
+ switch(count(runq->argv->words)){
+ default: Xerror("> requires singleton\n"); return;
+ case 0: Xerror("> requires file\n"); return;
+ case 1: break;
+ }
+ file=runq->argv->words->word;
+ if((f = create(file, 1, 0666))<0){
+ Xperror(file);
+ return;
+ }
+ pushredir(ROPEN, f, runq->code[runq->pc].i);
+ runq->pc++;
+ poplist();
+}
+
+void
+Xword(void)
+{
+ pushword(runq->code[runq->pc++].s);
+}
+
+void
+Xerror(char *s)
+{
+ pfmt(err, "rcsh: %s\n", s);
+ flush(err);
+ while(!runq->iflag)
+ Xreturn();
+}
+
+void
+Xperror(char *s)
+{
+ pfmt(err, "rcsh: %s: %r\n", s);
+ flush(err);
+ while(!runq->iflag)
+ Xreturn();
+}
diff --git a/utils/rcsh/glob.c b/utils/rcsh/glob.c
new file mode 100644
index 00000000..7640eeb1
--- /dev/null
+++ b/utils/rcsh/glob.c
@@ -0,0 +1,286 @@
+#include "rc.h"
+
+char *globname;
+Word *globv;
+
+int matchfn(char *s, char *p);
+int globsize(char *p);
+
+/*
+ * delete all the GLOB marks from s, in place
+ */
+void
+deglob(char *s)
+{
+ char *t=s;
+ do{
+ if(*t==GLOB) t++;
+ *s++=*t;
+ }while(*t++);
+}
+
+int
+globcmp(const void *s, const void *t)
+{
+ return strcmp(*(char**)s, *(char**)t);
+}
+
+void
+globsort(Word *left, Word *right)
+{
+ char **list;
+ Word *a;
+ int n=0;
+ for(a=left;a!=right;a=a->next) n++;
+ list=(char **)malloc(n*sizeof(char *));
+ for(a=left,n=0;a!=right;a=a->next,n++) list[n]=a->word;
+ qsort((void*)list, (size_t)n, sizeof(char*), globcmp);
+ for(a=left,n=0;a!=right;a=a->next,n++) a->word=list[n];
+ free(list);
+}
+
+/*
+ * Push names prefixed by globname and suffixed by a match of p onto the astack.
+ * namep points to the end of the prefix in globname.
+ */
+void
+globdir(char *p, char *namep)
+{
+ char *t, *q, *newp;
+ Direntry *dp, *dq;
+ Dir *dir;
+
+ /* scan the pattern looking for a component with a metacharacter in it */
+ if(*p=='\0'){
+ globv=newword(globname, globv);
+ return;
+ }
+ t=namep;
+ newp=p;
+ while(*newp){
+ if(*newp==GLOB)
+ break;
+ *t=*newp++;
+ if(*t++=='/'){
+ namep=t;
+ p=newp;
+ }
+ }
+ /* If we ran out of pattern, append the name if accessible */
+ if(*newp=='\0'){
+ *t='\0';
+ if(access(globname, 0)==0)
+ globv=newword(globname, globv);
+ return;
+ }
+ /* read the directory and recur for any entry that matches */
+ *namep='\0';
+ t = globname;
+ if(*t == 0)
+ t = ".";
+ q = strdup(t);
+ if (q[strlen(q)-1] == '/')
+ q[strlen(q)-1] = 0;
+ if((dir=dirstat(q)) == nil || !(dir->mode&0x80000000)){
+ free(dir);
+ return;
+ }
+ free(dir);
+ dq = readdirect(q);
+ if(dq == 0){
+ fprint(2, "could not open %s: %r\n", q);
+ return;
+ }
+ while(*newp!='/' && *newp!='\0')
+ newp++;
+ for(dp = dq;dp->name; dp++){
+ strcpy(namep, dp->name);
+ if(matchfn(namep, p))
+ globdir(newp, namep+strlen(namep));
+ free(dp->name);
+ }
+ free(dq);
+}
+
+/*
+ * Push all file names matched by p on the current thread's stack.
+ * If there are no matches, the list consists of p.
+ */
+void
+glob(char *p)
+{
+ Word *svglobv=globv;
+ int globlen=globsize(p);
+
+ if(globlen == 0){
+ deglob(p);
+ globv=newword(p, globv);
+ return;
+ }
+ globname=malloc(globlen);
+ globname[0]='\0';
+ globdir(p, globname);
+ free(globname);
+ if(svglobv==globv){
+ deglob(p);
+ globv=newword(p, globv);
+ }
+ else
+ globsort(globv, svglobv);
+}
+
+
+/*
+ * Do p and q point at equal utf codes
+ */
+int
+equtf(char *p, char *q)
+{
+ if(*p!=*q)
+ return 0;
+ if(twobyte(*p)) return p[1]==q[1];
+ if(threebyte(*p)){
+ if(p[1]!=q[1]) return 0;
+ if(p[1]=='\0') return 1; /* broken code at end of string! */
+ return p[2]==q[2];
+ }
+ return 1;
+}
+
+/*
+ * Return a pointer to the next utf code in the string,
+ * not jumping past nuls in broken utf codes!
+ */
+char *
+nextutf(char *p)
+{
+ if(twobyte(*p))
+ return p[1]=='\0'?p+1:p+2;
+ if(threebyte(*p))
+ return p[1]=='\0'?p+1:p[2]=='\0'?p+2:p+3;
+ return p+1;
+}
+
+/*
+ * Convert the utf code at *p to a unicode value
+ */
+int
+unicode(char *p)
+{
+ int u=*p&0xff;
+ if(twobyte(u))
+ return ((u&0x1f)<<6)|(p[1]&0x3f);
+ if(threebyte(u))
+ return (u<<12)|((p[1]&0x3f)<<6)|(p[2]&0x3f);
+ return u;
+}
+
+/*
+ * Does the string s match the pattern p
+ * . and .. are only matched by patterns starting with .
+ * * matches any sequence of characters
+ * ? matches any single character
+ * [...] matches the enclosed list of characters
+ */
+int
+matchfn(char *s, char *p)
+{
+ if(s[0]=='.' && (s[1]=='\0' || s[1]=='.' && s[2]=='\0') && p[0]!='.')
+ return 0;
+ return match(s, p, '/');
+}
+
+int
+match(char *s, char *p, int stop)
+{
+ int compl, hit, lo, hi, t, c;
+
+ for(;*p!=stop && *p!='\0';s=nextutf(s),p=nextutf(p)) {
+ if(*p!=GLOB){
+ if(!equtf(p, s)) return 0;
+ }
+ else switch(*++p){
+ case GLOB:
+ if(*s!=GLOB) return 0;
+ break;
+ case '*':
+ for(;;){
+ if(match(s, nextutf(p), stop)) return 1;
+ if(!*s) break;
+ s=nextutf(s);
+ }
+ return 0;
+ case '?':
+ if(*s=='\0') return 0;
+ break;
+ case '[':
+ if(*s=='\0') return 0;
+ c=unicode(s);
+ p++;
+ compl=*p=='~';
+ if(compl) p++;
+ hit=0;
+ while(*p!=']'){
+ if(*p=='\0') return 0; /* syntax error */
+ lo=unicode(p);
+ p=nextutf(p);
+ if(*p!='-') hi=lo;
+ else{
+ p++;
+ if(*p=='\0') return 0; /* syntax error */
+ hi=unicode(p);
+ p=nextutf(p);
+ if(hi<lo){ t=lo; lo=hi; hi=t; }
+ }
+ if(lo<=c && c<=hi) hit=1;
+ }
+ if(compl) hit=!hit;
+ if(!hit) return 0;
+ break;
+ }
+ }
+ return *s=='\0';
+}
+
+void
+globlist1(Word *gl)
+{
+ if(gl){
+ globlist1(gl->next);
+ glob(gl->word);
+ }
+}
+
+void
+globlist(void)
+{
+ Word *a;
+
+ globv=0;
+ globlist1(runq->argv->words);
+ poplist();
+ pushlist();
+ if(globv){
+ for(a=globv;a->next;a=a->next);
+ a->next=runq->argv->words;
+ runq->argv->words=globv;
+ }
+}
+
+#define NDIR 128
+int
+globsize(char *p)
+{
+ ulong isglob=0, globlen=NDIR+1;
+
+ for(;*p;p++){
+ if(*p==GLOB){
+ p++;
+ if(*p!=GLOB) isglob++;
+ globlen+=*p=='*'?NDIR:1;
+ }
+ else
+ globlen++;
+ }
+ return isglob?globlen:0;
+}
diff --git a/utils/rcsh/here.c b/utils/rcsh/here.c
new file mode 100644
index 00000000..bd1f79cc
--- /dev/null
+++ b/utils/rcsh/here.c
@@ -0,0 +1,145 @@
+#include "rc.h"
+#include "y.tab.h"
+
+Here *here, **ehere;
+int ser;
+
+char tmp[]="/tmp/here0000.0000";
+char hex[]="0123456789abcdef";
+
+void psubst(Io*, char*);
+void pstrs(Io*, Word*);
+
+void hexnum(char *p, int n)
+{
+ *p++=hex[(n>>12)&0xF];
+ *p++=hex[(n>>8)&0xF];
+ *p++=hex[(n>>4)&0xF];
+ *p=hex[n&0xF];
+}
+
+Tree *
+heredoc(Tree *tag)
+{
+ Here *h=new(Here);
+
+ if(tag->type!=WORD)
+ yyerror("Bad here tag");
+ h->next=0;
+ if(here)
+ *ehere=h;
+ else
+ here=h;
+ ehere=&h->next;
+ h->tag=tag;
+ hexnum(&tmp[9], getpid());
+ hexnum(&tmp[14], ser++);
+ h->name=strdup(tmp);
+ return token(tmp, WORD);
+}
+/*
+ * bug: lines longer than NLINE get split -- this can cause spurious
+ * missubstitution, or a misrecognized EOF marker.
+ */
+#define NLINE 4096
+void
+readhere(void)
+{
+ Here *h, *nexth;
+ Io *f;
+ char *s, *tag;
+ int c, subst;
+ char line[NLINE+1];
+
+ for(h=here;h;h=nexth){
+ subst=!h->tag->quoted;
+ tag=h->tag->str;
+ c=create(h->name, 1, 0666);
+ if(c<0) yyerror("can't create here document");
+ f=openfd(c);
+ s=line;
+ pprompt();
+ while((c=rchr(runq->cmdfd))!=EOF){
+ if(c=='\n' || s==&line[NLINE]){
+ *s='\0';
+ if(strcmp(line, tag)==0) break;
+ if(subst) psubst(f, line);
+ else pstr(f, line);
+ s=line;
+ if(c=='\n'){
+ pprompt();
+ pchr(f, c);
+ }
+ else *s++=c;
+ }
+ else *s++=c;
+ }
+ flush(f);
+ closeio(f);
+ cleanhere(h->name);
+ nexth=h->next;
+ free(h);
+ }
+ here=0;
+ doprompt=1;
+}
+
+void
+psubst(Io *f, char *s)
+{
+ char *t, *u;
+ int savec, n;
+ Word *star;
+
+ while(*s){
+ if(*s!='$'){
+ if(0xa0<=(*s&0xff) && (*s&0xff)<=0xf5){
+ pchr(f, *s++);
+ if(*s=='\0') break;
+ }
+ else if(0xf6<=(*s&0xff) && (*s&0xff)<=0xf7){
+ pchr(f, *s++);
+ if(*s=='\0') break;
+ pchr(f, *s++);
+ if(*s=='\0') break;
+ }
+ pchr(f, *s++);
+ }
+ else{
+ t=++s;
+ if(*t=='$') pchr(f, *t++);
+ else{
+ while(*t && idchr(*t)) t++;
+ savec=*t;
+ *t='\0';
+ n=0;
+ for(u=s;*u && '0'<=*u && *u<='9';u++) n=n*10+*u-'0';
+ if(n && *u=='\0'){
+ star=vlook("*")->val;
+ if(star && 1<=n && n<=count(star)){
+ while(--n) star=star->next;
+ pstr(f, star->word);
+ }
+ }
+ else
+ pstrs(f, vlook(s)->val);
+ *t=savec;
+ if(savec=='^') t++;
+ }
+ s=t;
+ }
+ }
+}
+
+void
+pstrs(Io *f, Word *a)
+{
+ if(a){
+ while(a->next && a->next->word){
+ pstr(f, a->word);
+ pchr(f, ' ');
+ a=a->next;
+ }
+ pstr(f, a->word);
+ }
+}
diff --git a/utils/rcsh/io.c b/utils/rcsh/io.c
new file mode 100644
index 00000000..6ac2555e
--- /dev/null
+++ b/utils/rcsh/io.c
@@ -0,0 +1,238 @@
+#include "rc.h"
+
+int pfmtnest=0;
+
+void pdec(Io*, long);
+void poct(Io*, ulong);
+void phex(Io*, long);
+void pquo(Io*, char*);
+void pwrd(Io*, char*);
+void pcmd(Io*, Tree*);
+void pval(Io*, Word*);
+
+void
+pfmt(Io *f, char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ pfmtnest++;
+ for(;*fmt;fmt++) {
+ if(*fmt!='%') pchr(f, *fmt);
+ else switch(*++fmt){
+ case '\0': va_end(ap); return;
+ case 'c': pchr(f, va_arg(ap, int)); break;
+ case 'd': pdec(f, va_arg(ap, int)); break;
+ case 'o': poct(f, va_arg(ap, unsigned)); break;
+ case 'p': phex(f, (long)va_arg(ap, char *)); break; /*unportable*/
+ case 'Q': pquo(f, va_arg(ap, char *)); break;
+ case 'q': pwrd(f, va_arg(ap, char *)); break;
+ case 'r': perr(f); break;
+ case 's': pstr(f, va_arg(ap, char *)); break;
+ case 't': pcmd(f, va_arg(ap, Tree *)); break;
+ case 'v': pval(f, va_arg(ap, Word *)); break;
+ default: pchr(f, *fmt); break;
+ }
+ }
+ va_end(ap);
+ if(--pfmtnest==0) flush(f);
+}
+
+void
+perr(Io *f)
+{
+ char err[ERRMAX];
+
+ err[0] = 0;
+ errstr(err, sizeof err);
+ pstr(f, err);
+ errstr(err, sizeof err);
+}
+
+void
+pquo(Io *f, char *s)
+{
+ pchr(f, '\'');
+ for(;*s;s++)
+ if(*s=='\'') pfmt(f, "''");
+ else pchr(f, *s);
+ pchr(f, '\'');
+}
+
+void
+pwrd(Io *f, char *s)
+{
+ char *t;
+ for(t=s;*t;t++)
+ if(!wordchr(*t))
+ break;
+ if(t==s || *t)
+ pquo(f, s);
+ else
+ pstr(f, s);
+}
+
+void
+phex(Io *f, long p)
+{
+ int n;
+ for(n=28;n>=0;n-=4) pchr(f, "0123456789ABCDEF"[(p>>n)&0xF]);
+}
+
+void
+pstr(Io *f, char *s)
+{
+ if(s==0)
+ s="(null)";
+ while(*s)
+ pchr(f, *s++);
+}
+
+void
+pdec(Io *f, long n)
+{
+ if(n<0){
+ n=-n;
+ if(n>=0){
+ pchr(f, '-');
+ pdec(f, n);
+ return;
+ }
+ /* n is two's complement minimum integer */
+ n=1-n;
+ pchr(f, '-');
+ pdec(f, n/10);
+ pchr(f, n%10+'1');
+ return;
+ }
+ if(n>9) pdec(f, n/10);
+ pchr(f, n%10+'0');
+}
+
+void
+poct(Io *f, ulong n)
+{
+ if(n>7) poct(f, n>>3);
+ pchr(f, (n&7)+'0');
+}
+
+void
+pval(Io *f, Word *a)
+{
+ if(a){
+ while(a->next && a->next->word){
+ pwrd(f, a->word);
+ pchr(f, ' ');
+ a=a->next;
+ }
+ pwrd(f, a->word);
+ }
+}
+
+int
+fullbuf(Io *f, int c)
+{
+ flush(f);
+ return *f->bufp++=c;
+}
+
+void
+flush(Io *f)
+{
+ int n;
+ char *s;
+ if(f->strp){
+ n=f->ebuf-f->strp;
+ f->strp=realloc(f->strp, n+101);
+ if(f->strp==0)
+ panic("Can't realloc %d bytes in flush!", n+101);
+ f->bufp=f->strp+n;
+ f->ebuf=f->bufp+100;
+ for(s=f->bufp;s<=f->ebuf;s++) *s='\0';
+ }
+ else{
+ n=f->bufp-f->buf;
+ if(n && write(f->fd, f->buf, n) < 0){
+/* write(3, "Write error\n", 12);
+ if(ntrap.ref)
+ dotrap();
+*/
+ }
+ f->bufp=f->buf;
+ f->ebuf=f->buf+NBUF;
+ }
+}
+
+Io *
+openfd(int fd)
+{
+ Io *f = new(Io);
+ f->fd = fd;
+ f->bufp = f->ebuf = f->buf;
+ f->strp = 0;
+ return f;
+}
+
+Io *
+openstr(void)
+{
+ Io *f=new(struct Io);
+ char *s;
+ f->fd=-1;
+ f->bufp=f->strp=malloc(101);
+ f->ebuf=f->bufp+100;
+ for(s=f->bufp;s<=f->ebuf;s++)
+ *s='\0';
+ return f;
+}
+
+/*
+ * Open a corebuffer to read. EOF occurs after reading len
+ * characters from buf.
+ */
+Io *
+opencore(char *s, int len)
+{
+ Io *f;
+ char *buf;
+
+ f = new(Io);
+ buf = malloc(len);
+ f->fd = -1;
+ f->bufp = f->strp=buf;
+ f->ebuf = buf+len;
+ memmove(buf, s, len);
+
+ return f;
+}
+
+void
+rewind(Io *io)
+{
+ if(io->fd==-1) {
+ io->bufp = io->strp;
+ } else {
+ io->bufp = io->ebuf = io->buf;
+ seek(io->fd, 0L, 0);
+ }
+}
+
+void
+closeio(Io *io)
+{
+ if(io->fd>=0)
+ close(io->fd);
+ if(io->strp)
+ free(io->strp);
+ free(io);
+}
+
+int
+emptybuf(Io *f)
+{
+ int n;
+ if(f->fd==-1 || (n=read(f->fd, f->buf, NBUF))<=0) return EOF;
+ f->bufp=f->buf;
+ f->ebuf=f->buf+n;
+ return *f->bufp++&0xff;
+}
diff --git a/utils/rcsh/lex.c b/utils/rcsh/lex.c
new file mode 100644
index 00000000..d31a94d8
--- /dev/null
+++ b/utils/rcsh/lex.c
@@ -0,0 +1,398 @@
+#include "rc.h"
+#include "y.tab.h"
+
+#define NTOK 8192
+
+int getnext(void);
+
+int future=EOF;
+int doprompt=1;
+int inquote;
+int nerror;
+char *promptstr;
+
+char tok[NTOK];
+
+int lastdol; /* was the last token read '$' or '$#' or '"'? */
+int lastword; /* was the last token read a word or compound word terminator? */
+int lastc;
+
+void
+kinit(void)
+{
+ kenter(FOR, "for");
+ kenter(IN, "in");
+ kenter(WHILE, "while");
+ kenter(IF, "if");
+ kenter(NOT, "not");
+ kenter(TWIDDLE, "~");
+ kenter(BANG, "!");
+ kenter(SUBSHELL, "@");
+ kenter(SWITCH, "switch");
+ kenter(FN, "fn");
+}
+
+int
+wordchr(int c)
+{
+ return !strchr("\n \t\r#;&|^$=`'{}()<>", c) && c!=EOF;
+}
+
+int
+idchr(int c)
+{
+ /*
+ * Formerly:
+ * return 'a'<=c && c<='z' || 'A'<=c && c<='Z' || '0'<=c && c<='9'
+ * || c=='_' || c=='*';
+ */
+ return c>' ' && !strchr("!\"#$%&'()+,-./:;<=>?@[\\]^`{|}~", c);
+}
+
+/*
+ * Look ahead in the input stream
+ */
+int
+nextc(void)
+{
+ if(future==EOF)
+ future=getnext();
+ return future;
+}
+
+/*
+ * Consume the lookahead character.
+ */
+int
+advance(void)
+{
+ int c=nextc();
+ lastc=future;
+ future=EOF;
+ return c;
+}
+
+/*
+ * read a character from the input stream
+ */
+int
+getnext(void)
+{
+ register int c;
+ static peekc=EOF;
+ if(peekc!=EOF){
+ c=peekc;
+ peekc=EOF;
+ return c;
+ }
+ if(runq->eof) return EOF;
+ if(doprompt)
+ pprompt();
+ c=rchr(runq->cmdfd);
+ if(!inquote && c=='\\'){
+ c=rchr(runq->cmdfd);
+ if(c=='\n'){
+ doprompt=1;
+ c=' ';
+ }
+ else{
+ peekc=c;
+ c='\\';
+ }
+ }
+ doprompt=doprompt || c=='\n' || c==EOF;
+ if(c==EOF) runq->eof++;
+ else if(flag['V'] || ndot>=2 && flag['v'])
+ pchr(err, c);
+ return c;
+}
+
+void
+pprompt(void)
+{
+ Var *prompt;
+
+ if(runq->iflag){
+ pstr(err, promptstr);
+ flush(err);
+ prompt=vlook("prompt");
+ if(prompt->val && prompt->val->next)
+ promptstr=prompt->val->next->word;
+ else
+ promptstr="\t";
+ }
+ runq->lineno++;
+ doprompt=0;
+}
+
+void
+skipwhite(void)
+{
+ int c;
+ for(;;){
+ c=nextc();
+ if(c=='#'){ /* Why did this used to be if(!inquote && c=='#') ?? */
+ for(;;){
+ c=nextc();
+ if(c=='\n' || c==EOF) break;
+ advance();
+ }
+ }
+ if(c==' ' || c=='\t' || c=='\r') advance();
+ else return;
+ }
+}
+
+void
+skipnl(void)
+{
+ int c;
+ for(;;){
+ skipwhite();
+ c=nextc();
+ if(c!='\n') return;
+ advance();
+ }
+}
+
+int
+nextis(int c)
+{
+ if(nextc()==c){
+ advance();
+ return 1;
+ }
+ return 0;
+}
+
+char *
+addtok(char *p, int val)
+{
+ if(p==0) return 0;
+ if(p==&tok[NTOK]){
+ *p=0;
+ yyerror("token buffer too short");
+ return 0;
+ }
+ *p++=val;
+ return p;
+}
+
+char *
+addutf(char *p, int c)
+{
+ p=addtok(p, c);
+ if(twobyte(c)) /* 2-byte escape */
+ return addtok(p, advance());
+ if(threebyte(c)){ /* 3-byte escape */
+ p=addtok(p, advance());
+ return addtok(p, advance());
+ }
+ return p;
+}
+
+int
+yylex(void)
+{
+ int c, d=nextc();
+ char *w=tok;
+ Tree *t;
+
+ yylval.tree=0;
+ /*
+ * Embarassing sneakiness: if the last token read was a quoted or unquoted
+ * WORD then we alter the meaning of what follows. If the next character
+ * is `(', we return SUB (a subscript paren) and consume the `('. Otherwise,
+ * if the next character is the first character of a simple or compound word,
+ * we insert a `^' before it.
+ */
+ if(lastword){
+ lastword=0;
+ if(d=='('){
+ advance();
+ strcpy(tok, "( [SUB]");
+ return SUB;
+ }
+ if(wordchr(d) || d=='\'' || d=='`' || d=='$' || d=='"'){
+ strcpy(tok, "^");
+ return '^';
+ }
+ }
+ inquote=0;
+ skipwhite();
+ switch(c=advance()){
+ case EOF:
+ lastdol=0;
+ strcpy(tok, "EOF");
+ return EOF;
+ case '$':
+ lastdol=1;
+ if(nextis('#')){
+ strcpy(tok, "$#");
+ return COUNT;
+ }
+ if(nextis('"')){
+ strcpy(tok, "$\"");
+ return '"';
+ }
+ strcpy(tok, "$");
+ return '$';
+ case '&':
+ lastdol=0;
+ if(nextis('&')){
+ skipnl();
+ strcpy(tok, "&&");
+ return ANDAND;
+ }
+ strcpy(tok, "&");
+ return '&';
+ case '|':
+ lastdol=0;
+ if(nextis(c)){
+ skipnl();
+ strcpy(tok, "||");
+ return OROR;
+ }
+ case '<':
+ case '>':
+ lastdol=0;
+ /*
+ * funny redirection tokens:
+ * redir: arrow | arrow '[' fd ']'
+ * arrow: '<' | '<<' | '>' | '>>' | '|'
+ * fd: digit | digit '=' | digit '=' digit
+ * digit: '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'
+ * some possibilities are nonsensical and get a message.
+ */
+ *w++=c;
+ t=newtree();
+ switch(c){
+ case '|':
+ t->type=PIPE;
+ t->fd0=1;
+ t->fd1=0;
+ break;
+ case '>':
+ t->type=REDIR;
+ if(nextis(c)){
+ t->rtype=APPEND;
+ *w++=c;
+ }
+ else t->rtype=WRITE;
+ t->fd0=1;
+ break;
+ case '<':
+ t->type=REDIR;
+ if(nextis(c)){
+ t->rtype=HERE;
+ *w++=c;
+ }
+ else t->rtype=READ;
+ t->fd0=0;
+ break;
+ }
+ if(nextis('[')){
+ *w++='[';
+ c=advance();
+ *w++=c;
+ if(c<'0' || '9'<c){
+ RedirErr:
+ *w=0;
+ yyerror(t->type==PIPE?"pipe syntax"
+ :"redirection syntax");
+ return EOF;
+ }
+ t->fd0=0;
+ do{
+ t->fd0=t->fd0*10+c-'0';
+ *w++=c;
+ c=advance();
+ }while('0'<=c && c<='9');
+ if(c=='='){
+ *w++='=';
+ if(t->type==REDIR)
+ t->type=DUP;
+ c=advance();
+ if('0'<=c && c<='9'){
+ t->rtype=DUPFD;
+ t->fd1=t->fd0;
+ t->fd0=0;
+ do{
+ t->fd0=t->fd0*10+c-'0';
+ *w++=c;
+ c=advance();
+ }while('0'<=c && c<='9');
+ }
+ else{
+ if(t->type==PIPE) goto RedirErr;
+ t->rtype=CLOSE;
+ }
+ }
+ if(c!=']' || t->type==DUP && (t->rtype==HERE || t->rtype==APPEND))
+ goto RedirErr;
+ *w++=']';
+ }
+ *w='\0';
+ yylval.tree=t;
+ if(t->type==PIPE) skipnl();
+ return t->type;
+ case '\'':
+ lastdol=0;
+ lastword=1;
+ inquote=1;
+ for(;;){
+ c=advance();
+ if(c==EOF) break;
+ if(c=='\''){
+ if(nextc()!='\'')
+ break;
+ advance();
+ }
+ w=addutf(w, c);
+ }
+ if(w!=0) *w='\0';
+ t=token(tok, WORD);
+ t->quoted=1;
+ yylval.tree=t;
+ return t->type;
+ }
+ if(!wordchr(c)){
+ lastdol=0;
+ tok[0]=c;
+ tok[1]='\0';
+ return c;
+ }
+ for(;;){
+ /* next line should have (char)c==GLOB, but ken's compiler is broken */
+ if(c=='*' || c=='[' || c=='?' || c==(unsigned char)GLOB)
+ w=addtok(w, GLOB);
+ w=addutf(w, c);
+ c=nextc();
+ if(lastdol?!idchr(c):!wordchr(c)) break;
+ advance();
+ }
+Out:
+ lastword=1;
+ lastdol=0;
+ if(w!=0) *w='\0';
+ t=klook(tok);
+ if(t->type!=WORD) lastword=0;
+ t->quoted=0;
+ yylval.tree=t;
+ return t->type;
+}
+
+void
+yyerror(char *m)
+{
+ pfmt(err, "rc: ");
+ if(runq->cmdfile) pfmt(err, "file %s: ", runq->cmdfile);
+ if(!runq->iflag) pfmt(err, "line %d: ", runq->lineno);
+ if(tok[0] && tok[0]!='\n') pfmt(err, "token %q: ", tok);
+ pfmt(err, "%s\n", m);
+ flush(err);
+ lastword=0;
+ lastdol=0;
+ while(lastc!='\n' && lastc!=EOF) advance();
+ nerror++;
+}
diff --git a/utils/rcsh/main.c b/utils/rcsh/main.c
new file mode 100644
index 00000000..93cd2951
--- /dev/null
+++ b/utils/rcsh/main.c
@@ -0,0 +1,228 @@
+#include "rc.h"
+
+int flag[256];
+Io *err;
+char *argv0;
+
+Thread *runq;
+int ndot;
+
+
+void
+main(int argc, char *argv[])
+{
+ int i;
+ Code bootstrap[17];
+ char *cflag, *cp;
+ char rcmain[200];
+ Var *infroot;
+ char **p;
+
+ cflag = 0;
+
+ /* default to interactive mode */
+ flag['i']++;
+
+ /* hack for DOS-style options, when rcsh started from MS-land */
+ for (p = argv+1; *p && **p == '/'; p++)
+ **p = '-';
+
+ argv0 = *argv;
+ ARGBEGIN{
+ default:
+ fprint(2, "usage: %s: [-seiIlvxr] [-c string] [file [args]]\n", argv0);
+ exits("usage");
+ case 'e': flag['e']++; break;
+ case 'c': cflag = ARGF(); break;
+ case 'i': flag['i']++; break;
+ case 'I': flag['i'] = 0; break;
+ case 'l': flag['l']++; break;
+ case 'r': flag['r']++; break;
+ case 's': flag['s']++; break;
+ case 'S': flag['S']++; break; /* sub shell */
+ case 'v': flag['v']++; break;
+ case 'V': flag['V']++; break;
+ case 'x': flag['x']++; break;
+ }ARGEND
+
+ err = openfd(2);
+
+ kinit();
+ vinit();
+
+ cp = ROOT;
+ if(0 && strlen(argv0))
+ sprint(rcmain, "%s/../lib/rcmain", argv0);
+ else{
+ infroot = vlook("ROOT");
+ if(infroot->val)
+ cp = infroot->val->word;
+ }
+ sprint(rcmain, "%s/utils/lib/rcmain", cp);
+
+ setvar("rcname", newword(argv0, 0));
+ if(cflag)
+ setvar("cflag", newword(cflag, 0));
+ else
+ setvar("cflag", 0);
+
+ /* bootstrap == . rcmain $* */
+ i=0;
+ bootstrap[i++].i=1;
+ bootstrap[i++].f=Xmark;
+ bootstrap[i++].f=Xword;
+ bootstrap[i++].s="*";
+ bootstrap[i++].f=Xassign;
+ bootstrap[i++].f=Xmark;
+ bootstrap[i++].f=Xmark;
+ bootstrap[i++].f=Xword;
+ bootstrap[i++].s="*";
+ bootstrap[i++].f=Xdol;
+ bootstrap[i++].f=Xword;
+ bootstrap[i++].s=rcmain;
+ bootstrap[i++].f=Xword;
+ bootstrap[i++].s=".";
+ bootstrap[i++].f=Xsimple;
+ bootstrap[i++].f=Xexit;
+ bootstrap[i].i=0;
+ start(bootstrap, 1, 0);
+ pushlist();
+ for(i=argc-1;i>=0;i--)
+ pushword(argv[i]);
+
+ for(;;){
+ if(flag['r']) pfnc(err, runq);
+ runq->pc++;
+ (*runq->code[runq->pc-1].f)();
+ if(ntrap.ref)
+ dotrap();
+ }
+}
+
+void
+panic(char *s, int n)
+{
+ pfmt(err, "rc: ");
+ pfmt(err, s, n);
+ pchr(err, '\n');
+ flush(err);
+ pfmt(err, "aborting\n");
+ flush(err);
+ exits("aborting");
+}
+
+void
+setstatus(char *s)
+{
+ setvar("status", newword(s, 0));
+}
+
+char *
+getstatus(void)
+{
+ Var *status=vlook("status");
+
+ return status->val?status->val->word:"";
+}
+
+int
+truestatus(void)
+{
+ char *s;
+ for(s=getstatus();*s;s++)
+ if(*s!='|' && *s!='0') return 0;
+ return 1;
+}
+
+char *
+concstatus(char *s, char *t)
+{
+ static char v[NSTATUS+1];
+ int n=strlen(s);
+ strncpy(v, s, NSTATUS);
+ if(n<NSTATUS){
+ v[n]='|';
+ strncpy(v+n+1, t, NSTATUS-n-1);
+ }
+ v[NSTATUS]='\0';
+ return v;
+}
+
+/*
+ * Start executing the given code at the given pc with the given redirection
+ */
+void
+start(Code *c, int pc, Var *local)
+{
+ Thread *p = new(Thread);
+
+ memset(p, 0, sizeof(Thread));
+ p->code = codecopy(c);
+ p->pc = pc;
+ if(runq) {
+ p->redir = runq->redir;
+ p->startredir = runq->redir;
+ }
+ p->local = local;
+ p->lineno = 1;
+ p->ret = runq;
+ runq=p;
+}
+
+void
+execcmds(Io *f)
+{
+ static Code rdcmds[4];
+ static int first=1;
+
+ if(first){
+ rdcmds[0].i=1;
+ rdcmds[1].f=Xrdcmds;
+ rdcmds[2].f=Xreturn;
+ first=0;
+ }
+ start(rdcmds, 1, runq->local);
+ runq->cmdfd=f;
+ runq->iflast=0;
+}
+
+void
+waitfor(uint pid)
+{
+ int e;
+ char estr[64];
+
+ e = procwait(pid);
+ if(e != 0) {
+ sprint(estr, "error code %d", e);
+ setstatus(estr);
+ } else
+ setstatus("");
+}
+
+char **
+procargv(char *s0, char *s1, char *s2, char *s3, Word *w)
+{
+ int n, i;
+ Word *p;
+ char **argv;
+
+ for(p=w,n=5; p; p=p->next,n++);
+ ;
+
+ argv = malloc(n*sizeof(char*));
+ i = 0;
+ if(s0)
+ argv[i++] = s0;
+ if(s1)
+ argv[i++] = s1;
+ if(s2)
+ argv[i++] = s2;
+ if(s3)
+ argv[i++] = s3;
+ for(p=w; p; p=p->next)
+ argv[i++] = p->word;
+ argv[i] = 0;
+ return argv;
+}
+
diff --git a/utils/rcsh/mkfile b/utils/rcsh/mkfile
new file mode 100644
index 00000000..b4c818cf
--- /dev/null
+++ b/utils/rcsh/mkfile
@@ -0,0 +1,53 @@
+<../../mkconfig
+
+#
+# this directory contains a stripped-down version of rc
+# it is only build for Windows Nt and Windows 95
+
+TARG=rcsh
+
+OFILES= code.$O\
+ exec.$O\
+ glob.$O\
+ here.$O\
+ io.$O\
+ lex.$O\
+ main.$O\
+ $TARGMODEL.$O\
+ pcmd.$O\
+ pfnc.$O\
+ simple.$O\
+ trap.$O\
+ tree.$O\
+ var.$O\
+ word.$O\
+ y.tab.$O\
+
+HFILES= rc.h\
+ y.tab.h\
+
+YFILES= syn.y
+
+LIBS=9
+
+BIN=$ROOT/$OBJDIR/bin
+
+<$ROOT/mkfiles/mkone-$SHELLTYPE
+
+CFLAGS= $CFLAGS '-DROOT="'$ROOT'"'
+
+$BIN/%:Q: $O.out
+ echo $TARG must be installed manually on Windows systems
+ echo use: cp $O.out $target
+ cp $O.out $target
+
+install:V: $ROOT/utils/lib/rcmain
+
+$ROOT/utils/lib/rcmain:Q: rcmain
+ echo $prereq must be installed manually on Windows systems
+ echo use: cp $prereq $target
+ cp $prereq $target
+
+Posix.c Inferno.c:QV:
+ echo $TARG is only built on Windows NT or Windows 95
+ exit 1
diff --git a/utils/rcsh/pcmd.c b/utils/rcsh/pcmd.c
new file mode 100644
index 00000000..9c5eb8a6
--- /dev/null
+++ b/utils/rcsh/pcmd.c
@@ -0,0 +1,110 @@
+#include "rc.h"
+#include "y.tab.h"
+
+char nl='\n'; /* change to semicolon for bourne-proofing */
+
+#define c0 t->child[0]
+#define c1 t->child[1]
+#define c2 t->child[2]
+
+void
+pdeglob(Io *f, char *s)
+{
+ while(*s){
+ if(*s==GLOB) s++;
+ pchr(f, *s++);
+ }
+}
+
+void
+pcmd(Io *f, Tree *t)
+{
+ if(t==0)
+ return;
+
+ switch(t->type){
+ default: pfmt(f, "bad %d %p %p %p", t->type, c0, c1, c2); break;
+ case '$': pfmt(f, "$%t", c0); break;
+ case '"': pfmt(f, "$\"%t", c0); break;
+ case '&': pfmt(f, "%t&", c0); break;
+ case '^': pfmt(f, "%t^%t", c0, c1); break;
+ case '`': pfmt(f, "`%t", c0); break;
+ case ANDAND: pfmt(f, "%t && %t", c0, c1); break;
+ case ARGLIST: pfmt(f, "%t %t", c0, c1); break;
+ case BANG: pfmt(f, "! %t", c0); break;
+ case BRACE: pfmt(f, "{%t}", c0); break;
+ case COUNT: pfmt(f, "$#%t", c0); break;
+ case FN: pfmt(f, "fn %t %t", c0, c1); break;
+ case IF: pfmt(f, "if%t%t", c0, c1); break;
+ case NOT: pfmt(f, "if not %t", c0); break;
+ case OROR: pfmt(f, "%t || %t", c0, c1); break;
+ case PCMD:
+ case PAREN: pfmt(f, "(%t)", c0); break;
+ case SUB: pfmt(f, "$%t(%t)", c0, c1); break;
+ case SIMPLE: pfmt(f, "%t", c0); break;
+ case SUBSHELL: pfmt(f, "@ %t", c0); break;
+ case SWITCH: pfmt(f, "switch %t %t", c0, c1); break;
+ case TWIDDLE: pfmt(f, "~ %t %t", c0, c1); break;
+ case WHILE: pfmt(f, "while %t%t", c0, c1); break;
+ case ';':
+ if(c0){
+ if(c1) pfmt(f, "%t%c%t", c0, nl, c1);
+ else pfmt(f, "%t", c0);
+ }
+ else pfmt(f, "%t", c1);
+ break;
+ case WORDS:
+ if(c0) pfmt(f, "%t ", c0);
+ pfmt(f, "%t", c1);
+ break;
+ case FOR:
+ pfmt(f, "for(%t", c0);
+ if(c1) pfmt(f, " in %t", c1);
+ pfmt(f, ")%t", c2);
+ break;
+ case WORD:
+ if(t->quoted) pfmt(f, "%Q", t->str);
+ else pdeglob(f, t->str);
+ break;
+ case DUP:
+ pfmt(f, ">[%d=", t->fd0);
+ if(t->rtype==DUPFD)
+ pfmt(f, "%d", t->fd1);
+ /* else t->rtype == CLOSE */
+ pchr(f, ']');
+ break;
+ case PIPEFD:
+ case REDIR:
+ switch(t->rtype){
+ case HERE:
+ pchr(f, '<');
+ case READ:
+ pchr(f, '<');
+ if(t->fd0!=0)
+ pfmt(f, "[%d]", t->fd0);
+ break;
+ case APPEND:
+ pchr(f, '>');
+ case WRITE:
+ pchr(f, '>');
+ if(t->fd0!=1)
+ pfmt(f, "[%d]", t->fd0);
+ break;
+ }
+ pfmt(f, "%t", c0);
+ if(c1) pfmt(f, " %t", c1);
+ break;
+ case '=':
+ pfmt(f, "%t=%t", c0, c1);
+ if(c2) pfmt(f, " %t", c2);
+ break;
+ case PIPE:
+ pfmt(f, "%t|", c0);
+ if(t->fd1==0){
+ if(t->fd0!=1) pfmt(f, "[%d]", t->fd0);
+ }
+ else pfmt(f, "[%d=%d]", t->fd0, t->fd1);
+ pfmt(f, "%t", c1);
+ break;
+ }
+}
diff --git a/utils/rcsh/pfnc.c b/utils/rcsh/pfnc.c
new file mode 100644
index 00000000..4116b087
--- /dev/null
+++ b/utils/rcsh/pfnc.c
@@ -0,0 +1,70 @@
+#include "rc.h"
+
+struct{
+ void (*f)(void);
+ char *name;
+}fname[]={
+ Xappend, "Xappend",
+ Xasync, "Xasync",
+ Xbang, "Xbang",
+ Xclose, "Xclose",
+ Xdup, "Xdup",
+ Xeflag, "Xeflag",
+ Xexit, "Xexit",
+ Xfalse, "Xfalse",
+ Xifnot, "Xifnot",
+ Xjump, "Xjump",
+ Xmark, "Xmark",
+ Xpopm, "Xpopm",
+ Xread, "Xread",
+ Xreturn, "Xreturn",
+ Xtrue, "Xtrue",
+ Xif, "Xif",
+ Xwastrue, "Xwastrue",
+ Xword, "Xword",
+ Xwrite, "Xwrite",
+ Xmatch, "Xmatch",
+ Xcase, "Xcase",
+ Xconc, "Xconc",
+ Xassign, "Xassign",
+ Xdol, "Xdol",
+ Xcount, "Xcount",
+ Xlocal, "Xlocal",
+ Xunlocal, "Xunlocal",
+ Xfn, "Xfn",
+ Xdelfn, "Xdelfn",
+ Xpipe, "Xpipe",
+ Xpipewait, "Xpipewait",
+ Xrdcmds, "Xrdcmds",
+ Xbackq, "Xbackq",
+ Xpipefd, "Xpipefd",
+ Xsubshell, "Xsubshell",
+ Xdelhere, "Xdelhere",
+ Xfor, "Xfor",
+ Xglob, "Xglob",
+ Xsimple, "Xsimple",
+ Xqdol, "Xqdol",
+ 0
+};
+
+void
+pfnc(Io *fd, Thread *t)
+{
+ int i;
+ void (*fn)(void)=t->code[t->pc].f;
+ List *a;
+
+ pfmt(fd, "pid %d cycle %p %d ", getpid(), t->code, t->pc);
+ for(i=0;fname[i].f;i++) {
+ if(fname[i].f==fn) {
+ pstr(fd, fname[i].name);
+ break;
+ }
+ }
+ if(!fname[i].f)
+ pfmt(fd, "%p", fn);
+ for(a=t->argv;a;a=a->next)
+ pfmt(fd, " (%v)", a->words);
+ pchr(fd, '\n');
+ flush(fd);
+}
diff --git a/utils/rcsh/rc.h b/utils/rcsh/rc.h
new file mode 100644
index 00000000..f7d56746
--- /dev/null
+++ b/utils/rcsh/rc.h
@@ -0,0 +1,295 @@
+#include <lib9.h>
+
+#define Lock Rclock
+#define Ref Rcref
+
+typedef union Code Code;
+typedef struct Tree Tree;
+typedef struct Thread Thread;
+typedef struct Word Word;
+typedef struct Var Var;
+typedef struct List List;
+typedef struct Redir Redir;
+typedef struct Io Io;
+typedef struct Here Here;
+typedef struct Ref Ref;
+typedef struct Lock Lock;
+typedef struct Direntry Direntry;
+
+#define EOF (-1)
+#define NBUF 512
+
+/* values for Tree->rtype */
+#define APPEND 1
+#define WRITE 2
+#define READ 3
+#define HERE 4
+#define DUPFD 5
+#define CLOSE 6
+
+/*
+ * redir types
+ */
+#define ROPEN 1 /* dup2(from, to); close(from); */
+#define RDUP 2 /* dup2(from, to); */
+#define RCLOSE 3 /* close(from); */
+
+#define NSTATUS 64 /* length of status (from plan 9) */
+
+#define IWS 0x01 /* inter word seperator when word lists are stored in env variables */
+
+/*
+ * Glob character escape in strings:
+ * In a string, GLOB must be followed by *?[ or GLOB.
+ * GLOB* matches any string
+ * GLOB? matches any single character
+ * GLOB[...] matches anything in the brackets
+ * GLOBGLOB matches GLOB
+ */
+#define GLOB ((char)0x02)
+
+/*
+ * The first word of any code vector is a reference count.
+ * Always create a new reference to a code vector by calling codecopy(.).
+ * Always call codefree(.) when deleting a reference.
+ */
+union Code {
+ void (*f)(void);
+ int i;
+ char *s;
+};
+
+
+struct Tree
+{
+ int type;
+ int rtype, fd0, fd1; /* details of REDIR PIPE DUP tokens */
+ char *str;
+ int quoted;
+ int iskw;
+ Tree *child[3];
+ Tree *next;
+};
+
+struct Thread
+{
+ Code *code; /* code for this thread */
+ int pc; /* code[pc] is the next instruction */
+ List *argv; /* argument stack */
+ Redir *redir; /* redirection stack */
+ Redir *startredir; /* redir inheritance point */
+ Var *local; /* list of local variables */
+ char *cmdfile; /* file name in Xrdcmd */
+ Io *cmdfd; /* file descriptor for Xrdcmd */
+ int iflast; /* static `if not' checking */
+ int eof; /* is cmdfd at eof? */
+ int iflag; /* interactive? */
+ int lineno; /* linenumber */
+ int pid; /* process for Xpipewait to wait for */
+ char status[NSTATUS]; /* status for Xpipewait */
+ Tree *treenodes; /* tree nodes created by this process */
+ Thread *ret; /* who continues when this finishes */
+};
+
+struct Io
+{
+ int fd;
+ char *bufp;
+ char *ebuf;
+ char *strp;
+ char buf[NBUF];
+};
+
+struct Var
+{
+ char *name; /* ascii name */
+ Word *val; /* value */
+ int changed;
+ Code *fn; /* pointer to function's code vector */
+ int fnchanged;
+ int pc; /* pc of start of function */
+ Var *next; /* next on hash or local list */
+};
+
+struct Word
+{
+ char *word;
+ Word *next;
+};
+
+struct List
+{
+ Word *words;
+ List *next;
+};
+
+struct Redir
+{
+ char type; /* what to do */
+ short from, to; /* what to do it to */
+ Redir *next; /* what else to do (reverse order) */
+};
+
+struct Here{
+ Tree *tag;
+ char *name;
+ Here *next;
+};
+
+struct Lock {
+ int val;
+};
+
+struct Ref
+{
+ Lock lk;
+ int ref;
+};
+
+struct Direntry
+{
+ int isdir;
+ char *name;
+};
+
+/* main.c */
+void start(Code *c, int pc, Var *local);
+
+/* lex.c */
+void yyerror(char*);
+int yylex(void);
+int yyparse(void);
+int wordchr(int);
+int idchr(int);
+
+/* code.c */
+int compile(Tree*);
+Code *codecopy(Code*);
+void codefree(Code*);
+void cleanhere(char *f);
+
+void skipnl(void);
+
+void panic(char*, int);
+
+/* var.c */
+void kinit(void);
+void vinit(void);
+Var *vlook(char*);
+Var *gvlook(char*);
+Var *newvar(char*, Var*);
+void setvar(char*, Word*);
+void updenv(void);
+void kenter(int type, char *name);
+
+/* glob.c */
+void deglob(char*);
+void globlist(void);
+int match(char *s, char *p, int stop);
+
+/* main.c */
+void setstatus(char *s);
+char *getstatus(void);
+int truestatus(void);
+void execcmds(Io*);
+char *concstatus(char *s, char *t);
+char **procargv(char*, char*, char*, char*, Word *w);
+
+void freewords(Word*);
+
+/* tree.c */
+Tree *newtree(void);
+Tree *token(char*, int), *klook(char*), *tree1(int, Tree*);
+Tree *tree2(int, Tree*, Tree*), *tree3(int, Tree*, Tree*, Tree*);
+Tree *mung1(Tree*, Tree*), *mung2(Tree*, Tree*, Tree*);
+Tree *mung3(Tree*, Tree*, Tree*, Tree*), *epimung(Tree*, Tree*);
+Tree *simplemung(Tree*), *heredoc(Tree*);
+void freetree(Tree*);
+void freenodes(void);
+
+/* here.c */
+Tree *heredoc(Tree *tag);
+
+/* exec.c */
+extern void Xappend(void), Xasync(void), Xbackq(void), Xbang(void), Xclose(void);
+extern void Xconc(void), Xcount(void), Xdelfn(void), Xdol(void), Xqdol(void), Xdup(void);
+extern void Xexit(void), Xfalse(void), Xfn(void), Xfor(void), Xglob(void);
+extern void Xjump(void), Xmark(void), Xmatch(void), Xpipe(void), Xread(void);
+extern void Xunredir(void), Xstar(void), Xreturn(void), Xsubshell(void);
+extern void Xtrue(void), Xword(void), Xwrite(void), Xpipefd(void), Xcase(void);
+extern void Xlocal(void), Xunlocal(void), Xassign(void), Xsimple(void), Xpopm(void);
+extern void Xrdcmds(void), Xwastrue(void), Xif(void), Xifnot(void), Xpipewait(void);
+extern void Xdelhere(void), Xpopredir(void), Xsub(void), Xeflag(void), Xsettrue(void);
+extern void Xerror(char*), Xperror(char*);
+
+/* word.c */
+Word *newword(char*, Word*);
+void pushlist(void);
+void poplist(void);
+void pushword(char*);
+void popword(void);
+int count(Word*);
+Word *copywords(Word*, Word*);
+void pushredir(int, int, int);
+void turfredir(void);
+char *list2str(Word*);
+void freelist(Word*);
+Word *conclist(Word*, Word*, Word*);
+Word *subwords(Word*, int, Word*, Word*);
+
+/* io.c */
+#define pchr(b, c) if((b)->bufp==(b)->ebuf)fullbuf((b), (c));else (*(b)->bufp++=(c))
+#define rchr(b) ((b)->bufp==(b)->ebuf?emptybuf(b):(*(b)->bufp++&0xff))
+
+Io *openfd(int), *openstr(void), *opencore(char*, int);
+int emptybuf(Io*);
+void closeio(Io*);
+void flush(Io*);
+int fullbuf(Io*, int);
+
+void pfmt(Io*, char*, ...);
+void perr(Io*);
+void pstr(Io*, char*);
+void pfnc(Io*, Thread*);
+
+void pprompt(void);
+
+/* trap.c */
+void dotrap(void);
+void dointr(void);
+
+void waitfor(uint);
+
+/* nt.c */
+
+Direntry* readdirect(char*);
+void fatal(char*, ...);
+uint proc(char**, int, int, int);
+int procwait(uint);
+int refinc(Ref*);
+int refdec(Ref*);
+int pipe(int*);
+
+/*
+ * onebyte(c), twobyte(c), threebyte(c)
+ * Is c the first character of a one- two- or three-byte utf sequence?
+ */
+#define onebyte(c) ((c&0x80)==0x00)
+#define twobyte(c) ((c&0xe0)==0xc0)
+#define threebyte(c) ((c&0xf0)==0xe0)
+
+#define new(type) ((type *)malloc(sizeof(type)))
+
+
+extern Tree *cmdtree;
+extern Thread *runq;
+extern Io *err;
+extern int flag[256];
+extern int doprompt;
+extern char *promptstr;
+extern int ndot;
+extern int nerror;
+extern Code *codebuf;
+extern int eflagok;
+extern int interrupted;
+extern Ref ntrap;
diff --git a/utils/rcsh/rcmain b/utils/rcsh/rcmain
new file mode 100644
index 00000000..f74190ca
--- /dev/null
+++ b/utils/rcsh/rcmain
@@ -0,0 +1,29 @@
+# rcmain: 9pm version
+if(~ $#home 0) home=/
+if(~ $#ifs 0) ifs='
+'
+switch($#prompt){
+case 0
+case 1
+ prompt=('% ' ' ')
+}
+if(~ $rcname v.out) prompt=('broken! ' ' ')
+if(! ~ $#cflag 0){
+ if(flag l && test -r $home/lib/profile) . $home/lib/profile
+ status=''
+ eval $cflag
+}
+if not if(flag i){
+ if(flag l && test -r $home/lib/profile) . $home/lib/profile
+ status=''
+ if(! ~ $#* 0) . $*
+ if not . -i 'stdin$'
+}
+if not {
+ if(~ $#* 0) . 'stdin$'
+ if not{
+ status=''
+ . $*
+ }
+}
+exit $status
diff --git a/utils/rcsh/rcpath b/utils/rcsh/rcpath
new file mode 100644
index 00000000..cb19ff4f
--- /dev/null
+++ b/utils/rcsh/rcpath
@@ -0,0 +1,13 @@
+# rcmain: 9pm version
+PF='Program Files'
+MVS='Microsoft Visual Studio'
+PATH=D:/Users/Inferno/Nt/386/bin
+PATH=$PATH';'D:/apps/mks/mksnt
+PATH=$PATH';'C:/WINNT/system32
+PATH=$PATH';'C:/WINNT
+PATH=$PATH';'D:/$PF/$MVS/Common/Tools/WinNT
+PATH=$PATH';'D:/$PF/$MVS/Common/MSDev98/Bin
+PATH=$PATH';'D:/$PF/$MVS/Common/Tools
+PATH=$PATH';'D:/$PF/$MVS/VC98/bin
+PATH=$PATH';'D:/MSSQL7/BINN
+PATH=$PATH';'D:/$PF/Mts
diff --git a/utils/rcsh/simple.c b/utils/rcsh/simple.c
new file mode 100644
index 00000000..1c3a89b3
--- /dev/null
+++ b/utils/rcsh/simple.c
@@ -0,0 +1,528 @@
+#include "rc.h"
+
+typedef struct Builtin Builtin;
+
+struct Builtin
+{
+ char *name;
+ void (*fnc)(void);
+};
+
+int exitnext(void);
+void execexec(void);
+void execfunc(Var *func);
+void execcd(void);
+void execwhatis(void);
+void execeval(void);
+void execexit(void);
+void execshift(void);
+void execwait(void);
+void execdot(void);
+void execflag(void);
+
+Builtin builtin[]={
+ "cd", execcd,
+ "whatis", execwhatis,
+ "eval", execeval,
+ "exec", execexec, /* but with popword first */
+ "exit", execexit,
+ "shift", execshift,
+ "wait", execwait,
+ ".", execdot,
+ "flag", execflag,
+ 0
+};
+
+int mapfd(int fd);
+
+void
+Xsimple(void)
+{
+ Word *a;
+ Thread *p=runq;
+ Var *v;
+ Builtin *bp;
+ uint pid;
+ char **argv;
+
+ globlist();
+ a=runq->argv->words;
+ if(a==0){
+ Xerror("empty argument list");
+ return;
+ }
+ if(flag['x'])
+ pfmt(err, "%v\n", p->argv->words); /* wrong, should do redirs */
+
+ v=gvlook(a->word);
+ if(v->fn)
+ execfunc(v);
+ else{
+ if(strcmp(a->word, "builtin")==0){
+ if(count(a)==1){
+ pfmt(err, "builtin: empty argument list\n");
+ setstatus("empty arg list");
+ poplist();
+ return;
+ }
+ a=a->next;
+ popword();
+ }
+ for(bp=builtin;bp->name;bp++) {
+ if(strcmp(a->word, bp->name)==0){
+ (*bp->fnc)();
+ return;
+ }
+ }
+
+ updenv();
+ argv = procargv(0, 0, 0, 0, runq->argv->words);
+ pid = proc(argv, mapfd(0), mapfd(1), mapfd(2));
+ free(argv);
+
+ if(pid == 0)
+ pfmt(err, "%s: %r\n", runq->argv->words->word);
+ else
+ waitfor(pid);
+ poplist();
+#ifdef XXX
+ if(exitnext()){
+ /* fork and wait is redundant */
+ pushword("exec");
+ execexec();
+ Xexit();
+ }
+ else{
+ flush(err);
+ updenv();
+ switch(pid=fork()){
+ case -1:
+ Xperror("try again");
+ return;
+ case 0:
+ pushword("exec");
+ execexec();
+ Exit("can't exec");
+ default:
+ poplist();
+ /* interrupts don't get us out */
+ while(Waitfor(pid, 1) < 0)
+ ;
+ }
+ }
+#endif
+ }
+}
+
+Word nullpath={ "", 0};
+
+Word *
+searchpath(char *w)
+{
+ Word *path;
+
+ return &nullpath;
+
+ if(strncmp(w, "/", 1) == 0
+ || strncmp(w, "#", 1) == 0
+ || *w && w[1] == ':'
+ || strncmp(w, "./", 2) == 0
+ || strncmp(w, "../", 3) == 0
+ || (path=vlook("path")->val) == 0)
+ path=&nullpath;
+ return path;
+}
+
+/*
+ * Search through the following code to see if we're just going to exit.
+ */
+int
+exitnext(void)
+{
+ Code *c=&runq->code[runq->pc];
+
+ while(c->f==Xpopredir)
+ c++;
+ return c->f==Xexit;
+}
+
+#ifdef XXX
+
+void
+doredir(Redir *rp)
+{
+ if(rp){
+ doredir(rp->next);
+ switch(rp->type){
+ case ROPEN:
+ if(rp->from!=rp->to){
+ Dup(rp->from, rp->to);
+ close(rp->from);
+ }
+ break;
+ case RDUP: Dup(rp->from, rp->to); break;
+ case RCLOSE: close(rp->from); break;
+ }
+ }
+}
+
+
+#endif
+
+void
+execexec(void)
+{
+ popword(); /* "exec" */
+ if(runq->argv->words==0){
+ Xerror("empty argument list");
+ return;
+ }
+ fatal("execexec not done yet");
+/*
+ doredir(runq->redir);
+ Execute(runq->argv->words, searchpath(runq->argv->words->word));
+*/
+
+ poplist();
+}
+
+void
+execfunc(Var *func)
+{
+ Word *starval;
+
+ popword();
+ starval=runq->argv->words;
+ runq->argv->words=0;
+ poplist();
+ start(func->fn, func->pc, 0);
+ runq->local=newvar(strdup("*"), runq->local);
+ runq->local->val=starval;
+ runq->local->changed=1;
+}
+
+void
+execcd(void)
+{
+ Word *a=runq->argv->words;
+ Word *cdpath;
+ char dir[512];
+
+ setstatus("can't cd");
+ cdpath=vlook("cdpath")->val;
+ switch(count(a)){
+ default:
+ pfmt(err, "Usage: cd [directory]\n");
+ break;
+ case 2:
+ if(a->next->word[0]=='/' || cdpath==0) cdpath=&nullpath;
+ for(;cdpath;cdpath=cdpath->next){
+ strcpy(dir, cdpath->word);
+ if(dir[0]) strcat(dir, "/");
+ strcat(dir, a->next->word);
+ if(chdir(dir)>=0){
+ if(strlen(cdpath->word)
+ && strcmp(cdpath->word, ".")!=0)
+ pfmt(err, "%s\n", dir);
+ setstatus("");
+ break;
+ }
+ }
+ if(cdpath==0) pfmt(err, "Can't cd %s\n", a->next->word);
+ break;
+ case 1:
+ a=vlook("home")->val;
+ if(count(a)>=1){
+ if(chdir(a->word)>=0)
+ setstatus("");
+ else
+ pfmt(err, "Can't cd %s\n", a->word);
+ }
+ else
+ pfmt(err, "Can't cd -- $home empty\n");
+ break;
+ }
+ poplist();
+}
+
+void
+execexit(void)
+{
+ switch(count(runq->argv->words)){
+ default: pfmt(err, "Usage: exit [status]\nExiting anyway\n");
+ case 2: setstatus(runq->argv->words->next->word);
+ case 1: Xexit();
+ }
+}
+
+void
+execflag(void)
+{
+ char *letter, *val;
+ switch(count(runq->argv->words)){
+ case 2:
+ setstatus(flag[runq->argv->words->next->word[0]]?"":"flag not set");
+ break;
+ case 3:
+ letter=runq->argv->words->next->word;
+ val=runq->argv->words->next->next->word;
+ if(strlen(letter)==1){
+ if(strcmp(val, "+")==0){
+ flag[letter[0]]=1;
+ break;
+ }
+ if(strcmp(val, "-")==0){
+ flag[letter[0]]=0;
+ break;
+ }
+ }
+ default:
+ Xerror("Usage: flag [letter] [+-]");
+ return;
+ }
+ poplist();
+}
+
+void
+execshift(void)
+{
+ int n;
+ Word *a;
+ Var *star;
+ switch(count(runq->argv->words)){
+ default:
+ pfmt(err, "Usage: shift [n]\n");
+ setstatus("shift usage");
+ poplist();
+ return;
+ case 2: n=atoi(runq->argv->words->next->word); break;
+ case 1: n=1; break;
+ }
+ star=vlook("*");
+ for(;n && star->val;--n){
+ a=star->val->next;
+ free(star->val->word);
+ free(star->val);
+ star->val=a;
+ star->changed=1;
+ }
+ setstatus("");
+ poplist();
+}
+
+int
+octal(char *s)
+{
+ int n=0;
+ while(*s==' ' || *s=='\t' || *s=='\n') s++;
+ while('0'<=*s && *s<='7') n=n*8+*s++-'0';
+ return n;
+}
+
+void
+execeval(void)
+{
+ char *cmdline, *s, *t;
+ int len=0;
+ Word *ap;
+
+ if(count(runq->argv->words)<=1){
+ Xerror("Usage: eval cmd ...");
+ return;
+ }
+ eflagok=1;
+ for(ap=runq->argv->words->next;ap;ap=ap->next)
+ len+=1+strlen(ap->word);
+ cmdline=malloc(len);
+ s=cmdline;
+ for(ap=runq->argv->words->next;ap;ap=ap->next){
+ for(t=ap->word;*t;) *s++=*t++;
+ *s++=' ';
+ }
+ s[-1]='\n';
+ poplist();
+ execcmds(opencore(cmdline, len));
+ free(cmdline);
+}
+
+void
+execdot(void)
+{
+ int iflag=0;
+ int fd;
+ List *av;
+ Thread *p=runq;
+ char *zero;
+ char file[512];
+ Word *path;
+ static int first=1;
+ static Code dotcmds[14];
+
+ if(first) {
+ dotcmds[0].i=1;
+ dotcmds[1].f=Xmark;
+ dotcmds[2].f=Xword;
+ dotcmds[3].s="0";
+ dotcmds[4].f=Xlocal;
+ dotcmds[5].f=Xmark;
+ dotcmds[6].f=Xword;
+ dotcmds[7].s="*";
+ dotcmds[8].f=Xlocal;
+ dotcmds[9].f=Xrdcmds;
+ dotcmds[10].f=Xunlocal;
+ dotcmds[11].f=Xunlocal;
+ dotcmds[12].f=Xreturn;
+ first=0;
+ } else
+ eflagok=1;
+ popword();
+ if(p->argv->words && strcmp(p->argv->words->word, "-i")==0){
+ iflag=1;
+ popword();
+ }
+ /* get input file */
+ if(p->argv->words==0){
+ Xerror("Usage: . [-i] file [arg ...]");
+ return;
+ }
+ zero=strdup(p->argv->words->word);
+ popword();
+ strcpy(file, "**No file name**");
+ fd = -1;
+ if(strcmp(zero, "stdin$") == 0)
+ fd = dup(0);
+ else{
+ for(path=searchpath(zero);path;path=path->next){
+ strcpy(file, path->word);
+ if(file[0])
+ strcat(file, "/");
+ strcat(file, zero);
+ if((fd=open(file, 0))>=0)
+ break;
+ }
+ }
+ if(fd<0){
+ Xperror(file);
+ return;
+ }
+ /* set up for a new command loop */
+ start(dotcmds, 1, 0);
+ pushredir(RCLOSE, fd, 0);
+ runq->cmdfile=zero;
+ runq->cmdfd=openfd(fd);
+ runq->iflag=iflag;
+ runq->iflast=0;
+ /* push $* value */
+ pushlist();
+ runq->argv->words=p->argv->words;
+ /* free caller's copy of $* */
+ av=p->argv;
+ p->argv=av->next;
+ free(av);
+ /* push $0 value */
+ pushlist();
+ pushword(zero);
+ ndot++;
+}
+
+void
+execwhatis(void)
+{ /* mildly wrong -- should fork before writing */
+ Word *a, *b, *path;
+ Var *v;
+ Builtin *bp;
+ char file[512];
+ Io out[1];
+ int found, sep;
+
+ a=runq->argv->words->next;
+ if(a==0){
+ Xerror("Usage: whatis name ...");
+ return;
+ }
+ setstatus("");
+ out->fd=mapfd(1);
+ out->bufp=out->buf;
+ out->ebuf=&out->buf[NBUF];
+ out->strp=0;
+ for(;a;a=a->next){
+ v=vlook(a->word);
+ if(v->val){
+ pfmt(out, "%s=", a->word);
+ if(v->val->next==0)
+ pfmt(out, "%q\n", v->val->word);
+ else{
+ sep='(';
+ for(b=v->val;b && b->word;b=b->next){
+ pfmt(out, "%c%q", sep, b->word);
+ sep=' ';
+ }
+ pfmt(out, ")\n");
+ }
+ found=1;
+ }
+ else
+ found=0;
+ v=gvlook(a->word);
+ if(v->fn) pfmt(out, "fn %s %s\n", v->name, v->fn[v->pc-1].s);
+ else{
+ for(bp=builtin;bp->name;bp++)
+ if(strcmp(a->word, bp->name)==0){
+ pfmt(out, "builtin %s\n", a->word);
+ break;
+ }
+ if(!bp->name){
+ for(path=searchpath(a->word);path;path=path->next){
+ strcpy(file, path->word);
+ if(file[0]) strcat(file, "/");
+ strcat(file, a->word);
+#ifdef XXX
+ if(Executable(file)){
+ pfmt(out, "%s\n", file);
+ break;
+ }
+#endif
+ }
+ if(!path && !found){
+ pfmt(err, "%s: not found\n", a->word);
+ setstatus("not found");
+ }
+ }
+ }
+ }
+ poplist();
+ flush(err);
+}
+
+void
+execwait(void)
+{
+ fprint(2, "wait: not done yet");
+
+#ifdef XXX
+ switch(count(runq->argv->words)){
+ default: Xerror("Usage: wait [pid]"); return;
+ case 2: Waitfor(atoi(runq->argv->words->next->word), 0); break;
+ case 1: Waitfor(-1, 0); break;
+ }
+ poplist();
+#endif
+}
+
+int
+mapfd(int fd)
+{
+ Redir *rp;
+ for(rp=runq->redir;rp;rp=rp->next){
+ switch(rp->type){
+ case RCLOSE:
+ if(rp->from==fd) fd=-1;
+ break;
+ case RDUP:
+ case ROPEN:
+ if(rp->to==fd) fd=rp->from;
+ break;
+ }
+ }
+ return fd;
+}
diff --git a/utils/rcsh/syn.y b/utils/rcsh/syn.y
new file mode 100644
index 00000000..b0201ef0
--- /dev/null
+++ b/utils/rcsh/syn.y
@@ -0,0 +1,90 @@
+%{
+#include "rc.h"
+%}
+
+/* operator priorities -- lowest first */
+%left IF WHILE FOR SWITCH ')' NOT
+%left ANDAND OROR
+%left BANG SUBSHELL
+%left PIPE
+%left '^'
+%right '$' COUNT '"'
+%left SUB
+
+%term FOR IN WHILE IF NOT TWIDDLE BANG SUBSHELL SWITCH FN
+%term WORD REDIR DUP PIPE SUB
+%term SIMPLE ARGLIST WORDS BRACE PAREN PCMD PIPEFD /* not used in syntax */
+
+
+%union{
+ Tree *tree;
+};
+
+%type<tree> line paren brace body cmdsa cmdsan assign epilog redir
+%type<tree> cmd simple first word comword keyword words
+%type<tree> NOT FOR IN WHILE IF TWIDDLE BANG SUBSHELL SWITCH FN
+%type<tree> WORD REDIR DUP PIPE
+
+%%
+
+rc: { return 1;}
+| line '\n' {return !compile($1);}
+line: cmd
+| cmdsa line {$$=tree2(';', $1, $2);}
+body: cmd
+| cmdsan body {$$=tree2(';', $1, $2);}
+cmdsa: cmd ';'
+| cmd '&' {$$=tree1('&', $1);}
+cmdsan: cmdsa
+| cmd '\n'
+brace: '{' body '}' {$$=tree1(BRACE, $2);}
+paren: '(' body ')' {$$=tree1(PCMD, $2);}
+assign: first '=' word {$$=tree2('=', $1, $3);}
+epilog: {$$=0;}
+| redir epilog {$$=mung2($1, $1->child[0], $2);}
+redir: REDIR word {$$=mung1($1, $1->rtype==HERE?heredoc($2):$2);}
+| DUP
+cmd: {$$=0;}
+| brace epilog {$$=epimung($1, $2);}
+| IF paren {skipnl();} cmd
+ {$$=mung2($1, $2, $4);}
+| IF NOT {skipnl();} cmd {$$=mung1($2, $4);}
+| FOR '(' word IN words ')' {skipnl();} cmd
+ {$$=mung3($1, $3, tree1(PAREN, $5), $8);}
+| FOR '(' word ')' {skipnl();} cmd
+ {$$=mung3($1, $3, 0, $6);}
+| WHILE paren {skipnl();} cmd
+ {$$=mung2($1, $2, $4);}
+| SWITCH word {skipnl();} brace
+ {$$=tree2(SWITCH, $2, $4);}
+| simple {$$=simplemung($1);}
+| TWIDDLE word words {$$=mung2($1, $2, $3);}
+| cmd ANDAND cmd {$$=tree2(ANDAND, $1, $3);}
+| cmd OROR cmd {$$=tree2(OROR, $1, $3);}
+| cmd PIPE cmd {$$=mung2($2, $1, $3);}
+| redir cmd %prec BANG {$$=mung2($1, $1->child[0], $2);}
+| assign cmd %prec BANG {$$=mung3($1, $1->child[0], $1->child[1], $2);}
+| BANG cmd {$$=mung1($1, $2);}
+| SUBSHELL cmd {$$=mung1($1, $2);}
+| FN words brace {$$=tree2(FN, $2, $3);}
+| FN words {$$=tree1(FN, $2);}
+simple: first
+| simple word {$$=tree2(ARGLIST, $1, $2);}
+| simple redir {$$=tree2(ARGLIST, $1, $2);}
+first: comword
+| first '^' word {$$=tree2('^', $1, $3);}
+word: keyword {$1->type=WORD;}
+| comword
+| word '^' word {$$=tree2('^', $1, $3);}
+comword: '$' word {$$=tree1('$', $2);}
+| '$' word SUB words ')' {$$=tree2(SUB, $2, $4);}
+| '"' word {$$=tree1('"', $2);}
+| COUNT word {$$=tree1(COUNT, $2);}
+| WORD
+| '`' brace {$$=tree1('`', $2);}
+| '(' words ')' {$$=tree1(PAREN, $2);}
+/* | REDIR brace {$$=mung1($1, $2); $$->type=PIPEFD;} */
+
+keyword: FOR|IN|WHILE|IF|NOT|TWIDDLE|BANG|SUBSHELL|SWITCH|FN
+words: {$$=0;}
+| words word {$$=tree2(WORDS, $1, $2);}
diff --git a/utils/rcsh/trap.c b/utils/rcsh/trap.c
new file mode 100644
index 00000000..7bae2cbc
--- /dev/null
+++ b/utils/rcsh/trap.c
@@ -0,0 +1,42 @@
+#include "rc.h"
+
+int interrupted;
+Ref ntrap;
+
+/* runs in a different thread */
+void
+dointr(void)
+{
+ refinc(&ntrap);
+ interrupted = 1;
+}
+
+void
+dotrap(void)
+{
+ Var *trapreq;
+ Word *starval;
+
+ while(refdec(&ntrap) >= 0) {
+ if(flag['S'])
+ exits(truestatus()?"":getstatus());
+ starval=vlook("*")->val;
+ trapreq=vlook("sysint");
+ if(trapreq->fn){
+ start(trapreq->fn, trapreq->pc, (Var*)0);
+ runq->local=newvar(strdup("*"), runq->local);
+ runq->local->val=copywords(starval, (Word*)0);
+ runq->local->changed=1;
+ runq->redir=runq->startredir=0;
+ } else {
+ /*
+ * run the stack down until we uncover the
+ * command reading loop. Xreturn will exit
+ * if there is none (i.e. if this is not
+ * an interactive rc.)
+ */
+ while(!runq->iflag)
+ Xreturn();
+ }
+ }
+}
diff --git a/utils/rcsh/tree.c b/utils/rcsh/tree.c
new file mode 100644
index 00000000..145f9c77
--- /dev/null
+++ b/utils/rcsh/tree.c
@@ -0,0 +1,140 @@
+#include "rc.h"
+#include "y.tab.h"
+
+Tree *Treenodes;
+
+/*
+ * create and clear a new Tree node, and add it
+ * to the node list.
+ */
+Tree *
+newtree(void)
+{
+ Tree *t=new(Tree);
+ t->iskw=0;
+ t->str=0;
+ t->child[0]=t->child[1]=t->child[2]=0;
+ t->next=Treenodes;
+ Treenodes=t;
+ return t;
+}
+
+void
+freenodes(void)
+{
+ Tree *t, *u;
+ for(t=Treenodes;t;t=u){
+ u=t->next;
+ if(t->str)
+ free(t->str);
+ free(t);
+ }
+ Treenodes=0;
+}
+
+Tree *
+tree1(int type, Tree *c0)
+{
+ return tree3(type, c0, 0, 0);
+}
+
+Tree *
+tree2(int type, Tree *c0, Tree *c1)
+{
+ return tree3(type, c0, c1, 0);
+}
+
+Tree *
+tree3(int type, Tree *c0, Tree *c1, Tree *c2)
+{
+ Tree *t;
+ if(type==';'){
+ if(c0==0) return c1;
+ if(c1==0) return c0;
+ }
+ t=newtree();
+ t->type=type;
+ t->child[0]=c0;
+ t->child[1]=c1;
+ t->child[2]=c2;
+ return t;
+}
+
+Tree *
+mung1(Tree *t, Tree *c0)
+{
+ t->child[0]=c0;
+ return t;
+}
+
+Tree *
+mung2(Tree *t, Tree *c0, Tree *c1)
+{
+ t->child[0]=c0;
+ t->child[1]=c1;
+ return t;
+}
+
+Tree *
+mung3(Tree *t, Tree *c0, Tree *c1, Tree *c2)
+{
+ t->child[0]=c0;
+ t->child[1]=c1;
+ t->child[2]=c2;
+ return t;
+}
+
+Tree *
+epimung(Tree *comp, Tree *epi)
+{
+ Tree *p;
+ if(epi==0) return comp;
+ for(p=epi;p->child[1];p=p->child[1]);
+ p->child[1]=comp;
+ return epi;
+}
+
+/*
+ * Add a SIMPLE node at the root of t and percolate all the redirections
+ * up to the root.
+ */
+Tree *
+simplemung(Tree *t)
+{
+ Tree *u;
+ Io *s;
+ t=tree1(SIMPLE, t);
+ s=openstr();
+ pfmt(s, "%t", t);
+ t->str=strdup(s->strp);
+ closeio(s);
+ for(u=t->child[0];u->type==ARGLIST;u=u->child[0]){
+ if(u->child[1]->type==DUP
+ || u->child[1]->type==REDIR){
+ u->child[1]->child[1]=t;
+ t=u->child[1];
+ u->child[1]=0;
+ }
+ }
+ return t;
+}
+
+Tree *
+token(char *str, int type)
+{
+ Tree *t=newtree();
+ t->type=type;
+ t->str=strdup(str);
+ return t;
+}
+
+void
+freetree(Tree *p)
+{
+ if(p==0) return;
+ freetree(p->child[0]);
+ freetree(p->child[1]);
+ freetree(p->child[2]);
+ if(p->str) free(p->str);
+ free((char *)p);
+}
diff --git a/utils/rcsh/var.c b/utils/rcsh/var.c
new file mode 100644
index 00000000..cee8adf5
--- /dev/null
+++ b/utils/rcsh/var.c
@@ -0,0 +1,240 @@
+#include "rc.h"
+#include "y.tab.h"
+
+extern char **_environ;
+
+typedef struct Kw Kw;
+
+#define NKW 30
+#define NVAR 521
+
+struct Kw{
+ char *name;
+ int type;
+ Kw *next;
+};
+
+void updenvlocal(Var *v);
+void addenv(Var *v);
+
+Kw *kw[NKW];
+Var *gvar[NVAR];
+
+int
+hash(char *s, int n)
+{
+ int h=0, i=1;
+
+ while(*s)
+ h+=*s++*i++;
+ h%=n;
+ return h<0?h+n:h;
+}
+
+void
+kenter(int type, char *name)
+{
+ int h=hash(name, NKW);
+ Kw *p=new(Kw);
+ p->type=type;
+ p->name=name;
+ p->next=kw[h];
+ kw[h]=p;
+}
+
+void
+vinit(void)
+{
+ char **env, *name, *val, *p;
+ int i;
+ Word *w;
+ Io *f;
+ int n;
+ Var *v;
+
+ env = _environ;
+ for(i=0; env[i]; free(name), i++) {
+ name = strdup(env[i]);
+ p = strchr(name, '=');
+ if(p == 0 || p == name)
+ continue;
+ *p = 0;
+ val = p+1;
+ n = strlen(val);
+ if(n == 0)
+ continue;
+
+ if(strncmp(name, "fn#", 3)!=0) {
+ /* variable */
+ w = 0;
+ p = val+n-1;
+ while(*p) {
+ if(*p == IWS)
+ *p-- = 0;
+ for(; *p && *p != IWS; p--)
+ ;
+ w=newword(p+1, w);
+ }
+ setvar(name, w);
+ vlook(name)->changed=0;
+ } else {
+ /* function */
+ f = opencore(val, n);
+ execcmds(f);
+ }
+ }
+ v = vlook("path");
+ p = getenv("path");
+ if(v->val == 0 && p)
+ v->val = newword(p, 0);
+}
+
+
+Tree *
+klook(char *name)
+{
+ Kw *p;
+ Tree *t=token(name, WORD);
+ for(p=kw[hash(name, NKW)];p;p=p->next) {
+ if(strcmp(p->name, name)==0){
+ t->type=p->type;
+ t->iskw=1;
+ break;
+ }
+ }
+ return t;
+}
+
+Var *
+gvlook(char *name)
+{
+ int h=hash(name, NVAR);
+ Var *v;
+ for(v=gvar[h]; v; v=v->next)
+ if(strcmp(v->name, name)==0)
+ return v;
+
+ return gvar[h]=newvar(strdup(name), gvar[h]);
+}
+
+Var *
+vlook(char *name)
+{
+ Var *v;
+ if(runq)
+ for(v=runq->local; v; v=v->next)
+ if(strcmp(v->name, name)==0)
+ return v;
+ return gvlook(name);
+}
+
+void
+setvar(char *name, Word *val)
+{
+ Var *v=vlook(name);
+ freewords(v->val);
+ v->val=val;
+ v->changed=1;
+}
+
+Var *
+newvar(char *name, Var *next)
+{
+ Var *v=new(Var);
+ v->name=name;
+ v->val=0;
+ v->fn=0;
+ v->changed=0;
+ v->fnchanged=0;
+ v->next=next;
+ return v;
+}
+
+
+void
+execfinit(void)
+{
+}
+
+void
+updenv(void)
+{
+ Var *v, **h;
+
+ for(h=gvar;h!=&gvar[NVAR];h++)
+ for(v=*h;v;v=v->next)
+ addenv(v);
+
+ if(runq)
+ updenvlocal(runq->local);
+}
+
+static void
+envput(char *var, char *val)
+{
+ int i, n;
+ char *e;
+ char buf[256];
+
+ snprint(buf, sizeof(buf), "%s=%s", var, val);
+ n = strlen(var);
+ for(i = 0;;i++){
+ e = environ[i];
+ if(e == 0)
+ break;
+ if(strncmp(e, var, n) == 0){
+ free(e);
+ environ[i] = strdup(buf);
+ return;
+ }
+ }
+ environ = realloc(environ, (i+2)*sizeof(char*));
+ environ[i++] = strdup(buf);
+ environ[i] = 0;
+}
+
+void
+addenv(Var *v)
+{
+ char buf[100], *p;
+ Io *f;
+ Word *w;
+ int i, n;
+
+ if(v->changed){
+ v->changed=0;
+ p = 0;
+ n = 0;
+ if(v->val) {
+ for(w=v->val; w; w=w->next) {
+ i = strlen(w->word);
+ p = realloc(p, n+i+1);
+ memmove(p+n, w->word, i);
+ n+=i;
+ p[n++] = IWS;
+ }
+ p[n-1] = 0;
+ envput(v->name, p);
+ } else
+ envput(v->name, "");
+ free(p);
+ }
+
+ if(v->fnchanged){
+ v->fnchanged=0;
+ snprint(buf, sizeof(buf), "fn#%s", v->name);
+ f = openstr();
+ pfmt(f, "fn %s %s\n", v->name, v->fn[v->pc-1].s);
+ envput(buf, f->strp);
+ closeio(f);
+ }
+}
+
+void
+updenvlocal(Var *v)
+{
+ if(v){
+ updenvlocal(v->next);
+ addenv(v);
+ }
+}
diff --git a/utils/rcsh/word.c b/utils/rcsh/word.c
new file mode 100644
index 00000000..7cbab88e
--- /dev/null
+++ b/utils/rcsh/word.c
@@ -0,0 +1,173 @@
+#include "rc.h"
+
+Word *
+newword(char *wd, Word *next)
+{
+ Word *p=new(Word);
+ p->word=strdup(wd);
+ p->next=next;
+ return p;
+}
+
+void
+pushword(char *wd)
+{
+ if(runq->argv==0)
+ panic("pushword but no argv!", 0);
+ runq->argv->words=newword(wd, runq->argv->words);
+}
+
+void
+popword(void)
+{
+ Word *p;
+
+ if(runq->argv==0)
+ panic("popword but no argv!", 0);
+ p=runq->argv->words;
+ if(p==0)
+ panic("popword but no word!", 0);
+ runq->argv->words=p->next;
+ free(p->word);
+ free(p);
+}
+
+void
+freewords(Word *w)
+{
+ Word *nw;
+ while(w){
+ free(w->word);
+ nw=w->next;
+ free(w);
+ w=nw;
+ }
+}
+
+void
+freelist(Word *w)
+{
+ Word *nw;
+ while(w){
+ nw=w->next;
+ free(w->word);
+ free(w);
+ w=nw;
+ }
+}
+
+void
+pushlist(void)
+{
+ List *p=new(List);
+ p->next=runq->argv;
+ p->words=0;
+ runq->argv=p;
+}
+
+void
+poplist(void)
+{
+ List *p=runq->argv;
+ if(p==0)
+ panic("poplist but no argv", 0);
+ freelist(p->words);
+ runq->argv=p->next;
+ free(p);
+}
+
+int
+count(Word *w)
+{
+ int n;
+ for(n=0;w;n++)
+ w=w->next;
+ return n;
+}
+
+/*
+ * copy arglist a, adding the copy to the front of tail
+ */
+Word *
+copywords(Word *a, Word *tail)
+{
+ Word *v=0, **end;
+ for(end=&v;a;a=a->next,end=&(*end)->next)
+ *end=newword(a->word, 0);
+ *end=tail;
+ return v;
+}
+
+char *
+list2str(Word *words)
+{
+ char *value, *s, *t;
+ int len=0;
+ Word *ap;
+
+ for(ap=words;ap;ap=ap->next)
+ len+=1+strlen(ap->word);
+ value=malloc(len+1);
+ s=value;
+ for(ap=words;ap;ap=ap->next){
+ for(t=ap->word;*t;) *s++=*t++;
+ *s++=' ';
+ }
+ if(s==value)
+ *s='\0';
+ else
+ s[-1]='\0';
+ return value;
+}
+
+Word *
+subwords(Word *val, int len, Word *sub, Word *a)
+{
+ int n;
+ char *s;
+
+ if(!sub) return a;
+ a=subwords(val, len, sub->next, a);
+ s=sub->word;
+ deglob(s);
+ n=0;
+ while('0'<=*s && *s<='9') n=n*10+ *s++ -'0';
+ if(n<1 || len<n) return a;
+ for(;n!=1;--n) val=val->next;
+ return newword(val->word, a);
+}
+
+
+void
+pushredir(int type, int from, int to)
+{
+ Redir *rp=new(Redir);
+ rp->type=type;
+ rp->from=from;
+ rp->to=to;
+ rp->next=runq->redir;
+ runq->redir=rp;
+}
+
+void
+turfredir(void)
+{
+ while(runq->redir!=runq->startredir)
+ Xpopredir();
+}
+
+Word*
+conclist(Word *lp, Word *rp, Word *tail)
+{
+ char *buf;
+ Word *v;
+ if(lp->next || rp->next)
+ tail=conclist(lp->next==0?lp:lp->next, rp->next==0?rp:rp->next,
+ tail);
+ buf=malloc(strlen(lp->word)+strlen(rp->word)+1);
+ strcpy(buf, lp->word);
+ strcat(buf, rp->word);
+ v=newword(buf, tail);
+ free(buf);
+ return v;
+}