shithub: rc

Download patch

ref: 7d9c52753489ca5c16c073a25149e3854f373e22
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Mon Jan 3 16:48:21 EST 2022

initial commit (from 9front 189731aad01e09db1807c78af421c615ed3a3242)

--- /dev/null
+++ b/Makefile
@@ -1,0 +1,51 @@
+TARG=rc
+
+OFILES=\
+	code.o\
+	exec.o\
+	getflags.o\
+	glob.o\
+	here.o\
+	io.o\
+	lex.o\
+	pcmd.o\
+	pfnc.o\
+	simple.o\
+	subr.o\
+	trap.o\
+	tree.o\
+	var.o\
+	havefork.o\
+	unix.o\
+	y.tab.o\
+
+HFILES=rc.h\
+	y.tab.h\
+	io.h\
+	exec.h\
+	fns.h\
+	getflags.h\
+
+YFILES=syn.y
+
+PREFIX=/usr/local
+
+all: $(TARG)
+
+install: $(TARG) rcmain.unix
+	cp $(TARG) $(PREFIX)/bin/
+	cp rcmain.unix $(PREFIX)/lib/rcmain
+
+$(TARG): $(OFILES)
+	$(CC) $(CFLAGS) $(LDFLAGS) -o $(TARG) $(OFILES)
+
+y.tab.h y.tab.c: $(YFILES)
+	$(YACC) -d $(YFILES)
+
+unix.o:	unix.c
+	$(CC) $(CFLAGS) '-DPREFIX="$(PREFIX)"' -c unix.c
+
+$(OFILES): $(HFILES)
+
+clean:
+	rm -f $(OFILES) $(TARG) y.tab.? y.debug
--- /dev/null
+++ b/code.c
@@ -1,0 +1,547 @@
+#include "rc.h"
+#include "io.h"
+#include "exec.h"
+#include "fns.h"
+#include "getflags.h"
+#define	c0	t->child[0]
+#define	c1	t->child[1]
+#define	c2	t->child[2]
+code *codebuf;
+int codep, ncode, codeline;
+#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);
+void outcode(tree*, int);
+void codeswitch(tree*, int);
+int iscase(tree*);
+code *codecopy(code*);
+void codefree(code*);
+
+int
+morecode(void)
+{
+	ncode+=ncode;
+	codebuf = (code *)erealloc((char *)codebuf, ncode*sizeof codebuf[0]);
+	return 0;
+}
+
+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 = emalloc(ncode*sizeof codebuf[0]);
+	codep = 0;
+	codeline = 0;			/* force source */
+	emiti(0);			/* reference count */
+	emits(estrdup(lex->file));	/* source file name */
+	outcode(t, !lex->qflag && flag['e']!=0);
+	if(nerror){
+		free(codebuf);
+		return 0;
+	}
+	emitf(Xreturn);
+	emitf(0);
+	return 1;
+}
+
+/*
+ * called on a tree where we expect eigther
+ * a pattern or a string instead of a glob to
+ * remove the GLOB chars from the strings
+ * or set glob to -1 for pattern so not Xglob
+ * is inserted when compiling the tree.
+ */
+void
+noglobs(tree *t, int pattern)
+{
+Again:
+	if(t==0)
+		return;
+	if(t->type==WORD && t->glob){
+		if(pattern)
+			t->glob=-1;
+		else{
+			deglob(t->str);
+			t->glob=0;
+		}
+	}
+	if(t->type==WORDS || t->type=='^'){
+		t->glob=0;
+		noglobs(c1, pattern);
+		t = c0;
+		goto Again;
+	}
+}
+
+void
+outcode(tree *t, int eflag)
+{
+	void (*f)(void);
+	int p, q;
+	tree *tt;
+	if(t==0)
+		return;
+	if(t->type!=NOT && t->type!=';')
+		lex->iflast = 0;
+	if(t->line != codeline){
+		codeline = t->line;
+		if(codebuf && codep >= 2 && codebuf[codep-2].f == Xsrcline)
+			codebuf[codep-1].i = codeline;
+		else {
+			emitf(Xsrcline);
+			emiti(codeline);
+		}
+	}
+	switch(t->type){
+	default:
+		pfmt(err, "bad type %d in outcode\n", t->type);
+		break;
+	case '$':
+		emitf(Xmark);
+		noglobs(c0, 0);
+		outcode(c0, eflag);
+		emitf(Xdol);
+		break;
+	case '"':
+		emitf(Xmark);
+		emitf(Xmark);
+		noglobs(c0, 0);
+		outcode(c0, eflag);
+		emitf(Xdol);
+		emitf(Xqw);
+		emitf(Xpush);
+		break;
+	case SUB:
+		emitf(Xmark);
+		noglobs(c0, 0);
+		outcode(c0, eflag);
+		emitf(Xmark);
+		noglobs(c1, 0);
+		outcode(c1, eflag);
+		emitf(Xsub);
+		break;
+	case '&':
+		emitf(Xasync);
+		p = emiti(0);
+
+		/* undocumented? */
+		emitf(Xmark);
+		emitf(Xword);
+		emits(estrdup("/dev/null"));
+		emitf(Xread);
+		emiti(0);
+
+		/* insert rfork s for plan9 */
+		f = builtinfunc("rfork");
+		if(f){
+			emitf(Xmark);
+			emitf(Xword);
+			emits(estrdup("s"));
+			emitf(Xword);
+			emits(estrdup("rfork"));
+			emitf(f);
+		}
+
+		codeline = 0;	/* force source */
+		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(Xmark);
+		if(c0){
+			noglobs(c0, 0);
+			outcode(c0, 0);
+		} else {
+			emitf(Xmark);
+			emitf(Xword);
+			emits(estrdup("ifs"));
+			emitf(Xdol);
+		}
+		emitf(Xqw);
+		emitf(Xbackq);
+		p = emiti(0);
+		codeline = 0;	/* force source */
+		outcode(c1, 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);
+		noglobs(c0, 0);
+		outcode(c0, eflag);
+		emitf(Xcount);
+		break;
+	case FN:
+		emitf(Xmark);
+		noglobs(c0, 0);
+		outcode(c0, eflag);
+		if(c1){
+			emitf(Xfn);
+			p = emiti(0);
+			emits(fnstr(c1));
+			codeline = 0;	/* force source */
+			outcode(c1, eflag);
+			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(!lex->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);
+		p = emiti(0);
+		codeline = 0;	/* force source */
+		outcode(c0, eflag);
+		emitf(Xexit);
+		stuffdot(p);
+		if(eflag)
+			emitf(Xeflag);
+		break;
+	case SWITCH:
+		codeswitch(t, eflag);
+		break;
+	case TWIDDLE:
+		emitf(Xmark);
+		noglobs(c1, 1);
+		outcode(c1, eflag);
+		emitf(Xmark);
+		outcode(c0, eflag);
+		emitf(Xqw);
+		emitf(Xmatch);
+		if(eflag)
+			emitf(Xeflag);
+		break;
+	case WHILE:
+		q = codep;
+		outcode(c0, 0);
+		if(q==codep)
+			emitf(Xsettrue);	/* empty condition == while(true) */
+		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);
+		}
+		else{
+			emitf(Xmark);
+			emitf(Xword);
+			emits(estrdup("*"));
+			emitf(Xdol);
+		}
+		emitf(Xmark);		/* dummy value for Xlocal */
+		emitf(Xmark);
+		noglobs(c0, 0);
+		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(t->str);
+		t->str=0;	/* passed ownership */
+		break;
+	case DUP:
+		if(t->rtype==DUPFD){
+			emitf(Xdup);
+			emiti(t->fd0);
+			emiti(t->fd1);
+		}
+		else{
+			emitf(Xclose);
+			emiti(t->fd0);
+		}
+		outcode(c1, eflag);
+		emitf(Xpopredir);
+		break;
+	case PIPEFD:
+		emitf(Xpipefd);
+		emiti(t->rtype);
+		p = emiti(0);
+		codeline = 0;	/* force source */
+		outcode(c0, eflag);
+		emitf(Xexit);
+		stuffdot(p);
+		break;
+	case REDIR:
+		if(t->rtype!=HERE){
+			emitf(Xmark);
+			outcode(c0, eflag);
+		}
+		switch(t->rtype){
+		case APPEND:
+			emitf(Xappend);
+			break;
+		case WRITE:
+			emitf(Xwrite);
+			break;
+		case READ:
+			emitf(Xread);
+			break;
+		case RDWR:
+			emitf(Xrdwr);
+			break;
+		case HERE:
+			emitf(c0->quoted?Xhereq:Xhere);
+			emits(t->str);
+			t->str=0;	/* passed ownership */
+			break;
+		}
+		emiti(t->fd0);
+		outcode(c1, eflag);
+		emitf(Xpopredir);
+		break;
+	case '=':
+		tt = t;
+		for(;t && t->type=='=';t = c2);
+		if(t){					/* var=value cmd */
+			for(t = tt;t->type=='=';t = c2){
+				emitf(Xmark);
+				outcode(c1, eflag);
+				emitf(Xmark);
+				noglobs(c0, 0);
+				outcode(c0, eflag);
+				emitf(Xlocal);		/* push var for cmd */
+			}
+			outcode(t, eflag);		/* gen. code for cmd */
+			for(t = tt; t->type == '='; t = c2)
+				emitf(Xunlocal);	/* pop var */
+		}
+		else{					/* var=value */
+			for(t = tt;t;t = c2){
+				emitf(Xmark);
+				outcode(c1, eflag);
+				emitf(Xmark);
+				noglobs(c0, 0);
+				outcode(c0, eflag);
+				emitf(Xassign);	/* set var permanently */
+			}
+		}
+		t = tt;	/* so tests below will work */
+		break;
+	case PIPE:
+		emitf(Xpipe);
+		emiti(t->fd0);
+		emiti(t->fd1);
+		p = emiti(0);
+		q = emiti(0);
+		codeline = 0;	/* force source */
+		outcode(c0, eflag);
+		emitf(Xexit);
+		stuffdot(p);
+		codeline = 0;	/* force source */
+		outcode(c1, eflag);
+		emitf(Xreturn);
+		stuffdot(q);
+		emitf(Xpipewait);
+		break;
+	}
+	if(t->glob > 0)
+		emitf(Xglob);
+	if(t->type!=NOT && t->type!=';')
+		lex->iflast = t->type==IF;
+	else if(c0)
+		lex->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]==0
+	|| c1->child[0]->type!=';'
+	|| !iscase(c1->child[0]->child[0])){
+		yyerror("case missing in switch");
+		return;
+	}
+	emitf(Xmark);
+	outcode(c0, eflag);
+	emitf(Xqw);
+	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) {
+			noglobs(c1, 1);
+			outcode(c1, eflag);
+		}
+		emitf(Xcase);
+		nextcase = emiti(0);
+		t = tt;
+		for(;;){
+			if(t->type==';'){
+				if(iscase(c0)) break;
+				outcode(c0, eflag);
+				t = c1;
+			}
+			else{
+				if(!iscase(t)) 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+2;p->f;p++){
+		if(p->f==Xappend || p->f==Xclose || p->f==Xread || p->f==Xwrite
+		|| p->f==Xrdwr
+		|| p->f==Xasync || p->f==Xbackq || p->f==Xcase || p->f==Xfalse
+		|| p->f==Xfor || p->f==Xjump
+		|| p->f==Xsrcline
+		|| p->f==Xsubshell || p->f==Xtrue) p++;
+		else if(p->f==Xdup || p->f==Xpipefd) p+=2;
+		else if(p->f==Xpipe) p+=4;
+		else if(p->f==Xhere || p->f==Xhereq) free(p[1].s), p+=2;
+		else if(p->f==Xword) free((++p)->s);
+		else if(p->f==Xfn){
+			free(p[2].s);
+			p+=2;
+		}
+	}
+	free(cp[1].s);
+	free(cp);
+}
--- /dev/null
+++ b/exec.c
@@ -1,0 +1,1159 @@
+#include "rc.h"
+#include "getflags.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+
+/*
+ * Start executing the given code at the given pc with the given redirection
+ */
+void
+start(code *c, int pc, var *local, redir *redir)
+{
+	thread *p = new(thread);
+	p->code = codecopy(c);
+	p->line = 0;
+	p->pc = pc;
+	p->argv = 0;
+	p->redir = p->startredir = redir;
+	p->lex = 0;
+	p->local = local;
+	p->iflag = 0;
+	p->pid = 0;
+	p->status = 0;
+	p->ret = runq;
+	runq = p;
+}
+
+void
+startfunc(var *func, word *starval, var *local, redir *redir)
+{
+	start(func->fn, func->pc, local, redir);
+	runq->local = newvar("*", runq->local);
+	runq->local->val = starval;
+	runq->local->changed = 1;
+}
+
+static void
+popthread(void)
+{
+	thread *p = runq;
+	while(p->argv) poplist();
+	while(p->local && (p->ret==0 || p->local!=p->ret->local))
+		Xunlocal();
+	runq = p->ret;
+	if(p->lex) freelexer(p->lex);
+	codefree(p->code);
+	free(p->status);
+	free(p);
+}
+
+word*
+Newword(char *s, word *next)
+{
+	word *p=new(word);
+	p->word = s;
+	p->next = next;
+	return p;
+}
+word*
+newword(char *s, word *next)
+{
+	return Newword(estrdup(s), next);
+}
+word*
+Pushword(char *s)
+{
+	word *p;
+	if(s==0)
+		panic("null pushword", 0);
+	if(runq->argv==0)
+		panic("pushword but no argv!", 0);
+	p = Newword(s, runq->argv->words);
+	runq->argv->words = p;
+	return p;
+}
+word*
+pushword(char *s)
+{
+	return Pushword(estrdup(s));
+}
+char*
+Freeword(word *p)
+{
+	char *s = p->word;
+	free(p);
+	return s;
+}
+void
+freewords(word *w)
+{
+	word *p;
+	while((p = w)!=0){
+		w = w->next;
+		free(Freeword(p));
+	}
+}
+char*
+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;
+	return Freeword(p);
+}
+void
+popword(void)
+{
+	free(Popword());
+}
+
+void
+pushlist(void)
+{
+	list *p = new(list);
+	p->words = 0;
+	p->next = runq->argv;
+	runq->argv = p;
+}
+word*
+Poplist(void)
+{
+	word *w;
+	list *p = runq->argv;
+	if(p==0)
+		panic("poplist but no argv", 0);
+	w = p->words;
+	runq->argv = p->next;
+	free(p);
+	return w;
+}
+void
+poplist(void)
+{
+	freewords(Poplist());
+}
+
+int
+count(word *w)
+{
+	int n;
+	for(n = 0;w;n++) w = w->next;
+	return n;
+}
+
+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;
+}
+
+static void
+dontclose(int fd)
+{
+	redir *rp;
+
+	if(fd<0)
+		return;
+	for(rp = runq->redir; rp != runq->startredir; rp = rp->next){
+		if(rp->type == RCLOSE && rp->from == fd){
+			rp->type = 0;
+			break;
+		}
+	}
+}
+
+/*
+ * we are about to start a new thread that should exit on
+ * return, so the current stack is not needed anymore.
+ * free all the threads and lexers, but preserve the
+ * redirections and anything referenced by local.
+ */
+void
+turfstack(var *local)
+{
+	while(local){
+		thread *p;
+
+		for(p = runq; p && p->local == local; p = p->ret)
+			p->local = local->next;
+		local = local->next;
+	}
+	while(runq) {
+		if(runq->lex) dontclose(runq->lex->input->fd);
+		popthread();
+	}
+}
+
+void
+shuffleredir(void)
+{
+	redir **rr, *rp;
+
+	rp = runq->redir;
+	if(rp==0)
+		return;
+	runq->redir = rp->next;
+	rp->next = runq->startredir;
+	for(rr = &runq->redir; *rr != rp->next; rr = &((*rr)->next))
+		;
+	*rr = rp;
+}
+
+/*
+ * get command line flags, initialize keywords & traps.
+ * get values from environment.
+ * set $pid, $cflag, $*
+ * fabricate bootstrap code and start it (*=(argv);. -bq /usr/lib/rcmain $*)
+ * start interpreting code
+ */
+char *argv0="rc";
+
+void
+main(int argc, char *argv[])
+{
+	code bootstrap[20];
+	char num[12];
+	char *rcmain=Rcmain;
+
+	int i;
+	argv0 = argv[0];
+	argc = getflags(argc, argv, "srdiIlxebpvVc:1m:1[command]", 1);
+	if(argc==-1)
+		usage("[file [arg ...]]");
+	if(argv[0][0]=='-')
+		flag['l'] = flagset;
+	if(flag['I'])
+		flag['i'] = 0;
+	else if(flag['i']==0 && argc==1 && Isatty(0)) flag['i'] = flagset;
+	if(flag['m']) rcmain = flag['m'][0];
+	err = openiofd(2);
+	kinit();
+	Trapinit();
+	Vinit();
+	inttoascii(num, mypid = getpid());
+	setvar("pid", newword(num, (word *)0));
+	setvar("cflag", flag['c']?newword(flag['c'][0], (word *)0)
+				:(word *)0);
+	setvar("rcname", newword(argv[0], (word *)0));
+	bootstrap[0].i = 1;
+	bootstrap[1].s="*bootstrap*";
+	bootstrap[2].f = Xmark;
+	bootstrap[3].f = Xword;
+	bootstrap[4].s="*";
+	bootstrap[5].f = Xassign;
+	bootstrap[6].f = Xmark;
+	bootstrap[7].f = Xmark;
+	bootstrap[8].f = Xword;
+	bootstrap[9].s="*";
+	bootstrap[10].f = Xdol;
+	bootstrap[11].f = Xword;
+	bootstrap[12].s = rcmain;
+	bootstrap[13].f = Xword;
+	bootstrap[14].s="-bq";
+	bootstrap[15].f = Xword;
+	bootstrap[16].s=".";
+	bootstrap[17].f = Xsimple;
+	bootstrap[18].f = Xexit;
+	bootstrap[19].f = 0;
+	start(bootstrap, 2, (var*)0, (redir*)0);
+
+	/* prime bootstrap argv */
+	pushlist();
+	for(i = argc-1;i!=0;--i) pushword(argv[i]);
+
+	for(;;){
+		if(flag['r'])
+			pfnc(err, runq);
+		(*runq->code[runq->pc++].f)();
+		if(ntrap)
+			dotrap();
+	}
+}
+/*
+ * 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(split){... 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
+ * Xdol(name)				get variable value
+ * 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
+ * Xglob(word)				glob word inplace
+ * 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
+ * Xpush(words)				push words down a list
+ * Xqw(words)				quote words inplace
+ * Xrdwr(file)[fd]			open file for reading and writing
+ * Xread(file)[fd]			open file to read
+ * Xreturn				kill thread
+ * Xsimple(args)			run command and wait
+ * Xsrcline[line]			set current source line number
+ * 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
+ */
+
+void
+Xappend(void)
+{
+	char *file;
+	int fd;
+
+	switch(count(runq->argv->words)){
+	default:
+		Xerror1(">> requires singleton");
+		return;
+	case 0:
+		Xerror1(">> requires file");
+		return;
+	case 1:
+		break;
+	}
+	file = runq->argv->words->word;
+	if((fd = Open(file, 1))<0 && (fd = Creat(file))<0){
+		Xerror3(">> can't open", file, Errstr());
+		return;
+	}
+	Seek(fd, 0L, 2);
+	pushredir(ROPEN, fd, runq->code[runq->pc++].i);
+	poplist();
+}
+
+void
+Xsettrue(void)
+{
+	setstatus("");
+}
+
+void
+Xbang(void)
+{
+	setstatus(truestatus()?"false":"");
+}
+
+void
+Xclose(void)
+{
+	pushredir(RCLOSE, runq->code[runq->pc++].i, 0);
+}
+
+void
+Xdup(void)
+{
+	pushredir(RDUP, runq->code[runq->pc].i, runq->code[runq->pc+1].i);
+	runq->pc+=2;
+}
+
+void
+Xeflag(void)
+{
+	if(!truestatus()) Xexit();
+}
+
+void
+Xexit(void)
+{
+	static int beenhere = 0;
+
+	if(getpid()==mypid && !beenhere){
+		var *trapreq = vlook("sigexit");
+		word *starval = vlook("*")->val;
+		if(trapreq->fn){
+			beenhere = 1;
+			--runq->pc;
+			startfunc(trapreq, copywords(starval, (word*)0), (var*)0, (redir*)0);
+			return;
+		}
+	}
+	Exit();
+}
+
+void
+Xfalse(void)
+{
+	if(truestatus()) runq->pc = runq->code[runq->pc].i;
+	else runq->pc++;
+}
+int ifnot;		/* dynamic if not flag */
+
+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
+Xmark(void)
+{
+	pushlist();
+}
+
+void
+Xpopm(void)
+{
+	poplist();
+}
+
+void
+Xpush(void)
+{
+	word *t, *h = Poplist();
+	for(t = h; t->next; t = t->next)
+		;
+	t->next = runq->argv->words;
+	runq->argv->words = h;
+}
+
+static int
+herefile(char *tmp)
+{
+	char *s = tmp+strlen(tmp)-1;
+	static int ser;
+	int fd, i;
+
+	i = ser++;
+	while(*s == 'Y'){
+		*s-- = (i%26) + 'A';
+		i = i/26;
+	}
+	i = getpid();
+	while(*s == 'X'){
+		*s-- = (i%10) + '0';
+		i = i/10;
+	}
+	s++;
+	for(i='a'; i<'z'; i++){
+		if(access(tmp, 0)!=0 && (fd = Creat(tmp))>=0)
+			return fd;
+		*s = i;
+	}
+	return -1;
+}
+
+void
+Xhere(void)
+{
+	char file[]="/tmp/hereXXXXXXXXXXYY";
+	int fd;
+	io *io;
+
+	if((fd = herefile(file))<0){
+		Xerror3("<< can't get temp file", file, Errstr());
+		return;
+	}
+	io = openiofd(fd);
+	psubst(io, (unsigned char*)runq->code[runq->pc++].s);
+	flushio(io);
+	closeio(io);
+
+	/* open for reading and unlink */
+	if((fd = Open(file, 3))<0){
+		Xerror3("<< can't open", file, Errstr());
+		return;
+	}
+	pushredir(ROPEN, fd, runq->code[runq->pc++].i);
+}
+
+void
+Xhereq(void)
+{
+	char file[]="/tmp/hereXXXXXXXXXXYY", *body;
+	int fd;
+
+	if((fd = herefile(file))<0){
+		Xerror3("<< can't get temp file", file, Errstr());
+		return;
+	}
+	body = runq->code[runq->pc++].s;
+	Write(fd, body, strlen(body));
+	Close(fd);
+
+	/* open for reading and unlink */
+	if((fd = Open(file, 3))<0){
+		Xerror3("<< can't open", file, Errstr());
+		return;
+	}
+	pushredir(ROPEN, fd, runq->code[runq->pc++].i);
+}
+
+void
+Xread(void)
+{
+	char *file;
+	int fd;
+
+	switch(count(runq->argv->words)){
+	default:
+		Xerror1("< requires singleton");
+		return;
+	case 0:
+		Xerror1("< requires file");
+		return;
+	case 1:
+		break;
+	}
+	file = runq->argv->words->word;
+	if((fd = Open(file, 0))<0){
+		Xerror3("< can't open", file, Errstr());
+		return;
+	}
+	pushredir(ROPEN, fd, runq->code[runq->pc++].i);
+	poplist();
+}
+
+void
+Xrdwr(void)
+{
+	char *file;
+	int fd;
+
+	switch(count(runq->argv->words)){
+	default:
+		Xerror1("<> requires singleton");
+		return;
+	case 0:
+		Xerror1("<> requires file");
+		return;
+	case 1:
+		break;
+	}
+	file = runq->argv->words->word;
+	if((fd = Open(file, 2))<0){
+		Xerror3("<> can't open", file, Errstr());
+		return;
+	}
+	pushredir(ROPEN, fd, runq->code[runq->pc++].i);
+	poplist();
+}
+
+void
+Xpopredir(void)
+{
+	redir *rp = runq->redir;
+
+	if(rp==0)
+		panic("Xpopredir null!", 0);
+	runq->redir = rp->next;
+	if(rp->type==ROPEN)
+		Close(rp->from);
+	free(rp);
+}
+
+void
+Xreturn(void)
+{
+	while(runq->redir!=runq->startredir)
+		Xpopredir();
+	popthread();
+	if(runq==0)
+		Exit();
+}
+
+void
+Xtrue(void)
+{
+	if(truestatus()) runq->pc++;
+	else runq->pc = runq->code[runq->pc].i;
+}
+
+void
+Xif(void)
+{
+	ifnot = 1;
+	if(truestatus()) runq->pc++;
+	else runq->pc = runq->code[runq->pc].i;
+}
+
+void
+Xwastrue(void)
+{
+	ifnot = 0;
+}
+
+void
+Xword(void)
+{
+	pushword(runq->code[runq->pc++].s);
+}
+
+void
+Xwrite(void)
+{
+	char *file;
+	int fd;
+
+	switch(count(runq->argv->words)){
+	default:
+		Xerror1("> requires singleton");
+		return;
+	case 0:
+		Xerror1("> requires file");
+		return;
+	case 1:
+		break;
+	}
+	file = runq->argv->words->word;
+	if((fd = Creat(file))<0){
+		Xerror3("> can't create", file, Errstr());
+		return;
+	}
+	pushredir(ROPEN, fd, runq->code[runq->pc++].i);
+	poplist();
+}
+
+void
+Xmatch(void)
+{
+	word *p;
+	char *s;
+
+	setstatus("no match");
+	s = runq->argv->words->word;
+	for(p = runq->argv->next->words;p;p = p->next)
+		if(match(s, p->word, '\0')){
+			setstatus("");
+			break;
+		}
+	poplist();
+	poplist();
+}
+
+void
+Xcase(void)
+{
+	word *p;
+	char *s;
+	int ok = 0;
+
+	s = runq->argv->next->words->word;
+	for(p = runq->argv->words;p;p = p->next){
+		if(match(s, p->word, '\0')){
+			ok = 1;
+			break;
+		}
+	}
+	if(ok)
+		runq->pc++;
+	else
+		runq->pc = runq->code[runq->pc].i;
+	poplist();
+}
+
+static word*
+conclist(word *lp, word *rp, word *tail)
+{
+	word *v, *p, **end;
+	int ln, rn;
+
+	for(end = &v;;){
+		ln = strlen(lp->word), rn = strlen(rp->word);
+		p = Newword(emalloc(ln+rn+1), (word *)0);
+		memmove(p->word, lp->word, ln);
+		memmove(p->word+ln, rp->word, rn+1);
+		*end = p, end = &p->next;
+		if(lp->next == 0 && rp->next == 0)
+			break;
+		if(lp->next) lp = lp->next;
+		if(rp->next) rp = rp->next;
+	}
+	*end = tail;
+	return v;
+}
+
+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){
+			Xerror1("null list in concatenation");
+			return;
+		}
+		if(lc!=1 && rc!=1 && lc!=rc){
+			Xerror1("mismatched list lengths in concatenation");
+			return;
+		}
+		vp = conclist(lp, rp, vp);
+	}
+	poplist();
+	poplist();
+	runq->argv->words = vp;
+}
+
+void
+Xassign(void)
+{
+	var *v;
+
+	if(count(runq->argv->words)!=1){
+		Xerror1("= variable name not singleton!");
+		return;
+	}
+	v = vlook(runq->argv->words->word);
+	poplist();
+	freewords(v->val);
+	v->val = Poplist();
+	v->changed = 1;
+}
+
+/*
+ * 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;
+}
+
+void
+Xdol(void)
+{
+	word *a, *star;
+	char *s, *t;
+	int n;
+
+	if(count(runq->argv->words)!=1){
+		Xerror1("$ variable name not singleton!");
+		return;
+	}
+	n = 0;
+	s = runq->argv->words->word;
+	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
+Xqw(void)
+{
+	char *s, *d;
+	word *a, *p;
+	int n;
+
+	a = runq->argv->words;
+	if(a==0){
+		pushword("");
+		return;
+	}
+	if(a->next==0)
+		return;
+	n=0;
+	for(p=a;p;p=p->next)
+		n+=1+strlen(p->word);
+	s = emalloc(n+1);
+	d = s;
+	d += strlen(strcpy(d, a->word));
+	for(p=a->next;p;p=p->next){
+		*d++=' ';
+		d += strlen(strcpy(d, p->word));
+	}
+	free(a->word);
+	freewords(a->next);
+	a->word = s;
+	a->next = 0;
+}
+
+static word*
+copynwords(word *a, word *tail, int n)
+{
+	word *v, **end;
+	
+	v = 0;
+	end = &v;
+	while(n-- > 0){
+		*end = newword(a->word, 0);
+		end = &(*end)->next;
+		a = a->next;
+	}
+	*end = tail;
+	return v;
+}
+
+static word*
+subwords(word *val, int len, word *sub, word *a)
+{
+	int n, m;
+	char *s;
+
+	if(sub==0)
+		return a;
+	a = subwords(val, len, sub->next, a);
+	s = sub->word;
+	m = 0;
+	n = 0;
+	while('0'<=*s && *s<='9')
+		n = n*10+ *s++ -'0';
+	if(*s == '-'){
+		if(*++s == 0)
+			m = len - n;
+		else{
+			while('0'<=*s && *s<='9')
+				m = m*10+ *s++ -'0';
+			m -= n;
+		}
+	}
+	if(n<1 || n>len || m<0)
+		return a;
+	if(n+m>len)
+		m = len-n;
+	while(--n > 0)
+		val = val->next;
+	return copynwords(val, a, m+1);
+}
+
+void
+Xsub(void)
+{
+	word *a, *v;
+	char *s;
+
+	if(count(runq->argv->next->words)!=1){
+		Xerror1("$() variable name not singleton!");
+		return;
+	}
+	s = runq->argv->next->words->word;
+	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
+Xcount(void)
+{
+	word *a;
+	char *s, *t, num[12];
+	int n;
+
+	if(count(runq->argv->words)!=1){
+		Xerror1("$# variable name not singleton!");
+		return;
+	}
+	n = 0;
+	s = runq->argv->words->word;
+	for(t = s;'0'<=*t && *t<='9';t++) n = n*10+*t-'0';
+	if(n==0 || *t){
+		a = vlook(s)->val;
+		inttoascii(num, count(a));
+	}
+	else{
+		a = vlook("*")->val;
+		inttoascii(num, a && 1<=n && n<=count(a)?1:0);
+	}
+	poplist();
+	pushword(num);
+}
+
+void
+Xlocal(void)
+{
+	if(count(runq->argv->words)!=1){
+		Xerror1("local variable name must be singleton");
+		return;
+	}
+	runq->local = newvar(runq->argv->words->word, runq->local);
+	poplist();
+	runq->local->val = Poplist();
+	runq->local->changed = 1;
+}
+
+void
+Xunlocal(void)
+{
+	var *hid, *v = runq->local;
+	if(v==0)
+		panic("Xunlocal: no locals!", 0);
+	runq->local = v->next;
+	hid = vlook(v->name);
+	hid->changed = 1;
+	freevar(v);
+}
+
+void
+Xfn(void)
+{
+	var *v;
+	word *a;
+	int pc = runq->pc;
+	runq->pc = runq->code[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 = pc+2;
+		v->fnchanged = 1;
+	}
+	poplist();
+}
+
+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();
+}
+
+static char*
+concstatus(char *s, char *t)
+{
+	int n, m;
+
+	if(t==0) return s;
+	if(s==0) return t;
+	n = strlen(s);
+	m = strlen(t);
+	s = erealloc(s, n+m+2);
+	if(n > 0) s[n++]='|';
+	memmove(s+n, t, m+1);
+	free(t);
+	return s;
+}
+
+void
+Xpipewait(void)
+{
+	char *old = Getstatus();
+	if(runq->pid==-1){
+		Setstatus(concstatus(runq->status, old));
+		runq->status=0;
+	}else{
+		while(Waitfor(runq->pid) < 0)
+			;
+		runq->pid=-1;
+		Setstatus(concstatus(Getstatus(), old));
+	}
+}
+
+static char *promptstr;
+
+void
+Xrdcmds(void)
+{
+	thread *p = runq;
+
+	if(flag['s'] && !truestatus())
+		pfmt(err, "status=%v\n", vlook("status")->val);
+	flushio(err);
+
+	lex = p->lex;
+	if(p->iflag){
+		word *prompt = vlook("prompt")->val;
+		if(prompt)
+			promptstr = prompt->word;
+		else
+			promptstr="% ";
+	}
+	Noerror();
+	nerror = 0;
+	if(yyparse()){
+		if(p->iflag && (!lex->eof || Eintr())){
+			if(Eintr()){
+				pchr(err, '\n');
+				lex->eof = 0;
+			}
+			--p->pc;	/* go back for next command */
+		}
+	}
+	else{
+		if(lex->eof){
+			dontclose(lex->input->fd);
+			freelexer(lex);
+			p->lex = 0;
+		} else
+			--p->pc;	/* re-execute Xrdcmds after codebuf runs */
+		start(codebuf, 2, p->local, p->redir);
+	}
+	lex = 0;
+	freenodes();
+}
+
+void
+pprompt(void)
+{
+	word *prompt;
+
+	if(!runq->iflag)
+		return;
+
+	Prompt(promptstr);
+	doprompt = 0;
+
+	prompt = vlook("prompt")->val;
+	if(prompt && prompt->next)
+		promptstr = prompt->next->word;
+	else
+		promptstr = "\t";
+}
+
+char*
+srcfile(thread *p)
+{
+	return p->code[1].s;
+}
+
+void
+Xerror1(char *s)
+{
+	setstatus("error");
+	pfln(err, srcfile(runq), runq->line);
+	pfmt(err, ": %s\n", s);
+	flushio(err);
+	while(!runq->iflag) Xreturn();
+}
+void
+Xerror2(char *s, char *e)
+{
+	setstatus(e);
+	pfln(err, srcfile(runq), runq->line);
+	pfmt(err, ": %s: %s\n", s, e);
+	flushio(err);
+	while(!runq->iflag) Xreturn();
+}
+void
+Xerror3(char *s, char *m, char *e)
+{
+	setstatus(e);
+	pfln(err, srcfile(runq), runq->line);
+	pfmt(err, ": %s: %s: %s\n", s, m, e);
+	flushio(err);
+	while(!runq->iflag) Xreturn();
+}
+
+void
+Setstatus(char *s)
+{
+	setvar("status", Newword(s?s:estrdup(""), (word *)0));
+}
+void
+setstatus(char *s)
+{
+	Setstatus(estrdup(s));
+}
+char*
+Getstatus(void)
+{
+	var *status = vlook("status");
+	word *val = status->val;
+	if(val==0) return 0;
+	status->val=0;
+	status->changed=1;
+	freewords(val->next);
+	return Freeword(val);
+}
+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;
+}
+
+void
+Xfor(void)
+{
+	word *a = runq->argv->words;
+	if(a==0){
+		poplist();
+		runq->pc = runq->code[runq->pc].i;
+	}
+	else{
+		runq->argv->words = a->next;
+		a->next = 0;
+		freewords(runq->local->val);
+		runq->local->val = a;
+		runq->local->changed = 1;
+		runq->pc++;
+	}
+}
+
+void
+Xglob(void)
+{
+	globword(runq->argv->words);
+}
+
+void
+Xsrcline(void)
+{
+	runq->line = runq->code[runq->pc++].i;
+}
--- /dev/null
+++ b/exec.h
@@ -1,0 +1,86 @@
+/*
+ * Definitions used in the interpreter
+ */
+extern void Xappend(void), Xasync(void), Xbackq(void), Xbang(void), Xclose(void);
+extern void Xconc(void), Xcount(void), Xdelfn(void), Xdol(void), Xqw(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), Xhere(void), Xhereq(void);
+extern void Xrdwr(void), Xsrcline(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), Xpush(void);
+extern void Xrdcmds(void), Xwastrue(void), Xif(void), Xifnot(void), Xpipewait(void);
+extern void Xpopredir(void), Xsub(void), Xeflag(void), Xsettrue(void);
+extern void Xerror1(char*);
+extern void Xerror2(char*,char*);
+extern void Xerror3(char*,char*,char*);
+
+/*
+ * word lists are in correct order,
+ * i.e. word0->word1->word2->word3->0
+ */
+struct word{
+	char *word;
+	word *next;
+};
+struct list{
+	word *words;
+	list *next;
+};
+word *newword(char *, word *), *copywords(word *, word *);
+
+struct redir{
+	int type;			/* what to do */
+	int from, to;			/* what to do it to */
+	redir *next;			/* what else to do (reverse order) */
+};
+
+/*
+ * redir types
+ */
+#define	ROPEN	1			/* dup2(from, to); close(from); */
+#define	RDUP	2			/* dup2(from, to); */
+#define	RCLOSE	3			/* close(from); */
+void	shuffleredir(void);
+
+struct thread{
+	code *code;			/* code for this thread */
+	int pc;				/* code[pc] is the next instruction */
+	int line;			/* source code line for Xsrcline */
+	list *argv;			/* argument stack */
+	redir *redir;			/* redirection stack */
+	redir *startredir;		/* redir inheritance point */
+	var *local;			/* list of local variables */
+	lexer *lex;			/* lexer for Xrdcmds */
+	int iflag;			/* interactive? */
+	int pid;			/* process for Xpipewait to wait for */
+	char *status;			/* status for Xpipewait */
+	thread *ret;			/* who continues when this finishes */
+};
+
+thread *runq;
+void turfstack(var*);
+
+code *codecopy(code*);
+code *codebuf;				/* compiler output */
+extern int ifnot;
+
+int ntrap;				/* number of outstanding traps */
+int trap[NSIG];				/* number of outstanding traps per type */
+struct builtin{
+	char *name;
+	void (*fnc)(void);
+};
+extern void (*builtinfunc(char *name))(void);
+
+void execcd(void), execwhatis(void), execeval(void), execexec(void);
+int execforkexec(void);
+void execexit(void), execshift(void);
+void execwait(void), execumask(void), execdot(void), execflag(void);
+void execfunc(var*), execcmds(io*, char*, var*, redir*);
+void startfunc(var*, word*, var*, redir*);
+
+char *srcfile(thread*);
+char *getstatus(void);
+
+extern char *argv0;
--- /dev/null
+++ b/fns.h
@@ -1,0 +1,70 @@
+void	Abort(void);
+int	Chdir(char*);
+void	Close(int);
+void	Closedir(void*);
+int	Creat(char*);
+int	Dup(int, int);
+int	Dup1(int);
+int	Eintr(void);
+int	Executable(char*);
+void	Exec(char**);
+void	Exit(void);
+char*	Errstr(void);
+char*	Freeword(word*);
+int	Fork(void);
+char*	Getstatus(void);
+int	Isatty(int);
+word*	Newword(char*,word*);
+void	Noerror(void);
+int	Open(char*, int);
+void*	Opendir(char*);
+word*	Poplist(void);
+char*	Popword(void);
+word*	Pushword(char*);
+long	Read(int, void*, long);
+char*	Readdir(void*, int);
+long	Seek(int, long, long);
+void	Setstatus(char*);
+void	Trapinit(void);
+void	Updenv(void);
+void	Vinit(void);
+int	Waitfor(int);
+long	Write(int, void*, long);
+void	addwaitpid(int);
+void	clearwaitpids(void);
+void	codefree(code*);
+int	compile(tree*);
+int	count(word*);
+char*	deglob(char*);
+void	delwaitpid(int);
+void	dotrap(void);
+void	freenodes(void);
+void	freewords(word*);
+void	globword(word*);
+int	havewaitpid(int);
+int	idchr(int);
+void	inttoascii(char*, int);
+void	kinit(void);
+int	mapfd(int);
+int	match(char*, char*, int);
+char*	makepath(char*, char*);
+void	panic(char*, int);
+void	pfln(io*, char*, int);
+void	poplist(void);
+void	popword(void);
+void	pprompt(void);
+void	Prompt(char*);
+void	psubst(io*, unsigned char*);
+void	pushlist(void);
+void	pushredir(int, int, int);
+word*	pushword(char*);
+char*	readhere(tree*, io*);
+void	setstatus(char*);
+void	skipnl(void);
+void	start(code*, int, var*, redir*);
+int	truestatus(void);
+void	usage(char*);
+int	wordchr(int);
+void	yyerror(char*);
+int	yylex(void);
+int	yyparse(void);
--- /dev/null
+++ b/getflags.c
@@ -1,0 +1,234 @@
+#include "rc.h"
+#include "getflags.h"
+#include "fns.h"
+char *flagset[] = {"<flag>"};
+char **flag[NFLAG];
+char *cmdname;
+static char *flagarg="";
+static void reverse(char**, char**);
+static int scanflag(int, char*);
+static void errn(char*, int);
+static void errs(char*);
+static void errc(int);
+static int reason;
+#define	RESET	1
+#define	FEWARGS	2
+#define	FLAGSYN	3
+#define	BADFLAG	4
+static int badflag;
+
+int
+getflags(int argc, char *argv[], char *flags, int stop)
+{
+	char *s;
+	int i, j, c, count;
+	flagarg = flags;
+	if(cmdname==0)
+		cmdname = argv[0];
+
+	i = 1;
+	while(i!=argc){
+		if(argv[i][0] != '-' || argv[i][1] == '\0'){
+			if(stop)		/* always true in rc */
+				return argc;
+			i++;
+			continue;
+		}
+		s = argv[i]+1;
+		while(*s){
+			c=*s++;
+			count = scanflag(c, flags);
+			if(count==-1)
+				return -1;
+			if(flag[c]){ reason = RESET; badflag = c; return -1; }
+			if(count==0){
+				flag[c] = flagset;
+				if(*s=='\0'){
+					for(j = i+1;j<=argc;j++)
+						argv[j-1] = argv[j];
+					--argc;
+				}
+			}
+			else{
+				if(*s=='\0'){
+					for(j = i+1;j<=argc;j++)
+						argv[j-1] = argv[j];
+					--argc;
+					s = argv[i];
+				}
+				if(argc-i<count){
+					reason = FEWARGS;
+					badflag = c;
+					return -1;
+				}
+				reverse(argv+i, argv+argc);
+				reverse(argv+i, argv+argc-count);
+				reverse(argv+argc-count+1, argv+argc);
+				argc-=count;
+				flag[c] = argv+argc+1;
+				flag[c][0] = s;
+				s="";
+			}
+		}
+	}
+	return argc;
+}
+
+static void
+reverse(char **p, char **q)
+{
+	char *t;
+	for(;p<q;p++,--q){ t=*p; *p=*q; *q = t; }
+}
+
+static int
+scanflag(int c, char *f)
+{
+	int fc, count;
+	if(0<=c && c<NFLAG)
+		while(*f){
+			if(*f==' '){
+				f++;
+				continue;
+			}
+			fc=*f++;
+			if(*f==':'){
+				f++;
+				if(*f<'0' || '9'<*f){ reason = FLAGSYN; return -1; }
+				count = 0;
+				while('0'<=*f && *f<='9') count = count*10+*f++-'0';
+			}
+			else
+				count = 0;
+			if(*f=='['){
+				do{
+					f++;
+					if(*f=='\0'){ reason = FLAGSYN; return -1; }
+				}while(*f!=']');
+				f++;
+			}
+			if(c==fc)
+				return count;
+		}
+	reason = BADFLAG;
+	badflag = c;
+	return -1;
+}
+
+void
+usage(char *tail)
+{
+	char *s, *t, c;
+	int count, nflag = 0;
+	switch(reason){
+	case RESET:
+		errs("Flag -");
+		errc(badflag);
+		errs(": set twice\n");
+		break;
+	case FEWARGS:
+		errs("Flag -");
+		errc(badflag);
+		errs(": too few arguments\n");
+		break;
+	case FLAGSYN:
+		errs("Bad argument to getflags!\n");
+		break;
+	case BADFLAG:
+		errs("Illegal flag -");
+		errc(badflag);
+		errc('\n');
+		break;
+	}
+	errs("Usage: ");
+	errs(cmdname);
+	for(s = flagarg;*s;){
+		c=*s;
+		if(*s++==' ')
+			continue;
+		if(*s==':'){
+			s++;
+			count = 0;
+			while('0'<=*s && *s<='9') count = count*10+*s++-'0';
+		}
+		else count = 0;
+		if(count==0){
+			if(nflag==0)
+				errs(" [-");
+			nflag++;
+			errc(c);
+		}
+		if(*s=='['){
+			s++;
+			while(*s!=']' && *s!='\0') s++;
+			if(*s==']')
+				s++;
+		}
+	}
+	if(nflag)
+		errs("]");
+	for(s = flagarg;*s;){
+		c=*s;
+		if(*s++==' ')
+			continue;
+		if(*s==':'){
+			s++;
+			count = 0;
+			while('0'<=*s && *s<='9') count = count*10+*s++-'0';
+		}
+		else count = 0;
+		if(count!=0){
+			errs(" [-");
+			errc(c);
+			if(*s=='['){
+				s++;
+				t = s;
+				while(*s!=']' && *s!='\0') s++;
+				errs(" ");
+				errn(t, s-t);
+				if(*s==']')
+					s++;
+			}
+			else
+				while(count--) errs(" arg");
+			errs("]");
+		}
+		else if(*s=='['){
+			s++;
+			while(*s!=']' && *s!='\0') s++;
+			if(*s==']')
+				s++;
+		}
+	}
+	if(tail){
+		errs(" ");
+		errs(tail);
+	}
+	errs("\n");
+	setstatus("bad flags");
+	Exit();
+}
+
+static void
+errn(char *s, int count)
+{
+	while(count){ errc(*s++); --count; }
+}
+
+static void
+errs(char *s)
+{
+	while(*s) errc(*s++);
+}
+#define	NBUF	80
+static char buf[NBUF], *bufp = buf;
+
+static void
+errc(int c)
+{
+	*bufp++=c;
+	if(bufp==&buf[NBUF] || c=='\n'){
+		Write(2, buf, bufp-buf);
+		bufp = buf;
+	}
+}
--- /dev/null
+++ b/getflags.h
@@ -1,0 +1,7 @@
+#define	NFLAG	128
+
+extern char **flag[NFLAG];
+extern char *cmdname;
+extern char *flagset[];
+
+int getflags(int, char*[], char*, int);
--- /dev/null
+++ b/glob.c
@@ -1,0 +1,259 @@
+#include "rc.h"
+#include "exec.h"
+#include "fns.h"
+
+/*
+ * delete all the GLOB marks from s, in place
+ */
+char*
+deglob(char *s)
+{
+	char *r = strchr(s, GLOB);
+	if(r){
+		char *w = r++;
+		do{
+			if(*r==GLOB)
+				r++;
+			*w++=*r;
+		}while(*r++);
+	}
+	return s;
+}
+
+static int
+globcmp(const void *s, const void *t)
+{
+	return strcmp(*(char**)s, *(char**)t);
+}
+
+static void
+globsort(word *left, word *right)
+{
+	char **list;
+	word *a;
+	int n = 0;
+	for(a = left;a!=right;a = a->next) n++;
+	list = (char **)emalloc(n*sizeof(char *));
+	for(a = left,n = 0;a!=right;a = a->next,n++) list[n] = a->word;
+	qsort((void *)list, n, sizeof(void *), globcmp);
+	for(a = left,n = 0;a!=right;a = a->next,n++) a->word = list[n];
+	free(list);
+}
+
+/*
+ * 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
+ */
+
+static 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, '/');
+}
+
+static void
+pappend(char **pdir, char *name)
+{
+	char *path = makepath(*pdir, name);
+	free(*pdir);
+	*pdir = path;
+}
+
+static word*
+globdir(word *list, char *pattern, char *name)
+{
+	char *slash, *glob, *entry;
+	void *dir;
+
+#ifdef Plan9
+	/* append slashes, Readdir() already filtered directories */
+	while(*pattern=='/'){
+		pappend(&name, "/");
+		pattern++;
+	}
+#endif
+	if(*pattern=='\0')
+		return Newword(name, list);
+
+	/* scan the pattern looking for a component with a metacharacter in it */
+	glob=strchr(pattern, GLOB);
+
+	/* If we ran out of pattern, append the name if accessible */
+	if(glob==0){
+		pappend(&name, pattern);
+		if(access(name, 0)==0)
+			return Newword(name, list);
+		goto out;
+	}
+
+	*glob='\0';
+	slash=strrchr(pattern, '/');
+	if(slash){
+		*slash='\0';
+		pappend(&name, pattern);
+		*slash='/';
+		pattern=slash+1;
+	}
+	*glob=GLOB;
+
+	/* read the directory and recur for any entry that matches */
+	dir = Opendir(name[0]?name:".");
+	if(dir==0)
+		goto out;
+	slash=strchr(glob, '/');
+	while((entry=Readdir(dir, slash!=0)) != 0){
+		if(matchfn(entry, pattern))
+			list = globdir(list, slash?slash:"", makepath(name, entry));
+	}
+	Closedir(dir);
+out:
+	free(name);
+	return list;
+}
+
+/*
+ * Subsitute a word with its glob in place.
+ */
+void
+globword(word *w)
+{
+	word *left, *right;
+
+	if(w==0 || strchr(w->word, GLOB)==0)
+		return;
+	right = w->next;
+	left = globdir(right, w->word, estrdup(""));
+	if(left == right) {
+		deglob(w->word);
+	} else {
+		free(w->word);
+		globsort(left, right);
+		w->next = left->next;
+		w->word = Freeword(left);
+	}
+}
+
+/*
+ * Return a pointer to the next utf code in the string,
+ * not jumping past nuls in broken utf codes!
+ */
+static char*
+nextutf(char *p)
+{
+	int i, n, c = *p;
+
+	if(onebyte(c))
+		return p+1;
+	if(twobyte(c))
+		n = 2;
+	else if(threebyte(c))
+		n = 3;
+	else
+		n = 4;
+	for(i = 1; i < n; i++)
+		if(!xbyte(p[i]))
+			break;
+	return p+i;
+}
+
+/*
+ * Convert the utf code at *p to a unicode value
+ */
+static int
+unicode(char *p)
+{
+	int c = *p;
+
+	if(onebyte(c))
+		return c&0xFF;
+	if(twobyte(c)){
+		if(xbyte(p[1]))
+			return ((c&0x1F)<<6) | (p[1]&0x3F);
+	} else if(threebyte(c)){
+		if(xbyte(p[1]) && xbyte(p[2]))
+			return ((c&0x0F)<<12) | ((p[1]&0x3F)<<6) | (p[2]&0x3F);
+	} else if(fourbyte(c)){
+		if(xbyte(p[1]) && xbyte(p[2]) && xbyte(p[3]))
+			return ((c&0x07)<<18) | ((p[1]&0x3F)<<12) | ((p[2]&0x3F)<<6) | (p[3]&0x3F);
+	}
+	return -1;
+}
+
+/*
+ * Do p and q point at equal utf codes
+ */
+static int
+equtf(char *p, char *q)
+{
+	if(*p!=*q)
+ 		return 0;
+	return unicode(p) == unicode(q);
+}
+
+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';
+}
--- /dev/null
+++ b/havefork.c
@@ -1,0 +1,240 @@
+#include "rc.h"
+#include "getflags.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+
+static int *waitpids;
+static int nwaitpids;
+
+void
+addwaitpid(int pid)
+{
+	waitpids = erealloc(waitpids, (nwaitpids+1)*sizeof waitpids[0]);
+	waitpids[nwaitpids++] = pid;
+}
+
+void
+delwaitpid(int pid)
+{
+	int r, w;
+	
+	for(r=w=0; r<nwaitpids; r++)
+		if(waitpids[r] != pid)
+			waitpids[w++] = waitpids[r];
+	nwaitpids = w;
+}
+
+void
+clearwaitpids(void)
+{
+	nwaitpids = 0;
+}
+
+int
+havewaitpid(int pid)
+{
+	int i;
+
+	for(i=0; i<nwaitpids; i++)
+		if(waitpids[i] == pid)
+			return 1;
+	return 0;
+}
+
+void
+Xasync(void)
+{
+	int pid;
+	char npid[10];
+
+	switch(pid = Fork()){
+	case -1:
+		Xerror2("try again", Errstr());
+		break;
+	case 0:
+		clearwaitpids();
+		start(runq->code, runq->pc+1, runq->local, runq->redir);
+		runq->ret = 0;
+		break;
+	default:
+		addwaitpid(pid);
+		runq->pc = runq->code[runq->pc].i;
+		inttoascii(npid, pid);
+		setvar("apid", newword(npid, (word *)0));
+		break;
+	}
+}
+
+void
+Xpipe(void)
+{
+	thread *p = runq;
+	int pid, pc = p->pc;
+	int lfd = p->code[pc++].i;
+	int rfd = p->code[pc++].i;
+	int pfd[2];
+
+	if(pipe(pfd)<0){
+		Xerror2("can't get pipe", Errstr());
+		return;
+	}
+	switch(pid = Fork()){
+	case -1:
+		Xerror2("try again", Errstr());
+		break;
+	case 0:
+		clearwaitpids();
+		Close(pfd[PRD]);
+		start(p->code, pc+2, runq->local, runq->redir);
+		runq->ret = 0;
+		pushredir(ROPEN, pfd[PWR], lfd);
+		break;
+	default:
+		addwaitpid(pid);
+		Close(pfd[PWR]);
+		start(p->code, p->code[pc].i, runq->local, runq->redir);
+		pushredir(ROPEN, pfd[PRD], rfd);
+		p->pc = p->code[pc+1].i;
+		p->pid = pid;
+		break;
+	}
+}
+
+/*
+ * Who should wait for the exit from the fork?
+ */
+
+void
+Xbackq(void)
+{
+	int pid, pfd[2];
+	char *s, *split;
+	word *end, **link;
+	io *f;
+
+	if(pipe(pfd)<0){
+		Xerror2("can't make pipe", Errstr());
+		return;
+	}
+	switch(pid = Fork()){
+	case -1:
+		Xerror2("try again", Errstr());
+		Close(pfd[PRD]);
+		Close(pfd[PWR]);
+		return;
+	case 0:
+		clearwaitpids();
+		Close(pfd[PRD]);
+		start(runq->code, runq->pc+1, runq->local, runq->redir);
+		pushredir(ROPEN, pfd[PWR], 1);
+		return;
+	default:
+		addwaitpid(pid);
+		Close(pfd[PWR]);
+
+		split = Popword();
+		poplist();
+		f = openiofd(pfd[PRD]);
+		end = runq->argv->words;
+		link = &runq->argv->words;
+		while((s = rstr(f, split)) != 0){
+			*link = Newword(s, (word*)0);
+			link = &(*link)->next;
+		}
+		*link = end;
+		closeio(f);
+		free(split);
+
+		Waitfor(pid);
+
+		runq->pc = runq->code[runq->pc].i;
+		return;
+	}
+}
+
+void
+Xpipefd(void)
+{
+	thread *p = runq;
+	int pid, pc = p->pc;
+	char name[40];
+	int pfd[2];
+	int sidefd, mainfd;
+
+	if(pipe(pfd)<0){
+		Xerror2("can't get pipe", Errstr());
+		return;
+	}
+	if(p->code[pc].i==READ){
+		sidefd = pfd[PWR];
+		mainfd = pfd[PRD];
+	}
+	else{
+		sidefd = pfd[PRD];
+		mainfd = pfd[PWR];
+	}
+	switch(pid = Fork()){
+	case -1:
+		Xerror2("try again", Errstr());
+		break;
+	case 0:
+		clearwaitpids();
+		Close(mainfd);
+		start(p->code, pc+2, runq->local, runq->redir);
+		pushredir(ROPEN, sidefd, p->code[pc].i==READ?1:0);
+		runq->ret = 0;
+		break;
+	default:
+		addwaitpid(pid);
+		Close(sidefd);
+		pushredir(ROPEN, mainfd, mainfd);
+		shuffleredir();	/* shuffle redir to bottom of stack for Xpopredir() */
+		strcpy(name, Fdprefix);
+		inttoascii(name+strlen(name), mainfd);
+		pushword(name);
+		p->pc = p->code[pc+1].i;
+		break;
+	}
+}
+
+void
+Xsubshell(void)
+{
+	int pid;
+
+	switch(pid = Fork()){
+	case -1:
+		Xerror2("try again", Errstr());
+		break;
+	case 0:
+		clearwaitpids();
+		start(runq->code, runq->pc+1, runq->local, runq->redir);
+		runq->ret = 0;
+		break;
+	default:
+		addwaitpid(pid);
+		while(Waitfor(pid) < 0)
+			;
+		runq->pc = runq->code[runq->pc].i;
+		break;
+	}
+}
+
+int
+execforkexec(void)
+{
+	int pid;
+
+	switch(pid = Fork()){
+	case -1:
+		return -1;
+	case 0:
+		clearwaitpids();
+		pushword("exec");
+		execexec();
+		/* does not return */
+	}
+	addwaitpid(pid);
+	return pid;
+}
--- /dev/null
+++ b/here.c
@@ -1,0 +1,113 @@
+#include "rc.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+
+void psubst(io*, unsigned char*);
+void pstrs(io*, word*);
+
+char*
+readhere(tree *tag, io *in)
+{
+	io *out;
+	char c, *m;
+
+	if(tag->type!=WORD){
+		yyerror("Bad here tag");
+		return 0;
+	}
+	pprompt();
+	out = openiostr();
+	m = tag->str;
+	while((c = rchr(in)) != EOF){
+		if(c=='\0'){
+			yyerror("NUL bytes in here doc");
+			closeio(out);
+			return 0;
+		}
+		if(c=='\n'){
+			lex->line++;
+			if(m && *m=='\0'){
+				out->bufp -= m - tag->str;
+				*out->bufp='\0';
+				break;
+			}
+			pprompt();
+			m = tag->str;
+		} else if(m){
+			if(*m == c){
+				m++;
+			} else {
+				m = 0;
+			}
+		}
+		pchr(out, c);
+	}
+	doprompt = 1;
+	return closeiostr(out);
+}
+
+void
+psubst(io *f, unsigned char *s)
+{
+	unsigned char *t, *u;
+	word *star;
+	int savec, n;
+
+	while(*s){
+		if(*s!='$'){
+			if(0xa0 <= *s && *s <= 0xf5){
+				pchr(f, *s++);
+				if(*s=='\0')
+					break;
+			}
+			else if(0xf6 <= *s && *s <= 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((char *)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);
+	}
+}
--- /dev/null
+++ b/io.c
@@ -1,0 +1,302 @@
+#include "rc.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+
+enum {
+	NBUF = 8192,
+};
+
+void
+vpfmt(io *f, char *fmt, va_list ap)
+{
+	for(;*fmt;fmt++) {
+		if(*fmt!='%') {
+			pchr(f, *fmt);
+			continue;
+		}
+		if(*++fmt == '\0')		/* "blah%"? */
+			break;
+		switch(*fmt){
+		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':
+			pptr(f, va_arg(ap, void*));
+			break;
+		case 'Q':
+			pquo(f, va_arg(ap, char *));
+			break;
+		case 'q':
+			pwrd(f, va_arg(ap, char *));
+			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;
+		}
+	}
+}
+
+void
+pfmt(io *f, char *fmt, ...)
+{
+	va_list ap;
+	va_start(ap, fmt);
+	vpfmt(f, fmt, ap);
+	va_end(ap);
+}
+
+void
+pchr(io *b, int c)
+{
+	if(b->bufp>=b->ebuf)
+		flushio(b);
+	*b->bufp++=c;
+}
+
+int
+rchr(io *b)
+{
+	if(b->bufp>=b->ebuf)
+		return emptyiobuf(b);
+	return *b->bufp++;
+}
+
+char*
+rstr(io *b, char *stop)
+{
+	char *s, *p;
+	int l, m, n;
+
+	do {
+		l = rchr(b);
+		if(l == EOF)
+			return 0;
+	} while(l && strchr(stop, l));
+	b->bufp--;
+
+	s = 0;
+	l = 0;
+	for(;;){
+		p = (char*)b->bufp;
+		n = (char*)b->ebuf - p;
+		if(n > 0){
+			for(m = 0; m < n; m++){
+				if(strchr(stop, p[m])==0)
+					continue;
+
+				b->bufp += m+1;
+				if(m > 0 || s==0){
+					s = erealloc(s, l+m+1);
+					memmove(s+l, p, m);
+					l += m;
+				}
+				s[l]='\0';
+				return s;
+			}
+			s = erealloc(s, l+m+1);
+			memmove(s+l, p, m);
+			l += m;
+			b->bufp += m;
+		}
+		if(emptyiobuf(b) == EOF){
+			if(s) s[l]='\0';
+			return s;
+		}
+		b->bufp--;
+	}
+}
+
+void
+pquo(io *f, char *s)
+{
+	pchr(f, '\'');
+	for(;*s;s++){
+		if(*s=='\'')
+			pchr(f, *s);
+		pchr(f, *s);
+	}
+	pchr(f, '\'');
+}
+
+void
+pwrd(io *f, char *s)
+{
+	char *t;
+	for(t = s;*t;t++) if(*t >= 0 && strchr("`^#*[]=|\\?${}()'<>&;", *t)) break;
+	if(t==s || *t)
+		pquo(f, s);
+	else pstr(f, s);
+}
+
+void
+pptr(io *f, void *p)
+{
+	static char hex[] = "0123456789ABCDEF";
+	unsigned long long v;
+	int n;
+
+	v = (unsigned long long)p;
+	if(sizeof(v) == sizeof(p) && v>>32)
+		for(n = 60;n>=32;n-=4) pchr(f, hex[(v>>n)&0xF]);
+	for(n = 28;n>=0;n-=4) pchr(f, hex[(v>>n)&0xF]);
+}
+
+void
+pstr(io *f, char *s)
+{
+	if(s==0)
+		s="(null)";
+	while(*s) pchr(f, *s++);
+}
+
+void
+pdec(io *f, int 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, unsigned n)
+{
+	if(n>7)
+		poct(f, n>>3);
+	pchr(f, (n&7)+'0');
+}
+
+void
+pval(io *f, word *a)
+{
+	if(a==0)
+		return;
+	while(a->next && a->next->word){
+		pwrd(f, (char *)a->word);
+		pchr(f, ' ');
+		a = a->next;
+	}
+	pwrd(f, (char *)a->word);
+}
+
+io*
+newio(unsigned char *buf, int len, int fd)
+{
+	io *f = new(io);
+	f->buf = buf;
+	f->bufp = buf;
+	f->ebuf = buf+len;
+	f->fd = fd;
+	return f;
+}
+
+/*
+ * Open a string buffer for writing.
+ */
+io*
+openiostr(void)
+{
+	unsigned char *buf = emalloc(100+1);
+	memset(buf, '\0', 100+1);
+	return newio(buf, 100, -1);
+}
+
+/*
+ * Return the buf, free the io
+ */
+char*
+closeiostr(io *f)
+{
+	void *buf = f->buf;
+	free(f);
+	return buf;
+}
+
+/*
+ * Use a open file descriptor for reading.
+ */
+io*
+openiofd(int fd)
+{
+	return newio(emalloc(NBUF), 0, fd);
+}
+
+/*
+ * Open a corebuffer to read.  EOF occurs after reading len
+ * characters from buf.
+ */
+io*
+openiocore(void *buf, int len)
+{
+	return newio(buf, len, -1);
+}
+
+void
+flushio(io *f)
+{
+	int n;
+
+	if(f->fd<0){
+		n = f->ebuf - f->buf;
+		f->buf = erealloc(f->buf, n+n+1);
+		f->bufp = f->buf + n;
+		f->ebuf = f->bufp + n;
+		memset(f->bufp, '\0', n+1);
+	}
+	else{
+		n = f->bufp - f->buf;
+		if(n && Write(f->fd, f->buf, n) != n){
+			Write(2, "Write error\n", 12);
+			if(ntrap)
+				dotrap();
+		}
+		f->bufp = f->buf;
+		f->ebuf = f->buf+NBUF;
+	}
+}
+
+void
+closeio(io *f)
+{
+	if(f->fd>=0) Close(f->fd);
+	free(closeiostr(f));
+}
+
+int
+emptyiobuf(io *f)
+{
+	int n;
+	if(f->fd<0 || (n = Read(f->fd, f->buf, NBUF))<=0) return EOF;
+	f->bufp = f->buf;
+	f->ebuf = f->buf + n;
+	return *f->bufp++;
+}
--- /dev/null
+++ b/io.h
@@ -1,0 +1,29 @@
+#define	EOF	(-1)
+
+struct io{
+	int	fd;
+	unsigned char *buf, *bufp, *ebuf;
+	io	*next;
+};
+io *err;
+
+io *openiofd(int), *openiostr(void), *openiocore(void*, int);
+void pchr(io*, int);
+int rchr(io*);
+char *rstr(io*, char*);
+char *closeiostr(io*);
+void closeio(io*);
+int emptyiobuf(io*);
+void flushio(io*);
+void pdec(io*, int);
+void poct(io*, unsigned);
+void pptr(io*, void*);
+void pquo(io*, char*);
+void pwrd(io*, char*);
+void pstr(io*, char*);
+void pcmd(io*, tree*);
+void pval(io*, word*);
+void pfun(io*, void(*)(void));
+void pfnc(io*, thread*);
+void pfmt(io*, char*, ...);
+void vpfmt(io*, char*, va_list);
--- /dev/null
+++ b/lex.c
@@ -1,0 +1,435 @@
+#include "rc.h"
+#include "io.h"
+#include "getflags.h"
+#include "fns.h"
+
+lexer *lex;
+
+int doprompt = 1;
+int nerror;
+
+int
+wordchr(int c)
+{
+	return !strchr("\n \t#;&|^$=`'{}()<>", 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);
+}
+
+lexer*
+newlexer(io *input, char *file)
+{
+	lexer *n = new(struct lexer);
+	n->input = input;
+	n->file = file;
+	n->line = 1;
+	n->eof = 0;
+	n->future = EOF;
+	n->peekc = '{';
+	n->epilog = "}\n";
+	n->lastc = 0;
+	n->inquote = 0;
+	n->incomm = 0;
+	n->lastword = 0;
+	n->lastdol = 0;
+	n->iflast = 0;
+	n->qflag = 0;
+	n->tok[0] = 0;
+	return n;
+}
+
+void
+freelexer(lexer *p)
+{
+	closeio(p->input);
+	free(p->file);
+	free(p);
+}
+
+/*
+ * read a character from the input stream
+ */	
+static int
+getnext(void)
+{
+	int c;
+
+	if(lex->peekc!=EOF){
+		c = lex->peekc;
+		lex->peekc = EOF;
+		return c;
+	}
+	if(lex->eof){
+epilog:
+		if(*lex->epilog)
+			return *lex->epilog++;
+		doprompt = 1;
+		return EOF;
+	}
+	if(doprompt)
+		pprompt();
+	c = rchr(lex->input);
+	if(c=='\\' && !lex->inquote){
+		c = rchr(lex->input);
+		if(c=='\n' && !lex->incomm){		/* don't continue a comment */
+			doprompt = 1;
+			c=' ';
+		}
+		else{
+			lex->peekc = c;
+			c='\\';
+		}
+	}
+	if(c==EOF){
+		lex->eof = 1;
+		goto epilog;
+	} else {
+		if(c=='\n')
+			doprompt = 1;
+		if((!lex->qflag && flag['v']!=0) || flag['V'])
+			pchr(err, c);
+	}
+	return c;
+}
+
+/*
+ * Look ahead in the input stream
+ */
+static int
+nextc(void)
+{
+	if(lex->future==EOF)
+		lex->future = getnext();
+	return lex->future;
+}
+
+/*
+ * Consume the lookahead character.
+ */
+static int
+advance(void)
+{
+	int c = nextc();
+	lex->lastc = lex->future;
+	lex->future = EOF;
+	if(c == '\n')
+		lex->line++;
+	return c;
+}
+
+static void
+skipwhite(void)
+{
+	int c;
+	for(;;){
+		c = nextc();
+		/* Why did this used to be  if(!inquote && c=='#') ?? */
+		if(c=='#'){
+			lex->incomm = 1;
+			for(;;){
+				c = nextc();
+				if(c=='\n' || c==EOF) {
+					lex->incomm = 0;
+					break;
+				}
+				advance();
+			}
+		}
+		if(c==' ' || c=='\t')
+			advance();
+		else return;
+	}
+}
+
+void
+skipnl(void)
+{
+	int c;
+	for(;;){
+		skipwhite();
+		c = nextc();
+		if(c!='\n')
+			return;
+		advance();
+	}
+}
+
+static int
+nextis(int c)
+{
+	if(nextc()==c){
+		advance();
+		return 1;
+	}
+	return 0;
+}
+
+static char*
+addtok(char *p, int val)
+{
+	if(p==0)
+		return 0;
+	if(p==&lex->tok[NTOK-1]){
+		*p = 0;
+		yyerror("token buffer too short");
+		return 0;
+	}
+	*p++=val;
+	return p;
+}
+
+static char*
+addutf(char *p, int c)
+{
+	int i, n;
+
+	p = addtok(p, c);	/* 1-byte UTF runes are special */
+	if(onebyte(c))
+		return p;
+	if(twobyte(c))
+		n = 2;
+	else if(threebyte(c))
+		n = 3;
+	else
+		n = 4;
+	for(i = 1; i < n; i++) {
+		c = nextc();
+		if(c == EOF || !xbyte(c))
+			break;
+		p = addtok(p, advance());
+	}
+	return p;
+}
+
+int
+yylex(void)
+{
+	int glob, c, d = nextc();
+	char *tok = lex->tok;
+	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(lex->lastword){
+		lex->lastword = 0;
+		if(d=='('){
+			advance();
+			strcpy(tok, "( [SUB]");
+			return SUB;
+		}
+		if(wordchr(d) || d=='\'' || d=='`' || d=='$' || d=='"'){
+			strcpy(tok, "^");
+			return '^';
+		}
+	}
+	lex->inquote = 0;
+	skipwhite();
+	switch(c = advance()){
+	case EOF:
+		lex->lastdol = 0;
+		strcpy(tok, "EOF");
+		return EOF;
+	case '$':
+		lex->lastdol = 1;
+		if(nextis('#')){
+			strcpy(tok, "$#");
+			return COUNT;
+		}
+		if(nextis('"')){
+			strcpy(tok, "$\"");
+			return '"';
+		}
+		strcpy(tok, "$");
+		return '$';
+	case '&':
+		lex->lastdol = 0;
+		if(nextis('&')){
+			skipnl();
+			strcpy(tok, "&&");
+			return ANDAND;
+		}
+		strcpy(tok, "&");
+		return '&';
+	case '|':
+		lex->lastdol = 0;
+		if(nextis(c)){
+			skipnl();
+			strcpy(tok, "||");
+			return OROR;
+		}
+	case '<':
+	case '>':
+		lex->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 if (nextis('>')){
+				t->rtype = RDWR;
+				*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 '\'':
+		lex->lastdol = 0;
+		lex->lastword = 1;
+		lex->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)){
+		lex->lastdol = 0;
+		tok[0] = c;
+		tok[1]='\0';
+		return c;
+	}
+	glob = 0;
+	for(;;){
+		if(c=='*' || c=='[' || c=='?' || c==GLOB){
+			glob = 1;
+			w = addtok(w, GLOB);
+		}
+		w = addutf(w, c);
+		c = nextc();
+		if(lex->lastdol?!idchr(c):!wordchr(c)) break;
+		advance();
+	}
+
+	lex->lastword = 1;
+	lex->lastdol = 0;
+	if(w!=0)
+		*w='\0';
+	t = klook(tok);
+	if(t->type!=WORD)
+		lex->lastword = 0;
+	else
+		t->glob = glob;
+	t->quoted = 0;
+	yylval.tree = t;
+	return t->type;
+}
+
+void
+yyerror(char *m)
+{
+	pfln(err, lex->file, lex->line);
+	pstr(err, ": ");
+	if(lex->tok[0] && lex->tok[0]!='\n')
+		pfmt(err, "token %q: ", lex->tok);
+	pfmt(err, "%s\n", m);
+	flushio(err);
+
+	lex->lastword = 0;
+	lex->lastdol = 0;
+	while(lex->lastc!='\n' && lex->lastc!=EOF) advance();
+	nerror++;
+
+	setstatus(m);
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,46 @@
+</$objtype/mkfile
+
+TARG=rc
+OFILES=\
+	code.$O\
+	exec.$O\
+	getflags.$O\
+	glob.$O\
+	here.$O\
+	io.$O\
+	lex.$O\
+	pcmd.$O\
+	pfnc.$O\
+	simple.$O\
+	subr.$O\
+	trap.$O\
+	tree.$O\
+	var.$O\
+	havefork.$O\
+	plan9.$O\
+	y.tab.$O\
+
+HFILES=rc.h\
+	y.tab.h\
+	io.h\
+	exec.h\
+	fns.h\
+	getflags.h\
+
+YFILES=syn.y
+
+BIN=/$objtype/bin
+
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${OFILES:%.$O=%.c}\
+	$YFILES\
+	${TARG:%=/386/bin/%}\
+
+CFLAGS=$CFLAGS -DPlan9
+
+</sys/src/cmd/mkone
+
+clean:V:
+	rm -f [$OS].out *.[$OS] y.tab.? y.debug $TARG
--- /dev/null
+++ b/pcmd.c
@@ -1,0 +1,169 @@
+#include "rc.h"
+#include "io.h"
+#include "fns.h"
+
+#define	c0	t->child[0]
+#define	c1	t->child[1]
+#define	c2	t->child[2]
+
+static void
+pdeglob(io *f, char *s)
+{
+	while(*s){
+		if(*s==GLOB)
+			s++;
+		pchr(f, *s++);
+	}
+}
+
+static int ntab = 0;
+
+static char*
+tabs(void)
+{
+	return "\t\t\t\t\t\t\t\t"+8-(ntab%8);
+}
+
+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%t", c0, c1);
+	break;
+	case ANDAND:	pfmt(f, "%t && %t", c0, c1);
+	break;
+	case BANG:	pfmt(f, "! %t", c0);
+	break;
+	case BRACE:
+			ntab++;
+			pfmt(f, "{\n%s%t", tabs(), c0);
+			ntab--;
+			pfmt(f, "\n%s}", tabs());
+	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 ARGLIST:
+		if(c0==0)
+			pfmt(f, "%t", c1);
+		else if(c1==0)
+			pfmt(f, "%t", c0);
+		else
+			pfmt(f, "%t %t", c0, c1);
+		break;
+	case ';':
+		if(c0){
+			pfmt(f, "%t", c0);
+			if(c1){
+				if(c0->line==c1->line)
+					pstr(f, "; ");
+				else
+					pfmt(f, "\n%s", tabs());
+				pfmt(f, "%t", c1);
+			}
+		}
+		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:
+		if(t->rtype==DUPFD)
+			pfmt(f, ">[%d=%d]", t->fd1, t->fd0); /* yes, fd1, then fd0; read lex.c */
+		else
+			pfmt(f, ">[%d=]", t->fd0);
+		pfmt(f, "%t", c1);
+		break;
+	case PIPEFD:
+	case REDIR:
+		pchr(f, ' ');
+		switch(t->rtype){
+		case HERE:
+			if(c1)
+				pfmt(f, "%t ", c1);
+			pchr(f, '<');
+		case READ:
+		case RDWR:
+			pchr(f, '<');
+			if(t->rtype==RDWR)
+				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(t->rtype == HERE)
+			pfmt(f, "\n%s%s\n", t->str, c0->str);
+		else 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;
+	}
+}
--- /dev/null
+++ b/pfnc.c
@@ -1,0 +1,78 @@
+#include "rc.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.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",
+	Xpush, "Xpush",
+	Xrdwr, "Xrdwr",
+	Xread, "Xread",
+	Xhere, "Xhere",
+	Xhereq, "Xhereq",
+	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",
+	Xpopredir, "Xpopredir",
+	Xrdcmds, "Xrdcmds",
+	Xbackq, "Xbackq",
+	Xpipefd, "Xpipefd",
+	Xsubshell, "Xsubshell",
+	Xfor, "Xfor",
+	Xglob, "Xglob",
+	Xsimple, "Xsimple",
+	Xqw, "Xqw",
+	Xsrcline, "Xsrcline",
+0};
+
+void
+pfun(io *f, void (*fn)(void))
+{
+	int i;
+	for(i = 0;fname[i].f;i++) if(fname[i].f==fn){
+		pstr(f, fname[i].name);
+		return;
+	}
+	pfmt(f, "%p", fn);
+}
+
+void
+pfnc(io *f, thread *t)
+{
+	list *a;
+
+	pfln(f, srcfile(t), t->line);
+	pfmt(f, " pid %d cycle %p %d ", getpid(), t->code, t->pc);
+	pfun(f, t->code[t->pc].f);
+	for(a = t->argv;a;a = a->next) pfmt(f, " (%v)", a->words);
+	pchr(f, '\n');
+	flushio(f);
+}
--- /dev/null
+++ b/plan9.c
@@ -1,0 +1,494 @@
+/*
+ * Plan 9 versions of system-specific functions
+ *	By convention, exported routines herein have names beginning with an
+ *	upper case letter.
+ */
+#include "rc.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+#include "getflags.h"
+
+static void execrfork(void);
+static void execfinit(void);
+
+builtin Builtin[] = {
+	"cd",		execcd,
+	"whatis",	execwhatis,
+	"eval",		execeval,
+	"exec",		execexec,	/* but with popword first */
+	"exit",		execexit,
+	"shift",	execshift,
+	"wait",		execwait,
+	".",		execdot,
+	"flag",		execflag,
+	"finit",	execfinit,
+	"rfork",	execrfork,
+	0
+};
+
+char Rcmain[]="/rc/lib/rcmain";
+char Fdprefix[]="/fd/";
+
+char *Signame[] = {
+	"sigexit",	"sighup",	"sigint",	"sigquit",
+	"sigalrm",	"sigkill",	"sigfpe",	"sigterm",
+	0
+};
+static char *syssigname[] = {
+	"exit",		/* can't happen */
+	"hangup",
+	"interrupt",
+	"quit",		/* can't happen */
+	"alarm",
+	"kill",
+	"sys: fp: ",
+	"term",
+	0
+};
+
+/*
+ * finit could be removed but is kept for
+ * backwards compatibility, see: rcmain.plan9
+ */
+static void
+execfinit(void)
+{
+	char *cmds = estrdup("for(i in '/env/fn#'*){. -bq $i}\n");
+	int line = runq->line;
+	poplist();
+	execcmds(openiocore(cmds, strlen(cmds)), estrdup(srcfile(runq)), runq->local, runq->redir);
+	runq->lex->line = line;
+	runq->lex->qflag = 1;
+}
+
+static void
+execrfork(void)
+{
+	int arg;
+	char *s;
+
+	switch(count(runq->argv->words)){
+	case 1:
+		arg = RFENVG|RFNAMEG|RFNOTEG;
+		break;
+	case 2:
+		arg = 0;
+		for(s = runq->argv->words->next->word;*s;s++) switch(*s){
+		default:
+			goto Usage;
+		case 'n':
+			arg|=RFNAMEG;  break;
+		case 'N':
+			arg|=RFCNAMEG;
+			break;
+		case 'm':
+			arg|=RFNOMNT;  break;
+		case 'e':
+			arg|=RFENVG;   break;
+		case 'E':
+			arg|=RFCENVG;  break;
+		case 's':
+			arg|=RFNOTEG;  break;
+		case 'f':
+			arg|=RFFDG;    break;
+		case 'F':
+			arg|=RFCFDG;   break;
+		}
+		break;
+	default:
+	Usage:
+		pfmt(err, "Usage: %s [fnesFNEm]\n", runq->argv->words->word);
+		setstatus("rfork usage");
+		poplist();
+		return;
+	}
+	if(rfork(arg)==-1){
+		pfmt(err, "%s: %s failed\n", argv0, runq->argv->words->word);
+		setstatus("rfork failed");
+	} else {
+		if(arg & RFCFDG){
+			redir *rp;
+			for(rp = runq->redir; rp; rp = rp->next)
+				rp->type = 0;
+		}
+		setstatus("");
+	}
+	poplist();
+}
+
+char*
+Env(char *name, int fn)
+{
+	static char buf[128];
+
+	strcpy(buf, "/env/");
+	if(fn) strcat(buf, "fn#");
+	return strncat(buf, name, sizeof(buf)-1);
+}
+
+void
+Vinit(void)
+{
+	int dir, fd, i, n;
+	Dir *ent;
+
+	dir = Open(Env("", 0), 0);
+	if(dir<0){
+		pfmt(err, "%s: can't open: %s\n", argv0, Errstr());
+		return;
+	}
+	for(;;){
+		ent = 0;
+		n = dirread(dir, &ent);
+		if(n <= 0)
+			break;
+		for(i = 0; i<n; i++){
+			if(ent[i].length<=0 || strncmp(ent[i].name, "fn#", 3)==0)
+				continue;
+			if((fd = Open(Env(ent[i].name, 0), 0))>=0){
+				io *f = openiofd(fd);
+				word *w = 0, **wp = &w;
+				char *s;
+				while((s = rstr(f, "")) != 0){
+					*wp = Newword(s, (word*)0);
+					wp = &(*wp)->next;
+				}
+				closeio(f);
+				setvar(ent[i].name, w);
+				vlook(ent[i].name)->changed = 0;
+			}
+		}
+		free(ent);
+	}
+	Close(dir);
+}
+
+char*
+Errstr(void)
+{
+	static char err[ERRMAX];
+	rerrstr(err, sizeof err);
+	return err;
+}
+
+int
+Waitfor(int pid)
+{
+	thread *p;
+	Waitmsg *w;
+
+	if(pid >= 0 && !havewaitpid(pid))
+		return 0;
+
+	while((w = wait()) != nil){
+		delwaitpid(w->pid);
+		if(w->pid==pid){
+			setstatus(w->msg);
+			free(w);
+			return 0;
+		}
+		for(p = runq->ret;p;p = p->ret)
+			if(p->pid==w->pid){
+				p->pid=-1;
+				p->status = estrdup(w->msg);
+				break;
+			}
+		free(w);
+	}
+
+	if(strcmp(Errstr(), "interrupted")==0) return -1;
+	return 0;
+}
+
+static void
+addenv(var *v)
+{
+	word *w;
+	int fd;
+	io *f;
+
+	if(v->changed){
+		v->changed = 0;
+		if((fd = Creat(Env(v->name, 0)))<0)
+			pfmt(err, "%s: can't open: %s\n", argv0, Errstr());
+		else{
+			f = openiofd(fd);
+			for(w = v->val;w;w = w->next){
+				pstr(f, w->word);
+				pchr(f, '\0');
+			}
+			flushio(f);
+			closeio(f);
+		}
+	}
+	if(v->fnchanged){
+		v->fnchanged = 0;
+		if((fd = Creat(Env(v->name, 1)))<0)
+			pfmt(err, "%s: can't open: %s\n", argv0, Errstr());
+		else{
+			f = openiofd(fd);
+			if(v->fn)
+				pfmt(f, "fn %q %s\n", v->name, v->fn[v->pc-1].s);
+			flushio(f);
+			closeio(f);
+		}
+	}
+}
+
+static void
+updenvlocal(var *v)
+{
+	if(v){
+		updenvlocal(v->next);
+		addenv(v);
+	}
+}
+
+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);
+	if(err)
+		flushio(err);
+}
+
+void
+Exec(char **argv)
+{
+	exec(argv[0], argv+1);
+}
+
+int
+Fork(void)
+{
+	Updenv();
+	return rfork(RFPROC|RFFDG|RFREND);
+}
+
+
+typedef struct readdir readdir;
+struct readdir {
+	Dir	*dbuf;
+	int	i, n;
+	int	fd;
+};
+
+void*
+Opendir(char *name)
+{
+	readdir *rd;
+	int fd;
+	if((fd = Open(name, 0))<0)
+		return 0;
+	rd = new(readdir);
+	rd->dbuf = 0;
+	rd->i = 0;
+	rd->n = 0;
+	rd->fd = fd;
+	return rd;
+}
+
+static int
+trimdirs(Dir *d, int nd)
+{
+	int r, w;
+
+	for(r=w=0; r<nd; r++)
+		if(d[r].mode&DMDIR)
+			d[w++] = d[r];
+	return w;
+}
+
+char*
+Readdir(void *arg, int onlydirs)
+{
+	readdir *rd = arg;
+	int n;
+Again:
+	if(rd->i>=rd->n){	/* read */
+		free(rd->dbuf);
+		rd->dbuf = 0;
+		n = dirread(rd->fd, &rd->dbuf);
+		if(n>0){
+			if(onlydirs){
+				n = trimdirs(rd->dbuf, n);
+				if(n == 0)
+					goto Again;
+			}	
+			rd->n = n;
+		}else
+			rd->n = 0;
+		rd->i = 0;
+	}
+	if(rd->i>=rd->n)
+		return 0;
+	return rd->dbuf[rd->i++].name;
+}
+
+void
+Closedir(void *arg)
+{
+	readdir *rd = arg;
+	Close(rd->fd);
+	free(rd->dbuf);
+	free(rd);
+}
+
+static int interrupted = 0;
+
+static void
+notifyf(void*, char *s)
+{
+	int i;
+
+	for(i = 0;syssigname[i];i++) if(strncmp(s, syssigname[i], strlen(syssigname[i]))==0){
+		if(strncmp(s, "sys: ", 5)!=0) interrupted = 1;
+		goto Out;
+	}
+	noted(NDFLT);
+	return;
+Out:
+	if(strcmp(s, "interrupt")!=0 || trap[i]==0){
+		trap[i]++;
+		ntrap++;
+	}
+	noted(NCONT);
+}
+
+void
+Trapinit(void)
+{
+	notify(notifyf);
+}
+
+long
+Write(int fd, void *buf, long cnt)
+{
+	return write(fd, buf, cnt);
+}
+
+long
+Read(int fd, void *buf, long cnt)
+{
+	return read(fd, buf, cnt);
+}
+
+long
+Seek(int fd, long cnt, long whence)
+{
+	return seek(fd, cnt, whence);
+}
+
+int
+Executable(char *file)
+{
+	Dir *statbuf;
+	int ret;
+
+	statbuf = dirstat(file);
+	if(statbuf == nil)
+		return 0;
+	ret = ((statbuf->mode&0111)!=0 && (statbuf->mode&DMDIR)==0);
+	free(statbuf);
+	return ret;
+}
+
+int
+Open(char *file, int mode)
+{
+	static int tab[] = {OREAD,OWRITE,ORDWR,OREAD|ORCLOSE};
+	return open(file, tab[mode&3]);
+}
+
+void
+Close(int fd)
+{
+	close(fd);
+}
+
+int
+Creat(char *file)
+{
+	return create(file, OWRITE, 0666L);
+}
+
+int
+Dup(int a, int b)
+{
+	return dup(a, b);
+}
+
+int
+Dup1(int a)
+{
+	return dup(a, -1);
+}
+
+void
+Exit(void)
+{
+	Updenv();
+	exits(truestatus()?"":getstatus());
+}
+
+int
+Eintr(void)
+{
+	return interrupted;
+}
+
+void
+Noerror(void)
+{
+	interrupted = 0;
+}
+
+int
+Isatty(int fd)
+{
+	char buf[64];
+
+	if(fd2path(fd, buf, sizeof buf) != 0)
+		return 0;
+	/* might be /mnt/term/dev/cons */
+	return strlen(buf) >= 9 && strcmp(buf+strlen(buf)-9, "/dev/cons") == 0;
+}
+
+void
+Abort(void)
+{
+	abort();
+}
+
+static int newwdir;
+
+int
+Chdir(char *dir)
+{
+	newwdir = 1;
+	return chdir(dir);
+}
+
+void
+Prompt(char *s)
+{
+	pstr(err, s);
+	flushio(err);
+
+	if(newwdir){
+		char dir[4096];
+		int fd;
+		if((fd=Creat("/dev/wdir"))>=0){
+			getwd(dir, sizeof(dir));
+			Write(fd, dir, strlen(dir));
+			Close(fd);
+		}
+		newwdir = 0;
+	}
+}
--- /dev/null
+++ b/rc.h
@@ -1,0 +1,168 @@
+/*
+ * Plan9 is defined for plan 9
+ * otherwise its UNIX.
+ * Please don't litter the code with ifdefs.  The three below (and one in
+ * getflags) should be enough.
+ */
+#ifdef Plan9
+#include <u.h>
+#include <libc.h>
+#define NSIG	32
+#define	SIGINT	2
+#define	SIGQUIT	3
+#else
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#ifndef NSIG
+#define NSIG 32
+#endif
+#endif
+
+#define	YYMAXDEPTH	500
+#ifndef PAREN
+#include "y.tab.h"
+#endif
+typedef struct tree tree;
+typedef struct word word;
+typedef struct io io;
+typedef union code code;
+typedef struct var var;
+typedef struct list list;
+typedef struct lexer lexer;
+typedef struct redir redir;
+typedef struct thread thread;
+typedef struct builtin builtin;
+
+#pragma incomplete word
+#pragma incomplete io
+
+struct tree{
+	int	type;
+	int	rtype, fd0, fd1;	/* details of REDIR PIPE DUP tokens */
+	int	line;
+	char	glob;			/* 0=string, 1=glob, -1=pattern see globprop() and noglobs() */
+	char	quoted;
+	char	iskw;
+	char	*str;
+	tree	*child[3];
+	tree	*next;
+};
+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*);
+tree *globprop(tree*);
+char *fnstr(tree*);
+
+/*
+ * The first word of any code vector is a reference count
+ * and the second word is a string for srcfile().
+ * Code starts at pc 2. The last code word must be a zero
+ * terminator for codefree().
+ * 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;
+};
+
+#define	NTOK	8192
+
+struct lexer{
+	io	*input;
+	char	*file;
+	int	line;
+
+	char	*prolog;
+	char	*epilog;
+
+	int	peekc;
+	int	future;
+	int	lastc;
+
+	char	eof;
+	char	inquote;
+	char	incomm;
+	char	lastword;	/* was the last token read a word or compound word terminator? */
+	char	lastdol;	/* was the last token read '$' or '$#' or '"'? */
+	char	iflast;		/* static `if not' checking */
+
+	char	qflag;
+
+	char	tok[NTOK];
+};
+extern lexer *lex;		/* current lexer */
+lexer *newlexer(io*, char*);
+void freelexer(lexer*);
+
+#define	APPEND	1
+#define	WRITE	2
+#define	READ	3
+#define	HERE	4
+#define	DUPFD	5
+#define	CLOSE	6
+#define RDWR	7
+
+struct var{
+	var	*next;		/* next on hash or local list */
+	word	*val;		/* value */
+	code	*fn;		/* pointer to function's code vector */
+	int	pc;		/* pc of start of function */
+	char	fnchanged;
+	char	changed;
+	char	name[];
+};
+var *vlook(char*), *gvlook(char*), *newvar(char*, var*);
+void setvar(char*, word*), freevar(var*);
+
+#define	NVAR	521
+
+var *gvar[NVAR];				/* hash for globals */
+
+#define	new(type)	((type *)emalloc(sizeof(type)))
+
+void *emalloc(long);
+void *erealloc(void *, long);
+char *estrdup(char*);
+
+int mypid;
+
+/*
+ * 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)0x01)
+/*
+ * Is c the first character of a utf sequence?
+ */
+#define	onebyte(c)	(((c)&0x80)==0x00)
+#define twobyte(c)	(((c)&0xe0)==0xc0)
+#define threebyte(c)	(((c)&0xf0)==0xe0)
+#define fourbyte(c)	(((c)&0xf8)==0xf0)
+#define xbyte(c)	(((c)&0xc0)==0x80)
+
+extern char **argp;
+extern char **args;
+extern int nerror;		/* number of errors encountered during compilation */
+extern int doprompt;		/* is it time for a prompt? */
+
+/*
+ * Which fds are the reading/writing end of a pipe?
+ * Unfortunately, this can vary from system to system.
+ * 9th edition Unix doesn't care, the following defines
+ * work on plan 9.
+ */
+#define	PRD	0
+#define	PWR	1
+extern char Rcmain[], Fdprefix[];
--- /dev/null
+++ b/rcmain.plan9
@@ -1,0 +1,41 @@
+# rcmain: Plan 9 version
+if(~ $#home 0) home=/
+if(~ $#ifs 0) ifs=' 	
+'
+switch($#prompt){
+case 0
+	prompt=('% ' '	')
+case 1
+	prompt=($prompt '	')
+}
+if(~ $rcname ?.out) prompt=('broken! ' '	')
+if(flag p) path=/bin
+if not{
+	for(i in '/env/fn#'*){
+		. -bq $i
+	}
+	if(~ $#path 0) path=(/bin .)
+}
+fn sigexit
+if(! ~ $#cflag 0){
+	if(flag l){
+		. -q /rc/lib/rcmain.local
+		. -q $home/lib/profile
+	}
+	status=''
+	eval $cflag
+}
+if not if(flag i){
+	if(flag l){
+		. -q /rc/lib/rcmain.local
+		. -q $home/lib/profile
+	}
+	status=''
+	if(! ~ $#* 0) . $*
+	. -i '#d/0'
+}
+if not if(~ $#* 0) . '#d/0'
+if not{
+	status=''
+	. $*
+}
--- /dev/null
+++ b/rcmain.unix
@@ -1,0 +1,38 @@
+# rcmain: unix version
+if(~ $#home 0) home=$HOME
+if(~ $#ifs 0) ifs=' 	
+'
+profile=$home/.rcrc
+switch($#prompt){
+case 0
+	prompt=('% ' '	')
+case 1
+	prompt=($prompt '	')
+}
+if(~ $rcname ?.out) prompt=('broken! ' '	')
+if(flag p) path=/bin
+if not {
+	finit
+	if(~ $#path 0) path=(. /bin /usr/bin /usr/local/bin)
+}
+fn sigexit
+if(! ~ $#cflag 0){
+	if(flag l) {
+		. -q $profile
+	}
+	status=''
+	eval $cflag
+}
+if not if(flag i){
+	if(flag l) {
+		. -q $profile
+	}
+	status=''
+	if(! ~ $#* 0) . $*
+	. -i /dev/fd/0
+}
+if not if(~ $#* 0) . /dev/fd/0
+if not{
+	status=''
+	. $*
+}
--- /dev/null
+++ b/simple.c
@@ -1,0 +1,537 @@
+/*
+ * Maybe `simple' is a misnomer.
+ */
+#include "rc.h"
+#include "getflags.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+
+/*
+ * Search through the following code to see if we're just going to exit.
+ */
+int
+exitnext(void){
+	int i=ifnot;
+	thread *p=runq;
+	code *c;
+loop:
+	c=&p->code[p->pc];
+	while(1){
+		if(c->f==Xpopredir || c->f==Xunlocal)
+			c++;
+		else if(c->f==Xsrcline)
+			c += 2;
+		else if(c->f==Xwastrue){
+			c++;
+			i=0;
+		}
+		else if(c->f==Xifnot){
+			if(i)
+				c += 2;
+			else
+				c = &p->code[c[1].i];
+		}
+		else if(c->f==Xreturn){
+			p = p->ret;
+			if(p==0)
+				return 1;
+			goto loop;
+		}else
+			break;
+	}
+	return c->f==Xexit;
+}
+
+void (*builtinfunc(char *name))(void)
+{
+	extern builtin Builtin[];
+	builtin *bp;
+
+	for(bp = Builtin;bp->name;bp++)
+		if(strcmp(name, bp->name)==0)
+			return bp->fnc;
+	return 0;
+}
+
+void
+Xsimple(void)
+{
+	void (*f)(void);
+	word *a;
+	var *v;
+	int pid;
+
+	a = runq->argv->words;
+	if(a==0){
+		Xerror1("empty argument list");
+		return;
+	}
+	if(flag['x'])
+		pfmt(err, "%v\n", a); /* wrong, should do redirs */
+	v = gvlook(a->word);
+	if(v->fn)
+		execfunc(v);
+	else{
+		if(strcmp(a->word, "builtin")==0){
+			a = a->next;
+			if(a==0){
+				Xerror1("builtin: empty argument list");
+				return;
+			}
+			popword();	/* "builtin" */
+		}
+		f = builtinfunc(a->word);
+		if(f){
+			(*f)();
+			return;
+		}
+		if(exitnext()){
+			/* fork and wait is redundant */
+			pushword("exec");
+			execexec();
+			/* does not return */
+		}
+		else{
+			if((pid = execforkexec()) < 0){
+				Xerror2("try again", Errstr());
+				return;
+			}
+			poplist();
+
+			/* interrupts don't get us out */
+			while(Waitfor(pid) < 0)
+				;
+		}
+	}
+}
+
+static 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;
+		}
+	}
+}
+
+word*
+searchpath(char *w, char *v)
+{
+	static struct word nullpath = { "", 0 };
+	word *path;
+
+	if(w[0] && w[0] != '/' && w[0] != '#' &&
+	  (w[0] != '.' || (w[1] && w[1] != '/' && (w[1] != '.' || w[2] && w[2] != '/')))){
+		path = vlook(v)->val;
+		if(path)
+			return path;
+	}
+	return &nullpath;
+}
+
+char*
+makepath(char *dir, char *file)
+{
+	char *path;
+	int m, n = strlen(dir);
+	if(n==0) return estrdup(file);
+	while (n > 0 && dir[n-1]=='/') n--;
+	while (file[0]=='/') file++;
+	m = strlen(file);
+	path = emalloc(n + m + 2);
+	if(n>0) memmove(path, dir, n);
+	path[n++]='/';
+	memmove(path+n, file, m+1);
+	return path;
+}
+
+static char**
+mkargv(word *a)
+{
+	char **argv = (char **)emalloc((count(a)+2)*sizeof(char *));
+	char **argp = argv+1;
+	for(;a;a = a->next) *argp++=a->word;
+	*argp = 0;
+	return argv;
+}
+
+void
+execexec(void)
+{
+	char **argv;
+	word *path;
+
+	popword();	/* "exec" */
+	if(runq->argv->words==0){
+		Xerror1("exec: empty argument list");
+		return;
+	}
+	argv = mkargv(runq->argv->words);
+	Updenv();
+	doredir(runq->redir);
+	for(path = searchpath(argv[1], "path"); path; path = path->next){
+		argv[0] = makepath(path->word, argv[1]);
+		Exec(argv);
+	}
+	setstatus(Errstr());
+	pfln(err, srcfile(runq), runq->line);
+	pfmt(err, ": %s: %s\n", argv[1], getstatus());
+	Xexit();
+}
+
+void
+execfunc(var *func)
+{
+	popword();	/* name */
+	startfunc(func, Poplist(), runq->local, runq->redir);
+}
+
+void
+execcd(void)
+{
+	word *a = runq->argv->words;
+	word *cdpath;
+	char *dir;
+
+	setstatus("can't cd");
+	switch(count(a)){
+	default:
+		pfmt(err, "Usage: cd [directory]\n");
+		break;
+	case 2:
+		a = a->next;
+		for(cdpath = searchpath(a->word, "cdpath"); cdpath; cdpath = cdpath->next){
+			dir = makepath(cdpath->word, a->word);
+			if(Chdir(dir)>=0){
+				if(cdpath->word[0] != '\0' && strcmp(cdpath->word, ".") != 0)
+					pfmt(err, "%s\n", dir);
+				free(dir);
+				setstatus("");
+				break;
+			}
+			free(dir);
+		}
+		if(cdpath==0)
+			pfmt(err, "Can't cd %s: %s\n", a->word, Errstr());
+		break;
+	case 1:
+		a = vlook("home")->val;
+		if(a){
+			if(Chdir(a->word)>=0)
+				setstatus("");
+			else
+				pfmt(err, "Can't cd %s: %s\n", a->word, Errstr());
+		}
+		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
+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>0 && star->val;--n){
+		a = star->val->next;
+		free(Freeword(star->val));
+		star->val = a;
+		star->changed = 1;
+	}
+	setstatus("");
+	poplist();
+}
+
+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;
+}
+
+void
+execcmds(io *input, char *file, var *local, redir *redir)
+{
+	static union code rdcmds[5];
+
+	if(rdcmds[0].i==0){
+		rdcmds[0].i = 1;
+		rdcmds[1].s="*rdcmds*";
+		rdcmds[2].f = Xrdcmds;
+		rdcmds[3].f = Xreturn;
+		rdcmds[4].f = 0;
+	}
+
+	if(exitnext()) turfstack(local);
+
+	start(rdcmds, 2, local, redir);
+	runq->lex = newlexer(input, file);
+}
+
+void
+execeval(void)
+{
+	char *cmds;
+	int len;
+	io *f;
+
+	popword();	/* "eval" */
+
+	if(runq->argv->words==0){
+		Xerror1("Usage: eval cmd ...");
+		return;
+	}
+	Xqw();		/* make into single word */
+	cmds = Popword();
+	len = strlen(cmds);
+	cmds[len++] = '\n';
+	poplist();
+
+	f = openiostr();
+	pfln(f, srcfile(runq), runq->line);
+	pstr(f, " *eval*");
+
+	execcmds(openiocore(cmds, len), closeiostr(f), runq->local, runq->redir);
+}
+
+void
+execdot(void)
+{
+	int fd, bflag, iflag, qflag;
+	word *path, *argv;
+	char *file;
+
+	popword();	/* "." */
+
+	bflag = iflag = qflag = 0;
+	while(runq->argv->words && runq->argv->words->word[0]=='-'){
+		char *f = runq->argv->words->word+1;
+		if(*f == '-'){
+			popword();
+			break;
+		}
+		for(; *f; f++){
+			switch(*f){
+			case 'b':
+				bflag = 1;
+				continue;
+			case 'i':
+				iflag = 1;
+				continue;
+			case 'q':
+				qflag = 1;
+				continue;
+			}
+			goto Usage;
+		}
+		popword();
+	}
+
+	/* get input file */
+	if(runq->argv->words==0){
+Usage:
+		Xerror1("Usage: . [-biq] file [arg ...]");
+		return;
+	}
+	argv = Poplist();
+		
+	file = 0;
+	fd = -1;
+	for(path = searchpath(argv->word, "path"); path; path = path->next){
+		file = makepath(path->word, argv->word);
+		fd = Open(file, 0);
+		if(fd >= 0)
+			break;
+		if(strcmp(file, "/dev/stdin")==0){	/* for sun & ucb */
+			fd = Dup1(0);
+			if(fd>=0)
+				break;
+		}
+		free(file);
+	}
+	if(fd<0){
+		if(!qflag)
+			Xerror3(". can't open", argv->word, Errstr());
+		freewords(argv);
+		return;
+	}
+
+	execcmds(openiofd(fd), file, (var*)0, runq->redir);
+	pushredir(RCLOSE, fd, 0);
+	runq->lex->qflag = qflag;
+	runq->iflag = iflag;
+	if(iflag || !bflag && flag['b']==0){
+		runq->lex->peekc=EOF;
+		runq->lex->epilog="";
+	}
+
+	runq->local = newvar("*", runq->local);
+	runq->local->val = argv->next;
+	argv->next=0;
+	runq->local->changed = 1;
+
+	runq->local = newvar("0", runq->local);
+	runq->local->val = argv;
+	runq->local->changed = 1;
+}
+
+void
+execflag(void)
+{
+	char *letter, *val;
+	switch(count(runq->argv->words)){
+	case 2:
+		setstatus(flag[(unsigned char)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[(unsigned char)letter[0]] = flagset;
+				break;
+			}
+			if(strcmp(val, "-")==0){
+				flag[(unsigned char)letter[0]] = 0;
+				break;
+			}
+		}
+	default:
+		Xerror1("Usage: flag [letter] [+-]");
+		return;
+	}
+	poplist();
+}
+
+void
+execwhatis(void){	/* mildly wrong -- should fork before writing */
+	word *a, *b, *path;
+	var *v;
+	char *file;
+	io *out;
+	int found, sep;
+	a = runq->argv->words->next;
+	if(a==0){
+		Xerror1("Usage: whatis name ...");
+		return;
+	}
+	setstatus("");
+	out = openiofd(mapfd(1));
+	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=' ';
+				}
+				pstr(out, ")\n");
+			}
+			found = 1;
+		}
+		else
+			found = 0;
+		v = gvlook(a->word);
+		if(v->fn)
+			pfmt(out, "fn %q %s\n", v->name, v->fn[v->pc-1].s);
+		else{
+			if(builtinfunc(a->word))
+				pfmt(out, "builtin %s\n", a->word);
+			else {
+				for(path = searchpath(a->word, "path"); path; path = path->next){
+					file = makepath(path->word, a->word);
+					if(Executable(file)){
+						pfmt(out, "%s\n", file);
+						free(file);
+						break;
+					}
+					free(file);
+				}
+				if(!path && !found){
+					pfmt(err, "%s: not found\n", a->word);
+					setstatus("not found");
+				}
+			}
+		}
+		flushio(out);
+	}
+	poplist();
+	free(closeiostr(out));	/* don't close fd */
+}
+
+void
+execwait(void)
+{
+	switch(count(runq->argv->words)){
+	default:
+		Xerror1("Usage: wait [pid]");
+		return;
+	case 2:
+		Waitfor(atoi(runq->argv->words->next->word));
+		break;
+	case 1:
+		Waitfor(-1);
+		break;
+	}
+	poplist();
+}
--- /dev/null
+++ b/subr.c
@@ -1,0 +1,75 @@
+#include "rc.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+
+void *
+emalloc(long n)
+{
+	void *p = malloc(n);
+	if(p==0)
+		panic("Can't malloc %d bytes", n);
+	return p;
+}
+
+void*
+erealloc(void *p, long n)
+{
+	p = realloc(p, n);
+	if(p==0 && n!=0)
+		panic("Can't realloc %d bytes\n", n);
+	return p;
+}
+
+char*
+estrdup(char *s)
+{
+	int n = strlen(s)+1;
+	char *d = emalloc(n);
+	memmove(d, s, n);
+	return d;
+}
+
+void
+pfln(io *fd, char *file, int line)
+{
+	if(file && line)
+		pfmt(fd, "%s:%d", file, line);
+	else if(file)
+		pstr(fd, file);
+	else
+		pstr(fd, argv0);
+}
+
+static char *bp;
+
+static void
+iacvt(int n)
+{
+	if(n<0){
+		*bp++='-';
+		n=-n;	/* doesn't work for n==-inf */
+	}
+	if(n/10)
+		iacvt(n/10);
+	*bp++=n%10+'0';
+}
+
+void
+inttoascii(char *s, int n)
+{
+	bp = s;
+	iacvt(n);
+	*bp='\0';
+}
+
+void
+panic(char *s, int n)
+{
+	pfmt(err, "%s: ", argv0);
+	pfmt(err, s, n);
+	pchr(err, '\n');
+	flushio(err);
+
+	Abort();
+}
--- /dev/null
+++ b/syn.y
@@ -1,0 +1,92 @@
+%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 */
+/* operator priorities -- lowest first */
+%left IF WHILE FOR SWITCH ')' NOT
+%left ANDAND OROR
+%left BANG SUBSHELL
+%left PIPE
+%left '^'
+%right '$' COUNT '"'
+%left SUB
+%{
+#include "rc.h"
+#include "fns.h"
+%}
+%union{
+	struct 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, $2))->str=$1->rtype==HERE?readhere($2,lex->input):0;}
+|	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
+	/*
+	 * if ``words'' is nil, we need a tree element to distinguish between 
+	 * for(i in ) and for(i), the former being a loop over the empty set
+	 * and the latter being the implicit argument loop.  so if $5 is nil
+	 * (the empty set), we represent it as "()".  don't parenthesize non-nil
+	 * functions, to avoid growing parentheses every time we reread the
+	 * definition.
+	 */
+				{$$=mung3($1, $3, $5 ? $5 : tree1(PAREN, $5), $8);}
+|	FOR '(' word ')' {skipnl();} cmd
+				{$$=mung3($1, $3, (tree*)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		{$$=globprop(tree2('^', $1, $3));}
+word:	keyword			{lex->lastword=1; $1->type=WORD;}
+|	comword
+|	word '^' word		{$$=globprop(tree2('^', $1, $3));}
+comword: '$' word		{$$=tree1('$', $2);}
+|	'$' word SUB words ')'	{$$=tree2(SUB, $2, $4);}
+|	'"' word		{$$=tree1('"', $2);}
+|	COUNT word		{$$=tree1(COUNT, $2);}
+|	WORD
+|	'`' brace		{$$=tree2('`', (tree*)0, $2);}
+|	'`' word brace		{$$=tree2('`', $2, $3);}
+|	'(' words ')'		{$$=tree1(PAREN, $2);}
+|	REDIR brace		{$$=mung1($1, $2); $$->type=PIPEFD;}
+keyword: FOR|IN|WHILE|IF|NOT|TWIDDLE|BANG|SUBSHELL|SWITCH|FN
+words:				{$$=(tree*)0;}
+|	words word		{$$=tree2(WORDS, $1, $2);}
--- /dev/null
+++ b/trap.c
@@ -1,0 +1,32 @@
+#include "rc.h"
+#include "exec.h"
+#include "fns.h"
+#include "io.h"
+extern char *Signame[];
+
+void
+dotrap(void)
+{
+	int i;
+	var *trapreq;
+	word *starval;
+	starval = vlook("*")->val;
+	while(ntrap) for(i = 0;i<NSIG;i++) while(trap[i]){
+		--trap[i];
+		--ntrap;
+		if(getpid()!=mypid) Exit();
+		trapreq = vlook(Signame[i]);
+		if(trapreq->fn)
+			startfunc(trapreq, copywords(starval, (word*)0), (var*)0, (redir*)0);
+		else if(i==SIGINT || i==SIGQUIT){
+			/*
+			 * 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();
+		}
+		else Exit();
+	}
+}
--- /dev/null
+++ b/tree.c
@@ -1,0 +1,180 @@
+#include "rc.h"
+#include "io.h"
+#include "fns.h"
+
+/*
+ * create and clear a new tree node, and add it
+ * to the node list.
+ */
+static tree *treefree, *treenodes;
+
+tree*
+newtree(void)
+{
+	tree *t;
+
+	t = treefree;
+	if(t==0)
+		t = new(tree);
+	else
+		treefree = t->next;
+	t->quoted = 0;
+	t->glob = 0;
+	t->iskw = 0;
+	t->str = 0;
+	t->child[0] = t->child[1] = t->child[2] = 0;
+	t->line = lex->line;
+	t->next = treenodes;
+	treenodes = t;
+	return t;
+}
+
+void
+freenodes(void)
+{
+	tree *t;
+
+	t = treenodes;
+	while(t){
+		if(t->str){
+			free(t->str);
+			t->str = 0;
+		}
+		t->child[0] = t->child[1] = t->child[2] = 0;
+		if(t->next==0){
+			t->next = treefree;
+			treefree = treenodes;
+			break;
+		}
+		t = t->next;
+	}
+	treenodes = 0;
+}
+
+tree*
+tree1(int type, tree *c0)
+{
+	return tree3(type, c0, (tree *)0, (tree *)0);
+}
+
+tree*
+tree2(int type, tree *c0, tree *c1)
+{
+	return tree3(type, c0, c1, (tree *)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;
+
+	if(c0)
+		t->line = c0->line;
+	else if(c1)
+		t->line = c1->line;
+	else if(c2)
+		t->line = c2->line;
+	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;
+
+	t = tree1(SIMPLE, t);
+	t->str = fnstr(t);
+	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;
+}
+
+char*
+fnstr(tree *t)
+{
+	io *f = openiostr();
+	pfmt(f, "%t", t);
+	return closeiostr(f);
+}
+
+tree*
+globprop(tree *t)
+{
+	tree *c0 = t->child[0];
+	tree *c1 = t->child[1];
+	if(t->glob==0){
+		if(c0->glob || c1->glob){
+			if(c0->glob)
+				c0->glob=-1;
+			if(c1->glob)
+				c1->glob=-1;
+			t->glob=1;
+		}
+	}
+	return t;
+}
+
+tree*
+token(char *str, int type)
+{
+	tree *t = newtree();
+	t->str = estrdup(str);
+	t->type = type;
+	return t;
+}
--- /dev/null
+++ b/unix.c
@@ -1,0 +1,420 @@
+/*
+ * Unix versions of system-specific functions
+ *	By convention, exported routines herein have names beginning with an
+ *	upper case letter.
+ */
+#include "rc.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+#include "getflags.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/wait.h>
+
+static void execfinit(void);
+
+builtin Builtin[] = {
+	"cd",		execcd,
+	"whatis",	execwhatis,
+	"eval",		execeval,
+	"exec",		execexec,	/* but with popword first */
+	"exit",		execexit,
+	"shift",	execshift,
+	"wait",		execwait,
+	".",		execdot,
+	"flag",		execflag,
+	"finit",	execfinit,
+	0
+};
+
+char Rcmain[] = PREFIX "/lib/rcmain";
+char Fdprefix[] = "/dev/fd/";
+
+char *Signame[NSIG];
+
+#define SEP '\1'
+extern char **environ;
+static char **envp;
+
+static void
+Xrdfn(void)
+{
+	char *s;
+	int len;
+
+	for(;*envp;envp++){
+		for(s=*envp;*s && *s!='(' && *s!='=';s++);
+		switch(*s){
+		case '(':		/* Bourne again */
+			if(strncmp(s, "()fn ", 5)!=0)
+				continue;
+			s=estrdup(s+2);
+			len=strlen(s);
+			s[len++]='\n';
+			envp++;
+			runq->pc--;	/* re-execute */
+			execcmds(openiocore(s, len), estrdup("*environ*"), runq->local, runq->redir);
+			runq->lex->qflag = 1;
+			return;
+		default:
+			continue;
+		}
+	}
+}
+
+static void
+execfinit(void)
+{
+	static union code rdfns[5];
+	if(rdfns[0].i==0){
+		rdfns[0].i = 1;
+		rdfns[1].s = "*rdfns*";
+		rdfns[2].f = Xrdfn;
+		rdfns[3].f = Xreturn;
+		rdfns[4].f = 0;
+	}
+	poplist();
+	envp=environ;
+	start(rdfns, 2, runq->local, runq->redir);
+}
+
+static int
+cmpenv(const void *aa, const void *ab)
+{
+	return strcmp(*(char**)aa, *(char**)ab);
+}
+
+static char**
+mkenv(void)
+{
+	char **env, **ep, *p, *q;
+	struct var **h, *v;
+	struct word *a;
+	int nvar = 0, nchr = 0, sep;
+
+	/*
+	 * Slightly kludgy loops look at locals then globals.
+	 * locals no longer exist - geoff
+	 */
+	for(h = gvar-1; h != &gvar[NVAR]; h++)
+	for(v = h >= gvar? *h: runq->local; v ;v = v->next){
+		if((v==vlook(v->name)) && v->val){
+			nvar++;
+			nchr+=strlen(v->name)+1;
+			for(a = v->val;a;a = a->next)
+				nchr+=strlen(a->word)+1;
+		}
+		if(v->fn){
+			nvar++;
+			nchr+=strlen(v->name)+strlen(v->fn[v->pc-1].s)+8;
+		}
+	}
+	env = (char **)emalloc((nvar+1)*sizeof(char *)+nchr);
+	ep = env;
+	p = (char *)&env[nvar+1];
+	for(h = gvar-1; h != &gvar[NVAR]; h++)
+	for(v = h >= gvar? *h: runq->local;v;v = v->next){
+		if((v==vlook(v->name)) && v->val){
+			*ep++=p;
+			q = v->name;
+			while(*q) *p++=*q++;
+			sep='=';
+			for(a = v->val;a;a = a->next){
+				*p++=sep;
+				sep = SEP;
+				q = a->word;
+				while(*q) *p++=*q++;
+			}
+			*p++='\0';
+		}
+		if(v->fn){
+			*ep++=p;
+			*p++='#'; *p++='('; *p++=')';	/* to fool Bourne */
+			*p++='f'; *p++='n'; *p++=' ';
+			q = v->name;
+			while(*q) *p++=*q++;
+			*p++=' ';
+			q = v->fn[v->pc-1].s;
+			while(*q) *p++=*q++;
+			*p++='\0';
+		}
+	}
+	*ep = 0;
+	qsort((void *)env, nvar, sizeof ep[0], cmpenv);
+	return env;	
+}
+
+static word*
+envval(char *s)
+{
+	char *t, c;
+	word *v;
+	for(t=s;*t&&*t!=SEP;t++);
+	c=*t;
+	*t='\0';
+	v=newword(s, c=='\0'?(word*)0:envval(t+1));
+	*t=c;
+	return v;
+}
+
+void
+Vinit(void)
+{
+	char *s;
+
+	for(envp=environ;*envp;envp++){
+		for(s=*envp;*s && *s!='(' && *s!='=';s++);
+		switch(*s){
+		case '=':
+			*s='\0';
+			setvar(*envp, envval(s+1));
+			*s='=';
+			break;
+		default: continue;
+		}
+	}
+}
+
+static void
+sighandler(int sig)
+{
+	trap[sig]++;
+	ntrap++;
+}
+
+void
+Trapinit(void)
+{
+	int i;
+
+	Signame[0] = "sigexit";
+
+#ifdef SIGINT
+	Signame[SIGINT] = "sigint";
+#endif
+#ifdef SIGTERM
+	Signame[SIGTERM] = "sigterm";
+#endif
+#ifdef SIGHUP
+	Signame[SIGHUP] = "sighup";
+#endif
+#ifdef SIGQUIT
+	Signame[SIGQUIT] = "sigquit";
+#endif
+#ifdef SIGPIPE
+	Signame[SIGPIPE] = "sigpipe";
+#endif
+#ifdef SIGUSR1
+	Signame[SIGUSR1] = "sigusr1";
+#endif
+#ifdef SIGUSR2
+	Signame[SIGUSR2] = "sigusr2";
+#endif
+#ifdef SIGBUS
+	Signame[SIGBUS] = "sigbus";
+#endif
+#ifdef SIGWINCH
+	Signame[SIGWINCH] = "sigwinch";
+#endif
+
+	for(i=1; i<NSIG; i++) if(Signame[i]){
+#ifdef SA_RESTART
+		struct sigaction a;
+
+		sigaction(i, NULL, &a);
+		a.sa_flags &= ~SA_RESTART;
+		a.sa_handler = sighandler;
+		sigaction(i, &a, NULL);
+#else
+		signal(i, sighandler);
+#endif
+	}
+}
+
+char*
+Errstr(void)
+{
+	return strerror(errno);
+}
+
+int
+Waitfor(int pid)
+{
+	thread *p;
+	char num[12];
+	int wpid, status;
+
+	if(pid >= 0 && !havewaitpid(pid))
+		return 0;
+	while((wpid = wait(&status))!=-1){
+		delwaitpid(wpid);
+		inttoascii(num, WIFSIGNALED(status)?WTERMSIG(status)+1000:WEXITSTATUS(status));
+		if(wpid==pid){
+			setstatus(num);
+			return 0;
+		}
+		for(p = runq->ret;p;p = p->ret)
+			if(p->pid==wpid){
+				p->pid=-1;
+				p->status = estrdup(num);
+				break;
+			}
+	}
+	if(Eintr()) return -1;
+	return 0;
+}
+
+static char **nextenv;
+
+void
+Updenv(void)
+{
+	if(nextenv){
+		free(nextenv);
+		nextenv = NULL;
+	}
+	if(err)
+		flushio(err);
+}
+
+void
+Exec(char **argv)
+{
+	if(nextenv==NULL) nextenv=mkenv();
+	execve(argv[0], argv+1, nextenv);
+}
+
+int
+Fork(void)
+{
+	Updenv();
+	return fork();
+}
+
+void*
+Opendir(char *name)
+{
+	return opendir(name);
+}
+
+char*
+Readdir(void *arg, int onlydirs)
+{
+	DIR *rd = arg;
+	struct dirent *ent = readdir(rd);
+	if(ent == NULL)
+		return 0;
+	return ent->d_name;
+}
+
+void
+Closedir(void *arg)
+{
+	DIR *rd = arg;
+	closedir(rd);
+}
+
+long
+Write(int fd, void *buf, long cnt)
+{
+	return write(fd, buf, cnt);
+}
+
+long
+Read(int fd, void *buf, long cnt)
+{
+	return read(fd, buf, cnt);
+}
+
+long
+Seek(int fd, long cnt, long whence)
+{
+	return lseek(fd, cnt, whence);
+}
+
+int
+Executable(char *file)
+{
+	return access(file, 01)==0;
+}
+
+int
+Open(char *file, int mode)
+{
+	static int tab[] = {O_RDONLY,O_WRONLY,O_RDWR,O_RDONLY};
+	int fd = open(file, tab[mode&3]);
+	if(fd >= 0 && mode == 3)
+		unlink(file);
+	return fd;
+}
+
+void
+Close(int fd)
+{
+	close(fd);
+}
+
+int
+Creat(char *file)
+{
+	return creat(file, 0666L);
+}
+
+int
+Dup(int a, int b)
+{
+	return dup2(a, b);
+}
+
+int
+Dup1(int a)
+{
+	return dup(a);
+}
+
+void
+Exit(void)
+{
+	Updenv();
+	exit(truestatus()?0:1);
+}
+
+int
+Eintr(void)
+{
+	return errno==EINTR;
+}
+
+void
+Noerror(void)
+{
+	errno=0;
+}
+
+int
+Isatty(int fd)
+{
+	return isatty(fd);
+}
+
+void
+Abort(void)
+{
+	abort();
+}
+
+int
+Chdir(char *dir)
+{
+	return chdir(dir);
+}
+
+void
+Prompt(char *s)
+{
+	pstr(err, s);
+	flushio(err);
+}
--- /dev/null
+++ b/var.c
@@ -1,0 +1,107 @@
+#include "rc.h"
+#include "exec.h"
+#include "fns.h"
+
+int
+hash(char *s, int n)
+{
+	int h = 0, i = 1;
+	while(*s) h+=*s++*i++;
+	h%=n;
+	return h<0?h+n:h;
+}
+#define	NKW	30
+struct kw{
+	char *name;
+	int type;
+	struct kw *next;
+}*kw[NKW];
+
+void
+kenter(int type, char *name)
+{
+	int h = hash(name, NKW);
+	struct kw *p = new(struct kw);
+	p->type = type;
+	p->name = name;
+	p->next = kw[h];
+	kw[h] = p;
+}
+
+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");
+}
+
+tree*
+klook(char *name)
+{
+	struct 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*
+newvar(char *name, var *next)
+{
+	int n = strlen(name)+1;
+	var *v = emalloc(sizeof(var)+n);
+	memmove(v->name, name, n);
+	v->next = next;
+	v->val = 0;
+	v->fn = 0;
+	v->changed = 0;
+	v->fnchanged = 0;
+	return v;
+}
+
+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(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;
+}
+
+void
+freevar(var *v)
+{
+	freewords(v->val);
+	free(v);
+}