shithub: riscv

Download patch

ref: b90036a062ca330ac5f667cd1ee503686cbe0b80
parent: 855cf4326f5a07d7142c2d8918f5fa856d912b85
author: cinap_lenrek <[email protected]>
date: Fri Dec 31 10:27:10 EST 2021

rc: fix everything

Untangle the lexer and interpreter thread state.

Fix the file and line number error reporting, getting rid of
Xsrcfile instruction, as the whole code block can only come
from a single file, stuff the source file in slot[1] of the
code block instead.

Remove limitations for globber (path element limits)
and be more intelligent about handling globbing by
inserting Xglob instruction only when needed and not
run it over every Xsimple argument list.

Remove fragile ndot magic and make it explicit by adding
the -q flag to . builtin command.

Add -b flag for full compilation.

Make exitnext() smart, so we can speculate thru rcmain and
avoid the fork().

Get rid of all print(2) format functions and use io
instead.

Improve the io library, adding rstr() to handle tokenization,
which allows us to look ahead in the already read buffer
for the terminators, avoiding alot of string copies.

Auto indent pcmd(), to make line number reporting more usefull.

Implement here documents properly, so they can work everywhere.

--- a/sys/man/1/rc
+++ b/sys/man/1/rc
@@ -615,7 +615,7 @@
 internal state.
 .PD 0
 .HP
-.BI . " file ..."
+.BI . " [-biq] file ..."
 .br
 Execute commands from
 .IR file .
@@ -625,6 +625,20 @@
 .I File
 is searched for using
 .BR $path .
+The flags
+.B -b
+and
+.B -i
+can be set for the new commands
+(see description below).
+The
+.B -q
+flag suppresses errors,
+inhibiting the effect of
+.B -e
+and
+.B -v
+flags of the main interpreter.
 .HP
 .BI builtin " command ..."
 .br
@@ -974,6 +988,10 @@
 .B -r
 Print debugging information (internal form of commands
 as they are executed).
+.TP
+.B -b
+Compile the command file as a whole before executing.
+This allows syntax checking of the whole file.
 .PD
 .SH FILES
 .TF $home/lib/profile
@@ -1003,7 +1021,5 @@
 .B $status
 changes
 .BR $status .
-.PP
-Functions containing here documents don't work.
 .PP
 Free carets don't get inserted next to keywords.
--- a/sys/src/cmd/rc/code.c
+++ b/sys/src/cmd/rc/code.c
@@ -7,13 +7,12 @@
 #define	c1	t->child[1]
 #define	c2	t->child[2]
 code *codebuf;
-int codep, ncode;
+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);
-char *fnstr(tree*);
 void outcode(tree*, int);
 void codeswitch(tree*, int);
 int iscase(tree*);
@@ -23,7 +22,7 @@
 int
 morecode(void)
 {
-	ncode+=100;
+	ncode+=ncode;
 	codebuf = (code *)erealloc((char *)codebuf, ncode*sizeof codebuf[0]);
 	return 0;
 }
@@ -42,53 +41,66 @@
 	ncode = 100;
 	codebuf = emalloc(ncode*sizeof codebuf[0]);
 	codep = 0;
+	codeline = 0;			/* force source */
 	emiti(0);			/* reference count */
-	outcode(t, flag['e']?1:0);
+	emits(estrdup(lex->file));	/* source file name */
+	outcode(t, !lex->qflag && flag['e']!=0);
 	if(nerror){
 		free(codebuf);
 		return 0;
 	}
-	readhere();
 	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
-cleanhere(char *f)
+noglobs(tree *t, int pattern)
 {
-	emitf(Xdelhere);
-	emits(estrdup(f));
+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;
+	}
 }
 
-char*
-fnstr(tree *t)
-{
-	io *f = openstr();
-	void *v;
-
-	pfmt(f, "%t", t);
-	v = f->strp;
-	f->strp = 0;
-	closeio(f);
-	return v;
-}
-
 void
 outcode(tree *t, int eflag)
 {
-	static int line;
+	void (*f)(void);
 	int p, q;
 	tree *tt;
-	char *f;
 	if(t==0)
 		return;
 	if(t->type!=NOT && t->type!=';')
-		runq->iflast = 0;
-	if(t->line != line){
-		line = t->line;
-		emitf(Xsrcline);
-		emiti(line);
+		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:
@@ -96,6 +108,7 @@
 		break;
 	case '$':
 		emitf(Xmark);
+		noglobs(c0, 0);
 		outcode(c0, eflag);
 		emitf(Xdol);
 		break;
@@ -102,14 +115,18 @@
 	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;
@@ -116,6 +133,26 @@
 	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);
@@ -134,8 +171,8 @@
 	case '`':
 		emitf(Xmark);
 		if(c0){
+			noglobs(c0, 0);
 			outcode(c0, 0);
-			emitf(Xglob);
 		} else {
 			emitf(Xmark);
 			emitf(Xword);
@@ -142,8 +179,10 @@
 			emits(estrdup("ifs"));
 			emitf(Xdol);
 		}
+		emitf(Xqw);
 		emitf(Xbackq);
 		p = emiti(0);
+		codeline = 0;	/* force source */
 		outcode(c1, 0);
 		emitf(Xexit);
 		stuffdot(p);
@@ -169,24 +208,20 @@
 		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));
-			if((f = curfile(runq)) != nil){
-				emitf(Xsrcfile);
-				emits(estrdup(f));
-			}
-			emitf(Xsrcline);
-			emiti(runq->lexline);
+			codeline = 0;	/* force source */
 			outcode(c1, eflag);
-			emitf(Xunlocal);	/* get rid of $* */
 			emitf(Xreturn);
 			stuffdot(p);
 		}
@@ -202,7 +237,7 @@
 		stuffdot(p);
 		break;
 	case NOT:
-		if(!runq->iflast)
+		if(!lex->iflast)
 			yyerror("`if not' does not follow `if(...)'");
 		emitf(Xifnot);
 		p = emiti(0);
@@ -229,6 +264,7 @@
 	case SUBSHELL:
 		emitf(Xsubshell);
 		p = emiti(0);
+		codeline = 0;	/* force source */
 		outcode(c0, eflag);
 		emitf(Xexit);
 		stuffdot(p);
@@ -240,9 +276,11 @@
 		break;
 	case TWIDDLE:
 		emitf(Xmark);
+		noglobs(c1, 1);
 		outcode(c1, eflag);
 		emitf(Xmark);
 		outcode(c0, eflag);
+		emitf(Xqw);
 		emitf(Xmatch);
 		if(eflag)
 			emitf(Xeflag);
@@ -267,7 +305,6 @@
 		emitf(Xmark);
 		if(c1){
 			outcode(c1, eflag);
-			emitf(Xglob);
 		}
 		else{
 			emitf(Xmark);
@@ -277,6 +314,7 @@
 		}
 		emitf(Xmark);		/* dummy value for Xlocal */
 		emitf(Xmark);
+		noglobs(c0, 0);
 		outcode(c0, eflag);
 		emitf(Xlocal);
 		p = emitf(Xfor);
@@ -288,19 +326,9 @@
 		emitf(Xunlocal);
 		break;
 	case WORD:
-		if(t->quoted){
-			emitf(Xword);
-			emits(estrdup(t->str));
-		} else {
-			if((q = Globsize(t->str)) > 0){
-				emitf(Xglobs);
-				emits(estrdup(t->str));
-				emiti(q);
-			} else {
-				emitf(Xword);
-				emits(deglob(estrdup(t->str)));
-			}
-		}
+		emitf(Xword);
+		emits(t->str);
+		t->str=0;	/* passed ownership */
 		break;
 	case DUP:
 		if(t->rtype==DUPFD){
@@ -319,6 +347,7 @@
 		emitf(Xpipefd);
 		emiti(t->rtype);
 		p = emiti(0);
+		codeline = 0;	/* force source */
 		outcode(c0, eflag);
 		emitf(Xexit);
 		stuffdot(p);
@@ -325,8 +354,13 @@
 		break;
 	case REDIR:
 		emitf(Xmark);
+		if(t->rtype==HERE){
+			/* replace end marker with mktmep() pattern */
+			free(c0->str);
+			c0->str=estrdup("/tmp/here.XXXXXXXXXXX");
+			c0->glob=0;
+		}
 		outcode(c0, eflag);
-		emitf(Xglob);
 		switch(t->rtype){
 		case APPEND:
 			emitf(Xappend);
@@ -335,12 +369,16 @@
 			emitf(Xwrite);
 			break;
 		case READ:
-		case HERE:
 			emitf(Xread);
 			break;
 		case RDWR:
 			emitf(Xrdwr);
 			break;
+		case HERE:
+			emitf(Xhere);
+			emits(t->str);
+			t->str=0;	/* passed ownership */
+			break;
 		}
 		emiti(t->fd0);
 		outcode(c1, eflag);
@@ -354,6 +392,7 @@
 				emitf(Xmark);
 				outcode(c1, eflag);
 				emitf(Xmark);
+				noglobs(c0, 0);
 				outcode(c0, eflag);
 				emitf(Xlocal);		/* push var for cmd */
 			}
@@ -366,6 +405,7 @@
 				emitf(Xmark);
 				outcode(c1, eflag);
 				emitf(Xmark);
+				noglobs(c0, 0);
 				outcode(c0, eflag);
 				emitf(Xassign);	/* set var permanently */
 			}
@@ -378,9 +418,11 @@
 		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);
@@ -387,9 +429,12 @@
 		emitf(Xpipewait);
 		break;
 	}
+	if(t->glob > 0)
+		emitf(Xglob);
 	if(t->type!=NOT && t->type!=';')
-		runq->iflast = t->type==IF;
-	else if(c0) runq->iflast = c0->type==IF;
+		lex->iflast = t->type==IF;
+	else if(c0)
+		lex->iflast = c0->type==IF;
 }
 /*
  * switch code looks like this:
@@ -427,6 +472,7 @@
 	}
 	emitf(Xmark);
 	outcode(c0, eflag);
+	emitf(Xqw);
 	emitf(Xjump);
 	nextcase = emiti(0);
 	out = emitf(Xjump);
@@ -436,7 +482,10 @@
 	while(t->type==';'){
 		tt = c1;
 		emitf(Xmark);
-		for(t = c0->child[0];t->type==ARGLIST;t = c0) outcode(c1, eflag);
+		for(t = c0->child[0];t->type==ARGLIST;t = c0) {
+			noglobs(c1, 1);
+			outcode(c1, eflag);
+		}
 		emitf(Xcase);
 		nextcase = emiti(0);
 		t = tt;
@@ -481,7 +530,7 @@
 	code *p;
 	if(--cp[0].i!=0)
 		return;
-	for(p = cp+1;p->f;p++){
+	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
@@ -490,12 +539,13 @@
 		|| 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==Xglobs || p->f==Xsrcfile) free(p[1].s), p+=2;
-		else if(p->f==Xword || p->f==Xdelhere) free((++p)->s);
+		else if(p->f==Xhere) 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);
 }
--- a/sys/src/cmd/rc/exec.c
+++ b/sys/src/cmd/rc/exec.c
@@ -3,66 +3,98 @@
 #include "exec.h"
 #include "io.h"
 #include "fns.h"
+
 /*
  * Start executing the given code at the given pc with the given redirection
  */
-char *argv0="rc";
-
 void
-start(code *c, int pc, var *local)
+start(code *c, int pc, var *local, redir *redir)
 {
-	struct thread *p = new(struct thread);
-
+	thread *p = new(thread);
 	p->code = codecopy(c);
-	p->line = runq?runq->line:0;
+	p->line = 0;
 	p->pc = pc;
 	p->argv = 0;
-	p->redir = p->startredir = runq?runq->redir:nil;
+	p->redir = p->startredir = redir;
+	p->lex = 0;
 	p->local = local;
-	p->cmdfile = nil;
-	p->cmdfd = 0;
-	p->eof = 0;
 	p->iflag = 0;
-	p->lineno = runq ? runq->lineno : 1;
 	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);
+}
+
 word*
-Newword(char *wd, word *next)
+Newword(char *s, word *next)
 {
-	word *p = new(word);
-	p->word = wd;
+	word *p=new(word);
+	p->word = s;
 	p->next = next;
-	p->glob = 0;
 	return p;
 }
 word*
-Pushword(char *wd)
+newword(char *s, word *next)
 {
-	word *w;
+	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);
-	w = Newword(wd, runq->argv->words);
-	runq->argv->words = w;
-	return w;
+	p = Newword(s, runq->argv->words);
+	runq->argv->words = p;
+	return p;
 }
-
 word*
-newword(char *wd, word *next)
+pushword(char *s)
 {
-	return Newword(estrdup(wd), next);
+	return Pushword(estrdup(s));
 }
-word*
-pushword(char *wd)
+char*
+Freeword(word *p)
 {
-	return Pushword(estrdup(wd));
+	char *s = p->word;
+	free(p);
+	return s;
 }
-
 void
-popword(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;
@@ -69,20 +101,12 @@
 	if(p==0)
 		panic("popword but no word!", 0);
 	runq->argv->words = p->next;
-	free(p->word);
-	free(p);
+	return Freeword(p);
 }
-
 void
-freelist(word *w)
+popword(void)
 {
-	word *nw;
-	while(w){
-		nw = w->next;
-		free(w->word);
-		free(w);
-		w = nw;
-	}
+	free(Popword());
 }
 
 void
@@ -89,21 +113,27 @@
 pushlist(void)
 {
 	list *p = new(list);
-	p->next = runq->argv;
 	p->words = 0;
+	p->next = runq->argv;
 	runq->argv = p;
 }
-
-void
-poplist(void)
+word*
+Poplist(void)
 {
+	word *w;
 	list *p = runq->argv;
 	if(p==0)
 		panic("poplist but no argv", 0);
-	freelist(p->words);
+	w = p->words;
 	runq->argv = p->next;
 	free(p);
+	return w;
 }
+void
+poplist(void)
+{
+	freewords(Poplist());
+}
 
 int
 count(word *w)
@@ -116,7 +146,7 @@
 void
 pushredir(int type, int from, int to)
 {
-	redir * rp = new(redir);
+	redir *rp = new(redir);
 	rp->type = type;
 	rp->from = from;
 	rp->to = to;
@@ -124,7 +154,44 @@
 	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;
@@ -139,33 +206,25 @@
 	*rr = rp;
 }
 
-var*
-newvar(char *name, var *next)
-{
-	var *v = new(var);
-	v->name = estrdup(name);
-	v->val = 0;
-	v->fn = 0;
-	v->changed = 0;
-	v->fnchanged = 0;
-	v->next = next;
-	return v;
-}
 /*
  * get command line flags, initialize keywords & traps.
  * get values from environment.
  * set $pid, $cflag, $*
- * fabricate bootstrap code and start it (*=(argv);. /usr/lib/rcmain $*)
+ * 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[17];
-	char num[12], *rcmain;
+	code bootstrap[20];
+	char num[12];
+	char *rcmain=Rcmain;
+
 	int i;
-	argc = getflags(argc, argv, "SsrdiIlxepvVc:1m:1[command]", 1);
+	argv0 = argv[0];
+	argc = getflags(argc, argv, "SsrdiIlxebpvVc:1m:1[command]", 1);
 	if(argc==-1)
 		usage("[file [arg ...]]");
 	if(argv[0][0]=='-')
@@ -173,8 +232,8 @@
 	if(flag['I'])
 		flag['i'] = 0;
 	else if(flag['i']==0 && argc==1 && Isatty(0)) flag['i'] = flagset;
-	rcmain = flag['m']?flag['m'][0]:Rcmain; 
-	err = openfd(2);
+	if(flag['m']) rcmain = flag['m'][0];
+	err = openiofd(2);
 	kinit();
 	Trapinit();
 	Vinit();
@@ -183,38 +242,36 @@
 	setvar("cflag", flag['c']?newword(flag['c'][0], (word *)0)
 				:(word *)0);
 	setvar("rcname", newword(argv[0], (word *)0));
-	i = 0;
-	bootstrap[i++].i = 1;
-	bootstrap[i++].f = Xmark;
-	bootstrap[i++].f = Xword;
-	bootstrap[i++].s="*";
-	bootstrap[i++].f = Xassign;
-	bootstrap[i++].f = Xmark;
-	bootstrap[i++].f = Xmark;
-	bootstrap[i++].f = Xword;
-	bootstrap[i++].s="*";
-	bootstrap[i++].f = Xdol;
-	bootstrap[i++].f = Xword;
-	bootstrap[i++].s = rcmain;
-	bootstrap[i++].f = Xword;
-	bootstrap[i++].s=".";
-	bootstrap[i++].f = Xsimple;
-	bootstrap[i++].f = Xexit;
-	bootstrap[i].i = 0;
-	start(bootstrap, 1, (var *)0);
-	runq->cmdfile = strdup("rc");
-	runq->lexline = 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();
-	argv0 = estrdup(argv[0]);
 	for(i = argc-1;i!=0;--i) pushword(argv[i]);
 
-
 	for(;;){
 		if(flag['r'])
 			pfnc(err, runq);
-		runq->pc++;
-		(*runq->code[runq->pc-1].f)();
+		(*runq->code[runq->pc++].f)();
 		if(ntrap)
 			dotrap();
 	}
@@ -236,15 +293,13 @@
  * Xconc(left, right)			concatenate, push results
  * Xcount(name)				push var count
  * Xdelfn(name)				delete function definition
- * Xdeltraps(names)			delete named traps
  * Xdol(name)				get variable value
- * Xqw(list)				quote list, push result
  * Xdup[i j]				dup file descriptor
  * Xexit				rc exits with status
  * Xfalse{...}				execute {} if false
- * Xfn(name){... Xreturn}			define function
+ * Xfn(name){... Xreturn}		define function
  * Xfor(var, list){... Xreturn}		for loop
- * Xglobs[string globsize]		push globbing string
+ * Xglob(word)				glob word inplace
  * Xjump[addr]				goto
  * Xlocal(name, val)			create local variable, assign value
  * Xmark				mark stack
@@ -254,19 +309,18 @@
  * 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
- * Xsettraps(names){... Xreturn}		define trap functions
- * Xshowtraps				print trap list
- * Xsimple(args)			run command and wait
  * 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
- * Xsrcline[line]			set current line number
- * Xsrcfile[file]			set current file name
  */
 
 void
@@ -273,7 +327,8 @@
 Xappend(void)
 {
 	char *file;
-	int f;
+	int fd;
+
 	switch(count(runq->argv->words)){
 	default:
 		Xerror1(">> requires singleton");
@@ -285,14 +340,12 @@
 		break;
 	}
 	file = runq->argv->words->word;
-	if((f = open(file, 1))<0 && (f = Creat(file))<0){
-		pfmt(err, "%s: ", file);
+	if((fd = Open(file, 1))<0 && (fd = Creat(file))<0){
 		Xerror("can't open");
 		return;
 	}
-	Seek(f, 0L, 2);
-	pushredir(ROPEN, f, runq->code[runq->pc].i);
-	runq->pc++;
+	Seek(fd, 0L, 2);
+	pushredir(ROPEN, fd, runq->code[runq->pc++].i);
 	poplist();
 }
 
@@ -311,8 +364,7 @@
 void
 Xclose(void)
 {
-	pushredir(RCLOSE, runq->code[runq->pc].i, 0);
-	runq->pc++;
+	pushredir(RCLOSE, runq->code[runq->pc++].i, 0);
 }
 
 void
@@ -325,26 +377,21 @@
 void
 Xeflag(void)
 {
-	if(eflagok && !truestatus()) Xexit();
+	if(!truestatus()) Xexit();
 }
 
 void
 Xexit(void)
 {
-	struct var *trapreq;
-	struct word *starval;
 	static int beenhere = 0;
+
 	if(getpid()==mypid && !beenhere){
-		trapreq = vlook("sigexit");
+		var *trapreq = vlook("sigexit");
+		word *starval = vlook("*")->val;
 		if(trapreq->fn){
 			beenhere = 1;
 			--runq->pc;
-			starval = vlook("*")->val;
-			start(trapreq->fn, trapreq->pc, (struct var *)0);
-			runq->local = newvar("*", runq->local);
-			runq->local->val = copywords(starval, (struct word *)0);
-			runq->local->changed = 1;
-			runq->redir = runq->startredir = 0;
+			startfunc(trapreq, copywords(starval, (word*)0), (var*)0, (redir*)0);
 			return;
 		}
 	}
@@ -387,74 +434,106 @@
 }
 
 void
-Xread(void)
+Xpush(void)
 {
+	word *t, *h = Poplist();
+	for(t = h; t->next; t = t->next)
+		;
+	t->next = runq->argv->words;
+	runq->argv->words = h;
+}
+
+void
+Xhere(void)
+{
 	char *file;
-	int f;
+	int fd;
+	io *io;
+
 	switch(count(runq->argv->words)){
 	default:
-		Xerror1("< requires singleton\n");
+		Xerror1("<< requires singleton");
 		return;
 	case 0:
-		Xerror1("< requires file\n");
+		Xerror1("<< requires file");
 		return;
 	case 1:
 		break;
 	}
-	file = runq->argv->words->word;
-	if((f = open(file, 0))<0){
-		pfmt(err, "%s: ", file);
+	file = mktemp(runq->argv->words->word);
+	if((fd = Creat(file))<0){
 		Xerror("can't open");
 		return;
 	}
-	pushredir(ROPEN, f, runq->code[runq->pc].i);
-	runq->pc++;
+	io = openiofd(fd);
+	psubst(io, (uchar*)runq->code[runq->pc++].s);
+	flushio(io);
+	closeio(io);
+	/* open for reading and unlink */
+	if((fd = Open(file, 3))<0){
+		Xerror("can't open");
+		return;
+	}
+	pushredir(ROPEN, fd, runq->code[runq->pc++].i);
 	poplist();
 }
 
 void
-Xrdwr(void)
+Xread(void)
 {
-	char *file;
-	int f;
+	int fd;
 
 	switch(count(runq->argv->words)){
 	default:
-		Xerror1("<> requires singleton\n");
+		Xerror1("< requires singleton");
 		return;
 	case 0:
-		Xerror1("<> requires file\n");
+		Xerror1("< requires file");
 		return;
 	case 1:
 		break;
 	}
-	file = runq->argv->words->word;
-	if((f = open(file, ORDWR))<0){
-		pfmt(err, "%s: ", file);
+	if((fd = Open(runq->argv->words->word, 0))<0){
 		Xerror("can't open");
 		return;
 	}
-	pushredir(ROPEN, f, runq->code[runq->pc].i);
-	runq->pc++;
+	pushredir(ROPEN, fd, runq->code[runq->pc++].i);
 	poplist();
 }
 
 void
-turfredir(void)
+Xrdwr(void)
 {
-	while(runq->redir!=runq->startredir)
-		Xpopredir();
+	int fd;
+
+	switch(count(runq->argv->words)){
+	default:
+		Xerror1("<> requires singleton");
+		return;
+	case 0:
+		Xerror1("<> requires file");
+		return;
+	case 1:
+		break;
+	}
+	if((fd = Open(runq->argv->words->word, 2))<0){
+		Xerror("can't open");
+		return;
+	}
+	pushredir(ROPEN, fd, runq->code[runq->pc++].i);
+	poplist();
 }
 
 void
 Xpopredir(void)
 {
-	struct redir *rp = runq->redir;
+	redir *rp = runq->redir;
+
 	if(rp==0)
-		panic("turfredir null!", 0);
+		panic("Xpopredir null!", 0);
 	runq->redir = rp->next;
 	if(rp->type==ROPEN)
-		close(rp->from);
+		Close(rp->from);
 	free(rp);
 }
 
@@ -461,13 +540,9 @@
 void
 Xreturn(void)
 {
-	struct thread *p = runq;
-	turfredir();
-	while(p->argv) poplist();
-	codefree(p->code);
-	free(p->cmdfile);
-	runq = p->ret;
-	free(p);
+	while(runq->redir!=runq->startredir)
+		Xpopredir();
+	popthread();
 	if(runq==0)
 		Exit(getstatus());
 }
@@ -500,71 +575,41 @@
 }
 
 void
-Xglobs(void)
-{
-	word *w = pushword(runq->code[runq->pc++].s);
-	w->glob = runq->code[runq->pc++].i;
-}
-
-void
 Xwrite(void)
 {
-	char *file;
-	int f;
+	int fd;
+
 	switch(count(runq->argv->words)){
 	default:
-		Xerror1("> requires singleton\n");
+		Xerror1("> requires singleton");
 		return;
 	case 0:
-		Xerror1("> requires file\n");
+		Xerror1("> requires file");
 		return;
 	case 1:
 		break;
 	}
-	file = runq->argv->words->word;
-	if((f = Creat(file))<0){
-		pfmt(err, "%s: ", file);
+	if((fd = Creat(runq->argv->words->word))<0){
 		Xerror("can't open");
 		return;
 	}
-	pushredir(ROPEN, f, runq->code[runq->pc].i);
-	runq->pc++;
+	pushredir(ROPEN, fd, runq->code[runq->pc++].i);
 	poplist();
 }
 
-char*
-list2str(word *words)
-{
-	char *value, *s, *t;
-	int len = 0;
-	word *ap;
-	for(ap = words;ap;ap = ap->next)
-		len+=1+strlen(ap->word);
-	value = emalloc(len+1);
-	s = value;
-	for(ap = words;ap;ap = ap->next){
-		for(t = ap->word;*t;) *s++=*t++;
-		*s++=' ';
-	}
-	if(s==value)
-		*s='\0';
-	else s[-1]='\0';
-	return value;
-}
-
 void
 Xmatch(void)
 {
 	word *p;
-	char *subject;
-	subject = list2str(runq->argv->words);
+	char *s;
+
 	setstatus("no match");
+	s = runq->argv->words->word;
 	for(p = runq->argv->next->words;p;p = p->next)
-		if(match(subject, p->word, '\0')){
+		if(match(s, p->word, '\0')){
 			setstatus("");
 			break;
 		}
-	free(subject);
 	poplist();
 	poplist();
 }
@@ -575,7 +620,8 @@
 	word *p;
 	char *s;
 	int ok = 0;
-	s = list2str(runq->argv->next->words);
+
+	s = runq->argv->next->words->word;
 	for(p = runq->argv->words;p;p = p->next){
 		if(match(s, p->word, '\0')){
 			ok = 1;
@@ -582,7 +628,6 @@
 			break;
 		}
 	}
-	free(s);
 	if(ok)
 		runq->pc++;
 	else
@@ -590,7 +635,7 @@
 	poplist();
 }
 
-word*
+static word*
 conclist(word *lp, word *rp, word *tail)
 {
 	word *v, *p, **end;
@@ -601,8 +646,6 @@
 		p = Newword(emalloc(ln+rn+1), (word *)0);
 		memmove(p->word, lp->word, ln);
 		memmove(p->word+ln, rp->word, rn+1);
-		if(lp->glob || rp->glob)
-			p->glob = Globsize(p->word);
 		*end = p, end = &p->next;
 		if(lp->next == 0 && rp->next == 0)
 			break;
@@ -636,41 +679,30 @@
 	runq->argv->words = vp;
 }
 
-char*
-Str(word *a)
-{
-	char *s = a->word;
-	if(a->glob){
-		a->glob = 0;
-		deglob(s);
-	}
-	return s;
-}
-
 void
 Xassign(void)
 {
 	var *v;
+
 	if(count(runq->argv->words)!=1){
 		Xerror1("variable name not singleton!");
 		return;
 	}
-	v = vlook(Str(runq->argv->words));
+	v = vlook(runq->argv->words->word);
 	poplist();
 	freewords(v->val);
-	v->val = globlist(runq->argv->words);
+	v->val = Poplist();
 	v->changed = 1;
-	runq->argv->words = 0;
-	poplist();
 }
+
 /*
  * 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;
@@ -683,12 +715,13 @@
 	word *a, *star;
 	char *s, *t;
 	int n;
+
 	if(count(runq->argv->words)!=1){
 		Xerror1("variable name not singleton!");
 		return;
 	}
-	s = Str(runq->argv->words);
 	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)
@@ -707,23 +740,34 @@
 void
 Xqw(void)
 {
-	char *s;
-	word *a;
+	char *s, *d;
+	word *a, *p;
+	int n;
 
 	a = runq->argv->words;
-	if(a && a->next == 0){
-		runq->argv->words = 0;
-		poplist();
-		a->next = runq->argv->words;
-		runq->argv->words = a;
+	if(a==0){
+		pushword("");
 		return;
 	}
-	s = list2str(a);
-	poplist();
-	Pushword(s);
+	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;
 }
 
-word*
+static word*
 copynwords(word *a, word *tail, int n)
 {
 	word *v, **end;
@@ -739,15 +783,16 @@
 	return v;
 }
 
-word*
+static word*
 subwords(word *val, int len, word *sub, word *a)
 {
 	int n, m;
 	char *s;
-	if(!sub)
+
+	if(sub==0)
 		return a;
 	a = subwords(val, len, sub->next, a);
-	s = Str(sub);
+	s = sub->word;
 	m = 0;
 	n = 0;
 	while('0'<=*s && *s<='9')
@@ -775,11 +820,12 @@
 {
 	word *a, *v;
 	char *s;
+
 	if(count(runq->argv->next->words)!=1){
 		Xerror1("variable name not singleton!");
 		return;
 	}
-	s = Str(runq->argv->next->words);
+	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);
@@ -792,15 +838,15 @@
 Xcount(void)
 {
 	word *a;
-	char *s, *t;
+	char *s, *t, num[12];
 	int n;
-	char num[12];
+
 	if(count(runq->argv->words)!=1){
 		Xerror1("variable name not singleton!");
 		return;
 	}
-	s = Str(runq->argv->words);
 	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;
@@ -818,59 +864,42 @@
 Xlocal(void)
 {
 	if(count(runq->argv->words)!=1){
-		Xerror1("variable name must be singleton\n");
+		Xerror1("variable name must be singleton");
 		return;
 	}
-	runq->local = newvar(Str(runq->argv->words), runq->local);
+	runq->local = newvar(runq->argv->words->word, runq->local);
 	poplist();
-	runq->local->val = globlist(runq->argv->words);
+	runq->local->val = Poplist();
 	runq->local->changed = 1;
-	runq->argv->words = 0;
-	poplist();
 }
 
 void
 Xunlocal(void)
 {
-	var *v = runq->local, *hid;
+	var *hid, *v = runq->local;
 	if(v==0)
 		panic("Xunlocal: no locals!", 0);
 	runq->local = v->next;
 	hid = vlook(v->name);
 	hid->changed = 1;
-	free(v->name);
-	freewords(v->val);
-	free(v);
+	freevar(v);
 }
 
 void
-freewords(word *w)
-{
-	word *nw;
-	while(w){
-		free(w->word);
-		nw = w->next;
-		free(w);
-		w = nw;
-	}
-}
-
-void
 Xfn(void)
 {
 	var *v;
 	word *a;
-	int end;
-	end = runq->code[runq->pc].i;
-	for(a = globlist(runq->argv->words);a;a = a->next){
+	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 = runq->pc+2;
+		v->pc = pc+2;
 		v->fnchanged = 1;
 	}
-	runq->pc = end;
 	poplist();
 }
 
@@ -889,7 +918,7 @@
 	poplist();
 }
 
-char*
+static char*
 concstatus(char *s, char *t)
 {
 	static char v[NSTATUS+1];
@@ -918,18 +947,20 @@
 	}
 }
 
+static char *promptstr;
+
 void
 Xrdcmds(void)
 {
-	struct thread *p = runq;
-	word *prompt;
+	thread *p = runq;
 
-	flush(err);
-	nerror = 0;
 	if(flag['s'] && !truestatus())
 		pfmt(err, "status=%v\n", vlook("status")->val);
-	if(runq->iflag){
-		prompt = vlook("prompt")->val;
+	flushio(err);
+
+	lex = p->lex;
+	if(p->iflag){
+		word *prompt = vlook("prompt")->val;
 		if(prompt)
 			promptstr = prompt->word;
 		else
@@ -936,44 +967,60 @@
 			promptstr="% ";
 	}
 	Noerror();
+	nerror = 0;
 	if(yyparse()){
-		if(!p->iflag || p->eof && !Eintr()){
-			closeio(p->cmdfd);
-			Xreturn();
-		}
-		else{
+		if(p->iflag && (!lex->eof || Eintr())){
 			if(Eintr()){
 				pchr(err, '\n');
-				p->eof = 0;
+				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 */
 		ntrap = 0;	/* avoid double-interrupts during blocked writes */
-		--p->pc;	/* re-execute Xrdcmds after codebuf runs */
-		start(codebuf, 1, runq->local);
+		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*
-curfile(thread *p)
+srcfile(thread *p)
 {
-	for(; p != nil; p = p->ret)
-		if(p->cmdfile != nil)
-			return p->cmdfile;
-	return "unknown";
+	return p->code[1].s;
 }
 
 void
 Xerror(char *s)
 {
-	if(strcmp(argv0, "rc")==0 || strcmp(argv0, "/bin/rc")==0)
-		pfmt(err, "rc:%d: %s: %r\n", runq->line, s);
-	else
-		pfmt(err, "%s:%d: %s: %r\n", curfile(runq), runq->line, s);
-	flush(err);
+	pfln(err, srcfile(runq), runq->line);
+	pfmt(err, ": %s: %r\n", s);
+	flushio(err);
 	setstatus("error");
 	while(!runq->iflag) Xreturn();
 }
@@ -981,11 +1028,9 @@
 void
 Xerror1(char *s)
 {
-	if(strcmp(argv0, "rc")==0 || strcmp(argv0, "/bin/rc")==0)
-		pfmt(err, "rc:%d: %s\n", runq->line, s);
-	else
-		pfmt(err, "%s:%d: %s\n", curfile(runq), runq->line, s);
-	flush(err);
+	pfln(err, srcfile(runq), runq->line);
+	pfmt(err, ": %s\n", s);
+	flushio(err);
 	setstatus("error");
 	while(!runq->iflag) Xreturn();
 }
@@ -1014,24 +1059,19 @@
 }
 
 void
-Xdelhere(void)
-{
-	Unlink(runq->code[runq->pc++].s);
-}
-
-void
 Xfor(void)
 {
-	if(runq->argv->words==0){
+	word *a = runq->argv->words;
+	if(a==0){
 		poplist();
 		runq->pc = runq->code[runq->pc].i;
 	}
 	else{
-		freelist(runq->local->val);
-		runq->local->val = runq->argv->words;
+		runq->argv->words = a->next;
+		a->next = 0;
+		freewords(runq->local->val);
+		runq->local->val = a;
 		runq->local->changed = 1;
-		runq->argv->words = runq->argv->words->next;
-		runq->local->val->next = 0;
 		runq->pc++;
 	}
 }
@@ -1039,7 +1079,7 @@
 void
 Xglob(void)
 {
-	globlist(runq->argv->words);
+	globword(runq->argv->words);
 }
 
 void
@@ -1046,11 +1086,4 @@
 Xsrcline(void)
 {
 	runq->line = runq->code[runq->pc++].i;
-}
-
-void
-Xsrcfile(void)
-{
-	free(runq->cmdfile);
-	runq->cmdfile = strdup(runq->code[runq->pc++].s);
 }
--- a/sys/src/cmd/rc/exec.h
+++ b/sys/src/cmd/rc/exec.h
@@ -4,15 +4,16 @@
 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);
-extern void Xrdwr(void), Xsrcline(void), Xsrcfile(void);
-extern void Xrdfn(void), Xunredir(void), Xstar(void), Xreturn(void), Xsubshell(void);
-extern void Xtrue(void), Xword(void), Xglobs(void), Xwrite(void), Xpipefd(void), Xcase(void);
-extern void Xlocal(void), Xunlocal(void), Xassign(void), Xsimple(void), Xpopm(void);
+extern void Xjump(void), Xmark(void), Xmatch(void), Xpipe(void), Xread(void), Xhere(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 Xdelhere(void), Xpopredir(void), Xsub(void), Xeflag(void), Xsettrue(void);
+extern void Xpopredir(void), Xsub(void), Xeflag(void), Xsettrue(void);
 extern void Xerror(char*);
 extern void Xerror1(char*);
+
 /*
  * word lists are in correct order,
  * i.e. word0->word1->word2->word3->0
@@ -20,7 +21,6 @@
 struct word{
 	char *word;
 	word *next;
-	int glob;			/* Globsize(word) */
 };
 struct list{
 	word *words;
@@ -27,12 +27,14 @@
 	list *next;
 };
 word *newword(char *, word *), *copywords(word *, word *);
+
 struct redir{
-	char type;			/* what to do */
-	short from, to;			/* what to do it to */
-	redir *next;		/* what else to do (reverse order) */
+	int type;			/* what to do */
+	int from, to;			/* what to do it to */
+	redir *next;			/* what else to do (reverse order) */
 };
-#define	NSTATUS	ERRMAX			/* length of status (from plan 9) */
+#define	NSTATUS	128			/* length of status */
+
 /*
  * redir types
  */
@@ -39,29 +41,30 @@
 #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 */
+	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 */
-	char *cmdfile;			/* file name in Xrdcmd */
-	io *cmdfd;			/* file descriptor for Xrdcmd */
-	int lexline;			/* file descriptor line */
-	int iflast;			/* static `if not' checking */
-	int eof;			/* is cmdfd at eof? */
+	lexer *lex;			/* lexer for Xrdcmds */
 	int iflag;			/* interactive? */
-	int lineno;			/* linenumber */
 	int pid;			/* process for Xpipewait to wait for */
 	char status[NSTATUS];		/* status for Xpipewait */
-	tree *treenodes;		/* tree nodes created by this process */
 	thread *ret;			/* who continues when this finishes */
 };
+
 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{
@@ -68,12 +71,14 @@
 	char *name;
 	void (*fnc)(void);
 };
-extern struct builtin Builtin[];
-int eflagok;			/* kludge flag so that -e doesn't exit in startup */
+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 *curfile(thread*);
\ No newline at end of file
+void execfunc(var*), execcmds(io*, char*, var*, redir*);
+void startfunc(var*, word*, var*, redir*);
+
+char *srcfile(thread*);
+char *getstatus(void);
--- a/sys/src/cmd/rc/fns.h
+++ b/sys/src/cmd/rc/fns.h
@@ -1,33 +1,37 @@
 void	Abort(void);
-void	Closedir(int);
+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	Execute(word*,  word*);
+void	Exec(char**);
 void	Exit(char*);
-int	ForkExecute(char*, char**, int, int, int);
-int	Globsize(char*);
+char*	Errstr(void);
+char*	Freeword(word*);
+int	Fork(void);
 int	Isatty(int);
+word*	Newword(char*,word*);
 void	Noerror(void);
-int	Opendir(char*);
+int	Open(char*, int);
+void*	Opendir(char*);
+word*	Poplist(void);
+char*	Popword(void);
+word*	Pushword(char*);
 long	Read(int, void*, long);
-int	Readdir(int, void*, int);
+char*	Readdir(void*, int);
 long	Seek(int, long, long);
 void	Trapinit(void);
-void	Unlink(char*);
 void	Updenv(void);
 void	Vinit(void);
 int	Waitfor(int, int);
 long	Write(int, void*, long);
 void	addwaitpid(int);
-int	advance(void);
-int	back(int);
-void	cleanhere(char*);
+void	clearwaitpids(void);
 void	codefree(code*);
 int	compile(tree*);
-char *	list2str(word*);
 int	count(word*);
 char*	deglob(char*);
 void	delwaitpid(int);
@@ -34,30 +38,28 @@
 void	dotrap(void);
 void	freenodes(void);
 void	freewords(word*);
-word*	globlist(word*);
+void	globword(word*);
 int	havewaitpid(int);
 int	idchr(int);
-void	inttoascii(char*, long);
+void	inttoascii(char*, int);
 void	kinit(void);
 int	mapfd(int);
 int	match(char*, char*, int);
-char**	mkargv(word*);
-void	clearwaitpids(void);
+char*	makepath(char*, char*);
 void	panic(char*, int);
-void	pathinit(void);
+void	pfln(io*, char*, int);
 void	poplist(void);
 void	popword(void);
 void	pprompt(void);
+void	Prompt(char*);
+void	psubst(io *f, uchar *s);
 void	pushlist(void);
 void	pushredir(int, int, int);
 word*	pushword(char*);
-void	readhere(void);
-word*	searchpath(char*, char*);
+char*	readhere(tree*, io*);
 void	setstatus(char*);
-void	setvar(char*, word*);
-void	shuffleredir(void);
 void	skipnl(void);
-void	start(code*, int, var*);
+void	start(code*, int, var*, redir*);
 int	truestatus(void);
 void	usage(char*);
 int	wordchr(int);
--- a/sys/src/cmd/rc/glob.c
+++ b/sys/src/cmd/rc/glob.c
@@ -5,18 +5,19 @@
 /*
  * delete all the GLOB marks from s, in place
  */
-
 char*
 deglob(char *s)
 {
-	char *b = s;
-	char *t = s;
-	do{
-		if(*t==GLOB)
-			t++;
-		*s++=*t;
-	}while(*t++);
-	return b;
+	char *r = strchr(s, GLOB);
+	if(r){
+		char *w = r++;
+		do{
+			if(*r==GLOB)
+				r++;
+			*w++=*r;
+		}while(*r++);
+	}
+	return s;
 }
 
 static int
@@ -55,50 +56,61 @@
 	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 *p, char *name, char *namep)
+globdir(word *list, char *pattern, char *name)
 {
-	char *t, *newp;
-	int f;
+	char *slash, *glob, *entry;
+	void *dir;
 
 	/* append slashes, Readdir() already filtered directories */
-	while(*p=='/'){
-		*namep++=*p++;
-		*namep='\0';
+	while(*pattern=='/'){
+		pappend(&name, "/");
+		pattern++;
 	}
-	if(*p=='\0')
-		return newword(name, list);
+	if(*pattern=='\0')
+		return Newword(name, list);
+
 	/* scan the pattern looking for a component with a metacharacter in it */
-	t = namep;
-	newp = p;
-	while(*newp){
-		if(*newp==GLOB)
-			break;
-		*t=*newp++;
-		if(*t++=='/'){
-			namep = t;
-			p = newp;
-		}
-	}
+	glob=strchr(pattern, GLOB);
+
 	/* If we ran out of pattern, append the name if accessible */
-	if(*newp=='\0'){
-		*t='\0';
+	if(glob==0){
+		pappend(&name, pattern);
 		if(access(name, 0)==0)
-			list = newword(name, list);
-		return list;
+			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 */
-	*namep='\0';
-	if((f = Opendir(name[0]?name:".")) >= 0){
-		while(*newp!='/' && *newp!='\0') newp++;
-		while(Readdir(f, namep, *newp=='/')){
-			if(matchfn(namep, p)){
-				for(t = namep;*t;t++);
-				list = globdir(list, newp, name, t);
-			}
-		}
-		Closedir(f);
+	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;
 }
 
@@ -105,41 +117,23 @@
 /*
  * Subsitute a word with its glob in place.
  */
-
-static void
+void
 globword(word *w)
 {
 	word *left, *right;
-	char *name;
 
-	if(w->glob == 0)
+	if(w==0 || strchr(w->word, GLOB)==0)
 		return;
-	name = emalloc(w->glob);
-	memset(name, 0, w->glob);
 	right = w->next;
-	left = globdir(right, w->word, name, name);
-	free(name);
+	left = globdir(right, w->word, estrdup(""));
 	if(left == right) {
 		deglob(w->word);
-		w->glob = 0;
 	} else {
 		free(w->word);
 		globsort(left, right);
-		*w = *left;
-		free(left);
+		w->next = left->next;
+		w->word = Freeword(left);
 	}
-}
-
-word*
-globlist(word *l)
-{
-	word *p, *q;
-
-	for(p=l;p;p=q){
-		q = p->next;
-		globword(p);
-	}
-	return l;
 }
 
 /*
--- a/sys/src/cmd/rc/havefork.c
+++ b/sys/src/cmd/rc/havefork.c
@@ -4,32 +4,61 @@
 #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 null = open("/dev/null", 0);
 	int pid;
 	char npid[10];
 
-	if(null<0){
-		Xerror("Can't open /dev/null\n");
-		return;
-	}
-	Updenv();
-	switch(pid = rfork(RFFDG|RFPROC|RFNOTEG)){
+	switch(pid = Fork()){
 	case -1:
-		close(null);
 		Xerror("try again");
 		break;
 	case 0:
 		clearwaitpids();
-		pushredir(ROPEN, null, 0);
-		start(runq->code, runq->pc+1, runq->local);
+		start(runq->code, runq->pc+1, runq->local, runq->redir);
 		runq->ret = 0;
 		break;
 	default:
 		addwaitpid(pid);
-		close(null);
 		runq->pc = runq->code[runq->pc].i;
 		inttoascii(npid, pid);
 		setvar("apid", newword(npid, (word *)0));
@@ -40,8 +69,8 @@
 void
 Xpipe(void)
 {
-	struct thread *p = runq;
-	int pc = p->pc, forkid;
+	thread *p = runq;
+	int pid, pc = p->pc;
 	int lfd = p->code[pc++].i;
 	int rfd = p->code[pc++].i;
 	int pfd[2];
@@ -50,25 +79,24 @@
 		Xerror("can't get pipe");
 		return;
 	}
-	Updenv();
-	switch(forkid = fork()){
+	switch(pid = Fork()){
 	case -1:
 		Xerror("try again");
 		break;
 	case 0:
 		clearwaitpids();
-		start(p->code, pc+2, runq->local);
+		Close(pfd[PRD]);
+		start(p->code, pc+2, runq->local, runq->redir);
 		runq->ret = 0;
-		close(pfd[PRD]);
 		pushredir(ROPEN, pfd[PWR], lfd);
 		break;
 	default:
-		addwaitpid(forkid);
-		start(p->code, p->code[pc].i, runq->local);
-		close(pfd[PWR]);
+		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 = forkid;
+		p->pid = pid;
 		break;
 	}
 }
@@ -76,74 +104,50 @@
 /*
  * Who should wait for the exit from the fork?
  */
-enum { Stralloc = 100, };
 
 void
 Xbackq(void)
 {
-	int c, l, pid;
-	int pfd[2];
-	char *s, *wd, *ewd, *stop;
-	struct io *f;
-	word *v, *nextv;
+	int pid, pfd[2];
+	char *s, *split;
+	word *end, **link;
+	io *f;
 
-	stop = "";
-	if(runq->argv && runq->argv->words)
-		stop = runq->argv->words->word;
 	if(pipe(pfd)<0){
 		Xerror("can't make pipe");
 		return;
 	}
-	Updenv();
-	switch(pid = fork()){
+	switch(pid = Fork()){
 	case -1:
 		Xerror("try again");
-		close(pfd[PRD]);
-		close(pfd[PWR]);
+		Close(pfd[PRD]);
+		Close(pfd[PWR]);
 		return;
 	case 0:
 		clearwaitpids();
-		close(pfd[PRD]);
-		start(runq->code, runq->pc+1, runq->local);
+		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]);
-		f = openfd(pfd[PRD]);
-		s = wd = ewd = 0;
-		v = 0;
-		while((c = rchr(f))!=EOF){
-			if(s==ewd){
-				l = s-wd;
-				wd = erealloc(wd, l+Stralloc);
-				ewd = wd+l+Stralloc-1;
-				s = wd+l;
-			}
-			if(strchr(stop, c)){
-				if(s!=wd){
-					*s='\0';
-					v = newword(wd, v);
-					s = wd;
-				}
-			}
-			else *s++=c;
+		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;
 		}
-		if(s!=wd){
-			*s='\0';
-			v = newword(wd, v);
-		}
-		free(wd);
+		*link = end;
 		closeio(f);
+		free(split);
+
 		Waitfor(pid, 0);
-		poplist();	/* ditch split in "stop" */
-		/* v points to reversed arglist -- reverse it onto argv */
-		while(v){
-			nextv = v->next;
-			v->next = runq->argv->words;
-			runq->argv->words = v;
-			v = nextv;
-		}
+
 		runq->pc = runq->code[runq->pc].i;
 		return;
 	}
@@ -152,8 +156,8 @@
 void
 Xpipefd(void)
 {
-	struct thread *p = runq;
-	int pc = p->pc, pid;
+	thread *p = runq;
+	int pid, pc = p->pc;
 	char name[40];
 	int pfd[2];
 	int sidefd, mainfd;
@@ -170,23 +174,22 @@
 		sidefd = pfd[PRD];
 		mainfd = pfd[PWR];
 	}
-	Updenv();
-	switch(pid = fork()){
+	switch(pid = Fork()){
 	case -1:
 		Xerror("try again");
 		break;
 	case 0:
 		clearwaitpids();
-		start(p->code, pc+2, runq->local);
-		close(mainfd);
+		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);
+		Close(sidefd);
 		pushredir(ROPEN, mainfd, mainfd);
-		shuffleredir();	/* shuffle redir to bottom of stack for turfredir() */
+		shuffleredir();	/* shuffle redir to bottom of stack for Xpopredir() */
 		strcpy(name, Fdprefix);
 		inttoascii(name+strlen(name), mainfd);
 		pushword(name);
@@ -200,14 +203,13 @@
 {
 	int pid;
 
-	Updenv();
-	switch(pid = fork()){
+	switch(pid = Fork()){
 	case -1:
 		Xerror("try again");
 		break;
 	case 0:
 		clearwaitpids();
-		start(runq->code, runq->pc+1, runq->local);
+		start(runq->code, runq->pc+1, runq->local, runq->redir);
 		runq->ret = 0;
 		break;
 	default:
@@ -222,10 +224,8 @@
 execforkexec(void)
 {
 	int pid;
-	int n;
-	char buf[ERRMAX];
 
-	switch(pid = fork()){
+	switch(pid = Fork()){
 	case -1:
 		return -1;
 	case 0:
@@ -232,10 +232,7 @@
 		clearwaitpids();
 		pushword("exec");
 		execexec();
-		strcpy(buf, "can't exec: ");
-		n = strlen(buf);
-		errstr(buf+n, ERRMAX-n);
-		Exit(buf);
+		/* does not return */
 	}
 	addwaitpid(pid);
 	return pid;
--- a/sys/src/cmd/rc/here.c
+++ b/sys/src/cmd/rc/here.c
@@ -2,93 +2,49 @@
 #include "exec.h"
 #include "io.h"
 #include "fns.h"
-struct here *here, **ehere;
-int ser = 0;
-char tmp[]="/tmp/here0000.0000";
-char hex[]="0123456789abcdef";
 
 void psubst(io*, uchar*);
 void pstrs(io*, word*);
 
-void
-hexnum(char *p, int n)
+char*
+readhere(tree *tag, io *in)
 {
-	*p++=hex[(n>>12)&0xF];
-	*p++=hex[(n>>8)&0xF];
-	*p++=hex[(n>>4)&0xF];
-	*p = hex[n&0xF];
-}
+	io *out;
+	char c, *m;
 
-tree*
-heredoc(tree *tag)
-{
-	struct here *h;
-
 	if(tag->type!=WORD){
 		yyerror("Bad here tag");
-		return nil;
+		return 0;
 	}
-	h = new(struct here);
-	h->next = 0;
-	if(here)
-		*ehere = h;
-	else
-		here = h;
-	ehere=&h->next;
-	h->tag = tag;
-	hexnum(&tmp[9], getpid());
-	hexnum(&tmp[14], ser++);
-	h->name = estrdup(tmp);
-	return token(tmp, WORD);
-}
-/*
- * bug: lines longer than NLINE get split -- this can cause spurious
- * missubstitution, or a misrecognized EOF marker.
- */
-#define	NLINE	4096
-
-void
-readhere(void)
-{
-	struct here *h, *nexth;
-	io *f;
-	char *s, *tag;
-	int c, subst;
-	char line[NLINE+1];
-	for(h = here;h;h = nexth){
-		subst=!h->tag->quoted;
-		tag = h->tag->str;
-		c = Creat(h->name);
-		if(c<0)
-			yyerror("can't create here document");
-		f = openfd(c);
-		s = line;
-		pprompt();
-		while((c = rchr(runq->cmdfd))!=EOF){
-			if(c=='\n' || s==&line[NLINE]){
-				*s='\0';
-				if(tag && strcmp(line, tag)==0) break;
-				if(subst)
-					psubst(f, (uchar *)line);
-				else
-					pstr(f, line);
-				s = line;
-				if(c=='\n'){
-					pprompt();
-					pchr(f, c);
-				}
-				else *s++=c;
+	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;
 			}
-			else *s++=c;
+			pprompt();
+			m = tag->str;
+		} else if(m){
+			if(*m == c){
+				m++;
+			} else {
+				m = 0;
+			}
 		}
-		flush(f);
-		closeio(f);
-		cleanhere(h->name);
-		nexth = h->next;
-		free(h);
+		pchr(out, c);
 	}
-	here = 0;
 	doprompt = 1;
+	return closeiostr(out);
 }
 
 void
--- a/sys/src/cmd/rc/io.c
+++ b/sys/src/cmd/rc/io.c
@@ -3,18 +3,13 @@
 #include "io.h"
 #include "fns.h"
 
-enum { Stralloc = 100, };
+enum {
+	NBUF = 8192,
+};
 
-int pfmtnest = 0;
-
 void
-pfmt(io *f, char *fmt, ...)
+vpfmt(io *f, char *fmt, va_list ap)
 {
-	va_list ap;
-	char err[ERRMAX];
-
-	va_start(ap, fmt);
-	pfmtnest++;
 	for(;*fmt;fmt++) {
 		if(*fmt!='%') {
 			pchr(f, *fmt);
@@ -42,16 +37,16 @@
 			pwrd(f, va_arg(ap, char *));
 			break;
 		case 'r':
-			errstr(err, sizeof err); pstr(f, err);
+			pstr(f, Errstr());
 			break;
 		case 's':
 			pstr(f, va_arg(ap, char *));
 			break;
 		case 't':
-			pcmd(f, va_arg(ap, struct tree *));
+			pcmd(f, va_arg(ap, tree *));
 			break;
 		case 'v':
-			pval(f, va_arg(ap, struct word *));
+			pval(f, va_arg(ap, word *));
 			break;
 		default:
 			pchr(f, *fmt);
@@ -58,35 +53,87 @@
 			break;
 		}
 	}
+}
+
+void
+pfmt(io *f, char *fmt, ...)
+{
+	va_list ap;
+	va_start(ap, fmt);
+	vpfmt(f, fmt, ap);
 	va_end(ap);
-	if(--pfmtnest==0)
-		flush(f);
 }
 
 void
 pchr(io *b, int c)
 {
-	if(b->bufp==b->ebuf)
-		fullbuf(b, c);
-	else *b->bufp++=c;
+	if(b->bufp>=b->ebuf)
+		flushio(b);
+	*b->bufp++=c;
 }
 
 int
 rchr(io *b)
 {
-	if(b->bufp==b->ebuf)
-		return emptybuf(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(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++)
+	for(;*s;s++){
 		if(*s=='\'')
-			pfmt(f, "''");
-		else pchr(f, *s);
+			pchr(f, *s);
+		pchr(f, *s);
+	}
 	pchr(f, '\'');
 }
 
@@ -154,112 +201,104 @@
 void
 pval(io *f, word *a)
 {
-	if(a){
-		while(a->next && a->next->word){
-			pwrd(f, (char *)a->word);
-			pchr(f, ' ');
-			a = a->next;
-		}
+	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);
 }
 
-int
-fullbuf(io *f, int c)
+io*
+newio(uchar *buf, int len, int fd)
 {
-	flush(f);
-	return *f->bufp++=c;
+	io *f = new(io);
+	f->buf = buf;
+	f->bufp = buf;
+	f->ebuf = buf+len;
+	f->fd = fd;
+	return f;
 }
 
-void
-flush(io *f)
+/*
+ * Open a string buffer for writing.
+ */
+io*
+openiostr(void)
 {
-	int n;
-
-	if(f->strp){
-		n = f->ebuf - f->strp;
-		f->strp = erealloc(f->strp, n+Stralloc+1);
-		f->bufp = f->strp + n;
-		f->ebuf = f->bufp + Stralloc;
-		memset(f->bufp, '\0', Stralloc+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;
-	}
+	uchar *buf = emalloc(100+1);
+	memset(buf, '\0', 100+1);
+	return newio(buf, 100, -1);
 }
 
-io*
-openfd(int fd)
+/*
+ * Return the buf, free the io
+ */
+char*
+closeiostr(io *f)
 {
-	io *f = new(struct io);
-	f->fd = fd;
-	f->bufp = f->ebuf = f->buf;
-	f->strp = 0;
-	return f;
+	void *buf = f->buf;
+	free(f);
+	return buf;
 }
 
+/*
+ * Use a open file descriptor for reading.
+ */
 io*
-openstr(void)
+openiofd(int fd)
 {
-	io *f = new(struct io);
-
-	f->fd = -1;
-	f->bufp = f->strp = emalloc(Stralloc+1);
-	f->ebuf = f->bufp + Stralloc;
-	memset(f->bufp, '\0', Stralloc+1);
-	return f;
+	return newio(emalloc(NBUF), 0, fd);
 }
+
 /*
  * Open a corebuffer to read.  EOF occurs after reading len
  * characters from buf.
  */
-
 io*
-opencore(char *s, int len)
+openiocore(uchar *buf, int len)
 {
-	io *f = new(struct io);
-	uchar *buf = emalloc(len);
-
-	f->fd = -1 /*open("/dev/null", 0)*/;
-	f->bufp = f->strp = buf;
-	f->ebuf = buf+len;
-	memmove(buf, s, len);
-	return f;
+	return newio(buf, len, -1);
 }
 
 void
-rewind(io *io)
+flushio(io *f)
 {
-	if(io->fd==-1)
-		io->bufp = io->strp;
+	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{
-		io->bufp = io->ebuf = io->buf;
-		Seek(io->fd, 0L, 0);
+		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 *io)
+closeio(io *f)
 {
-	if(io->fd>=0)
-		close(io->fd);
-	if(io->strp)
-		free(io->strp);
-	free(io);
+	if(f->fd>=0) Close(f->fd);
+	free(closeiostr(f));
 }
 
 int
-emptybuf(io *f)
+emptyiobuf(io *f)
 {
 	int n;
-	if(f->fd==-1 || (n = Read(f->fd, f->buf, NBUF))<=0) return EOF;
+	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++;
--- a/sys/src/cmd/rc/io.h
+++ b/sys/src/cmd/rc/io.h
@@ -1,20 +1,20 @@
 #define	EOF	(-1)
-#define	NBUF	512
 
 struct io{
 	int	fd;
-	uchar	*bufp, *ebuf, *strp;
-	uchar	buf[NBUF];
+	uchar	*buf, *bufp, *ebuf;
+	io	*next;
 };
 io *err;
 
-io *openfd(int), *openstr(void), *opencore(char *, int);
-int emptybuf(io*);
+io *openiofd(int), *openiostr(void), *openiocore(uchar*, int);
 void pchr(io*, int);
 int rchr(io*);
+char *rstr(io*, char*);
+char *closeiostr(io*);
 void closeio(io*);
-void flush(io*);
-int fullbuf(io*, int);
+int emptyiobuf(io*);
+void flushio(io*);
 void pdec(io*, int);
 void poct(io*, unsigned);
 void pptr(io*, void*);
@@ -23,5 +23,7 @@
 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);
--- a/sys/src/cmd/rc/lex.c
+++ b/sys/src/cmd/rc/lex.c
@@ -1,10 +1,13 @@
 #include "rc.h"
-#include "exec.h"
 #include "io.h"
 #include "getflags.h"
 #include "fns.h"
-int getnext(void);
 
+lexer *lex;
+
+int doprompt = 1;
+int nerror;
+
 int
 wordchr(int c)
 {
@@ -21,105 +24,109 @@
 	 */
 	return c>' ' && !strchr("!\"#$%&'()+,-./:;<=>?@[\\]^`{|}~", c);
 }
-int future = EOF;
-int doprompt = 1;
-int inquote;
-int incomm;
-int lastc;
-int ndot;
-int nerror;
-int nlexpath;
-int lexpathsz;
 
-/*
- * Look ahead in the input stream
- */
-
-int
-nextc(void)
+lexer*
+newlexer(io *input, char *file)
 {
-	if(future==EOF)
-		future = getnext();
-	return future;
+	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;
 }
-/*
- * Consume the lookahead character.
- */
-int
-advance(void)
+
+void
+freelexer(lexer *p)
 {
-	int c = nextc();
-	lastc = future;
-	future = EOF;
-	if(c == '\n')
-		runq->lexline++;
-	return c;
+	closeio(p->input);
+	free(p->file);
+	free(p);
 }
+
 /*
  * read a character from the input stream
  */	
-
-int
+static int
 getnext(void)
 {
 	int c;
-	static int peekc = EOF;
-	if(peekc!=EOF){
-		c = peekc;
-		peekc = EOF;
+
+	if(lex->peekc!=EOF){
+		c = lex->peekc;
+		lex->peekc = EOF;
 		return c;
 	}
-	if(runq->eof)
+	if(lex->eof){
+epilog:
+		if(*lex->epilog)
+			return *lex->epilog++;
+		doprompt = 1;
 		return EOF;
+	}
 	if(doprompt)
 		pprompt();
-	c = rchr(runq->cmdfd);
-	if(!inquote && c=='\\'){
-		c = rchr(runq->cmdfd);
-		if(c=='\n' && !incomm){		/* don't continue a comment */
+	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{
-			peekc = c;
+			lex->peekc = c;
 			c='\\';
 		}
 	}
-	doprompt = doprompt || c=='\n' || c==EOF;
-	if(c==EOF)
-		runq->eof++;
-	else if(flag['V'] || ndot>=2 && flag['v']) pchr(err, c);
+	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;
 }
 
-void
-pprompt(void)
+/*
+ * Look ahead in the input stream
+ */
+static int
+nextc(void)
 {
-	var *prompt;
-	if(runq->iflag){
-		pstr(err, promptstr);
-		flush(err);
-		if(newwdir){
-			char dir[4096];
-			int fd;
-			if((fd=open("/dev/wdir", OWRITE))>=0){
-				getwd(dir, sizeof(dir));
-				write(fd, dir, strlen(dir));
-				close(fd);
-			}
-			newwdir = 0;
-		}
-		prompt = vlook("prompt");
-		if(prompt->val && prompt->val->next)
-			promptstr = prompt->val->next->word;
-		else
-			promptstr="\t";
-	}
-	runq->lineno++;
-	doprompt = 0;
+	if(lex->future==EOF)
+		lex->future = getnext();
+	return lex->future;
 }
 
-void
+/*
+ * 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;
@@ -127,11 +134,11 @@
 		c = nextc();
 		/* Why did this used to be  if(!inquote && c=='#') ?? */
 		if(c=='#'){
-			incomm = 1;
+			lex->incomm = 1;
 			for(;;){
 				c = nextc();
 				if(c=='\n' || c==EOF) {
-					incomm = 0;
+					lex->incomm = 0;
 					break;
 				}
 				advance();
@@ -156,7 +163,7 @@
 	}
 }
 
-int
+static int
 nextis(int c)
 {
 	if(nextc()==c){
@@ -166,12 +173,12 @@
 	return 0;
 }
 
-char*
+static char*
 addtok(char *p, int val)
 {
 	if(p==0)
 		return 0;
-	if(p==&tok[NTOK-1]){
+	if(p==&lex->tok[NTOK-1]){
 		*p = 0;
 		yyerror("token buffer too short");
 		return 0;
@@ -180,7 +187,7 @@
 	return p;
 }
 
-char*
+static char*
 addutf(char *p, int c)
 {
 	uchar b, m;
@@ -202,16 +209,16 @@
 	return p;
 }
 
-int lastdol;	/* was the last token read '$' or '$#' or '"'? */
-int lastword;	/* was the last token read a word or compound word terminator? */
-
 int
 yylex(void)
 {
-	int c, d = nextc();
+	int glob, c, d = nextc();
+	char *tok = lex->tok;
 	char *w = tok;
-	struct tree *t;
+	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
@@ -219,8 +226,8 @@
 	 * if the next character is the first character of a simple or compound word,
 	 * we insert a `^' before it.
 	 */
-	if(lastword){
-		lastword = 0;
+	if(lex->lastword){
+		lex->lastword = 0;
 		if(d=='('){
 			advance();
 			strcpy(tok, "( [SUB]");
@@ -231,15 +238,15 @@
 			return '^';
 		}
 	}
-	inquote = 0;
+	lex->inquote = 0;
 	skipwhite();
 	switch(c = advance()){
 	case EOF:
-		lastdol = 0;
+		lex->lastdol = 0;
 		strcpy(tok, "EOF");
 		return EOF;
 	case '$':
-		lastdol = 1;
+		lex->lastdol = 1;
 		if(nextis('#')){
 			strcpy(tok, "$#");
 			return COUNT;
@@ -251,7 +258,7 @@
 		strcpy(tok, "$");
 		return '$';
 	case '&':
-		lastdol = 0;
+		lex->lastdol = 0;
 		if(nextis('&')){
 			skipnl();
 			strcpy(tok, "&&");
@@ -260,7 +267,7 @@
 		strcpy(tok, "&");
 		return '&';
 	case '|':
-		lastdol = 0;
+		lex->lastdol = 0;
 		if(nextis(c)){
 			skipnl();
 			strcpy(tok, "||");
@@ -268,7 +275,7 @@
 		}
 	case '<':
 	case '>':
-		lastdol = 0;
+		lex->lastdol = 0;
 		/*
 		 * funny redirection tokens:
 		 *	redir:	arrow | arrow '[' fd ']'
@@ -355,9 +362,9 @@
 			skipnl();
 		return t->type;
 	case '\'':
-		lastdol = 0;
-		lastword = 1;
-		inquote = 1;
+		lex->lastdol = 0;
+		lex->lastword = 1;
+		lex->inquote = 1;
 		for(;;){
 			c = advance();
 			if(c==EOF)
@@ -377,28 +384,51 @@
 		return t->type;
 	}
 	if(!wordchr(c)){
-		lastdol = 0;
+		lex->lastdol = 0;
 		tok[0] = c;
 		tok[1]='\0';
 		return c;
 	}
+	glob = 0;
 	for(;;){
-		if(c=='*' || c=='[' || c=='?' || c==GLOB)
+		if(c=='*' || c=='[' || c=='?' || c==GLOB){
+			glob = 1;
 			w = addtok(w, GLOB);
+		}
 		w = addutf(w, c);
 		c = nextc();
-		if(lastdol?!idchr(c):!wordchr(c)) break;
+		if(lex->lastdol?!idchr(c):!wordchr(c)) break;
 		advance();
 	}
 
-	lastword = 1;
-	lastdol = 0;
+	lex->lastword = 1;
+	lex->lastdol = 0;
 	if(w!=0)
 		*w='\0';
 	t = klook(tok);
 	if(t->type!=WORD)
-		lastword = 0;
+		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);
 }
--- a/sys/src/cmd/rc/pcmd.c
+++ b/sys/src/cmd/rc/pcmd.c
@@ -6,7 +6,7 @@
 #define	c1	t->child[1]
 #define	c2	t->child[2]
 
-void
+static void
 pdeglob(io *f, char *s)
 {
 	while(*s){
@@ -16,6 +16,14 @@
 	}
 }
 
+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)
 {
@@ -38,7 +46,11 @@
 	break;
 	case BANG:	pfmt(f, "! %t", c0);
 	break;
-	case BRACE:	pfmt(f, "{%t}", c0);
+	case BRACE:
+			ntab++;
+			pfmt(f, "{\n%s%t", tabs(), c0);
+			ntab--;
+			pfmt(f, "\n%s}", tabs());
 	break;
 	case COUNT:	pfmt(f, "$#%t", c0);
 	break;
@@ -75,9 +87,14 @@
 		break;
 	case ';':
 		if(c0){
-			if(c1)
-				pfmt(f, "%t\n%t", c0, c1);
-			else pfmt(f, "%t", 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;
@@ -109,6 +126,8 @@
 		pchr(f, ' ');
 		switch(t->rtype){
 		case HERE:
+			if(c1)
+				pfmt(f, "%t ", c1);
 			pchr(f, '<');
 		case READ:
 		case RDWR:
@@ -127,7 +146,9 @@
 			break;
 		}
 		pfmt(f, "%t", c0);
-		if(c1)
+		if(t->rtype == HERE)
+			pfmt(f, "\n%s%t\n", t->str, c0);
+		else if(c1)
 			pfmt(f, " %t", c1);
 		break;
 	case '=':
--- a/sys/src/cmd/rc/pfnc.c
+++ b/sys/src/cmd/rc/pfnc.c
@@ -18,8 +18,10 @@
 	Xjump, "Xjump",
 	Xmark, "Xmark",
 	Xpopm, "Xpopm",
+	Xpush, "Xpush",
 	Xrdwr, "Xrdwr",
 	Xread, "Xread",
+	Xhere, "Xhere",
 	Xreturn, "Xreturn",
 	Xtrue, "Xtrue",
 	Xif, "Xif",
@@ -44,11 +46,8 @@
 	Xbackq, "Xbackq",
 	Xpipefd, "Xpipefd",
 	Xsubshell, "Xsubshell",
-	Xdelhere, "Xdelhere",
 	Xfor, "Xfor",
 	Xglob, "Xglob",
-	Xglobs, "Xglobs",
-	Xrdfn, "Xrdfn",
 	Xsimple, "Xsimple",
 	Xqw, "Xqw",
 	Xsrcline, "Xsrcline",
@@ -55,20 +54,25 @@
 0};
 
 void
-pfnc(io *fd, thread *t)
+pfun(io *f, void (*fn)(void))
 {
 	int i;
-	void (*fn)(void) = t->code[t->pc].f;
-	list *a;
-
-	pfmt(fd, "%s:%d: pid %d cycle %p %d ", t->cmdfile, t->line, getpid(), t->code, t->pc);
 	for(i = 0;fname[i].f;i++) if(fname[i].f==fn){
-		pstr(fd, fname[i].name);
-		break;
+		pstr(f, fname[i].name);
+		return;
 	}
-	if(!fname[i].f)
-		pfmt(fd, "%p", fn);
-	for(a = t->argv;a;a = a->next) pfmt(fd, " (%v)", a->words);
-	pchr(fd, '\n');
-	flush(fd);
+	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);
 }
--- a/sys/src/cmd/rc/plan9.c
+++ b/sys/src/cmd/rc/plan9.c
@@ -9,16 +9,33 @@
 #include "fns.h"
 #include "getflags.h"
 
-enum {
-	Maxenvname = 128,	/* undocumented limit */
+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
 };
-char *syssigname[] = {
+static char *syssigname[] = {
 	"exit",		/* can't happen */
 	"hangup",
 	"interrupt",
@@ -29,32 +46,24 @@
 	"term",
 	0
 };
-char Rcmain[]="/rc/lib/rcmain";
-char Fdprefix[]="/fd/";
-void execfinit(void);
-void execbind(void);
-void execmount(void);
-void execnewpgrp(void);
-builtin Builtin[] = {
-	"cd",		execcd,
-	"whatis",	execwhatis,
-	"eval",		execeval,
-	"exec",		execexec,	/* but with popword first */
-	"exit",		execexit,
-	"shift",	execshift,
-	"wait",		execwait,
-	".",		execdot,
-	"finit",	execfinit,
-	"flag",		execflag,
-	"rfork",	execnewpgrp,
-	0
-};
 
-void
-execnewpgrp(void)
+static void
+execfinit(void)
 {
+	char *cmds = estrdup("for(i in '/env/fn#'*){. -bq $i}\n");
+	int line = runq->line;
+	poplist();
+	execcmds(openiocore((uchar*)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;
@@ -91,11 +100,11 @@
 		return;
 	}
 	if(rfork(arg)==-1){
-		pfmt(err, "rc: %s failed\n", runq->argv->words->word);
+		pfmt(err, "%s: %s failed\n", argv0, runq->argv->words->word);
 		setstatus("rfork failed");
 	} else {
 		if(arg & RFCFDG){
-			struct redir *rp;
+			redir *rp;
 			for(rp = runq->redir; rp; rp = rp->next)
 				rp->type = 0;
 		}
@@ -104,101 +113,66 @@
 	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, f, len, i, n, nent;
-	char *buf, *s;
-	char envname[Maxenvname];
-	word *val;
+	int dir, fd, i, n;
 	Dir *ent;
 
-	dir = open("/env", OREAD);
+	dir = Open(Env("", 0), 0);
 	if(dir<0){
-		pfmt(err, "rc: can't open /env: %r\n");
+		pfmt(err, "%s: can't open: %r\n", argv0);
 		return;
 	}
-	ent = nil;
 	for(;;){
-		nent = dirread(dir, &ent);
-		if(nent <= 0)
+		ent = 0;
+		n = dirread(dir, &ent);
+		if(n <= 0)
 			break;
-		for(i = 0; i<nent; i++){
-			len = ent[i].length;
-			if(len && strncmp(ent[i].name, "fn#", 3)!=0){
-				snprint(envname, sizeof envname, "/env/%s", ent[i].name);
-				if((f = open(envname, 0))>=0){
-					buf = emalloc(len+1);
-					n = readn(f, buf, len);
-					if (n <= 0)
-						buf[0] = '\0';
-					else
-						buf[n] = '\0';
-					val = 0;
-					/* Charitably add a 0 at the end if need be */
-					if(buf[len-1])
-						buf[len++]='\0';
-					s = buf+len-1;
-					for(;;){
-						while(s!=buf && s[-1]!='\0') --s;
-						val = newword(s, val);
-						if(s==buf)
-							break;
-						--s;
-					}
-					setvar(ent[i].name, val);
-					vlook(ent[i].name)->changed = 0;
-					close(f);
-					free(buf);
+		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);
+	Close(dir);
 }
 
-void
-Xrdfn(void)
+char*
+Errstr(void)
 {
-	if(runq->argv->words == 0)
-		poplist();
-	else {
-		free(runq->cmdfile);
-		int f = open(runq->argv->words->word, 0);
-		runq->cmdfile = strdup(runq->argv->words->word);
-		runq->lexline = 1;
-		runq->pc--;
-		popword();
-		if(f>=0) execcmds(openfd(f));
-	}
+	static char err[ERRMAX];
+	rerrstr(err, sizeof err);
+	return err;
 }
-union code rdfns[8];
 
-void
-execfinit(void)
-{
-	static int first = 1;
-	if(first){
-		rdfns[0].i = 1;
-		rdfns[1].f = Xmark;
-		rdfns[2].f = Xglobs;
-		rdfns[4].i = Globsize(rdfns[3].s = "/env/fn#\001*");
-		rdfns[5].f = Xglob;
-		rdfns[6].f = Xrdfn;
-		rdfns[7].f = Xreturn;
-		first = 0;
-	}
-	poplist();
-	start(rdfns, 1, runq->local);
-}
-
 int
 Waitfor(int pid, int)
 {
 	thread *p;
 	Waitmsg *w;
-	char errbuf[ERRMAX];
 
 	if(pid >= 0 && !havewaitpid(pid))
 		return 0;
@@ -218,54 +192,46 @@
 		free(w);
 	}
 
-	errstr(errbuf, sizeof errbuf);
-	if(strcmp(errbuf, "interrupted")==0) return -1;
+	if(strcmp(Errstr(), "interrupted")==0) return -1;
 	return 0;
 }
 
-char **
-mkargv(word *a)
-{
-	char **argv = (char **)emalloc((count(a)+2)*sizeof(char *));
-	char **argp = argv+1;	/* leave one at front for runcoms */
-	for(;a;a = a->next) *argp++=a->word;
-	*argp = 0;
-	return argv;
-}
-
-void
+static void
 addenv(var *v)
 {
-	char envname[Maxenvname];
 	word *w;
-	int f;
-	io *fd;
+	int fd;
+	io *f;
+
 	if(v->changed){
 		v->changed = 0;
-		snprint(envname, sizeof envname, "/env/%s", v->name);
-		if((f = Creat(envname))<0)
-			pfmt(err, "rc: can't open %s: %r\n", envname);
+		if((fd = Creat(Env(v->name, 0)))<0)
+			pfmt(err, "%s: can't open: %r\n", argv0);
 		else{
-			for(w = v->val;w;w = w->next)
-				write(f, w->word, strlen(w->word)+1L);
-			close(f);
+			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;
-		snprint(envname, sizeof envname, "/env/fn#%s", v->name);
-		if((f = Creat(envname))<0)
-			pfmt(err, "rc: can't open %s: %r\n", envname);
+		if((fd = Creat(Env(v->name, 1)))<0)
+			pfmt(err, "%s: can't open: %r\n", argv0);
 		else{
-			fd = openfd(f);
+			f = openiofd(fd);
 			if(v->fn)
-				pfmt(fd, "fn %q %s\n", v->name, v->fn[v->pc-1].s);
-			closeio(fd);
+				pfmt(f, "fn %q %s\n", v->name, v->fn[v->pc-1].s);
+			flushio(f);
+			closeio(f);
 		}
 	}
 }
 
-void
+static void
 updenvlocal(var *v)
 {
 	if(v){
@@ -283,80 +249,43 @@
 			addenv(v);
 	if(runq)
 		updenvlocal(runq->local);
+	flushio(err);
 }
 
-/* not used on plan 9 */
-int
-ForkExecute(char *, char **, int, int, int)
-{
-	return -1;
-}
-
 void
-Execute(word *args, word *path)
+Exec(char **argv)
 {
-	char **argv = mkargv(args);
-	char file[1024];
-	int nc, mc;
-
-	Updenv();
-	mc = strlen(argv[1])+1;
-	for(;path;path = path->next){
-		nc = strlen(path->word);
-		if(nc + mc >= sizeof file - 1){	/* 1 for / */
-			werrstr("command path name too long");
-			continue;
-		}
-		if(nc > 0){
-			memmove(file, path->word, nc);
-			file[nc++] = '/';
-		}
-		memmove(file+nc, argv[1], mc);
-		exec(file, argv+1);
-	}
-	rerrstr(file, sizeof file);
-	setstatus(file);
-	pfmt(err, "%s: %s\n", argv[1], file);
-	free(argv);
+	exec(argv[0], argv+1);
 }
-#define	NDIR	256		/* shoud be a better way */
 
 int
-Globsize(char *p)
+Fork(void)
 {
-	int isglob = 0, globlen = NDIR+1;
-	for(;*p;p++){
-		if(*p==GLOB){
-			p++;
-			if(*p!=GLOB)
-				isglob++;
-			globlen+=*p=='*'?NDIR:1;
-		}
-		else
-			globlen++;
-	}
-	return isglob?globlen:0;
+	Updenv();
+	return rfork(RFPROC|RFFDG|RFREND);
 }
-#define	NFD	50
 
-struct{
+
+typedef struct readdir readdir;
+struct readdir {
 	Dir	*dbuf;
-	int	i;
-	int	n;
-}dir[NFD];
+	int	i, n;
+	int	fd;
+};
 
-int
+void*
 Opendir(char *name)
 {
-	int f;
-
-	if((f = open(name, 0)) < 0)
-		return f;
-	if(f<NFD){
-		dir[f].i = 0;
-		dir[f].n = 0;
-	}
-	return f;
+	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
@@ -370,57 +299,52 @@
 	return w;
 }
 
-int
-Readdir(int f, void *p, int onlydirs)
+char*
+Readdir(void *arg, int onlydirs)
 {
+	readdir *rd = arg;
 	int n;
-
-	if(f<0 || f>=NFD)
-		return 0;
 Again:
-	if(dir[f].i==dir[f].n){	/* read */
-		free(dir[f].dbuf);
-		dir[f].dbuf = 0;
-		n = dirread(f, &dir[f].dbuf);
+	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(dir[f].dbuf, n);
+				n = trimdirs(rd->dbuf, n);
 				if(n == 0)
 					goto Again;
 			}	
-			dir[f].n = n;
+			rd->n = n;
 		}else
-			dir[f].n = 0;
-		dir[f].i = 0;
+			rd->n = 0;
+		rd->i = 0;
 	}
-	if(dir[f].i == dir[f].n)
+	if(rd->i>=rd->n)
 		return 0;
-	strncpy((char*)p, dir[f].dbuf[dir[f].i].name, NDIR);
-	dir[f].i++;
-	return 1;
+	return rd->dbuf[rd->i++].name;
 }
 
 void
-Closedir(int f)
+Closedir(void *arg)
 {
-	if(f>=0 && f<NFD){
-		free(dir[f].dbuf);
-		dir[f].i = 0;
-		dir[f].n = 0;
-		dir[f].dbuf = 0;
-	}
-	close(f);
+	readdir *rd = arg;
+	Close(rd->fd);
+	free(rd->dbuf);
+	free(rd);
 }
-int interrupted = 0;
-void
+
+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;
 	}
-	pfmt(err, "rc: note: %s\n", s);
 	noted(NDFLT);
 	return;
 Out:
@@ -428,10 +352,6 @@
 		trap[i]++;
 		ntrap++;
 	}
-	if(ntrap>=32){	/* rc is probably in a trap loop */
-		pfmt(err, "rc: Too many traps (trap %s), aborting\n", s);
-		abort();
-	}
 	noted(NCONT);
 }
 
@@ -441,12 +361,6 @@
 	notify(notifyf);
 }
 
-void
-Unlink(char *name)
-{
-	remove(name);
-}
-
 long
 Write(int fd, void *buf, long cnt)
 {
@@ -480,9 +394,22 @@
 }
 
 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, 1, 0666L);
+	return create(file, OWRITE, 0666L);
 }
 
 int
@@ -524,11 +451,6 @@
 
 	if(fd2path(fd, buf, sizeof buf) != 0)
 		return 0;
-
-	/* might be #c/cons during boot - fixed 22 april 2005, remove this later */
-	if(strcmp(buf, "#c/cons") == 0)
-		return 1;
-
 	/* might be /mnt/term/dev/cons */
 	return strlen(buf) >= 9 && strcmp(buf+strlen(buf)-9, "/dev/cons") == 0;
 }
@@ -537,51 +459,32 @@
 Abort(void)
 {
 	pfmt(err, "aborting\n");
-	flush(err);
 	Exit("aborting");
 }
 
-int *waitpids;
-int nwaitpids;
+static int newwdir;
 
-void
-addwaitpid(int pid)
+int
+Chdir(char *dir)
 {
-	waitpids = erealloc(waitpids, (nwaitpids+1)*sizeof waitpids[0]);
-	waitpids[nwaitpids++] = pid;
+	newwdir = 1;
+	return chdir(dir);
 }
 
 void
-delwaitpid(int pid)
+Prompt(char *s)
 {
-	int r, w;
-	
-	for(r=w=0; r<nwaitpids; r++)
-		if(waitpids[r] != pid)
-			waitpids[w++] = waitpids[r];
-	nwaitpids = w;
-}
+	pstr(err, s);
+	flushio(err);
 
-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;
-}
-
-/* avoid loading any floating-point library code */
-int
-_efgfmt(Fmt *)
-{
-	return -1;
+	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;
+	}
 }
--- a/sys/src/cmd/rc/rc.h
+++ b/sys/src/cmd/rc/rc.h
@@ -30,6 +30,7 @@
 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;
@@ -40,10 +41,11 @@
 struct tree{
 	int	type;
 	int	rtype, fd0, fd1;	/* details of REDIR PIPE DUP tokens */
-	char	*str;
-	int	quoted;
-	int	iskw;
 	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;
 };
@@ -52,12 +54,15 @@
 tree *tree2(int, tree*, tree*), *tree3(int, tree*, tree*, tree*);
 tree *mung1(tree*, tree*), *mung2(tree*, tree*, tree*);
 tree *mung3(tree*, tree*, tree*, tree*), *epimung(tree*, tree*);
-tree *simplemung(tree*), *heredoc(tree*);
-void freetree(tree*);
-tree *cmdtree;
+tree *simplemung(tree*);
+tree *globprop(tree*);
+char *fnstr(tree*);
 
 /*
- * The first word of any code vector is a reference count.
+ * 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.
  */
@@ -67,14 +72,35 @@
 	char	*s;
 };
 
-int newwdir;
-char *promptstr;
-int doprompt;
-
 #define	NTOK	8192
 
-char tok[NTOK];
+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
@@ -84,15 +110,16 @@
 #define RDWR	7
 
 struct var{
-	char	*name;		/* ascii name */
+	var	*next;		/* next on hash or local list */
 	word	*val;		/* value */
-	int	changed;
 	code	*fn;		/* pointer to function's code vector */
-	int	fnchanged;
 	int	pc;		/* pc of start of function */
-	var	*next;		/* next on hash or local list */
+	char	fnchanged;
+	char	changed;
+	char	name[];
 };
 var *vlook(char*), *gvlook(char*), *newvar(char*, var*);
+void setvar(char*, word*), freevar(var*);
 
 #define	NVAR	521
 
@@ -104,13 +131,6 @@
 void *erealloc(void *, long);
 char *estrdup(char*);
 
-#define	NOFILE	128		/* should come from <param.h> */
-
-struct here{
-	tree	*tag;
-	char	*name;
-	struct here *next;
-};
 int mypid;
 
 /*
@@ -141,12 +161,4 @@
  */
 #define	PRD	0
 #define	PWR	1
-char Rcmain[], Fdprefix[];
-/*
- * How many dot commands have we executed?
- * Used to ensure that -v flag doesn't print rcmain.
- */
-extern int ndot;
-extern int lastc;
-extern int lastword;
-char *getstatus(void);
+extern char Rcmain[], Fdprefix[];
--- a/sys/src/cmd/rc/simple.c
+++ b/sys/src/cmd/rc/simple.c
@@ -6,32 +6,63 @@
 #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){
-	code *c=&runq->code[runq->pc];
+	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->f==Xsrcfile)
+		else if(c->f==Xsrcline)
 			c += 2;
-		else
+		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;
-	struct builtin *bp;
 	int pid;
 
-	a = globlist(runq->argv->words);
+	a = runq->argv->words;
 	if(a==0){
 		Xerror1("empty argument list");
 		return;
@@ -43,28 +74,25 @@
 		execfunc(v);
 	else{
 		if(strcmp(a->word, "builtin")==0){
-			if(count(a)==1){
-				pfmt(err, "builtin: empty argument list\n");
-				setstatus("empty arg list");
-				poplist();
-				return;
-			}
 			a = a->next;
-			popword();
-		}
-		for(bp = Builtin;bp->name;bp++)
-			if(strcmp(a->word, bp->name)==0){
-				(*bp->fnc)();
+			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{
-			flush(err);
-			Updenv();	/* necessary so changes don't go out again */
 			if((pid = execforkexec()) < 0){
 				Xerror("try again");
 				return;
@@ -78,7 +106,7 @@
 	}
 }
 
-void
+static void
 doredir(redir *rp)
 {
 	if(rp){
@@ -87,7 +115,7 @@
 		case ROPEN:
 			if(rp->from!=rp->to){
 				Dup(rp->from, rp->to);
-				close(rp->from);
+				Close(rp->from);
 			}
 			break;
 		case RDUP:
@@ -94,7 +122,7 @@
 			Dup(rp->from, rp->to);
 			break;
 		case RCLOSE:
-			close(rp->from);
+			Close(rp->from);
 			break;
 		}
 	}
@@ -115,17 +143,53 @@
 	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("empty argument list");
 		return;
 	}
+	argv = mkargv(runq->argv->words);
+	Updenv();
 	doredir(runq->redir);
-	Execute(runq->argv->words, searchpath(runq->argv->words->word, "path"));
-	poplist();
+	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: %r\n", argv[1]);
 	Xexit();
 }
 
@@ -132,27 +196,10 @@
 void
 execfunc(var *func)
 {
-	word *starval;
-	popword();
-	starval = runq->argv->words;
-	runq->argv->words = 0;
-	poplist();
-	start(func->fn, func->pc, runq->local);
-	runq->local = newvar("*", runq->local);
-	runq->local->val = starval;
-	runq->local->changed = 1;
+	popword();	/* name */
+	startfunc(func, Poplist(), runq->local, runq->redir);
 }
 
-int
-dochdir(char *word)
-{
-	/* report to /dev/wdir if it exists and we're interactive */
-	if(chdir(word)<0)
-		return -1;
-	newwdir = 1;
-	return 1;
-}
-
 void
 execcd(void)
 {
@@ -168,11 +215,8 @@
 	case 2:
 		a = a->next;
 		for(cdpath = searchpath(a->word, "cdpath"); cdpath; cdpath = cdpath->next){
-			if(cdpath->word[0] != '\0')
-				dir = smprint("%s/%s", cdpath->word, a->word);
-			else
-				dir = estrdup(a->word);
-			if(dochdir(dir) >= 0){
+			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);
@@ -186,8 +230,8 @@
 		break;
 	case 1:
 		a = vlook("home")->val;
-		if(count(a)>=1){
-			if(dochdir(a->word)>=0)
+		if(a){
+			if(Chdir(a->word)>=0)
 				setstatus("");
 			else
 				pfmt(err, "Can't cd %s: %r\n", a->word);
@@ -233,8 +277,7 @@
 	star = vlook("*");
 	for(;n>0 && star->val;--n){
 		a = star->val->next;
-		free(star->val->word);
-		free(star->val);
+		free(Freeword(star->val));
 		star->val = a;
 		star->changed = 1;
 	}
@@ -261,94 +304,98 @@
 	}
 	return fd;
 }
-union code rdcmds[4];
 
 void
-execcmds(io *f)
+execcmds(io *input, char *file, var *local, redir *redir)
 {
-	static int first = 1;
+	static union code rdcmds[5];
 
-	if(first){
+	if(rdcmds[0].i==0){
 		rdcmds[0].i = 1;
-		rdcmds[1].f = Xrdcmds;
-		rdcmds[2].f = Xreturn;
-		first = 0;
+		rdcmds[1].s="*rdcmds*";
+		rdcmds[2].f = Xrdcmds;
+		rdcmds[3].f = Xreturn;
+		rdcmds[4].f = 0;
 	}
-	start(rdcmds, 1, runq->local);
-	runq->cmdfd = f;
-	runq->iflast = 0;
+
+	if(exitnext()) turfstack(local);
+
+	start(rdcmds, 2, local, redir);
+	runq->lex = newlexer(input, file);
 }
 
 void
 execeval(void)
 {
-	char *cmdline;
+	char *cmds;
 	int len;
-	if(count(runq->argv->words)<=1){
+	io *f;
+
+	popword();	/* "eval" */
+
+	if(runq->argv->words==0){
 		Xerror1("Usage: eval cmd ...");
 		return;
 	}
-	eflagok = 1;
-	cmdline = list2str(runq->argv->words->next);
-	len = strlen(cmdline);
-	cmdline[len] = '\n';
+	Xqw();		/* make into single word */
+	cmds = Popword();
+	len = strlen(cmds);
+	cmds[len++] = '\n';
 	poplist();
-	execcmds(opencore(cmdline, len+1));
-	free(cmdline);
+
+	f = openiostr();
+	pfln(f, srcfile(runq), runq->line);
+	pstr(f, " *eval*");
+
+	execcmds(openiocore((uchar*)cmds, len), closeiostr(f), runq->local, runq->redir);
 }
-union code dotcmds[14];
 
 void
 execdot(void)
 {
-	int iflag = 0;
-	int fd;
-	list *av;
-	thread *p = runq;
-	char *zero, *file;
-	word *path;
-	static int first = 1;
+	int fd, bflag, iflag, qflag;
+	word *path, *argv;
+	char *file;
 
-	if(first){
-		dotcmds[0].i = 1;
-		dotcmds[1].f = Xmark;
-		dotcmds[2].f = Xword;
-		dotcmds[3].s="0";
-		dotcmds[4].f = Xlocal;
-		dotcmds[5].f = Xmark;
-		dotcmds[6].f = Xword;
-		dotcmds[7].s="*";
-		dotcmds[8].f = Xlocal;
-		dotcmds[9].f = Xrdcmds;
-		dotcmds[10].f = Xunlocal;
-		dotcmds[11].f = Xunlocal;
-		dotcmds[12].f = Xreturn;
-		first = 0;
-	}
-	else
-		eflagok = 1;
+	popword();	/* "." */
 
-	popword();
-	if(p->argv->words && strcmp(p->argv->words->word, "-i")==0){
-		iflag = 1;
+	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(p->argv->words==0){
-		Xerror1("Usage: . [-i] file [arg ...]");
+	if(runq->argv->words==0){
+Usage:
+		Xerror1("Usage: . [-biq] file [arg ...]");
 		return;
 	}
-	zero = estrdup(p->argv->words->word);
-	popword();
+	argv = Poplist();
+		
+	file = 0;
 	fd = -1;
-	for(path = searchpath(zero, "path"); path; path = path->next){
-		if(path->word[0] != '\0')
-			file = smprint("%s/%s", path->word, zero);
-		else
-			file = estrdup(zero);
-
-		fd = open(file, 0);
-		free(file);
+	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 */
@@ -356,33 +403,31 @@
 			if(fd>=0)
 				break;
 		}
+		free(file);
 	}
 	if(fd<0){
-		pfmt(err, "%s: ", zero);
-		setstatus("can't open");
-		Xerror(".: can't open");
+		if(!qflag) Xerror(".: can't open");
+		freewords(argv);
 		return;
 	}
 
-	/* set up for a new command loop */
-	start(dotcmds, 1, (struct var *)0);
+	execcmds(openiofd(fd), file, (var*)0, runq->redir);
 	pushredir(RCLOSE, fd, 0);
-	runq->cmdfile = zero;
-	runq->cmdfd = openfd(fd);
-	runq->lexline = 1;
+	runq->lex->qflag = qflag;
 	runq->iflag = iflag;
-	runq->iflast = 0;
-	/* push $* value */
-	pushlist();
-	runq->argv->words = p->argv->words;
-	/* free caller's copy of $* */
-	av = p->argv;
-	p->argv = av->next;
-	free(av);
-	/* push $0 value */
-	pushlist();
-	pushword(zero);
-	ndot++;
+	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
@@ -417,9 +462,8 @@
 execwhatis(void){	/* mildly wrong -- should fork before writing */
 	word *a, *b, *path;
 	var *v;
-	struct builtin *bp;
 	char *file;
-	struct io out[1];
+	io *out;
 	int found, sep;
 	a = runq->argv->words->next;
 	if(a==0){
@@ -427,10 +471,7 @@
 		return;
 	}
 	setstatus("");
-	out->fd = mapfd(1);
-	out->bufp = out->buf;
-	out->ebuf = &out->buf[NBUF];
-	out->strp = 0;
+	out = openiofd(mapfd(1));
 	for(;a;a = a->next){
 		v = vlook(a->word);
 		if(v->val){
@@ -443,7 +484,7 @@
 					pfmt(out, "%c%q", sep, b->word);
 					sep=' ';
 				}
-				pfmt(out, ")\n");
+				pstr(out, ")\n");
 			}
 			found = 1;
 		}
@@ -453,19 +494,11 @@
 		if(v->fn)
 			pfmt(out, "fn %q %s\n", v->name, v->fn[v->pc-1].s);
 		else{
-			for(bp = Builtin;bp->name;bp++)
-				if(strcmp(a->word, bp->name)==0){
-					pfmt(out, "builtin %s\n", a->word);
-					break;
-				}
-			if(!bp->name){
-				for(path = searchpath(a->word, "path"); path;
-				    path = path->next){
-					if(path->word[0] != '\0')
-						file = smprint("%s/%s",
-							path->word, a->word);
-					else
-						file = estrdup(a->word);
+			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);
@@ -479,9 +512,10 @@
 				}
 			}
 		}
+		flushio(out);
 	}
 	poplist();
-	flush(err);
+	free(closeiostr(out));	/* don't close fd */
 }
 
 void
--- a/sys/src/cmd/rc/subr.c
+++ b/sys/src/cmd/rc/subr.c
@@ -9,7 +9,6 @@
 	void *p = malloc(n);
 	if(p==0)
 		panic("Can't malloc %d bytes", n);
-	setmalloctag(p, getcallerpc(&n));
 	return p;
 }
 
@@ -19,7 +18,6 @@
 	p = realloc(p, n);
 	if(p==0 && n!=0)
 		panic("Can't realloc %d bytes\n", n);
-	setrealloctag(p, getcallerpc(&p));
 	return p;
 }
 
@@ -26,37 +24,23 @@
 char*
 estrdup(char *s)
 {
-	char *d;
-	int n;
-
-	n = strlen(s)+1;
-	d = emalloc(n);
+	int n = strlen(s)+1;
+	char *d = emalloc(n);
 	memmove(d, s, n);
 	return d;
 }
 
-extern int lastword, lastdol;
-
 void
-yyerror(char *m)
+pfln(io *fd, char *file, int line)
 {
-	pfmt(err, "rc: ");
-	if(runq->cmdfile && !runq->iflag)
-		pfmt(err, "%s:%d: ", runq->cmdfile, runq->lineno);
-	else if(runq->cmdfile)
-		pfmt(err, "%s: ", runq->cmdfile);
-	else if(!runq->iflag)
-		pfmt(err, "line %d: ", runq->lineno);
-	if(tok[0] && tok[0]!='\n')
-		pfmt(err, "token %q: ", tok);
-	pfmt(err, "%s\n", m);
-	flush(err);
-	lastword = 0;
-	lastdol = 0;
-	while(lastc!='\n' && lastc!=EOF) advance();
-	nerror++;
-	setvar("status", newword(m, (word *)0));
+	if(file && line)
+		pfmt(fd, "%s:%d", file, line);
+	else if(file)
+		pstr(fd, file);
+	else
+		pstr(fd, argv0);
 }
+
 char *bp;
 
 static void
@@ -72,7 +56,7 @@
 }
 
 void
-inttoascii(char *s, long n)
+inttoascii(char *s, int n)
 {
 	bp = s;
 	iacvt(n);
@@ -82,9 +66,8 @@
 void
 panic(char *s, int n)
 {
-	pfmt(err, "rc: ");
+	pfmt(err, "%s: ", argv0);
 	pfmt(err, s, n);
 	pchr(err, '\n');
-	flush(err);
 	Abort();
 }
--- a/sys/src/cmd/rc/syn.y
+++ b/sys/src/cmd/rc/syn.y
@@ -36,7 +36,7 @@
 assign:	first '=' word		{$$=tree2('=', $1, $3);}
 epilog:				{$$=0;}
 |	redir epilog		{$$=mung2($1, $1->child[0], $2);}
-redir:	REDIR word		{$$=mung1($1, $1->rtype==HERE?heredoc($2):$2);}
+redir:	REDIR word		{($$=mung1($1, $2))->str=$1->rtype==HERE?readhere($2,lex->input):0;}
 |	DUP
 cmd:				{$$=0;}
 |	brace epilog		{$$=epimung($1, $2);}
@@ -54,7 +54,7 @@
 	 */
 				{$$=mung3($1, $3, $5 ? $5 : tree1(PAREN, $5), $8);}
 |	FOR '(' word ')' {skipnl();} cmd
-				{$$=mung3($1, $3, (struct tree *)0, $6);}
+				{$$=mung3($1, $3, (tree*)0, $6);}
 |	WHILE paren {skipnl();} cmd
 				{$$=mung2($1, $2, $4);}
 |	SWITCH word {skipnl();} brace
@@ -74,19 +74,19 @@
 |	simple word		{$$=tree2(ARGLIST, $1, $2);}
 |	simple redir		{$$=tree2(ARGLIST, $1, $2);}
 first:	comword	
-|	first '^' word		{$$=tree2('^', $1, $3);}
-word:	keyword			{lastword=1; $1->type=WORD;}
+|	first '^' word		{$$=globprop(tree2('^', $1, $3));}
+word:	keyword			{lex->lastword=1; $1->type=WORD;}
 |	comword
-|	word '^' word		{$$=tree2('^', $1, $3);}
+|	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('`', (struct tree*)0, $2);}
+|	'`' 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:				{$$=(struct tree*)0;}
+words:				{$$=(tree*)0;}
 |	words word		{$$=tree2(WORDS, $1, $2);}
--- a/sys/src/cmd/rc/trap.c
+++ b/sys/src/cmd/rc/trap.c
@@ -8,8 +8,8 @@
 dotrap(void)
 {
 	int i;
-	struct var *trapreq;
-	struct word *starval;
+	var *trapreq;
+	word *starval;
 	starval = vlook("*")->val;
 	while(ntrap) for(i = 0;i!=NSIG;i++) while(trap[i]){
 		--trap[i];
@@ -16,13 +16,8 @@
 		--ntrap;
 		if(getpid()!=mypid) Exit(getstatus());
 		trapreq = vlook(Signame[i]);
-		if(trapreq->fn){
-			start(trapreq->fn, trapreq->pc, (struct var *)0);
-			runq->local = newvar("*", runq->local);
-			runq->local->val = copywords(starval, (struct word *)0);
-			runq->local->changed = 1;
-			runq->redir = runq->startredir = 0;
-		}
+		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
--- a/sys/src/cmd/rc/tree.c
+++ b/sys/src/cmd/rc/tree.c
@@ -1,22 +1,30 @@
 #include "rc.h"
-#include "exec.h"
 #include "io.h"
 #include "fns.h"
-tree *treenodes;
+
 /*
  * create and clear a new tree node, and add it
  * to the node list.
  */
+static tree *treefree, *treenodes;
 
 tree*
 newtree(void)
 {
-	tree *t = new(tree);
+	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;
-	t->line = runq->lexline;
 	treenodes = t;
 	return t;
 }
@@ -24,12 +32,21 @@
 void
 freenodes(void)
 {
-	tree *t, *u;
-	for(t = treenodes;t;t = u){
-		u = t->next;
-		if(t->str)
+	tree *t;
+
+	t = treenodes;
+	while(t){
+		if(t->str){
 			free(t->str);
-		free(t);
+			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;
 }
@@ -61,6 +78,13 @@
 	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;
 }
 
@@ -98,22 +122,18 @@
 	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;
-	struct io *s;
 
 	t = tree1(SIMPLE, t);
-	s = openstr();
-	pfmt(s, "%t", t);
-	t->str = estrdup((char *)s->strp);
-	closeio(s);
+	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){
@@ -125,25 +145,36 @@
 	return t;
 }
 
-tree*
-token(char *str, int type)
+char*
+fnstr(tree *t)
 {
-	tree *t = newtree();
+	io *f = openiostr();
+	pfmt(f, "%t", t);
+	return closeiostr(f);
+}
 
-	t->type = type;
-	t->str = strdup(str);
+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;
 }
 
-void
-freetree(tree *p)
+tree*
+token(char *str, int type)
 {
-	if(p==0)
-		return;	
-	freetree(p->child[0]);
-	freetree(p->child[1]);
-	freetree(p->child[2]);
-	if(p->str)
-		free(p->str);
-	free(p);
+	tree *t = newtree();
+	t->str = estrdup(str);
+	t->type = type;
+	return t;
 }
--- a/sys/src/cmd/rc/var.c
+++ b/sys/src/cmd/rc/var.c
@@ -58,6 +58,20 @@
 }
 
 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);
@@ -79,8 +93,15 @@
 void
 setvar(char *name, word *val)
 {
-	struct var *v = vlook(name);
+	var *v = vlook(name);
 	freewords(v->val);
 	v->val = val;
 	v->changed = 1;
+}
+
+void
+freevar(var *v)
+{
+	freewords(v->val);
+	free(v);
 }