summaryrefslogtreecommitdiff
path: root/sys/src/cmd/rc
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/rc
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/rc')
-rwxr-xr-xsys/src/cmd/rc/code.c486
-rwxr-xr-xsys/src/cmd/rc/compiling.on.unix1866
-rwxr-xr-xsys/src/cmd/rc/exec.c996
-rwxr-xr-xsys/src/cmd/rc/exec.h76
-rwxr-xr-xsys/src/cmd/rc/fns.h67
-rwxr-xr-xsys/src/cmd/rc/getflags.c233
-rwxr-xr-xsys/src/cmd/rc/getflags.h7
-rwxr-xr-xsys/src/cmd/rc/glob.c264
-rwxr-xr-xsys/src/cmd/rc/havefork.c234
-rwxr-xr-xsys/src/cmd/rc/haventfork.c211
-rwxr-xr-xsys/src/cmd/rc/here.c152
-rwxr-xr-xsys/src/cmd/rc/io.c268
-rwxr-xr-xsys/src/cmd/rc/io.h27
-rwxr-xr-xsys/src/cmd/rc/lex.c378
-rwxr-xr-xsys/src/cmd/rc/mkfile63
-rwxr-xr-xsys/src/cmd/rc/pcmd.c147
-rwxr-xr-xsys/src/cmd/rc/pfnc.c71
-rwxr-xr-xsys/src/cmd/rc/plan9.c670
-rwxr-xr-xsys/src/cmd/rc/rc.h150
-rwxr-xr-xsys/src/cmd/rc/simple.c517
-rwxr-xr-xsys/src/cmd/rc/subr.c77
-rwxr-xr-xsys/src/cmd/rc/syn.y91
-rwxr-xr-xsys/src/cmd/rc/trap.c37
-rwxr-xr-xsys/src/cmd/rc/tree.c148
-rwxr-xr-xsys/src/cmd/rc/unix.c469
-rwxr-xr-xsys/src/cmd/rc/var.c86
-rwxr-xr-xsys/src/cmd/rc/win32.c560
27 files changed, 8351 insertions, 0 deletions
diff --git a/sys/src/cmd/rc/code.c b/sys/src/cmd/rc/code.c
new file mode 100755
index 000000000..7aad38bc1
--- /dev/null
+++ b/sys/src/cmd/rc/code.c
@@ -0,0 +1,486 @@
+#include "rc.h"
+#include "io.h"
+#include "exec.h"
+#include "fns.h"
+#include "getflags.h"
+#define c0 t->child[0]
+#define c1 t->child[1]
+#define c2 t->child[2]
+int codep, ncode;
+#define emitf(x) ((codep!=ncode || morecode()), codebuf[codep].f = (x), codep++)
+#define emiti(x) ((codep!=ncode || morecode()), codebuf[codep].i = (x), codep++)
+#define emits(x) ((codep!=ncode || morecode()), codebuf[codep].s = (x), codep++)
+void stuffdot(int);
+char *fnstr(tree*);
+void outcode(tree*, int);
+void codeswitch(tree*, int);
+int iscase(tree*);
+code *codecopy(code*);
+void codefree(code*);
+
+int
+morecode(void)
+{
+ ncode+=100;
+ codebuf = (code *)realloc((char *)codebuf, ncode*sizeof codebuf[0]);
+ if(codebuf==0)
+ panic("Can't realloc %d bytes in morecode!",
+ ncode*sizeof codebuf[0]);
+ return 0;
+}
+
+void
+stuffdot(int a)
+{
+ if(a<0 || codep<=a)
+ panic("Bad address %d in stuffdot", a);
+ codebuf[a].i = codep;
+}
+
+int
+compile(tree *t)
+{
+ ncode = 100;
+ codebuf = (code *)emalloc(ncode*sizeof codebuf[0]);
+ codep = 0;
+ emiti(0); /* reference count */
+ outcode(t, flag['e']?1:0);
+ if(nerror){
+ efree((char *)codebuf);
+ return 0;
+ }
+ readhere();
+ emitf(Xreturn);
+ emitf(0);
+ return 1;
+}
+
+void
+cleanhere(char *f)
+{
+ emitf(Xdelhere);
+ emits(strdup(f));
+}
+
+char*
+fnstr(tree *t)
+{
+ io *f = openstr();
+ void *v;
+ extern char nl;
+ char svnl = nl;
+
+ nl = ';';
+ pfmt(f, "%t", t);
+ nl = svnl;
+ v = f->strp;
+ f->strp = 0;
+ closeio(f);
+ return v;
+}
+
+void
+outcode(tree *t, int eflag)
+{
+ int p, q;
+ tree *tt;
+ if(t==0)
+ return;
+ if(t->type!=NOT && t->type!=';')
+ runq->iflast = 0;
+ switch(t->type){
+ default:
+ pfmt(err, "bad type %d in outcode\n", t->type);
+ break;
+ case '$':
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xdol);
+ break;
+ case '"':
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xqdol);
+ break;
+ case SUB:
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xmark);
+ outcode(c1, eflag);
+ emitf(Xsub);
+ break;
+ case '&':
+ emitf(Xasync);
+ if(havefork){
+ p = emiti(0);
+ outcode(c0, eflag);
+ emitf(Xexit);
+ stuffdot(p);
+ } else
+ emits(fnstr(c0));
+ break;
+ case ';':
+ outcode(c0, eflag);
+ outcode(c1, eflag);
+ break;
+ case '^':
+ emitf(Xmark);
+ outcode(c1, eflag);
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xconc);
+ break;
+ case '`':
+ emitf(Xbackq);
+ if(havefork){
+ p = emiti(0);
+ outcode(c0, 0);
+ emitf(Xexit);
+ stuffdot(p);
+ } else
+ emits(fnstr(c0));
+ break;
+ case ANDAND:
+ outcode(c0, 0);
+ emitf(Xtrue);
+ p = emiti(0);
+ outcode(c1, eflag);
+ stuffdot(p);
+ break;
+ case ARGLIST:
+ outcode(c1, eflag);
+ outcode(c0, eflag);
+ break;
+ case BANG:
+ outcode(c0, eflag);
+ emitf(Xbang);
+ break;
+ case PCMD:
+ case BRACE:
+ outcode(c0, eflag);
+ break;
+ case COUNT:
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xcount);
+ break;
+ case FN:
+ emitf(Xmark);
+ outcode(c0, eflag);
+ if(c1){
+ emitf(Xfn);
+ p = emiti(0);
+ emits(fnstr(c1));
+ outcode(c1, eflag);
+ emitf(Xunlocal); /* get rid of $* */
+ emitf(Xreturn);
+ stuffdot(p);
+ }
+ else
+ emitf(Xdelfn);
+ break;
+ case IF:
+ outcode(c0, 0);
+ emitf(Xif);
+ p = emiti(0);
+ outcode(c1, eflag);
+ emitf(Xwastrue);
+ stuffdot(p);
+ break;
+ case NOT:
+ if(!runq->iflast)
+ yyerror("`if not' does not follow `if(...)'");
+ emitf(Xifnot);
+ p = emiti(0);
+ outcode(c0, eflag);
+ stuffdot(p);
+ break;
+ case OROR:
+ outcode(c0, 0);
+ emitf(Xfalse);
+ p = emiti(0);
+ outcode(c1, eflag);
+ stuffdot(p);
+ break;
+ case PAREN:
+ outcode(c0, eflag);
+ break;
+ case SIMPLE:
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xsimple);
+ if(eflag)
+ emitf(Xeflag);
+ break;
+ case SUBSHELL:
+ emitf(Xsubshell);
+ if(havefork){
+ p = emiti(0);
+ outcode(c0, eflag);
+ emitf(Xexit);
+ stuffdot(p);
+ } else
+ emits(fnstr(c0));
+ if(eflag)
+ emitf(Xeflag);
+ break;
+ case SWITCH:
+ codeswitch(t, eflag);
+ break;
+ case TWIDDLE:
+ emitf(Xmark);
+ outcode(c1, eflag);
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xmatch);
+ if(eflag)
+ emitf(Xeflag);
+ break;
+ case WHILE:
+ q = codep;
+ outcode(c0, 0);
+ if(q==codep)
+ emitf(Xsettrue); /* empty condition == while(true) */
+ emitf(Xtrue);
+ p = emiti(0);
+ outcode(c1, eflag);
+ emitf(Xjump);
+ emiti(q);
+ stuffdot(p);
+ break;
+ case WORDS:
+ outcode(c1, eflag);
+ outcode(c0, eflag);
+ break;
+ case FOR:
+ emitf(Xmark);
+ if(c1){
+ outcode(c1, eflag);
+ emitf(Xglob);
+ }
+ else{
+ emitf(Xmark);
+ emitf(Xword);
+ emits(strdup("*"));
+ emitf(Xdol);
+ }
+ emitf(Xmark); /* dummy value for Xlocal */
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xlocal);
+ p = emitf(Xfor);
+ q = emiti(0);
+ outcode(c2, eflag);
+ emitf(Xjump);
+ emiti(p);
+ stuffdot(q);
+ emitf(Xunlocal);
+ break;
+ case WORD:
+ emitf(Xword);
+ emits(strdup(t->str));
+ break;
+ case DUP:
+ if(t->rtype==DUPFD){
+ emitf(Xdup);
+ emiti(t->fd0);
+ emiti(t->fd1);
+ }
+ else{
+ emitf(Xclose);
+ emiti(t->fd0);
+ }
+ outcode(c1, eflag);
+ emitf(Xpopredir);
+ break;
+ case PIPEFD:
+ emitf(Xpipefd);
+ emiti(t->rtype);
+ if(havefork){
+ p = emiti(0);
+ outcode(c0, eflag);
+ emitf(Xexit);
+ stuffdot(p);
+ } else {
+ emits(fnstr(c0));
+ }
+ break;
+ case REDIR:
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xglob);
+ switch(t->rtype){
+ case APPEND:
+ emitf(Xappend);
+ break;
+ case WRITE:
+ emitf(Xwrite);
+ break;
+ case READ:
+ case HERE:
+ emitf(Xread);
+ break;
+ case RDWR:
+ emitf(Xrdwr);
+ break;
+ }
+ emiti(t->fd0);
+ outcode(c1, eflag);
+ emitf(Xpopredir);
+ break;
+ case '=':
+ tt = t;
+ for(;t && t->type=='=';t = c2);
+ if(t){ /* var=value cmd */
+ for(t = tt;t->type=='=';t = c2){
+ emitf(Xmark);
+ outcode(c1, eflag);
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xlocal); /* push var for cmd */
+ }
+ outcode(t, eflag); /* gen. code for cmd */
+ for(t = tt; t->type == '='; t = c2)
+ emitf(Xunlocal); /* pop var */
+ }
+ else{ /* var=value */
+ for(t = tt;t;t = c2){
+ emitf(Xmark);
+ outcode(c1, eflag);
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xassign); /* set var permanently */
+ }
+ }
+ t = tt; /* so tests below will work */
+ break;
+ case PIPE:
+ emitf(Xpipe);
+ emiti(t->fd0);
+ emiti(t->fd1);
+ if(havefork){
+ p = emiti(0);
+ q = emiti(0);
+ outcode(c0, eflag);
+ emitf(Xexit);
+ stuffdot(p);
+ } else {
+ emits(fnstr(c0));
+ q = emiti(0);
+ }
+ outcode(c1, eflag);
+ emitf(Xreturn);
+ stuffdot(q);
+ emitf(Xpipewait);
+ break;
+ }
+ if(t->type!=NOT && t->type!=';')
+ runq->iflast = t->type==IF;
+ else if(c0) runq->iflast = c0->type==IF;
+}
+/*
+ * switch code looks like this:
+ * Xmark
+ * (get switch value)
+ * Xjump 1f
+ * out: Xjump leave
+ * 1: Xmark
+ * (get case values)
+ * Xcase 1f
+ * (commands)
+ * Xjump out
+ * 1: Xmark
+ * (get case values)
+ * Xcase 1f
+ * (commands)
+ * Xjump out
+ * 1:
+ * leave:
+ * Xpopm
+ */
+
+void
+codeswitch(tree *t, int eflag)
+{
+ int leave; /* patch jump address to leave switch */
+ int out; /* jump here to leave switch */
+ int nextcase; /* patch jump address to next case */
+ tree *tt;
+ if(c1->child[0]==nil
+ || c1->child[0]->type!=';'
+ || !iscase(c1->child[0]->child[0])){
+ yyerror("case missing in switch");
+ return;
+ }
+ emitf(Xmark);
+ outcode(c0, eflag);
+ emitf(Xjump);
+ nextcase = emiti(0);
+ out = emitf(Xjump);
+ leave = emiti(0);
+ stuffdot(nextcase);
+ t = c1->child[0];
+ while(t->type==';'){
+ tt = c1;
+ emitf(Xmark);
+ for(t = c0->child[0];t->type==ARGLIST;t = c0) outcode(c1, eflag);
+ emitf(Xcase);
+ nextcase = emiti(0);
+ t = tt;
+ for(;;){
+ if(t->type==';'){
+ if(iscase(c0)) break;
+ outcode(c0, eflag);
+ t = c1;
+ }
+ else{
+ if(!iscase(t)) outcode(t, eflag);
+ break;
+ }
+ }
+ emitf(Xjump);
+ emiti(out);
+ stuffdot(nextcase);
+ }
+ stuffdot(leave);
+ emitf(Xpopm);
+}
+
+int
+iscase(tree *t)
+{
+ if(t->type!=SIMPLE)
+ return 0;
+ do t = c0; while(t->type==ARGLIST);
+ return t->type==WORD && !t->quoted && strcmp(t->str, "case")==0;
+}
+
+code*
+codecopy(code *cp)
+{
+ cp[0].i++;
+ return cp;
+}
+
+void
+codefree(code *cp)
+{
+ code *p;
+ if(--cp[0].i!=0)
+ return;
+ for(p = cp+1;p->f;p++){
+ if(p->f==Xappend || p->f==Xclose || p->f==Xread || p->f==Xwrite
+ || p->f==Xrdwr
+ || p->f==Xasync || p->f==Xbackq || p->f==Xcase || p->f==Xfalse
+ || p->f==Xfor || p->f==Xjump
+ || p->f==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==Xword || p->f==Xdelhere) efree((++p)->s);
+ else if(p->f==Xfn){
+ efree(p[2].s);
+ p+=2;
+ }
+ }
+ efree((char *)cp);
+}
diff --git a/sys/src/cmd/rc/compiling.on.unix b/sys/src/cmd/rc/compiling.on.unix
new file mode 100755
index 000000000..cd20c56c7
--- /dev/null
+++ b/sys/src/cmd/rc/compiling.on.unix
@@ -0,0 +1,1866 @@
+From geoff@collyer.net Fri Dec 19 01:21:40 EST 2003
+Received: from plan9.cs.bell-labs.com ([135.104.9.2]) by plan9; Fri Dec 19 01:21:39 EST 2003
+Received: from collyer.net ([63.192.14.226]) by plan9; Fri Dec 19 01:21:35 EST 2003
+Message-ID: <c790d8b1e06b3918ad2c7848a3ae0ec7@collyer.net>
+subject: rc on unix, part 1
+From: Geoff Collyer <geoff@collyer.net>
+Date: Thu, 18 Dec 2003 22:21:33 -0800
+To: presotto@plan9.bell-labs.com, rsc@plan9.bell-labs.com, geoff@collyer.net
+MIME-Version: 1.0
+Content-Type: text/plain; charset="US-ASCII"
+Content-Transfer-Encoding: 7bit
+
+I got /sys/src/cmd/rc to compile under APE (in preparation for moving it
+to Unixes) with the following changed files. I cadged some include files
+from rsc but had to edit lib9.h slightly. I'll send the include files
+separately. I can't tell if it works yet, but it does complain about
+/usr/lib/rcmain being absent when I start it. Oh, and I haven't yet
+simulated the effect of the OCEXEC bit.
+
+
+# To unbundle, run this file
+echo mkfile
+sed 's/^X//' >mkfile <<'!'
+X</$objtype/mkfile
+
+TARG=rc
+COMMONOFILES=\
+X code.$O\
+X exec.$O\
+X getflags.$O\
+X glob.$O\
+X here.$O\
+X io.$O\
+X lex.$O\
+X pcmd.$O\
+X pfnc.$O\
+X simple.$O\
+X subr.$O\
+X trap.$O\
+X tree.$O\
+X var.$O\
+X havefork.$O\
+
+PLAN9OFILES=plan9.$O\
+
+UNIXOFILES=unix.$O\
+
+OFILES=$COMMONOFILES $UNIXOFILES y.tab.$O
+
+HFILES=rc.h\
+X x.tab.h\
+X io.h\
+X exec.h\
+X fns.h\
+
+YFILES=syn.y
+
+BIN=/$objtype/bin
+
+UPDATE=\
+X mkfile\
+X $HFILES\
+X ${COMMONOFILES:%.$O=%.c}\
+X ${UNIXOFILES:%.$O=%.c}\
+X ${PLAN9OFILES:%.$O=%.c}\
+X $YFILES\
+X ${TARG:%=/386/bin/%}\
+
+CC=pcc -c -B -I../include
+LD=pcc
+
+X</sys/src/cmd/mkone
+
+x.tab.h: y.tab.h
+X cmp -s x.tab.h y.tab.h || cp y.tab.h x.tab.h
+
+clean:V:
+X rm -f [$OS].out *.[$OS] [xy].tab.? y.debug $TARG
+
+regress: $O.out
+X cd test
+X mk
+
+unregress:V:
+X for(test in test/*.test) rc $test >$test.out
+
+listing:
+X pr mkfile $HFILES $FILES $FILES9 $FILESUNIX $YFILES|lp -du
+!
+echo simple.c
+sed 's/^X//' >simple.c <<'!'
+X/*
+X * Maybe `simple' is a misnomer.
+X */
+X#include "rc.h"
+X#include "getflags.h"
+X#include "exec.h"
+X#include "io.h"
+X#include "fns.h"
+X/*
+X * Search through the following code to see if we're just going to exit.
+X */
+exitnext(void){
+X union code *c=&runq->code[runq->pc];
+X while(c->f==Xpopredir) c++;
+X return c->f==Xexit;
+X}
+
+void
+XXsimple(void)
+X{
+X word *a;
+X thread *p = runq;
+X var *v;
+X struct builtin *bp;
+X int pid;
+X globlist();
+X a = runq->argv->words;
+X if(a==0){
+X Xerror1("empty argument list");
+X return;
+X }
+X if(flag['x'])
+X pfmt(err, "%v\n", p->argv->words); /* wrong, should do redirs */
+X v = gvlook(a->word);
+X if(v->fn)
+X execfunc(v);
+X else{
+X if(strcmp(a->word, "builtin")==0){
+X if(count(a)==1){
+X pfmt(err, "builtin: empty argument list\n");
+X setstatus("empty arg list");
+X poplist();
+X return;
+X }
+X a = a->next;
+X popword();
+X }
+X for(bp = Builtin;bp->name;bp++)
+X if(strcmp(a->word, bp->name)==0){
+X (*bp->fnc)();
+X return;
+X }
+X if(exitnext()){
+X /* fork and wait is redundant */
+X pushword("exec");
+X execexec();
+X Xexit();
+X }
+X else{
+X flush(err);
+X Updenv(); /* necessary so changes don't go out again */
+X if((pid = execforkexec()) < 0){
+X Xerror("try again");
+X return;
+X }
+
+X /* interrupts don't get us out */
+X poplist();
+X while(Waitfor(pid, 1) < 0)
+X ;
+X }
+X }
+X}
+struct word nullpath = { "", 0};
+
+void
+doredir(redir *rp)
+X{
+X if(rp){
+X doredir(rp->next);
+X switch(rp->type){
+X case ROPEN:
+X if(rp->from!=rp->to){
+X Dup(rp->from, rp->to);
+X close(rp->from);
+X }
+X break;
+X case RDUP:
+X Dup(rp->from, rp->to);
+X break;
+X case RCLOSE:
+X close(rp->from);
+X break;
+X }
+X }
+X}
+
+word*
+searchpath(char *w)
+X{
+X word *path;
+X if(strncmp(w, "/", 1)==0
+X || strncmp(w, "#", 1)==0
+X || strncmp(w, "./", 2)==0
+X || strncmp(w, "../", 3)==0
+X || (path = vlook("path")->val)==0)
+X path=&nullpath;
+X return path;
+X}
+
+void
+execexec(void)
+X{
+X popword(); /* "exec" */
+X if(runq->argv->words==0){
+X Xerror1("empty argument list");
+X return;
+X }
+X doredir(runq->redir);
+X Execute(runq->argv->words, searchpath(runq->argv->words->word));
+X poplist();
+X}
+
+void
+execfunc(var *func)
+X{
+X word *starval;
+X popword();
+X starval = runq->argv->words;
+X runq->argv->words = 0;
+X poplist();
+X start(func->fn, func->pc, (struct var *)0);
+X runq->local = newvar(strdup("*"), runq->local);
+X runq->local->val = starval;
+X runq->local->changed = 1;
+X}
+
+int
+dochdir(char *word)
+X{
+X /* report to /dev/wdir if it exists and we're interactive */
+X static int wdirfd = -2;
+X if(chdir(word)<0) return -1;
+X if(flag['i']!=0){
+X if(wdirfd==-2) /* try only once */
+X /* TODO: arrange close-on-exec on Unix */
+X wdirfd = open("/dev/wdir", OWRITE|OCEXEC);
+X if(wdirfd>=0)
+X write(wdirfd, word, strlen(word));
+X }
+X return 1;
+X}
+
+void
+execcd(void)
+X{
+X word *a = runq->argv->words;
+X word *cdpath;
+X char dir[512];
+X setstatus("can't cd");
+X cdpath = vlook("cdpath")->val;
+X switch(count(a)){
+X default:
+X pfmt(err, "Usage: cd [directory]\n");
+X break;
+X case 2:
+X if(a->next->word[0]=='/' || cdpath==0)
+X cdpath=&nullpath;
+X for(;cdpath;cdpath = cdpath->next){
+X strcpy(dir, cdpath->word);
+X if(dir[0])
+X strcat(dir, "/");
+X strcat(dir, a->next->word);
+X if(dochdir(dir)>=0){
+X if(strlen(cdpath->word)
+X && strcmp(cdpath->word, ".")!=0)
+X pfmt(err, "%s\n", dir);
+X setstatus("");
+X break;
+X }
+X }
+X if(cdpath==0)
+X pfmt(err, "Can't cd %s: %r\n", a->next->word);
+X break;
+X case 1:
+X a = vlook("home")->val;
+X if(count(a)>=1){
+X if(dochdir(a->word)>=0)
+X setstatus("");
+X else
+X pfmt(err, "Can't cd %s: %r\n", a->word);
+X }
+X else
+X pfmt(err, "Can't cd -- $home empty\n");
+X break;
+X }
+X poplist();
+X}
+
+void
+execexit(void)
+X{
+X switch(count(runq->argv->words)){
+X default:
+X pfmt(err, "Usage: exit [status]\nExiting anyway\n");
+X case 2:
+X setstatus(runq->argv->words->next->word);
+X case 1: Xexit();
+X }
+X}
+
+void
+execshift(void)
+X{
+X int n;
+X word *a;
+X var *star;
+X switch(count(runq->argv->words)){
+X default:
+X pfmt(err, "Usage: shift [n]\n");
+X setstatus("shift usage");
+X poplist();
+X return;
+X case 2:
+X n = atoi(runq->argv->words->next->word);
+X break;
+X case 1:
+X n = 1;
+X break;
+X }
+X star = vlook("*");
+X for(;n && star->val;--n){
+X a = star->val->next;
+X efree(star->val->word);
+X efree((char *)star->val);
+X star->val = a;
+X star->changed = 1;
+X }
+X setstatus("");
+X poplist();
+X}
+
+int
+octal(char *s)
+X{
+X int n = 0;
+X while(*s==' ' || *s=='\t' || *s=='\n') s++;
+X while('0'<=*s && *s<='7') n = n*8+*s++-'0';
+X return n;
+X}
+
+int
+mapfd(int fd)
+X{
+X redir *rp;
+X for(rp = runq->redir;rp;rp = rp->next){
+X switch(rp->type){
+X case RCLOSE:
+X if(rp->from==fd)
+X fd=-1;
+X break;
+X case RDUP:
+X case ROPEN:
+X if(rp->to==fd)
+X fd = rp->from;
+X break;
+X }
+X }
+X return fd;
+X}
+union code rdcmds[4];
+
+void
+execcmds(io *f)
+X{
+X static int first = 1;
+X if(first){
+X rdcmds[0].i = 1;
+X rdcmds[1].f = Xrdcmds;
+X rdcmds[2].f = Xreturn;
+X first = 0;
+X }
+X start(rdcmds, 1, runq->local);
+X runq->cmdfd = f;
+X runq->iflast = 0;
+X}
+
+void
+execeval(void)
+X{
+X char *cmdline, *s, *t;
+X int len = 0;
+X word *ap;
+X if(count(runq->argv->words)<=1){
+X Xerror1("Usage: eval cmd ...");
+X return;
+X }
+X eflagok = 1;
+X for(ap = runq->argv->words->next;ap;ap = ap->next)
+X len+=1+strlen(ap->word);
+X cmdline = emalloc(len);
+X s = cmdline;
+X for(ap = runq->argv->words->next;ap;ap = ap->next){
+X for(t = ap->word;*t;) *s++=*t++;
+X *s++=' ';
+X }
+X s[-1]='\n';
+X poplist();
+X execcmds(opencore(cmdline, len));
+X efree(cmdline);
+X}
+union code dotcmds[14];
+
+void
+execdot(void)
+X{
+X int iflag = 0;
+X int fd;
+X list *av;
+X thread *p = runq;
+X char *zero;
+X static int first = 1;
+X char file[512];
+X word *path;
+X if(first){
+X dotcmds[0].i = 1;
+X dotcmds[1].f = Xmark;
+X dotcmds[2].f = Xword;
+X dotcmds[3].s="0";
+X dotcmds[4].f = Xlocal;
+X dotcmds[5].f = Xmark;
+X dotcmds[6].f = Xword;
+X dotcmds[7].s="*";
+X dotcmds[8].f = Xlocal;
+X dotcmds[9].f = Xrdcmds;
+X dotcmds[10].f = Xunlocal;
+X dotcmds[11].f = Xunlocal;
+X dotcmds[12].f = Xreturn;
+X first = 0;
+X }
+X else
+X eflagok = 1;
+X popword();
+X if(p->argv->words && strcmp(p->argv->words->word, "-i")==0){
+X iflag = 1;
+X popword();
+X }
+X /* get input file */
+X if(p->argv->words==0){
+X Xerror1("Usage: . [-i] file [arg ...]");
+X return;
+X }
+X zero = strdup(p->argv->words->word);
+X popword();
+X fd=-1;
+X for(path = searchpath(zero);path;path = path->next){
+X strcpy(file, path->word);
+X if(file[0])
+X strcat(file, "/");
+X strcat(file, zero);
+X if((fd = open(file, 0))>=0) break;
+X if(strcmp(file, "/dev/stdin")==0){ /* for sun & ucb */
+X fd = Dup1(0);
+X if(fd>=0)
+X break;
+X }
+X }
+X if(fd<0){
+X pfmt(err, "%s: ", zero);
+X setstatus("can't open");
+X Xerror(".: can't open");
+X return;
+X }
+X /* set up for a new command loop */
+X start(dotcmds, 1, (struct var *)0);
+X pushredir(RCLOSE, fd, 0);
+X runq->cmdfile = zero;
+X runq->cmdfd = openfd(fd);
+X runq->iflag = iflag;
+X runq->iflast = 0;
+X /* push $* value */
+X pushlist();
+X runq->argv->words = p->argv->words;
+X /* free caller's copy of $* */
+X av = p->argv;
+X p->argv = av->next;
+X efree((char *)av);
+X /* push $0 value */
+X pushlist();
+X pushword(zero);
+X ndot++;
+X}
+
+void
+execflag(void)
+X{
+X char *letter, *val;
+X switch(count(runq->argv->words)){
+X case 2:
+X setstatus(flag[runq->argv->words->next->word[0]]?"":"flag not set");
+X break;
+X case 3:
+X letter = runq->argv->words->next->word;
+X val = runq->argv->words->next->next->word;
+X if(strlen(letter)==1){
+X if(strcmp(val, "+")==0){
+X flag[letter[0]] = flagset;
+X break;
+X }
+X if(strcmp(val, "-")==0){
+X flag[letter[0]] = 0;
+X break;
+X }
+X }
+X default:
+X Xerror1("Usage: flag [letter] [+-]");
+X return;
+X }
+X poplist();
+X}
+
+void
+execwhatis(void){ /* mildly wrong -- should fork before writing */
+X word *a, *b, *path;
+X var *v;
+X struct builtin *bp;
+X char file[512];
+X struct io out[1];
+X int found, sep;
+X a = runq->argv->words->next;
+X if(a==0){
+X Xerror1("Usage: whatis name ...");
+X return;
+X }
+X setstatus("");
+X out->fd = mapfd(1);
+X out->bufp = out->buf;
+X out->ebuf = &out->buf[NBUF];
+X out->strp = 0;
+X for(;a;a = a->next){
+X v = vlook(a->word);
+X if(v->val){
+X pfmt(out, "%s=", a->word);
+X if(v->val->next==0)
+X pfmt(out, "%q\n", v->val->word);
+X else{
+X sep='(';
+X for(b = v->val;b && b->word;b = b->next){
+X pfmt(out, "%c%q", sep, b->word);
+X sep=' ';
+X }
+X pfmt(out, ")\n");
+X }
+X found = 1;
+X }
+X else
+X found = 0;
+X v = gvlook(a->word);
+X if(v->fn)
+X pfmt(out, "fn %s %s\n", v->name, v->fn[v->pc-1].s);
+X else{
+X for(bp = Builtin;bp->name;bp++)
+X if(strcmp(a->word, bp->name)==0){
+X pfmt(out, "builtin %s\n", a->word);
+X break;
+X }
+X if(!bp->name){
+X for(path = searchpath(a->word);path;path = path->next){
+X strcpy(file, path->word);
+X if(file[0])
+X strcat(file, "/");
+X strcat(file, a->word);
+X if(Executable(file)){
+X pfmt(out, "%s\n", file);
+X break;
+X }
+X }
+X if(!path && !found){
+X pfmt(err, "%s: not found\n", a->word);
+X setstatus("not found");
+X }
+X }
+X }
+X }
+X poplist();
+X flush(err);
+X}
+
+void
+execwait(void)
+X{
+X switch(count(runq->argv->words)){
+X default:
+X Xerror1("Usage: wait [pid]");
+X return;
+X case 2:
+X Waitfor(atoi(runq->argv->words->next->word), 0);
+X break;
+X case 1:
+X Waitfor(-1, 0);
+X break;
+X }
+X poplist();
+X}
+!
+echo havefork.c
+sed 's/^X//' >havefork.c <<'!'
+X#include "rc.h"
+X#include "getflags.h"
+X#include "exec.h"
+X#include "io.h"
+X#include "fns.h"
+
+int havefork = 1;
+
+void
+XXasync(void)
+X{
+X int null = open("/dev/null", 0);
+X int pid;
+X char npid[10];
+X if(null<0){
+X Xerror("Can't open /dev/null\n");
+X return;
+X }
+X#ifdef Unix
+X pid = fork();
+X#else
+X pid = rfork(RFFDG|RFPROC|RFNOTEG);
+X#endif
+X switch(pid){
+X case -1:
+X close(null);
+X Xerror("try again");
+X break;
+X case 0:
+X pushredir(ROPEN, null, 0);
+X start(runq->code, runq->pc+1, runq->local);
+X runq->ret = 0;
+X break;
+X default:
+X close(null);
+X runq->pc = runq->code[runq->pc].i;
+X inttoascii(npid, pid);
+X setvar("apid", newword(npid, (word *)0));
+X break;
+X }
+X}
+
+void
+XXpipe(void)
+X{
+X struct thread *p = runq;
+X int pc = p->pc, forkid;
+X int lfd = p->code[pc++].i;
+X int rfd = p->code[pc++].i;
+X int pfd[2];
+X if(pipe(pfd)<0){
+X Xerror("can't get pipe");
+X return;
+X }
+X switch(forkid = fork()){
+X case -1:
+X Xerror("try again");
+X break;
+X case 0:
+X start(p->code, pc+2, runq->local);
+X runq->ret = 0;
+X close(pfd[PRD]);
+X pushredir(ROPEN, pfd[PWR], lfd);
+X break;
+X default:
+X start(p->code, p->code[pc].i, runq->local);
+X close(pfd[PWR]);
+X pushredir(ROPEN, pfd[PRD], rfd);
+X p->pc = p->code[pc+1].i;
+X p->pid = forkid;
+X break;
+X }
+X}
+
+X/*
+X * Who should wait for the exit from the fork?
+X */
+void
+XXbackq(void)
+X{
+X char wd[8193];
+X int c;
+X char *s, *ewd=&wd[8192], *stop;
+X struct io *f;
+X var *ifs = vlook("ifs");
+X word *v, *nextv;
+X int pfd[2];
+X int pid;
+X stop = ifs->val?ifs->val->word:"";
+X if(pipe(pfd)<0){
+X Xerror("can't make pipe");
+X return;
+X }
+X switch(pid = fork()){
+X case -1:
+X Xerror("try again");
+X close(pfd[PRD]);
+X close(pfd[PWR]);
+X return;
+X case 0:
+X close(pfd[PRD]);
+X start(runq->code, runq->pc+1, runq->local);
+X pushredir(ROPEN, pfd[PWR], 1);
+X return;
+X default:
+X close(pfd[PWR]);
+X f = openfd(pfd[PRD]);
+X s = wd;
+X v = 0;
+X while((c = rchr(f))!=EOF){
+X if(strchr(stop, c) || s==ewd){
+X if(s!=wd){
+X *s='\0';
+X v = newword(wd, v);
+X s = wd;
+X }
+X }
+X else *s++=c;
+X }
+X if(s!=wd){
+X *s='\0';
+X v = newword(wd, v);
+X }
+X closeio(f);
+X Waitfor(pid, 0);
+X /* v points to reversed arglist -- reverse it onto argv */
+X while(v){
+X nextv = v->next;
+X v->next = runq->argv->words;
+X runq->argv->words = v;
+X v = nextv;
+X }
+X runq->pc = runq->code[runq->pc].i;
+X return;
+X }
+X}
+
+void
+XXpipefd(void)
+X{
+X struct thread *p = runq;
+X int pc = p->pc;
+X char name[40];
+X int pfd[2];
+X int sidefd, mainfd;
+X if(pipe(pfd)<0){
+X Xerror("can't get pipe");
+X return;
+X }
+X if(p->code[pc].i==READ){
+X sidefd = pfd[PWR];
+X mainfd = pfd[PRD];
+X }
+X else{
+X sidefd = pfd[PRD];
+X mainfd = pfd[PWR];
+X }
+X switch(fork()){
+X case -1:
+X Xerror("try again");
+X break;
+X case 0:
+X start(p->code, pc+2, runq->local);
+X close(mainfd);
+X pushredir(ROPEN, sidefd, p->code[pc].i==READ?1:0);
+X runq->ret = 0;
+X break;
+X default:
+X close(sidefd);
+X pushredir(ROPEN, mainfd, mainfd); /* isn't this a noop? */
+X strcpy(name, Fdprefix);
+X inttoascii(name+strlen(name), mainfd);
+X pushword(name);
+X p->pc = p->code[pc+1].i;
+X break;
+X }
+X}
+
+void
+XXsubshell(void)
+X{
+X int pid;
+X switch(pid = fork()){
+X case -1:
+X Xerror("try again");
+X break;
+X case 0:
+X start(runq->code, runq->pc+1, runq->local);
+X runq->ret = 0;
+X break;
+X default:
+X Waitfor(pid, 1);
+X runq->pc = runq->code[runq->pc].i;
+X break;
+X }
+X}
+
+int
+execforkexec(void)
+X{
+X int pid;
+X int n;
+X char buf[ERRMAX];
+
+X switch(pid = fork()){
+X case -1:
+X return -1;
+X case 0:
+X pushword("exec");
+X execexec();
+X strcpy(buf, "can't exec: ");
+X n = strlen(buf);
+X errstr(buf+n, ERRMAX-n);
+X Exit(buf);
+X }
+X return pid;
+X}
+!
+echo rc.h
+sed 's/^X//' >rc.h <<'!'
+X/*
+X * Plan9 is defined for plan 9
+X * V9 is defined for 9th edition
+X * Sun is defined for sun-os
+X * Please don't litter the code with ifdefs. The three below should be enough.
+X */
+X#define Unix
+
+X#ifdef Plan9
+X#include <u.h>
+X#include <libc.h>
+X#define NSIG 32
+X#define SIGINT 2
+X#define SIGQUIT 3
+X#endif
+
+X#ifdef Unix
+X#define _POSIX_SOURCE
+X#define _BSD_EXTENSION
+
+X#include <stdlib.h>
+X#include <stdarg.h>
+X#include <string.h>
+X#include <unistd.h>
+X#include <fcntl.h>
+X#include <lib9.h>
+X#include <signal.h>
+X#endif
+
+X#ifndef ERRMAX
+X#define ERRMAX 128
+X#endif
+
+X#define YYMAXDEPTH 500
+X#ifndef PAREN
+X#include "x.tab.h"
+X#endif
+typedef struct tree tree;
+typedef struct word word;
+typedef struct io io;
+typedef union code code;
+typedef struct var var;
+typedef struct list list;
+typedef struct redir redir;
+typedef struct thread thread;
+typedef struct builtin builtin;
+
+struct tree{
+X int type;
+X int rtype, fd0, fd1; /* details of REDIR PIPE DUP tokens */
+X char *str;
+X int quoted;
+X int iskw;
+X tree *child[3];
+X tree *next;
+X};
+tree *newtree(void);
+tree *token(char*, int), *klook(char*), *tree1(int, tree*);
+tree *tree2(int, tree*, tree*), *tree3(int, tree*, tree*, tree*);
+tree *mung1(tree*, tree*), *mung2(tree*, tree*, tree*);
+tree *mung3(tree*, tree*, tree*, tree*), *epimung(tree*, tree*);
+tree *simplemung(tree*), *heredoc(tree*);
+void freetree(tree*);
+tree *cmdtree;
+X/*
+X * The first word of any code vector is a reference count.
+X * Always create a new reference to a code vector by calling codecopy(.).
+X * Always call codefree(.) when deleting a reference.
+X */
+union code{
+X void (*f)(void);
+X int i;
+X char *s;
+X};
+char *promptstr;
+int doprompt;
+X#define NTOK 8192
+char tok[NTOK];
+X#define APPEND 1
+X#define WRITE 2
+X#define READ 3
+X#define HERE 4
+X#define DUPFD 5
+X#define CLOSE 6
+struct var{
+X char *name; /* ascii name */
+X word *val; /* value */
+X int changed;
+X code *fn; /* pointer to function's code vector */
+X int fnchanged;
+X int pc; /* pc of start of function */
+X var *next; /* next on hash or local list */
+X};
+var *vlook(char*), *gvlook(char*), *newvar(char*, var*);
+X#define NVAR 521
+var *gvar[NVAR]; /* hash for globals */
+X#define new(type) ((type *)emalloc(sizeof(type)))
+char *emalloc(long);
+void *Malloc(ulong);
+void efree(char*);
+X#define NOFILE 128 /* should come from <param.h> */
+struct here{
+X tree *tag;
+X char *name;
+X struct here *next;
+X};
+int mypid;
+X/*
+X * Glob character escape in strings:
+X * In a string, GLOB must be followed by *?[ or GLOB.
+X * GLOB* matches any string
+X * GLOB? matches any single character
+X * GLOB[...] matches anything in the brackets
+X * GLOBGLOB matches GLOB
+X */
+X#define GLOB ((char)0x01)
+X/*
+X * onebyte(c), twobyte(c), threebyte(c)
+X * Is c the first character of a one- two- or three-byte utf sequence?
+X */
+X#define onebyte(c) ((c&0x80)==0x00)
+X#define twobyte(c) ((c&0xe0)==0xc0)
+X#define threebyte(c) ((c&0xf0)==0xe0)
+char **argp;
+char **args;
+int nerror; /* number of errors encountered during compilation */
+int doprompt; /* is it time for a prompt? */
+X/*
+X * Which fds are the reading/writing end of a pipe?
+X * Unfortunately, this can vary from system to system.
+X * 9th edition Unix doesn't care, the following defines
+X * work on plan 9.
+X */
+X#define PRD 0
+X#define PWR 1
+char Rcmain[], Fdprefix[];
+X#define register
+X/*
+X * How many dot commands have we executed?
+X * Used to ensure that -v flag doesn't print rcmain.
+X */
+int ndot;
+char *getstatus(void);
+int lastc;
+int lastword;
+!
+echo unix.c
+sed 's/^X//' >unix.c <<'!'
+X/*
+X * Unix versions of system-specific functions
+X * By convention, exported routines herein have names beginning with an
+X * upper case letter.
+X */
+X#include "rc.h"
+X#include "io.h"
+X#include "exec.h"
+X#include "getflags.h"
+X#include <errno.h>
+
+char Rcmain[]="/usr/lib/rcmain";
+char Fdprefix[]="/dev/fd/";
+
+void execfinit(void);
+
+struct builtin Builtin[] = {
+X "cd", execcd,
+X "whatis", execwhatis,
+X "eval", execeval,
+X "exec", execexec, /* but with popword first */
+X "exit", execexit,
+X "shift", execshift,
+X "wait", execwait,
+X "umask", execumask,
+X ".", execdot,
+X "finit", execfinit,
+X "flag", execflag,
+X 0
+X};
+X#define SEP '\1'
+char **environp;
+
+struct word*
+enval(s)
+register char *s;
+X{
+X char *t, c;
+X struct word *v;
+X for(t = s;*t && *t!=SEP;t++);
+X c=*t;
+X *t='\0';
+X v = newword(s, c=='\0'?(struct word *)0:enval(t+1));
+X *t = c;
+X return v;
+X}
+
+void
+Vinit(void)
+X{
+X extern char **environ;
+X char *s;
+X char **env = environ;
+X environp = env;
+X for(;*env;env++){
+X for(s=*env;*s && *s!='(' && *s!='=';s++);
+X switch(*s){
+X case '\0':
+X pfmt(err, "environment %q?\n", *env);
+X break;
+X case '=':
+X *s='\0';
+X setvar(*env, enval(s+1));
+X *s='=';
+X break;
+X case '(': /* ignore functions for now */
+X break;
+X }
+X }
+X}
+
+char **envp;
+
+void
+XXrdfn(void)
+X{
+X char *s;
+X int len;
+X for(;*envp;envp++){
+X for(s=*envp;*s && *s!='(' && *s!='=';s++);
+X switch(*s){
+X case '\0':
+X pfmt(err, "environment %q?\n", *envp);
+X break;
+X case '=': /* ignore variables */
+X break;
+X case '(': /* Bourne again */
+X s=*envp+3;
+X envp++;
+X len = strlen(s);
+X s[len]='\n';
+X execcmds(opencore(s, len+1));
+X s[len]='\0';
+X return;
+X }
+X }
+X Xreturn();
+X}
+
+union code rdfns[4];
+
+void
+execfinit(void)
+X{
+X static int first = 1;
+X if(first){
+X rdfns[0].i = 1;
+X rdfns[1].f = Xrdfn;
+X rdfns[2].f = Xjump;
+X rdfns[3].i = 1;
+X first = 0;
+X }
+X Xpopm();
+X envp = environp;
+X start(rdfns, 1, runq->local);
+X}
+
+int
+cmpenv(const void *aa, const void *ab)
+X{
+X char **a = aa, **b = ab;
+
+X return strcmp(*a, *b);
+X}
+
+char **
+mkenv(void)
+X{
+X char **env, **ep, *p, *q;
+X struct var **h, *v;
+X struct word *a;
+X int nvar = 0, nchr = 0, sep;
+
+X /*
+X * Slightly kludgy loops look at locals then globals.
+X * locals no longer exist - geoff
+X */
+X for(h = gvar-1; h != &gvar[NVAR]; h++)
+X for(v = h >= gvar? *h: runq->local; v ;v = v->next){
+X if((v==vlook(v->name)) && v->val){
+X nvar++;
+X nchr+=strlen(v->name)+1;
+X for(a = v->val;a;a = a->next)
+X nchr+=strlen(a->word)+1;
+X }
+X if(v->fn){
+X nvar++;
+X nchr+=strlen(v->name)+strlen(v->fn[v->pc-1].s)+8;
+X }
+X }
+X env = (char **)emalloc((nvar+1)*sizeof(char *)+nchr);
+X ep = env;
+X p = (char *)&env[nvar+1];
+X for(h = gvar-1; h != &gvar[NVAR]; h++)
+X for(v = h >= gvar? *h: runq->local;v;v = v->next){
+X if((v==vlook(v->name)) && v->val){
+X *ep++=p;
+X q = v->name;
+X while(*q) *p++=*q++;
+X sep='=';
+X for(a = v->val;a;a = a->next){
+X *p++=sep;
+X sep = SEP;
+X q = a->word;
+X while(*q) *p++=*q++;
+X }
+X *p++='\0';
+X }
+X if(v->fn){
+X *ep++=p;
+X *p++='#'; *p++='('; *p++=')'; /* to fool Bourne */
+X *p++='f'; *p++='n'; *p++=' ';
+X q = v->name;
+X while(*q) *p++=*q++;
+X *p++=' ';
+X q = v->fn[v->pc-1].s;
+X while(*q) *p++=*q++;
+X *p++='\0';
+X }
+X }
+X *ep = 0;
+X qsort((void *)env, nvar, sizeof ep[0], cmpenv);
+X return env;
+X}
+char *sigmsg[] = {
+X/* 0 normal */ 0,
+X/* 1 SIGHUP */ "Hangup",
+X/* 2 SIGINT */ 0,
+X/* 3 SIGQUIT */ "Quit",
+X/* 4 SIGILL */ "Illegal instruction",
+X/* 5 SIGTRAP */ "Trace/BPT trap",
+X/* 6 SIGIOT */ "abort",
+X/* 7 SIGEMT */ "EMT trap",
+X/* 8 SIGFPE */ "Floating exception",
+X/* 9 SIGKILL */ "Killed",
+X/* 10 SIGBUS */ "Bus error",
+X/* 11 SIGSEGV */ "Memory fault",
+X/* 12 SIGSYS */ "Bad system call",
+X/* 13 SIGPIPE */ 0,
+X/* 14 SIGALRM */ "Alarm call",
+X/* 15 SIGTERM */ "Terminated",
+X/* 16 unused */ "signal 16",
+X/* 17 SIGSTOP */ "Process stopped",
+X/* 18 unused */ "signal 18",
+X/* 19 SIGCONT */ "Process continued",
+X/* 20 SIGCHLD */ "Child death",
+X};
+
+void
+Waitfor(int pid, int persist)
+X{
+X int wpid, sig;
+X struct thread *p;
+X int wstat;
+X char wstatstr[12];
+
+X for(;;){
+X errno = 0;
+X wpid = wait(&wstat);
+X if(errno==EINTR && persist)
+X continue;
+X if(wpid==-1)
+X break;
+X sig = wstat&0177;
+X if(sig==0177){
+X pfmt(err, "trace: ");
+X sig = (wstat>>8)&0177;
+X }
+X if(sig>(sizeof sigmsg/sizeof sigmsg[0]) || sigmsg[sig]){
+X if(pid!=wpid)
+X pfmt(err, "%d: ", wpid);
+X if(sig<=(sizeof sigmsg/sizeof sigmsg[0]))
+X pfmt(err, "%s", sigmsg[sig]);
+X else if(sig==0177) pfmt(err, "stopped by ptrace");
+X else pfmt(err, "signal %d", sig);
+X if(wstat&0200)pfmt(err, " -- core dumped");
+X pfmt(err, "\n");
+X }
+X wstat = sig?sig+1000:(wstat>>8)&0xFF;
+X if(wpid==pid){
+X inttoascii(wstatstr, wstat);
+X setstatus(wstatstr);
+X break;
+X }
+X else{
+X for(p = runq->ret;p;p = p->ret)
+X if(p->pid==wpid){
+X p->pid=-1;
+X inttoascii(p->status, wstat);
+X break;
+X }
+X }
+X }
+X}
+
+char **
+mkargv(a)
+register struct word *a;
+X{
+X char **argv = (char **)emalloc((count(a)+2)*sizeof(char *));
+X char **argp = argv+1; /* leave one at front for runcoms */
+
+X for(;a;a = a->next)
+X *argp++=a->word;
+X *argp = 0;
+X return argv;
+X}
+
+void
+Updenv(void)
+X{
+X}
+
+void
+Execute(struct word *args, struct word *path)
+X{
+X char *msg="not found";
+X#ifdef ETXTBSY
+X int txtbusy = 0;
+X#endif
+X char **env = mkenv();
+X char **argv = mkargv(args);
+X char file[512];
+
+X for(;path;path = path->next){
+X strcpy(file, path->word);
+X if(file[0])
+X strcat(file, "/");
+X strcat(file, argv[1]);
+X#ifdef ETXTBSY
+ReExec:
+X#endif
+X execve(file, argv+1, env);
+X switch(errno){
+X case ENOEXEC:
+X pfmt(err, "%s: Bourne again\n", argv[1]);
+X argv[0]="sh";
+X argv[1] = strdup(file);
+X execve("/bin/sh", argv, env);
+X goto Bad;
+X#ifdef ETXTBSY
+X case ETXTBSY:
+X if(++txtbusy!=5){
+X sleep(txtbusy);
+X goto ReExec;
+X }
+X msg="text busy"; goto Bad;
+X#endif
+X case EACCES:
+X msg="no access";
+X break;
+X case ENOMEM:
+X msg="not enough memory"; goto Bad;
+X case E2BIG:
+X msg="too big"; goto Bad;
+X }
+X }
+Bad:
+X pfmt(err, "%s: %s\n", argv[1], msg);
+X efree((char *)env);
+X efree((char *)argv);
+X}
+
+X#define NDIR 14 /* should get this from param.h */
+
+Globsize(p)
+register char *p;
+X{
+X int isglob = 0, globlen = NDIR+1;
+X for(;*p;p++){
+X if(*p==GLOB){
+X p++;
+X if(*p!=GLOB)
+X isglob++;
+X globlen+=*p=='*'?NDIR:1;
+X }
+X else
+X globlen++;
+X }
+X return isglob?globlen:0;
+X}
+
+X#include <sys/types.h>
+X#include <dirent.h>
+
+X#define NDIRLIST 50
+
+DIR *dirlist[NDIRLIST];
+
+Opendir(name)
+char *name;
+X{
+X DIR **dp;
+X for(dp = dirlist;dp!=&dirlist[NDIRLIST];dp++)
+X if(*dp==0){
+X *dp = opendir(name);
+X return *dp?dp-dirlist:-1;
+X }
+X return -1;
+X}
+
+int
+Readdir(int f, char *p, int /* onlydirs */ )
+X{
+X struct dirent *dp = readdir(dirlist[f]);
+
+X if(dp==0)
+X return 0;
+X strcpy(p, dp->d_name);
+X return 1;
+X}
+
+void
+Closedir(int f)
+X{
+X closedir(dirlist[f]);
+X dirlist[f] = 0;
+X}
+
+char *Signame[] = {
+X "sigexit", "sighup", "sigint", "sigquit",
+X "sigill", "sigtrap", "sigiot", "sigemt",
+X "sigfpe", "sigkill", "sigbus", "sigsegv",
+X "sigsys", "sigpipe", "sigalrm", "sigterm",
+X "sig16", "sigstop", "sigtstp", "sigcont",
+X "sigchld", "sigttin", "sigttou", "sigtint",
+X "sigxcpu", "sigxfsz", "sig26", "sig27",
+X "sig28", "sig29", "sig30", "sig31",
+X 0,
+X};
+
+void
+gettrap(int sig)
+X{
+X signal(sig, gettrap);
+X trap[sig]++;
+X ntrap++;
+X if(ntrap>=NSIG){
+X pfmt(err, "rc: Too many traps (trap %d), dumping core\n", sig);
+X signal(SIGABRT, (void (*)())0);
+X kill(getpid(), SIGABRT);
+X }
+X}
+
+void
+Trapinit(void)
+X{
+X int i;
+X void (*sig)();
+
+X if(1 || flag['d']){ /* wrong!!! */
+X sig = signal(SIGINT, gettrap);
+X if(sig==SIG_IGN)
+X signal(SIGINT, SIG_IGN);
+X }
+X else{
+X for(i = 1;i<=NSIG;i++) if(i!=SIGCHLD){
+X sig = signal(i, gettrap);
+X if(sig==SIG_IGN)
+X signal(i, SIG_IGN);
+X }
+X }
+X}
+
+Unlink(name)
+char *name;
+X{
+X return unlink(name);
+X}
+Write(fd, buf, cnt)
+char *buf;
+X{
+X return write(fd, buf, cnt);
+X}
+Read(fd, buf, cnt)
+char *buf;
+X{
+X return read(fd, buf, cnt);
+X}
+Seek(fd, cnt, whence)
+long cnt;
+X{
+X return lseek(fd, cnt, whence);
+X}
+Executable(file)
+char *file;
+X{
+X return(access(file, 01)==0);
+X}
+Creat(file)
+char *file;
+X{
+X return creat(file, 0666);
+X}
+Dup(a, b){
+X return dup2(a, b);
+X}
+Dup1(a){
+X return dup(a);
+X}
+X/*
+X * Wrong: should go through components of a|b|c and return the maximum.
+X */
+void
+Exit(char *stat)
+X{
+X int n = 0;
+
+X while(*stat){
+X if(*stat!='|'){
+X if(*stat<'0' || '9'<*stat)
+X exit(1);
+X else n = n*10+*stat-'0';
+X }
+X stat++;
+X }
+X exit(n);
+X}
+Eintr(){
+X return errno==EINTR;
+X}
+
+void
+Noerror()
+X{
+X errno = 0;
+X}
+Isatty(fd){
+X return isatty(fd);
+X}
+
+void
+Abort()
+X{
+X abort();
+X}
+
+void
+execumask(void) /* wrong -- should fork before writing */
+X{
+X int m;
+X struct io out[1];
+X switch(count(runq->argv->words)){
+X default:
+X pfmt(err, "Usage: umask [umask]\n");
+X setstatus("umask usage");
+X poplist();
+X return;
+X case 2:
+X umask(octal(runq->argv->words->next->word));
+X break;
+X case 1:
+X umask(m = umask(0));
+X out->fd = mapfd(1);
+X out->bufp = out->buf;
+X out->ebuf=&out->buf[NBUF];
+X out->strp = 0;
+X pfmt(out, "%o\n", m);
+X break;
+X }
+X setstatus("");
+X poplist();
+X}
+
+void
+Memcpy(a, b, n)
+char *a, *b;
+X{
+X memmove(a, b, n);
+X}
+
+void*
+Malloc(unsigned long n)
+X{
+X return (void *)malloc(n);
+X}
+
+void
+errstr(char *buf, int len)
+X{
+X strncpy(buf, strerror(errno), len);
+X}
+!
+
+From geoff@collyer.net Fri Dec 19 01:23:26 EST 2003
+Received: from plan9.cs.bell-labs.com ([135.104.9.2]) by plan9; Fri Dec 19 01:23:25 EST 2003
+Received: from collyer.net ([63.192.14.226]) by plan9; Fri Dec 19 01:23:22 EST 2003
+Message-ID: <0b5ea130198a21a49139759d00d69939@collyer.net>
+subject: rc on unix, part 2
+From: Geoff Collyer <geoff@collyer.net>
+Date: Thu, 18 Dec 2003 22:23:21 -0800
+To: presotto@plan9.bell-labs.com, rsc@plan9.bell-labs.com, geoff@collyer.net
+MIME-Version: 1.0
+Content-Type: text/plain; charset="US-ASCII"
+Content-Transfer-Encoding: 7bit
+
+These are the include files I used to emulate plan 9's include
+files on Unix (APE).
+
+
+# To unbundle, run this file
+mkdir include
+echo include/bio.h
+sed 's/^X//' >include/bio.h <<'!'
+X#ifndef _BIOH_
+X#define _BIOH_ 1
+
+X#include <sys/types.h> /* for off_t */
+X#include <fcntl.h> /* for O_RDONLY, O_WRONLY */
+
+typedef struct Biobuf Biobuf;
+
+enum
+X{
+X Bsize = 8*1024,
+X Bungetsize = 4, /* space for ungetc */
+X Bmagic = 0x314159,
+X Beof = -1,
+X Bbad = -2,
+
+X Binactive = 0, /* states */
+X Bractive,
+X Bwactive,
+X Bracteof,
+
+X Bend
+X};
+
+struct Biobuf
+X{
+X int icount; /* neg num of bytes at eob */
+X int ocount; /* num of bytes at bob */
+X int rdline; /* num of bytes after rdline */
+X int runesize; /* num of bytes of last getrune */
+X int state; /* r/w/inactive */
+X int fid; /* open file */
+X int flag; /* magic if malloc'ed */
+X off_t offset; /* offset of buffer in file */
+X int bsize; /* size of buffer */
+X unsigned char* bbuf; /* pointer to beginning of buffer */
+X unsigned char* ebuf; /* pointer to end of buffer */
+X unsigned char* gbuf; /* pointer to good data in buf */
+X unsigned char b[Bungetsize+Bsize];
+X};
+
+X#define BGETC(bp)\
+X ((bp)->icount?(bp)->bbuf[(bp)->bsize+(bp)->icount++]:Bgetc((bp)))
+X#define BPUTC(bp,c)\
+X ((bp)->ocount?(bp)->bbuf[(bp)->bsize+(bp)->ocount++]=(c),0:Bputc((bp),(c)))
+X#define BOFFSET(bp)\
+X (((bp)->state==Bractive)?\
+X (bp)->offset + (bp)->icount:\
+X (((bp)->state==Bwactive)?\
+X (bp)->offset + ((bp)->bsize + (bp)->ocount):\
+X -1))
+X#define BLINELEN(bp)\
+X (bp)->rdline
+X#define BFILDES(bp)\
+X (bp)->fid
+
+int Bbuffered(Biobuf*);
+int Bfildes(Biobuf*);
+int Bflush(Biobuf*);
+int Bgetc(Biobuf*);
+int Bgetd(Biobuf*, double*);
+int Binit(Biobuf*, int, int);
+int Binits(Biobuf*, int, int, unsigned char*, int);
+int Blinelen(Biobuf*);
+off_t Boffset(Biobuf*);
+Biobuf* Bopen(char*, int);
+int Bprint(Biobuf*, char*, ...);
+int Bputc(Biobuf*, int);
+void* Brdline(Biobuf*, int);
+long Bread(Biobuf*, void*, long);
+off_t Bseek(Biobuf*, off_t, int);
+int Bterm(Biobuf*);
+int Bungetc(Biobuf*);
+long Bwrite(Biobuf*, void*, long);
+
+long Bgetrune(Biobuf*);
+int Bputrune(Biobuf*, long);
+int Bungetrune(Biobuf*);
+
+X#endif
+!
+echo include/fmt.h
+sed 's/^X//' >include/fmt.h <<'!'
+
+X/*
+X * The authors of this software are Rob Pike and Ken Thompson.
+X * Copyright (c) 2002 by Lucent Technologies.
+X * Permission to use, copy, modify, and distribute this software for any
+X * purpose without fee is hereby granted, provided that this entire notice
+X * is included in all copies of any software which is or includes a copy
+X * or modification of this software and in all copies of the supporting
+X * documentation for such software.
+X * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+X * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+X * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+X * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+X */
+
+X#ifndef _FMTH_
+X#define _FMTH_ 1
+
+X#include <stdarg.h>
+
+X#ifndef _UTFH_
+X#include <utf.h>
+X#endif
+
+typedef struct Fmt Fmt;
+struct Fmt{
+X unsigned char runes; /* output buffer is runes or chars? */
+X void *start; /* of buffer */
+X void *to; /* current place in the buffer */
+X void *stop; /* end of the buffer; overwritten if flush fails */
+X int (*flush)(Fmt *); /* called when to == stop */
+X void *farg; /* to make flush a closure */
+X int nfmt; /* num chars formatted so far */
+X va_list args; /* args passed to dofmt */
+X int r; /* % format Rune */
+X int width;
+X int prec;
+X unsigned long flags;
+X};
+
+enum{
+X FmtWidth = 1,
+X FmtLeft = FmtWidth << 1,
+X FmtPrec = FmtLeft << 1,
+X FmtSharp = FmtPrec << 1,
+X FmtSpace = FmtSharp << 1,
+X FmtSign = FmtSpace << 1,
+X FmtZero = FmtSign << 1,
+X FmtUnsigned = FmtZero << 1,
+X FmtShort = FmtUnsigned << 1,
+X FmtLong = FmtShort << 1,
+X FmtVLong = FmtLong << 1,
+X FmtComma = FmtVLong << 1,
+X FmtByte = FmtComma << 1,
+X FmtLDouble = FmtByte << 1,
+
+X FmtFlag = FmtLDouble << 1
+X};
+
+extern int print(char*, ...);
+extern char* seprint(char*, char*, char*, ...);
+extern char* vseprint(char*, char*, char*, va_list);
+extern int snprint(char*, int, char*, ...);
+extern int vsnprint(char*, int, char*, va_list);
+extern char* smprint(char*, ...);
+extern char* vsmprint(char*, va_list);
+extern int sprint(char*, char*, ...);
+extern int fprint(int, char*, ...);
+extern int vfprint(int, char*, va_list);
+
+extern int runesprint(Rune*, char*, ...);
+extern int runesnprint(Rune*, int, char*, ...);
+extern int runevsnprint(Rune*, int, char*, va_list);
+extern Rune* runeseprint(Rune*, Rune*, char*, ...);
+extern Rune* runevseprint(Rune*, Rune*, char*, va_list);
+extern Rune* runesmprint(char*, ...);
+extern Rune* runevsmprint(char*, va_list);
+
+extern int fmtfdinit(Fmt*, int, char*, int);
+extern int fmtfdflush(Fmt*);
+extern int fmtstrinit(Fmt*);
+extern char* fmtstrflush(Fmt*);
+
+extern int quotestrfmt(Fmt *f);
+extern void quotefmtinstall(void);
+extern int (*fmtdoquote)(int);
+
+
+extern int fmtinstall(int, int (*)(Fmt*));
+extern int dofmt(Fmt*, char*);
+extern int fmtprint(Fmt*, char*, ...);
+extern int fmtvprint(Fmt*, char*, va_list);
+extern int fmtrune(Fmt*, int);
+extern int fmtstrcpy(Fmt*, char*);
+
+extern double fmtstrtod(const char *, char **);
+extern double fmtcharstod(int(*)(void*), void*);
+
+X#endif
+!
+echo include/lib9.h
+sed 's/^X//' >include/lib9.h <<'!'
+X#include <string.h>
+X#include "utf.h"
+
+X#define nil ((void*)0)
+
+X#define uchar _fmtuchar
+X#define ushort _fmtushort
+X#define uint _fmtuint
+X#define ulong _fmtulong
+X#define vlong _fmtvlong
+X#define uvlong _fmtuvlong
+
+typedef unsigned char uchar;
+typedef unsigned short ushort;
+typedef unsigned int uint;
+typedef unsigned long ulong;
+
+X#define OREAD O_RDONLY
+X#define OWRITE O_WRONLY
+X#define ORDWR O_RDWR
+X#define OCEXEC 0
+!
+echo include/regexp9.h
+sed 's/^X//' >include/regexp9.h <<'!'
+X#ifndef _REGEXP9H_
+
+X#define _REGEXP9H_ 1
+X#include <utf.h>
+
+typedef struct Resub Resub;
+typedef struct Reclass Reclass;
+typedef struct Reinst Reinst;
+typedef struct Reprog Reprog;
+
+X/*
+X * Sub expression matches
+X */
+struct Resub{
+X union
+X {
+X char *sp;
+X Rune *rsp;
+X }s;
+X union
+X {
+X char *ep;
+X Rune *rep;
+X }e;
+X};
+
+X/*
+X * character class, each pair of rune's defines a range
+X */
+struct Reclass{
+X Rune *end;
+X Rune spans[64];
+X};
+
+X/*
+X * Machine instructions
+X */
+struct Reinst{
+X int type;
+X union {
+X Reclass *cp; /* class pointer */
+X Rune r; /* character */
+X int subid; /* sub-expression id for RBRA and LBRA */
+X Reinst *right; /* right child of OR */
+X }u1;
+X union { /* regexp relies on these two being in the same union */
+X Reinst *left; /* left child of OR */
+X Reinst *next; /* next instruction for CAT & LBRA */
+X }u2;
+X};
+
+X/*
+X * Reprogram definition
+X */
+struct Reprog{
+X Reinst *startinst; /* start pc */
+X Reclass class[16]; /* .data */
+X Reinst firstinst[5]; /* .text */
+X};
+
+extern Reprog *regcomp(char*);
+extern Reprog *regcomplit(char*);
+extern Reprog *regcompnl(char*);
+extern void regerror(char*);
+extern int regexec(Reprog*, char*, Resub*, int);
+extern void regsub(char*, char*, int, Resub*, int);
+
+extern int rregexec(Reprog*, Rune*, Resub*, int);
+extern void rregsub(Rune*, Rune*, Resub*, int);
+
+X#endif
+!
+echo include/utf.h
+sed 's/^X//' >include/utf.h <<'!'
+X#ifndef _UTFH_
+X#define _UTFH_ 1
+
+typedef unsigned short Rune; /* 16 bits */
+
+enum
+X{
+X UTFmax = 3, /* maximum bytes per rune */
+X Runesync = 0x80, /* cannot represent part of a UTF sequence (<) */
+X Runeself = 0x80, /* rune and UTF sequences are the same (<) */
+X Runeerror = 0x80, /* decoding error in UTF */
+X};
+
+X/*
+X * rune routines
+X */
+extern int runetochar(char*, Rune*);
+extern int chartorune(Rune*, char*);
+extern int runelen(long);
+extern int runenlen(Rune*, int);
+extern int fullrune(char*, int);
+extern int utflen(char*);
+extern int utfnlen(char*, long);
+extern char* utfrune(char*, long);
+extern char* utfrrune(char*, long);
+extern char* utfutf(char*, char*);
+extern char* utfecpy(char*, char*, char*);
+
+extern Rune* runestrcat(Rune*, Rune*);
+extern Rune* runestrchr(Rune*, Rune);
+extern int runestrcmp(Rune*, Rune*);
+extern Rune* runestrcpy(Rune*, Rune*);
+extern Rune* runestrncpy(Rune*, Rune*, long);
+extern Rune* runestrecpy(Rune*, Rune*, Rune*);
+extern Rune* runestrdup(Rune*);
+extern Rune* runestrncat(Rune*, Rune*, long);
+extern int runestrncmp(Rune*, Rune*, long);
+extern Rune* runestrrchr(Rune*, Rune);
+extern long runestrlen(Rune*);
+extern Rune* runestrstr(Rune*, Rune*);
+
+extern Rune tolowerrune(Rune);
+extern Rune totitlerune(Rune);
+extern Rune toupperrune(Rune);
+extern int isalpharune(Rune);
+extern int islowerrune(Rune);
+extern int isspacerune(Rune);
+extern int istitlerune(Rune);
+extern int isupperrune(Rune);
+
+X#endif
+!
+
diff --git a/sys/src/cmd/rc/exec.c b/sys/src/cmd/rc/exec.c
new file mode 100755
index 000000000..c0fb8593f
--- /dev/null
+++ b/sys/src/cmd/rc/exec.c
@@ -0,0 +1,996 @@
+#include "rc.h"
+#include "getflags.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+/*
+ * Start executing the given code at the given pc with the given redirection
+ */
+char *argv0="rc";
+
+void
+start(code *c, int pc, var *local)
+{
+ struct thread *p = new(struct thread);
+
+ p->code = codecopy(c);
+ p->pc = pc;
+ p->argv = 0;
+ p->redir = p->startredir = runq?runq->redir:0;
+ p->local = local;
+ p->cmdfile = 0;
+ p->cmdfd = 0;
+ p->eof = 0;
+ p->iflag = 0;
+ p->lineno = 1;
+ p->ret = runq;
+ runq = p;
+}
+
+word*
+newword(char *wd, word *next)
+{
+ word *p = new(word);
+ p->word = strdup(wd);
+ p->next = next;
+ return p;
+}
+
+void
+pushword(char *wd)
+{
+ if(runq->argv==0)
+ panic("pushword but no argv!", 0);
+ runq->argv->words = newword(wd, runq->argv->words);
+}
+
+void
+popword(void)
+{
+ word *p;
+ if(runq->argv==0)
+ panic("popword but no argv!", 0);
+ p = runq->argv->words;
+ if(p==0)
+ panic("popword but no word!", 0);
+ runq->argv->words = p->next;
+ efree(p->word);
+ efree((char *)p);
+}
+
+void
+freelist(word *w)
+{
+ word *nw;
+ while(w){
+ nw = w->next;
+ efree(w->word);
+ efree((char *)w);
+ w = nw;
+ }
+}
+
+void
+pushlist(void)
+{
+ list *p = new(list);
+ p->next = runq->argv;
+ p->words = 0;
+ runq->argv = p;
+}
+
+void
+poplist(void)
+{
+ list *p = runq->argv;
+ if(p==0)
+ panic("poplist but no argv", 0);
+ freelist(p->words);
+ runq->argv = p->next;
+ efree((char *)p);
+}
+
+int
+count(word *w)
+{
+ int n;
+ for(n = 0;w;n++) w = w->next;
+ return n;
+}
+
+void
+pushredir(int type, int from, int to)
+{
+ redir * rp = new(redir);
+ rp->type = type;
+ rp->from = from;
+ rp->to = to;
+ rp->next = runq->redir;
+ runq->redir = rp;
+}
+
+var*
+newvar(char *name, var *next)
+{
+ var *v = new(var);
+ v->name = name;
+ v->val = 0;
+ v->fn = 0;
+ v->changed = 0;
+ v->fnchanged = 0;
+ v->next = next;
+ return v;
+}
+/*
+ * get command line flags, initialize keywords & traps.
+ * get values from environment.
+ * set $pid, $cflag, $*
+ * fabricate bootstrap code and start it (*=(argv);. /usr/lib/rcmain $*)
+ * start interpreting code
+ */
+
+void
+main(int argc, char *argv[])
+{
+ code bootstrap[17];
+ char num[12], *rcmain;
+ int i;
+ argc = getflags(argc, argv, "SsrdiIlxepvVc:1m:1[command]", 1);
+ if(argc==-1)
+ usage("[file [arg ...]]");
+ if(argv[0][0]=='-')
+ flag['l'] = flagset;
+ if(flag['I'])
+ flag['i'] = 0;
+ else if(flag['i']==0 && argc==1 && Isatty(0)) flag['i'] = flagset;
+ rcmain = flag['m']?flag['m'][0]:Rcmain;
+ err = openfd(2);
+ kinit();
+ Trapinit();
+ Vinit();
+ inttoascii(num, mypid = getpid());
+ setvar("pid", newword(num, (word *)0));
+ setvar("cflag", flag['c']?newword(flag['c'][0], (word *)0)
+ :(word *)0);
+ setvar("rcname", newword(argv[0], (word *)0));
+ 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);
+ /* prime bootstrap argv */
+ pushlist();
+ argv0 = strdup(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)();
+ if(ntrap)
+ dotrap();
+ }
+}
+/*
+ * Opcode routines
+ * Arguments on stack (...)
+ * Arguments in line [...]
+ * Code in line with jump around {...}
+ *
+ * Xappend(file)[fd] open file to append
+ * Xassign(name, val) assign val to name
+ * Xasync{... Xexit} make thread for {}, no wait
+ * Xbackq{... Xreturn} make thread for {}, push stdout
+ * Xbang complement condition
+ * Xcase(pat, value){...} exec code on match, leave (value) on
+ * stack
+ * Xclose[i] close file descriptor
+ * Xconc(left, right) concatenate, push results
+ * Xcount(name) push var count
+ * Xdelfn(name) delete function definition
+ * Xdeltraps(names) delete named traps
+ * Xdol(name) get variable value
+ * Xqdol(name) concatenate variable components
+ * Xdup[i j] dup file descriptor
+ * Xexit rc exits with status
+ * Xfalse{...} execute {} if false
+ * Xfn(name){... Xreturn} define function
+ * Xfor(var, list){... Xreturn} for loop
+ * Xjump[addr] goto
+ * Xlocal(name, val) create local variable, assign value
+ * Xmark mark stack
+ * Xmatch(pat, str) match pattern, set status
+ * Xpipe[i j]{... Xreturn}{... Xreturn} construct a pipe between 2 new threads,
+ * wait for both
+ * Xpipefd[type]{... Xreturn} connect {} to pipe (input or output,
+ * depending on type), push /dev/fd/??
+ * Xpopm(value) pop value from stack
+ * 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
+ * Xsubshell{... Xexit} execute {} in a subshell and wait
+ * Xtrue{...} execute {} if true
+ * Xunlocal delete local variable
+ * Xword[string] push string
+ * Xwrite(file)[fd] open file to write
+ */
+
+void
+Xappend(void)
+{
+ char *file;
+ int f;
+ switch(count(runq->argv->words)){
+ default:
+ Xerror1(">> requires singleton");
+ return;
+ case 0:
+ Xerror1(">> requires file");
+ return;
+ case 1:
+ break;
+ }
+ file = runq->argv->words->word;
+ if((f = open(file, 1))<0 && (f = Creat(file))<0){
+ pfmt(err, "%s: ", file);
+ Xerror("can't open");
+ return;
+ }
+ Seek(f, 0L, 2);
+ pushredir(ROPEN, f, runq->code[runq->pc].i);
+ runq->pc++;
+ poplist();
+}
+
+void
+Xsettrue(void)
+{
+ setstatus("");
+}
+
+void
+Xbang(void)
+{
+ setstatus(truestatus()?"false":"");
+}
+
+void
+Xclose(void)
+{
+ pushredir(RCLOSE, runq->code[runq->pc].i, 0);
+ runq->pc++;
+}
+
+void
+Xdup(void)
+{
+ pushredir(RDUP, runq->code[runq->pc].i, runq->code[runq->pc+1].i);
+ runq->pc+=2;
+}
+
+void
+Xeflag(void)
+{
+ if(eflagok && !truestatus()) Xexit();
+}
+
+void
+Xexit(void)
+{
+ struct var *trapreq;
+ struct word *starval;
+ static int beenhere = 0;
+ if(getpid()==mypid && !beenhere){
+ trapreq = vlook("sigexit");
+ if(trapreq->fn){
+ beenhere = 1;
+ --runq->pc;
+ starval = vlook("*")->val;
+ start(trapreq->fn, trapreq->pc, (struct var *)0);
+ runq->local = newvar(strdup("*"), runq->local);
+ runq->local->val = copywords(starval, (struct word *)0);
+ runq->local->changed = 1;
+ runq->redir = runq->startredir = 0;
+ return;
+ }
+ }
+ Exit(getstatus());
+}
+
+void
+Xfalse(void)
+{
+ if(truestatus()) runq->pc = runq->code[runq->pc].i;
+ else runq->pc++;
+}
+int ifnot; /* dynamic if not flag */
+
+void
+Xifnot(void)
+{
+ if(ifnot)
+ runq->pc++;
+ else
+ runq->pc = runq->code[runq->pc].i;
+}
+
+void
+Xjump(void)
+{
+ runq->pc = runq->code[runq->pc].i;
+}
+
+void
+Xmark(void)
+{
+ pushlist();
+}
+
+void
+Xpopm(void)
+{
+ poplist();
+}
+
+void
+Xread(void)
+{
+ char *file;
+ int f;
+ switch(count(runq->argv->words)){
+ default:
+ Xerror1("< requires singleton\n");
+ return;
+ case 0:
+ Xerror1("< requires file\n");
+ return;
+ case 1:
+ break;
+ }
+ file = runq->argv->words->word;
+ if((f = open(file, 0))<0){
+ pfmt(err, "%s: ", file);
+ Xerror("can't open");
+ return;
+ }
+ pushredir(ROPEN, f, runq->code[runq->pc].i);
+ runq->pc++;
+ poplist();
+}
+
+void
+Xrdwr(void)
+{
+ char *file;
+ int f;
+
+ switch(count(runq->argv->words)){
+ default:
+ Xerror1("<> requires singleton\n");
+ return;
+ case 0:
+ Xerror1("<> requires file\n");
+ return;
+ case 1:
+ break;
+ }
+ file = runq->argv->words->word;
+ if((f = open(file, ORDWR))<0){
+ pfmt(err, "%s: ", file);
+ Xerror("can't open");
+ return;
+ }
+ pushredir(ROPEN, f, runq->code[runq->pc].i);
+ runq->pc++;
+ poplist();
+}
+
+void
+turfredir(void)
+{
+ while(runq->redir!=runq->startredir)
+ Xpopredir();
+}
+
+void
+Xpopredir(void)
+{
+ struct redir *rp = runq->redir;
+ if(rp==0)
+ panic("turfredir null!", 0);
+ runq->redir = rp->next;
+ if(rp->type==ROPEN)
+ close(rp->from);
+ efree((char *)rp);
+}
+
+void
+Xreturn(void)
+{
+ struct thread *p = runq;
+ turfredir();
+ while(p->argv) poplist();
+ codefree(p->code);
+ runq = p->ret;
+ efree((char *)p);
+ if(runq==0)
+ Exit(getstatus());
+}
+
+void
+Xtrue(void)
+{
+ if(truestatus()) runq->pc++;
+ else runq->pc = runq->code[runq->pc].i;
+}
+
+void
+Xif(void)
+{
+ ifnot = 1;
+ if(truestatus()) runq->pc++;
+ else runq->pc = runq->code[runq->pc].i;
+}
+
+void
+Xwastrue(void)
+{
+ ifnot = 0;
+}
+
+void
+Xword(void)
+{
+ pushword(runq->code[runq->pc++].s);
+}
+
+void
+Xwrite(void)
+{
+ char *file;
+ int f;
+ switch(count(runq->argv->words)){
+ default:
+ Xerror1("> requires singleton\n");
+ return;
+ case 0:
+ Xerror1("> requires file\n");
+ return;
+ case 1:
+ break;
+ }
+ file = runq->argv->words->word;
+ if((f = Creat(file))<0){
+ pfmt(err, "%s: ", file);
+ Xerror("can't open");
+ return;
+ }
+ pushredir(ROPEN, f, runq->code[runq->pc].i);
+ runq->pc++;
+ 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);
+ setstatus("no match");
+ for(p = runq->argv->next->words;p;p = p->next)
+ if(match(subject, p->word, '\0')){
+ setstatus("");
+ break;
+ }
+ efree(subject);
+ poplist();
+ poplist();
+}
+
+void
+Xcase(void)
+{
+ word *p;
+ char *s;
+ int ok = 0;
+ s = list2str(runq->argv->next->words);
+ for(p = runq->argv->words;p;p = p->next){
+ if(match(s, p->word, '\0')){
+ ok = 1;
+ break;
+ }
+ }
+ efree(s);
+ if(ok)
+ runq->pc++;
+ else
+ runq->pc = runq->code[runq->pc].i;
+ poplist();
+}
+
+word*
+conclist(word *lp, word *rp, word *tail)
+{
+ char *buf;
+ word *v;
+ if(lp->next || rp->next)
+ tail = conclist(lp->next==0? lp: lp->next,
+ rp->next==0? rp: rp->next, tail);
+ buf = emalloc(strlen(lp->word)+strlen((char *)rp->word)+1);
+ strcpy(buf, lp->word);
+ strcat(buf, rp->word);
+ v = newword(buf, tail);
+ efree(buf);
+ return v;
+}
+
+void
+Xconc(void)
+{
+ word *lp = runq->argv->words;
+ word *rp = runq->argv->next->words;
+ word *vp = runq->argv->next->next->words;
+ int lc = count(lp), rc = count(rp);
+ if(lc!=0 || rc!=0){
+ if(lc==0 || rc==0){
+ Xerror1("null list in concatenation");
+ return;
+ }
+ if(lc!=1 && rc!=1 && lc!=rc){
+ Xerror1("mismatched list lengths in concatenation");
+ return;
+ }
+ vp = conclist(lp, rp, vp);
+ }
+ poplist();
+ poplist();
+ runq->argv->words = vp;
+}
+
+void
+Xassign(void)
+{
+ var *v;
+ if(count(runq->argv->words)!=1){
+ Xerror1("variable name not singleton!");
+ return;
+ }
+ deglob(runq->argv->words->word);
+ v = vlook(runq->argv->words->word);
+ poplist();
+ globlist();
+ freewords(v->val);
+ v->val = runq->argv->words;
+ v->changed = 1;
+ runq->argv->words = 0;
+ poplist();
+}
+/*
+ * copy arglist a, adding the copy to the front of tail
+ */
+
+word*
+copywords(word *a, word *tail)
+{
+ word *v = 0, **end;
+ for(end=&v;a;a = a->next,end=&(*end)->next)
+ *end = newword(a->word, 0);
+ *end = tail;
+ return v;
+}
+
+void
+Xdol(void)
+{
+ word *a, *star;
+ char *s, *t;
+ int n;
+ if(count(runq->argv->words)!=1){
+ Xerror1("variable name not singleton!");
+ return;
+ }
+ s = runq->argv->words->word;
+ deglob(s);
+ n = 0;
+ for(t = s;'0'<=*t && *t<='9';t++) n = n*10+*t-'0';
+ a = runq->argv->next->words;
+ if(n==0 || *t)
+ a = copywords(vlook(s)->val, a);
+ else{
+ star = vlook("*")->val;
+ if(star && 1<=n && n<=count(star)){
+ while(--n) star = star->next;
+ a = newword(star->word, a);
+ }
+ }
+ poplist();
+ runq->argv->words = a;
+}
+
+void
+Xqdol(void)
+{
+ word *a, *p;
+ char *s;
+ int n;
+ if(count(runq->argv->words)!=1){
+ Xerror1("variable name not singleton!");
+ return;
+ }
+ s = runq->argv->words->word;
+ deglob(s);
+ a = vlook(s)->val;
+ poplist();
+ n = count(a);
+ if(n==0){
+ pushword("");
+ return;
+ }
+ for(p = a;p;p = p->next) n+=strlen(p->word);
+ s = emalloc(n);
+ if(a){
+ strcpy(s, a->word);
+ for(p = a->next;p;p = p->next){
+ strcat(s, " ");
+ strcat(s, p->word);
+ }
+ }
+ else
+ s[0]='\0';
+ pushword(s);
+ efree(s);
+}
+
+word*
+copynwords(word *a, word *tail, int n)
+{
+ word *v, **end;
+
+ v = 0;
+ end = &v;
+ while(n-- > 0){
+ *end = newword(a->word, 0);
+ end = &(*end)->next;
+ a = a->next;
+ }
+ *end = tail;
+ return v;
+}
+
+word*
+subwords(word *val, int len, word *sub, word *a)
+{
+ int n, m;
+ char *s;
+ if(!sub)
+ return a;
+ a = subwords(val, len, sub->next, a);
+ s = sub->word;
+ deglob(s);
+ m = 0;
+ n = 0;
+ while('0'<=*s && *s<='9')
+ n = n*10+ *s++ -'0';
+ if(*s == '-'){
+ if(*++s == 0)
+ m = len - n;
+ else{
+ while('0'<=*s && *s<='9')
+ m = m*10+ *s++ -'0';
+ m -= n;
+ }
+ }
+ if(n<1 || n>len || m<0)
+ return a;
+ if(n+m>len)
+ m = len-n;
+ while(--n > 0)
+ val = val->next;
+ return copynwords(val, a, m+1);
+}
+
+void
+Xsub(void)
+{
+ word *a, *v;
+ char *s;
+ if(count(runq->argv->next->words)!=1){
+ Xerror1("variable name not singleton!");
+ return;
+ }
+ s = runq->argv->next->words->word;
+ deglob(s);
+ a = runq->argv->next->next->words;
+ v = vlook(s)->val;
+ a = subwords(v, count(v), runq->argv->words, a);
+ poplist();
+ poplist();
+ runq->argv->words = a;
+}
+
+void
+Xcount(void)
+{
+ word *a;
+ char *s, *t;
+ int n;
+ char num[12];
+ if(count(runq->argv->words)!=1){
+ Xerror1("variable name not singleton!");
+ return;
+ }
+ s = runq->argv->words->word;
+ deglob(s);
+ n = 0;
+ for(t = s;'0'<=*t && *t<='9';t++) n = n*10+*t-'0';
+ if(n==0 || *t){
+ a = vlook(s)->val;
+ inttoascii(num, count(a));
+ }
+ else{
+ a = vlook("*")->val;
+ inttoascii(num, a && 1<=n && n<=count(a)?1:0);
+ }
+ poplist();
+ pushword(num);
+}
+
+void
+Xlocal(void)
+{
+ if(count(runq->argv->words)!=1){
+ Xerror1("variable name must be singleton\n");
+ return;
+ }
+ deglob(runq->argv->words->word);
+ runq->local = newvar(strdup(runq->argv->words->word), runq->local);
+ poplist();
+ globlist();
+ runq->local->val = runq->argv->words;
+ runq->local->changed = 1;
+ runq->argv->words = 0;
+ poplist();
+}
+
+void
+Xunlocal(void)
+{
+ var *v = runq->local, *hid;
+ if(v==0)
+ panic("Xunlocal: no locals!", 0);
+ runq->local = v->next;
+ hid = vlook(v->name);
+ hid->changed = 1;
+ efree(v->name);
+ freewords(v->val);
+ efree((char *)v);
+}
+
+void
+freewords(word *w)
+{
+ word *nw;
+ while(w){
+ efree(w->word);
+ nw = w->next;
+ efree((char *)w);
+ w = nw;
+ }
+}
+
+void
+Xfn(void)
+{
+ var *v;
+ word *a;
+ int end;
+ end = runq->code[runq->pc].i;
+ globlist();
+ for(a = runq->argv->words;a;a = a->next){
+ v = gvlook(a->word);
+ if(v->fn)
+ codefree(v->fn);
+ v->fn = codecopy(runq->code);
+ v->pc = runq->pc+2;
+ v->fnchanged = 1;
+ }
+ runq->pc = end;
+ poplist();
+}
+
+void
+Xdelfn(void)
+{
+ var *v;
+ word *a;
+ for(a = runq->argv->words;a;a = a->next){
+ v = gvlook(a->word);
+ if(v->fn)
+ codefree(v->fn);
+ v->fn = 0;
+ v->fnchanged = 1;
+ }
+ poplist();
+}
+
+char*
+concstatus(char *s, char *t)
+{
+ static char v[NSTATUS+1];
+ int n = strlen(s);
+ strncpy(v, s, NSTATUS);
+ if(n<NSTATUS){
+ v[n]='|';
+ strncpy(v+n+1, t, NSTATUS-n-1);
+ }
+ v[NSTATUS]='\0';
+ return v;
+}
+
+void
+Xpipewait(void)
+{
+ char status[NSTATUS+1];
+ if(runq->pid==-1)
+ setstatus(concstatus(runq->status, getstatus()));
+ else{
+ strncpy(status, getstatus(), NSTATUS);
+ status[NSTATUS]='\0';
+ Waitfor(runq->pid, 1);
+ runq->pid=-1;
+ setstatus(concstatus(getstatus(), status));
+ }
+}
+
+void
+Xrdcmds(void)
+{
+ struct thread *p = runq;
+ word *prompt;
+ flush(err);
+ nerror = 0;
+ if(flag['s'] && !truestatus())
+ pfmt(err, "status=%v\n", vlook("status")->val);
+ if(runq->iflag){
+ prompt = vlook("prompt")->val;
+ if(prompt)
+ promptstr = prompt->word;
+ else
+ promptstr="% ";
+ }
+ Noerror();
+ if(yyparse()){
+ if(!p->iflag || p->eof && !Eintr()){
+ if(p->cmdfile)
+ efree(p->cmdfile);
+ closeio(p->cmdfd);
+ Xreturn(); /* should this be omitted? */
+ }
+ else{
+ if(Eintr()){
+ pchr(err, '\n');
+ p->eof = 0;
+ }
+ --p->pc; /* go back for next command */
+ }
+ }
+ else{
+ ntrap = 0; /* avoid double-interrupts during blocked writes */
+ --p->pc; /* re-execute Xrdcmds after codebuf runs */
+ start(codebuf, 1, runq->local);
+ }
+ freenodes();
+}
+
+void
+Xerror(char *s)
+{
+ if(strcmp(argv0, "rc")==0 || strcmp(argv0, "/bin/rc")==0)
+ pfmt(err, "rc: %s: %r\n", s);
+ else
+ pfmt(err, "rc (%s): %s: %r\n", argv0, s);
+ flush(err);
+ setstatus("error");
+ while(!runq->iflag) Xreturn();
+}
+
+void
+Xerror1(char *s)
+{
+ if(strcmp(argv0, "rc")==0 || strcmp(argv0, "/bin/rc")==0)
+ pfmt(err, "rc: %s\n", s);
+ else
+ pfmt(err, "rc (%s): %s\n", argv0, s);
+ flush(err);
+ setstatus("error");
+ while(!runq->iflag) Xreturn();
+}
+
+void
+setstatus(char *s)
+{
+ setvar("status", newword(s, (word *)0));
+}
+
+char*
+getstatus(void)
+{
+ var *status = vlook("status");
+ return status->val?status->val->word:"";
+}
+
+int
+truestatus(void)
+{
+ char *s;
+ for(s = getstatus();*s;s++)
+ if(*s!='|' && *s!='0')
+ return 0;
+ return 1;
+}
+
+void
+Xdelhere(void)
+{
+ Unlink(runq->code[runq->pc++].s);
+}
+
+void
+Xfor(void)
+{
+ if(runq->argv->words==0){
+ poplist();
+ runq->pc = runq->code[runq->pc].i;
+ }
+ else{
+ freelist(runq->local->val);
+ runq->local->val = runq->argv->words;
+ runq->local->changed = 1;
+ runq->argv->words = runq->argv->words->next;
+ runq->local->val->next = 0;
+ runq->pc++;
+ }
+}
+
+void
+Xglob(void)
+{
+ globlist();
+}
diff --git a/sys/src/cmd/rc/exec.h b/sys/src/cmd/rc/exec.h
new file mode 100755
index 000000000..06d2991f5
--- /dev/null
+++ b/sys/src/cmd/rc/exec.h
@@ -0,0 +1,76 @@
+/*
+ * Definitions used in the interpreter
+ */
+extern void Xappend(void), Xasync(void), Xbackq(void), Xbang(void), Xclose(void);
+extern void Xconc(void), Xcount(void), Xdelfn(void), Xdol(void), Xqdol(void), Xdup(void);
+extern void Xexit(void), Xfalse(void), Xfn(void), Xfor(void), Xglob(void);
+extern void Xjump(void), Xmark(void), Xmatch(void), Xpipe(void), Xread(void);
+extern void Xrdwr(void);
+extern void Xrdfn(void), Xunredir(void), Xstar(void), Xreturn(void), Xsubshell(void);
+extern void Xtrue(void), Xword(void), Xwrite(void), Xpipefd(void), Xcase(void);
+extern void Xlocal(void), Xunlocal(void), Xassign(void), Xsimple(void), Xpopm(void);
+extern void Xrdcmds(void), Xwastrue(void), Xif(void), Xifnot(void), Xpipewait(void);
+extern void Xdelhere(void), Xpopredir(void), Xsub(void), Xeflag(void), Xsettrue(void);
+extern void Xerror(char*);
+extern void Xerror1(char*);
+/*
+ * word lists are in correct order,
+ * i.e. word0->word1->word2->word3->0
+ */
+struct word{
+ char *word;
+ word *next;
+};
+struct list{
+ word *words;
+ list *next;
+};
+word *newword(char *, word *), *copywords(word *, word *);
+struct redir{
+ char type; /* what to do */
+ short from, to; /* what to do it to */
+ struct redir *next; /* what else to do (reverse order) */
+};
+#define NSTATUS ERRMAX /* length of status (from plan 9) */
+/*
+ * redir types
+ */
+#define ROPEN 1 /* dup2(from, to); close(from); */
+#define RDUP 2 /* dup2(from, to); */
+#define RCLOSE 3 /* close(from); */
+struct thread{
+ union code *code; /* code for this thread */
+ int pc; /* code[pc] is the next instruction */
+ struct list *argv; /* argument stack */
+ struct redir *redir; /* redirection stack */
+ struct redir *startredir; /* redir inheritance point */
+ struct var *local; /* list of local variables */
+ char *cmdfile; /* file name in Xrdcmd */
+ struct io *cmdfd; /* file descriptor for Xrdcmd */
+ int iflast; /* static `if not' checking */
+ int eof; /* is cmdfd at eof? */
+ int iflag; /* interactive? */
+ int lineno; /* linenumber */
+ int pid; /* process for Xpipewait to wait for */
+ char status[NSTATUS]; /* status for Xpipewait */
+ tree *treenodes; /* tree nodes created by this process */
+ thread *ret; /* who continues when this finishes */
+};
+thread *runq;
+code *codecopy(code*);
+code *codebuf; /* compiler output */
+int ntrap; /* number of outstanding traps */
+int trap[NSIG]; /* number of outstanding traps per type */
+struct builtin{
+ char *name;
+ void (*fnc)(void);
+};
+extern struct builtin Builtin[];
+int eflagok; /* kludge flag so that -e doesn't exit in startup */
+int havefork;
+
+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 *);
diff --git a/sys/src/cmd/rc/fns.h b/sys/src/cmd/rc/fns.h
new file mode 100755
index 000000000..1559d9176
--- /dev/null
+++ b/sys/src/cmd/rc/fns.h
@@ -0,0 +1,67 @@
+void Abort(void);
+void Closedir(int);
+int Creat(char*);
+int Dup(int, int);
+int Dup1(int);
+int Eintr(void);
+int Executable(char*);
+void Execute(word*, word*);
+void Exit(char*);
+int ForkExecute(char*, char**, int, int, int);
+int Globsize(char*);
+int Isatty(int);
+void Memcpy(void*, void*, long);
+void Noerror(void);
+int Opendir(char*);
+long Read(int, void*, long);
+int Readdir(int, 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 codefree(code*);
+int compile(tree*);
+char * list2str(word*);
+int count(word*);
+void deglob(void*);
+void delwaitpid(int);
+void dotrap(void);
+void freenodes(void);
+void freewords(word*);
+void globlist(void);
+int havewaitpid(int);
+int idchr(int);
+void inttoascii(char*, long);
+void kinit(void);
+int mapfd(int);
+int match(void*, void*, int);
+int matchfn(void*, void*);
+char** mkargv(word*);
+void clearwaitpids(void);
+void panic(char*, int);
+void pathinit(void);
+void poplist(void);
+void popword(void);
+void pprompt(void);
+void pushlist(void);
+void pushredir(int, int, int);
+void pushword(char*);
+void readhere(void);
+word* searchpath(char*);
+void setstatus(char*);
+void setvar(char*, word*);
+void skipnl(void);
+void start(code*, int, var*);
+int truestatus(void);
+void usage(char*);
+int wordchr(int);
+void yyerror(char*);
+int yylex(void);
+int yyparse(void);
diff --git a/sys/src/cmd/rc/getflags.c b/sys/src/cmd/rc/getflags.c
new file mode 100755
index 000000000..3c2f9adeb
--- /dev/null
+++ b/sys/src/cmd/rc/getflags.c
@@ -0,0 +1,233 @@
+#include "rc.h"
+#include "getflags.h"
+#include "fns.h"
+char *flagset[] = {"<flag>"};
+char **flag[NFLAG];
+char *cmdname;
+static char *flagarg="";
+static void reverse(char**, char**);
+static int scanflag(int, char*);
+static void errn(char*, int);
+static void errs(char*);
+static void errc(int);
+static int reason;
+#define RESET 1
+#define FEWARGS 2
+#define FLAGSYN 3
+#define BADFLAG 4
+static int badflag;
+
+int
+getflags(int argc, char *argv[], char *flags, int stop)
+{
+ char *s;
+ int i, j, c, count;
+ flagarg = flags;
+ if(cmdname==0)
+ cmdname = argv[0];
+
+ i = 1;
+ while(i!=argc){
+ if(argv[i][0] != '-' || argv[i][1] == '\0'){
+ if(stop) /* always true in rc */
+ return argc;
+ i++;
+ continue;
+ }
+ s = argv[i]+1;
+ while(*s){
+ c=*s++;
+ count = scanflag(c, flags);
+ if(count==-1)
+ return -1;
+ if(flag[c]){ reason = RESET; badflag = c; return -1; }
+ if(count==0){
+ flag[c] = flagset;
+ if(*s=='\0'){
+ for(j = i+1;j<=argc;j++)
+ argv[j-1] = argv[j];
+ --argc;
+ }
+ }
+ else{
+ if(*s=='\0'){
+ for(j = i+1;j<=argc;j++)
+ argv[j-1] = argv[j];
+ --argc;
+ s = argv[i];
+ }
+ if(argc-i<count){
+ reason = FEWARGS;
+ badflag = c;
+ return -1;
+ }
+ reverse(argv+i, argv+argc);
+ reverse(argv+i, argv+argc-count);
+ reverse(argv+argc-count+1, argv+argc);
+ argc-=count;
+ flag[c] = argv+argc+1;
+ flag[c][0] = s;
+ s="";
+ }
+ }
+ }
+ return argc;
+}
+
+static void
+reverse(char **p, char **q)
+{
+ char *t;
+ for(;p<q;p++,--q){ t=*p; *p=*q; *q = t; }
+}
+
+static int
+scanflag(int c, char *f)
+{
+ int fc, count;
+ if(0<=c && c<NFLAG)
+ while(*f){
+ if(*f==' '){
+ f++;
+ continue;
+ }
+ fc=*f++;
+ if(*f==':'){
+ f++;
+ if(*f<'0' || '9'<*f){ reason = FLAGSYN; return -1; }
+ count = 0;
+ while('0'<=*f && *f<='9') count = count*10+*f++-'0';
+ }
+ else
+ count = 0;
+ if(*f=='['){
+ do{
+ f++;
+ if(*f=='\0'){ reason = FLAGSYN; return -1; }
+ }while(*f!=']');
+ f++;
+ }
+ if(c==fc)
+ return count;
+ }
+ reason = BADFLAG;
+ badflag = c;
+ return -1;
+}
+
+void
+usage(char *tail)
+{
+ char *s, *t, c;
+ int count, nflag = 0;
+ switch(reason){
+ case RESET:
+ errs("Flag -");
+ errc(badflag);
+ errs(": set twice\n");
+ break;
+ case FEWARGS:
+ errs("Flag -");
+ errc(badflag);
+ errs(": too few arguments\n");
+ break;
+ case FLAGSYN:
+ errs("Bad argument to getflags!\n");
+ break;
+ case BADFLAG:
+ errs("Illegal flag -");
+ errc(badflag);
+ errc('\n');
+ break;
+ }
+ errs("Usage: ");
+ errs(cmdname);
+ for(s = flagarg;*s;){
+ c=*s;
+ if(*s++==' ')
+ continue;
+ if(*s==':'){
+ s++;
+ count = 0;
+ while('0'<=*s && *s<='9') count = count*10+*s++-'0';
+ }
+ else count = 0;
+ if(count==0){
+ if(nflag==0)
+ errs(" [-");
+ nflag++;
+ errc(c);
+ }
+ if(*s=='['){
+ s++;
+ while(*s!=']' && *s!='\0') s++;
+ if(*s==']')
+ s++;
+ }
+ }
+ if(nflag)
+ errs("]");
+ for(s = flagarg;*s;){
+ c=*s;
+ if(*s++==' ')
+ continue;
+ if(*s==':'){
+ s++;
+ count = 0;
+ while('0'<=*s && *s<='9') count = count*10+*s++-'0';
+ }
+ else count = 0;
+ if(count!=0){
+ errs(" [-");
+ errc(c);
+ if(*s=='['){
+ s++;
+ t = s;
+ while(*s!=']' && *s!='\0') s++;
+ errs(" ");
+ errn(t, s-t);
+ if(*s==']')
+ s++;
+ }
+ else
+ while(count--) errs(" arg");
+ errs("]");
+ }
+ else if(*s=='['){
+ s++;
+ while(*s!=']' && *s!='\0') s++;
+ if(*s==']')
+ s++;
+ }
+ }
+ if(tail){
+ errs(" ");
+ errs(tail);
+ }
+ errs("\n");
+ Exit("bad flags");
+}
+
+static void
+errn(char *s, int count)
+{
+ while(count){ errc(*s++); --count; }
+}
+
+static void
+errs(char *s)
+{
+ while(*s) errc(*s++);
+}
+#define NBUF 80
+static char buf[NBUF], *bufp = buf;
+
+static void
+errc(int c)
+{
+ *bufp++=c;
+ if(bufp==&buf[NBUF] || c=='\n'){
+ Write(2, buf, bufp-buf);
+ bufp = buf;
+ }
+}
diff --git a/sys/src/cmd/rc/getflags.h b/sys/src/cmd/rc/getflags.h
new file mode 100755
index 000000000..84c4d8ee1
--- /dev/null
+++ b/sys/src/cmd/rc/getflags.h
@@ -0,0 +1,7 @@
+#define NFLAG 128
+
+extern char **flag[NFLAG];
+extern char *cmdname;
+extern char *flagset[];
+
+int getflags(int, char*[], char*, int);
diff --git a/sys/src/cmd/rc/glob.c b/sys/src/cmd/rc/glob.c
new file mode 100755
index 000000000..1c4983e40
--- /dev/null
+++ b/sys/src/cmd/rc/glob.c
@@ -0,0 +1,264 @@
+#include "rc.h"
+#include "exec.h"
+#include "fns.h"
+char *globname;
+struct word *globv;
+/*
+ * delete all the GLOB marks from s, in place
+ */
+
+void
+deglob(void *as)
+{
+ char *s = as;
+ char *t = s;
+ do{
+ if(*t==GLOB)
+ t++;
+ *s++=*t;
+ }while(*t++);
+}
+
+int
+globcmp(const void *s, const void *t)
+{
+ return strcmp(*(char**)s, *(char**)t);
+}
+
+void
+globsort(word *left, word *right)
+{
+ char **list;
+ word *a;
+ int n = 0;
+ for(a = left;a!=right;a = a->next) n++;
+ list = (char **)emalloc(n*sizeof(char *));
+ for(a = left,n = 0;a!=right;a = a->next,n++) list[n] = a->word;
+ qsort((void *)list, n, sizeof(void *), globcmp);
+ for(a = left,n = 0;a!=right;a = a->next,n++) a->word = list[n];
+ efree((char *)list);
+}
+/*
+ * Push names prefixed by globname and suffixed by a match of p onto the astack.
+ * namep points to the end of the prefix in globname.
+ */
+
+void
+globdir(uchar *p, uchar *namep)
+{
+ uchar *t, *newp;
+ int f;
+ /* scan the pattern looking for a component with a metacharacter in it */
+ if(*p=='\0'){
+ globv = newword(globname, globv);
+ return;
+ }
+ t = namep;
+ newp = p;
+ while(*newp){
+ if(*newp==GLOB)
+ break;
+ *t=*newp++;
+ if(*t++=='/'){
+ namep = t;
+ p = newp;
+ }
+ }
+ /* If we ran out of pattern, append the name if accessible */
+ if(*newp=='\0'){
+ *t='\0';
+ if(access(globname, 0)==0)
+ globv = newword(globname, globv);
+ return;
+ }
+ /* read the directory and recur for any entry that matches */
+ *namep='\0';
+ if((f = Opendir(globname[0]?globname:"."))<0) return;
+ while(*newp!='/' && *newp!='\0') newp++;
+ while(Readdir(f, namep, *newp=='/')){
+ if(matchfn(namep, p)){
+ for(t = namep;*t;t++);
+ globdir(newp, t);
+ }
+ }
+ Closedir(f);
+}
+/*
+ * Push all file names matched by p on the current thread's stack.
+ * If there are no matches, the list consists of p.
+ */
+
+void
+glob(void *ap)
+{
+ uchar *p = ap;
+ word *svglobv = globv;
+ int globlen = Globsize(ap);
+
+ if(!globlen){
+ deglob(p);
+ globv = newword((char *)p, globv);
+ return;
+ }
+ globname = emalloc(globlen);
+ globname[0]='\0';
+ globdir(p, (uchar *)globname);
+ efree(globname);
+ if(svglobv==globv){
+ deglob(p);
+ globv = newword((char *)p, globv);
+ }
+ else
+ globsort(globv, svglobv);
+}
+/*
+ * Do p and q point at equal utf codes
+ */
+
+int
+equtf(uchar *p, uchar *q)
+{
+ if(*p!=*q)
+ return 0;
+ if(twobyte(*p)) return p[1]==q[1];
+ if(threebyte(*p)){
+ if(p[1]!=q[1])
+ return 0;
+ if(p[1]=='\0')
+ return 1; /* broken code at end of string! */
+ return p[2]==q[2];
+ }
+ return 1;
+}
+/*
+ * Return a pointer to the next utf code in the string,
+ * not jumping past nuls in broken utf codes!
+ */
+
+uchar*
+nextutf(uchar *p)
+{
+ if(twobyte(*p)) return p[1]=='\0'?p+1:p+2;
+ if(threebyte(*p)) return p[1]=='\0'?p+1:p[2]=='\0'?p+2:p+3;
+ return p+1;
+}
+/*
+ * Convert the utf code at *p to a unicode value
+ */
+
+int
+unicode(uchar *p)
+{
+ int u = *p;
+
+ if(twobyte(u))
+ return ((u&0x1f)<<6)|(p[1]&0x3f);
+ if(threebyte(u))
+ return (u<<12)|((p[1]&0x3f)<<6)|(p[2]&0x3f);
+ return u;
+}
+/*
+ * Does the string s match the pattern p
+ * . and .. are only matched by patterns starting with .
+ * * matches any sequence of characters
+ * ? matches any single character
+ * [...] matches the enclosed list of characters
+ */
+
+int
+matchfn(void *as, void *ap)
+{
+ uchar *s = as, *p = ap;
+
+ if(s[0]=='.' && (s[1]=='\0' || s[1]=='.' && s[2]=='\0') && p[0]!='.')
+ return 0;
+ return match(s, p, '/');
+}
+
+int
+match(void *as, void *ap, int stop)
+{
+ int compl, hit, lo, hi, t, c;
+ uchar *s = as, *p = ap;
+
+ for(; *p!=stop && *p!='\0'; s = nextutf(s), p = nextutf(p)){
+ if(*p!=GLOB){
+ if(!equtf(p, s)) return 0;
+ }
+ else switch(*++p){
+ case GLOB:
+ if(*s!=GLOB)
+ return 0;
+ break;
+ case '*':
+ for(;;){
+ if(match(s, nextutf(p), stop)) return 1;
+ if(!*s)
+ break;
+ s = nextutf(s);
+ }
+ return 0;
+ case '?':
+ if(*s=='\0')
+ return 0;
+ break;
+ case '[':
+ if(*s=='\0')
+ return 0;
+ c = unicode(s);
+ p++;
+ compl=*p=='~';
+ if(compl)
+ p++;
+ hit = 0;
+ while(*p!=']'){
+ if(*p=='\0')
+ return 0; /* syntax error */
+ lo = unicode(p);
+ p = nextutf(p);
+ if(*p!='-')
+ hi = lo;
+ else{
+ p++;
+ if(*p=='\0')
+ return 0; /* syntax error */
+ hi = unicode(p);
+ p = nextutf(p);
+ if(hi<lo){ t = lo; lo = hi; hi = t; }
+ }
+ if(lo<=c && c<=hi)
+ hit = 1;
+ }
+ if(compl)
+ hit=!hit;
+ if(!hit)
+ return 0;
+ break;
+ }
+ }
+ return *s=='\0';
+}
+
+void
+globlist1(word *gl)
+{
+ if(gl){
+ globlist1(gl->next);
+ glob(gl->word);
+ }
+}
+
+void
+globlist(void)
+{
+ word *a;
+ globv = 0;
+ globlist1(runq->argv->words);
+ poplist();
+ pushlist();
+ if(globv){
+ for(a = globv;a->next;a = a->next);
+ a->next = runq->argv->words;
+ runq->argv->words = globv;
+ }
+}
diff --git a/sys/src/cmd/rc/havefork.c b/sys/src/cmd/rc/havefork.c
new file mode 100755
index 000000000..54d8f0925
--- /dev/null
+++ b/sys/src/cmd/rc/havefork.c
@@ -0,0 +1,234 @@
+#include "rc.h"
+#include "getflags.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+
+int havefork = 1;
+
+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;
+ }
+ switch(pid = rfork(RFFDG|RFPROC|RFNOTEG)){
+ case -1:
+ close(null);
+ Xerror("try again");
+ break;
+ case 0:
+ clearwaitpids();
+ pushredir(ROPEN, null, 0);
+ start(runq->code, runq->pc+1, runq->local);
+ 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));
+ break;
+ }
+}
+
+void
+Xpipe(void)
+{
+ struct thread *p = runq;
+ int pc = p->pc, forkid;
+ int lfd = p->code[pc++].i;
+ int rfd = p->code[pc++].i;
+ int pfd[2];
+ if(pipe(pfd)<0){
+ Xerror("can't get pipe");
+ return;
+ }
+ switch(forkid = fork()){
+ case -1:
+ Xerror("try again");
+ break;
+ case 0:
+ clearwaitpids();
+ start(p->code, pc+2, runq->local);
+ 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]);
+ pushredir(ROPEN, pfd[PRD], rfd);
+ p->pc = p->code[pc+1].i;
+ p->pid = forkid;
+ break;
+ }
+}
+
+enum { Wordmax = 8192, };
+
+/*
+ * Who should wait for the exit from the fork?
+ */
+void
+Xbackq(void)
+{
+ int c, pid;
+ int pfd[2];
+ char wd[Wordmax + 1];
+ char *s, *ewd = &wd[Wordmax], *stop;
+ struct io *f;
+ var *ifs = vlook("ifs");
+ word *v, *nextv;
+
+ stop = ifs->val? ifs->val->word: "";
+ if(pipe(pfd)<0){
+ Xerror("can't make pipe");
+ return;
+ }
+ switch(pid = fork()){
+ case -1:
+ Xerror("try again");
+ close(pfd[PRD]);
+ close(pfd[PWR]);
+ return;
+ case 0:
+ clearwaitpids();
+ close(pfd[PRD]);
+ start(runq->code, runq->pc+1, runq->local);
+ pushredir(ROPEN, pfd[PWR], 1);
+ return;
+ default:
+ addwaitpid(pid);
+ close(pfd[PWR]);
+ f = openfd(pfd[PRD]);
+ s = wd;
+ v = 0;
+ /*
+ * this isn't quite right for utf. stop could have utf
+ * in it, and we're processing the input as bytes, not
+ * utf encodings of runes. further, if we run out of
+ * room in wd, we can chop in the middle of a utf encoding
+ * (not to mention stepping on one of the bytes).
+ * presotto's Strings seem like the right data structure here.
+ */
+ while((c = rchr(f))!=EOF){
+ if(strchr(stop, c) || s==ewd){
+ if(s!=wd){
+ *s='\0';
+ v = newword(wd, v);
+ s = wd;
+ }
+ }
+ else *s++=c;
+ }
+ if(s!=wd){
+ *s='\0';
+ v = newword(wd, v);
+ }
+ closeio(f);
+ Waitfor(pid, 0);
+ /* 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;
+ }
+}
+
+void
+Xpipefd(void)
+{
+ struct thread *p = runq;
+ int pc = p->pc, pid;
+ char name[40];
+ int pfd[2];
+ int sidefd, mainfd;
+ if(pipe(pfd)<0){
+ Xerror("can't get pipe");
+ return;
+ }
+ if(p->code[pc].i==READ){
+ sidefd = pfd[PWR];
+ mainfd = pfd[PRD];
+ }
+ else{
+ sidefd = pfd[PRD];
+ mainfd = pfd[PWR];
+ }
+ switch(pid = fork()){
+ case -1:
+ Xerror("try again");
+ break;
+ case 0:
+ clearwaitpids();
+ start(p->code, pc+2, runq->local);
+ close(mainfd);
+ pushredir(ROPEN, sidefd, p->code[pc].i==READ?1:0);
+ runq->ret = 0;
+ break;
+ default:
+ addwaitpid(pid);
+ close(sidefd);
+ pushredir(ROPEN, mainfd, mainfd); /* isn't this a noop? */
+ strcpy(name, Fdprefix);
+ inttoascii(name+strlen(name), mainfd);
+ pushword(name);
+ p->pc = p->code[pc+1].i;
+ break;
+ }
+}
+
+void
+Xsubshell(void)
+{
+ int pid;
+ switch(pid = fork()){
+ case -1:
+ Xerror("try again");
+ break;
+ case 0:
+ clearwaitpids();
+ start(runq->code, runq->pc+1, runq->local);
+ runq->ret = 0;
+ break;
+ default:
+ addwaitpid(pid);
+ Waitfor(pid, 1);
+ runq->pc = runq->code[runq->pc].i;
+ break;
+ }
+}
+
+int
+execforkexec(void)
+{
+ int pid;
+ int n;
+ char buf[ERRMAX];
+
+ switch(pid = fork()){
+ case -1:
+ return -1;
+ case 0:
+ clearwaitpids();
+ pushword("exec");
+ execexec();
+ strcpy(buf, "can't exec: ");
+ n = strlen(buf);
+ errstr(buf+n, ERRMAX-n);
+ Exit(buf);
+ }
+ addwaitpid(pid);
+ return pid;
+}
diff --git a/sys/src/cmd/rc/haventfork.c b/sys/src/cmd/rc/haventfork.c
new file mode 100755
index 000000000..a5464ba5b
--- /dev/null
+++ b/sys/src/cmd/rc/haventfork.c
@@ -0,0 +1,211 @@
+#include "rc.h"
+#include "getflags.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+
+int havefork = 0;
+
+static char **
+rcargv(char *s)
+{
+ int argc;
+ char **argv;
+ word *p;
+
+ p = vlook("*")->val;
+ argv = malloc((count(p)+6)*sizeof(char*));
+ argc = 0;
+ argv[argc++] = argv0;
+ if(flag['e'])
+ argv[argc++] = "-Se";
+ else
+ argv[argc++] = "-S";
+ argv[argc++] = "-c";
+ argv[argc++] = s;
+ for(p = vlook("*")->val; p; p = p->next)
+ argv[argc++] = p->word;
+ argv[argc] = 0;
+ return argv;
+}
+
+void
+Xasync(void)
+{
+ uint pid;
+ char buf[20], **argv;
+
+ Updenv();
+
+ argv = rcargv(runq->code[runq->pc].s);
+ pid = ForkExecute(argv0, argv, -1, 1, 2);
+ free(argv);
+
+ if(pid == 0) {
+ Xerror("proc failed");
+ return;
+ }
+
+ runq->pc++;
+ sprint(buf, "%d", pid);
+ setvar("apid", newword(buf, (word *)0));
+}
+
+void
+Xbackq(void)
+{
+ char wd[8193], **argv;
+ int c;
+ char *s, *ewd=&wd[8192], *stop;
+ struct io *f;
+ var *ifs = vlook("ifs");
+ word *v, *nextv;
+ int pfd[2];
+ int pid;
+
+ stop = ifs->val?ifs->val->word:"";
+ if(pipe(pfd)<0){
+ Xerror("can't make pipe");
+ return;
+ }
+
+ Updenv();
+
+ argv = rcargv(runq->code[runq->pc].s);
+ pid = ForkExecute(argv0, argv, -1, pfd[1], 2);
+ free(argv);
+
+ close(pfd[1]);
+
+ if(pid == 0) {
+ Xerror("proc failed");
+ close(pfd[0]);
+ return;
+ }
+
+ f = openfd(pfd[0]);
+ s = wd;
+ v = 0;
+ while((c=rchr(f))!=EOF){
+ if(strchr(stop, c) || s==ewd){
+ if(s!=wd){
+ *s='\0';
+ v=newword(wd, v);
+ s=wd;
+ }
+ }
+ else *s++=c;
+ }
+ if(s!=wd){
+ *s='\0';
+ v=newword(wd, v);
+ }
+ closeio(f);
+ Waitfor(pid, 1);
+ /* v points to reversed arglist -- reverse it onto argv */
+ while(v){
+ nextv=v->next;
+ v->next=runq->argv->words;
+ runq->argv->words=v;
+ v=nextv;
+ }
+ runq->pc++;
+}
+
+void
+Xpipe(void)
+{
+ thread *p=runq;
+ int pc=p->pc, pid;
+ int rfd=p->code[pc+1].i;
+ int pfd[2];
+ char **argv;
+
+ if(pipe(pfd)<0){
+ Xerror1("can't get pipe");
+ return;
+ }
+
+ Updenv();
+
+ argv = rcargv(runq->code[pc+2].s);
+ pid = ForkExecute(argv0, argv, 0, pfd[1], 2);
+ free(argv);
+ close(pfd[1]);
+
+ if(pid == 0) {
+ Xerror("proc failed");
+ close(pfd[0]);
+ return;
+ }
+
+ start(p->code, pc+4, runq->local);
+ pushredir(ROPEN, pfd[0], rfd);
+ p->pc=p->code[pc+3].i;
+ p->pid=pid;
+}
+
+void
+Xpipefd(void)
+{
+ Abort();
+}
+
+void
+Xsubshell(void)
+{
+ char **argv;
+ int pid;
+
+ Updenv();
+
+ argv = rcargv(runq->code[runq->pc].s);
+ pid = ForkExecute(argv0, argv, -1, 1, 2);
+ free(argv);
+
+ if(pid < 0) {
+ Xerror("proc failed");
+ return;
+ }
+
+ Waitfor(pid, 1);
+ runq->pc++;
+}
+
+/*
+ * start a process running the cmd on the stack and return its pid.
+ */
+int
+execforkexec(void)
+{
+ char **argv;
+ char file[1024];
+ int nc;
+ word *path;
+ int pid;
+
+ if(runq->argv->words==0)
+ return -1;
+ argv = mkargv(runq->argv->words);
+
+ for(path = searchpath(runq->argv->words->word);path;path = path->next){
+ nc = strlen(path->word);
+ if(nc < sizeof file - 1){ /* 1 for / */
+ strcpy(file, path->word);
+ if(file[0]){
+ strcat(file, "/");
+ nc++;
+ }
+ if(nc+strlen(argv[1])<sizeof(file)){
+ strcat(file, argv[1]);
+ pid = ForkExecute(file, argv+1, mapfd(0), mapfd(1), mapfd(2));
+ if(pid >= 0){
+ free(argv);
+ return pid;
+ }
+ }
+ }
+ }
+ free(argv);
+ return -1;
+}
diff --git a/sys/src/cmd/rc/here.c b/sys/src/cmd/rc/here.c
new file mode 100755
index 000000000..d10ed6aa8
--- /dev/null
+++ b/sys/src/cmd/rc/here.c
@@ -0,0 +1,152 @@
+#include "rc.h"
+#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)
+{
+ *p++=hex[(n>>12)&0xF];
+ *p++=hex[(n>>8)&0xF];
+ *p++=hex[(n>>4)&0xF];
+ *p = hex[n&0xF];
+}
+
+tree*
+heredoc(tree *tag)
+{
+ struct here *h = new(struct here);
+ if(tag->type!=WORD)
+ yyerror("Bad here tag");
+ h->next = 0;
+ if(here)
+ *ehere = h;
+ else
+ here = h;
+ ehere=&h->next;
+ h->tag = tag;
+ hexnum(&tmp[9], getpid());
+ hexnum(&tmp[14], ser++);
+ h->name = strdup(tmp);
+ return token(tmp, WORD);
+}
+/*
+ * bug: lines longer than NLINE get split -- this can cause spurious
+ * missubstitution, or a misrecognized EOF marker.
+ */
+#define NLINE 4096
+
+void
+readhere(void)
+{
+ 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;
+ }
+ else *s++=c;
+ }
+ flush(f);
+ closeio(f);
+ cleanhere(h->name);
+ nexth = h->next;
+ efree((char *)h);
+ }
+ here = 0;
+ doprompt = 1;
+}
+
+void
+psubst(io *f, uchar *s)
+{
+ int savec, n;
+ uchar *t, *u;
+ word *star;
+ while(*s){
+ if(*s!='$'){
+ if(0xa0 <= *s && *s <= 0xf5){
+ pchr(f, *s++);
+ if(*s=='\0')
+ break;
+ }
+ else if(0xf6 <= *s && *s <= 0xf7){
+ pchr(f, *s++);
+ if(*s=='\0')
+ break;
+ pchr(f, *s++);
+ if(*s=='\0')
+ break;
+ }
+ pchr(f, *s++);
+ }
+ else{
+ t=++s;
+ if(*t=='$')
+ pchr(f, *t++);
+ else{
+ while(*t && idchr(*t)) t++;
+ savec=*t;
+ *t='\0';
+ n = 0;
+ for(u = s;*u && '0'<=*u && *u<='9';u++) n = n*10+*u-'0';
+ if(n && *u=='\0'){
+ star = vlook("*")->val;
+ if(star && 1<=n && n<=count(star)){
+ while(--n) star = star->next;
+ pstr(f, star->word);
+ }
+ }
+ else
+ pstrs(f, vlook((char *)s)->val);
+ *t = savec;
+ if(savec=='^')
+ t++;
+ }
+ s = t;
+ }
+ }
+}
+
+void
+pstrs(io *f, word *a)
+{
+ if(a){
+ while(a->next && a->next->word){
+ pstr(f, a->word);
+ pchr(f, ' ');
+ a = a->next;
+ }
+ pstr(f, a->word);
+ }
+}
diff --git a/sys/src/cmd/rc/io.c b/sys/src/cmd/rc/io.c
new file mode 100755
index 000000000..5480265cf
--- /dev/null
+++ b/sys/src/cmd/rc/io.c
@@ -0,0 +1,268 @@
+#include "rc.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+
+enum { Stralloc = 100, };
+
+int pfmtnest = 0;
+
+void
+pfmt(io *f, char *fmt, ...)
+{
+ va_list ap;
+ char err[ERRMAX];
+
+ va_start(ap, fmt);
+ pfmtnest++;
+ for(;*fmt;fmt++) {
+ if(*fmt!='%') {
+ pchr(f, *fmt);
+ continue;
+ }
+ if(*++fmt == '\0') /* "blah%"? */
+ break;
+ switch(*fmt){
+ case 'c':
+ pchr(f, va_arg(ap, int));
+ break;
+ case 'd':
+ pdec(f, va_arg(ap, int));
+ break;
+ case 'o':
+ poct(f, va_arg(ap, unsigned));
+ break;
+ case 'p':
+ pptr(f, va_arg(ap, void*));
+ break;
+ case 'Q':
+ pquo(f, va_arg(ap, char *));
+ break;
+ case 'q':
+ pwrd(f, va_arg(ap, char *));
+ break;
+ case 'r':
+ errstr(err, sizeof err); pstr(f, err);
+ break;
+ case 's':
+ pstr(f, va_arg(ap, char *));
+ break;
+ case 't':
+ pcmd(f, va_arg(ap, struct tree *));
+ break;
+ case 'v':
+ pval(f, va_arg(ap, struct word *));
+ break;
+ default:
+ pchr(f, *fmt);
+ break;
+ }
+ }
+ 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;
+}
+
+int
+rchr(io *b)
+{
+ if(b->bufp==b->ebuf)
+ return emptybuf(b);
+ return *b->bufp++;
+}
+
+void
+pquo(io *f, char *s)
+{
+ pchr(f, '\'');
+ for(;*s;s++)
+ if(*s=='\'')
+ pfmt(f, "''");
+ else pchr(f, *s);
+ pchr(f, '\'');
+}
+
+void
+pwrd(io *f, char *s)
+{
+ char *t;
+ for(t = s;*t;t++) if(*t >= 0 && needsrcquote(*t)) break;
+ if(t==s || *t)
+ pquo(f, s);
+ else pstr(f, s);
+}
+
+void
+pptr(io *f, void *v)
+{
+ int n;
+ uintptr p;
+
+ p = (uintptr)v;
+ if(sizeof(uintptr) == sizeof(uvlong) && p>>32)
+ for(n = 60;n>=32;n-=4) pchr(f, "0123456789ABCDEF"[(p>>n)&0xF]);
+
+ for(n = 28;n>=0;n-=4) pchr(f, "0123456789ABCDEF"[(p>>n)&0xF]);
+}
+
+void
+pstr(io *f, char *s)
+{
+ if(s==0)
+ s="(null)";
+ while(*s) pchr(f, *s++);
+}
+
+void
+pdec(io *f, int n)
+{
+ if(n<0){
+ n=-n;
+ if(n>=0){
+ pchr(f, '-');
+ pdec(f, n);
+ return;
+ }
+ /* n is two's complement minimum integer */
+ n = 1-n;
+ pchr(f, '-');
+ pdec(f, n/10);
+ pchr(f, n%10+'1');
+ return;
+ }
+ if(n>9)
+ pdec(f, n/10);
+ pchr(f, n%10+'0');
+}
+
+void
+poct(io *f, unsigned n)
+{
+ if(n>7)
+ poct(f, n>>3);
+ pchr(f, (n&7)+'0');
+}
+
+void
+pval(io *f, word *a)
+{
+ if(a){
+ 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)
+{
+ flush(f);
+ return *f->bufp++=c;
+}
+
+void
+flush(io *f)
+{
+ int n;
+
+ if(f->strp){
+ n = f->ebuf - f->strp;
+ f->strp = realloc(f->strp, n+Stralloc+1);
+ if(f->strp==0)
+ panic("Can't realloc %d bytes in flush!", 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;
+ }
+}
+
+io*
+openfd(int fd)
+{
+ io *f = new(struct io);
+ f->fd = fd;
+ f->bufp = f->ebuf = f->buf;
+ f->strp = 0;
+ return f;
+}
+
+io*
+openstr(void)
+{
+ 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;
+}
+/*
+ * Open a corebuffer to read. EOF occurs after reading len
+ * characters from buf.
+ */
+
+io*
+opencore(char *s, 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;
+ Memcpy(buf, s, len);
+ return f;
+}
+
+void
+rewind(io *io)
+{
+ if(io->fd==-1)
+ io->bufp = io->strp;
+ else{
+ io->bufp = io->ebuf = io->buf;
+ Seek(io->fd, 0L, 0);
+ }
+}
+
+void
+closeio(io *io)
+{
+ if(io->fd>=0)
+ close(io->fd);
+ if(io->strp)
+ efree(io->strp);
+ efree(io);
+}
+
+int
+emptybuf(io *f)
+{
+ int n;
+ if(f->fd==-1 || (n = Read(f->fd, f->buf, NBUF))<=0) return EOF;
+ f->bufp = f->buf;
+ f->ebuf = f->buf + n;
+ return *f->bufp++;
+}
diff --git a/sys/src/cmd/rc/io.h b/sys/src/cmd/rc/io.h
new file mode 100755
index 000000000..7d7cd146e
--- /dev/null
+++ b/sys/src/cmd/rc/io.h
@@ -0,0 +1,27 @@
+#define EOF (-1)
+#define NBUF 512
+
+struct io{
+ int fd;
+ uchar *bufp, *ebuf, *strp;
+ uchar buf[NBUF];
+};
+io *err;
+
+io *openfd(int), *openstr(void), *opencore(char *, int);
+int emptybuf(io*);
+void pchr(io*, int);
+int rchr(io*);
+void closeio(io*);
+void flush(io*);
+int fullbuf(io*, int);
+void pdec(io*, int);
+void poct(io*, unsigned);
+void pptr(io*, void*);
+void pquo(io*, char*);
+void pwrd(io*, char*);
+void pstr(io*, char*);
+void pcmd(io*, tree*);
+void pval(io*, word*);
+void pfnc(io*, thread*);
+void pfmt(io*, char*, ...);
diff --git a/sys/src/cmd/rc/lex.c b/sys/src/cmd/rc/lex.c
new file mode 100755
index 000000000..369348328
--- /dev/null
+++ b/sys/src/cmd/rc/lex.c
@@ -0,0 +1,378 @@
+#include "rc.h"
+#include "exec.h"
+#include "io.h"
+#include "getflags.h"
+#include "fns.h"
+int getnext(void);
+
+int
+wordchr(int c)
+{
+ return !strchr("\n \t#;&|^$=`'{}()<>", c) && c!=EOF;
+}
+
+int
+idchr(int c)
+{
+ /*
+ * Formerly:
+ * return 'a'<=c && c<='z' || 'A'<=c && c<='Z' || '0'<=c && c<='9'
+ * || c=='_' || c=='*';
+ */
+ return c>' ' && !strchr("!\"#$%&'()+,-./:;<=>?@[\\]^`{|}~", c);
+}
+int future = EOF;
+int doprompt = 1;
+int inquote;
+int incomm;
+/*
+ * Look ahead in the input stream
+ */
+
+int
+nextc(void)
+{
+ if(future==EOF)
+ future = getnext();
+ return future;
+}
+/*
+ * Consume the lookahead character.
+ */
+
+int
+advance(void)
+{
+ int c = nextc();
+ lastc = future;
+ future = EOF;
+ return c;
+}
+/*
+ * read a character from the input stream
+ */
+
+int
+getnext(void)
+{
+ int c;
+ static int peekc = EOF;
+ if(peekc!=EOF){
+ c = peekc;
+ peekc = EOF;
+ return c;
+ }
+ if(runq->eof)
+ return EOF;
+ if(doprompt)
+ pprompt();
+ c = rchr(runq->cmdfd);
+ if(!inquote && c=='\\'){
+ c = rchr(runq->cmdfd);
+ if(c=='\n' && !incomm){ /* don't continue a comment */
+ doprompt = 1;
+ c=' ';
+ }
+ else{
+ peekc = c;
+ c='\\';
+ }
+ }
+ doprompt = doprompt || c=='\n' || c==EOF;
+ if(c==EOF)
+ runq->eof++;
+ else if(flag['V'] || ndot>=2 && flag['v']) pchr(err, c);
+ return c;
+}
+
+void
+pprompt(void)
+{
+ var *prompt;
+ if(runq->iflag){
+ pstr(err, promptstr);
+ flush(err);
+ prompt = vlook("prompt");
+ if(prompt->val && prompt->val->next)
+ promptstr = prompt->val->next->word;
+ else
+ promptstr="\t";
+ }
+ runq->lineno++;
+ doprompt = 0;
+}
+
+void
+skipwhite(void)
+{
+ int c;
+ for(;;){
+ c = nextc();
+ /* Why did this used to be if(!inquote && c=='#') ?? */
+ if(c=='#'){
+ incomm = 1;
+ for(;;){
+ c = nextc();
+ if(c=='\n' || c==EOF) {
+ incomm = 0;
+ break;
+ }
+ advance();
+ }
+ }
+ if(c==' ' || c=='\t')
+ advance();
+ else return;
+ }
+}
+
+void
+skipnl(void)
+{
+ int c;
+ for(;;){
+ skipwhite();
+ c = nextc();
+ if(c!='\n')
+ return;
+ advance();
+ }
+}
+
+int
+nextis(int c)
+{
+ if(nextc()==c){
+ advance();
+ return 1;
+ }
+ return 0;
+}
+
+char*
+addtok(char *p, int val)
+{
+ if(p==0)
+ return 0;
+ if(p==&tok[NTOK-1]){
+ *p = 0;
+ yyerror("token buffer too short");
+ return 0;
+ }
+ *p++=val;
+ return p;
+}
+
+char*
+addutf(char *p, int c)
+{
+ p = addtok(p, c);
+ if(twobyte(c)) /* 2-byte escape */
+ return addtok(p, advance());
+ if(threebyte(c)){ /* 3-byte escape */
+ p = addtok(p, advance());
+ return addtok(p, advance());
+ }
+ return p;
+}
+int 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();
+ char *w = tok;
+ struct tree *t;
+ yylval.tree = 0;
+ /*
+ * Embarassing sneakiness: if the last token read was a quoted or unquoted
+ * WORD then we alter the meaning of what follows. If the next character
+ * is `(', we return SUB (a subscript paren) and consume the `('. Otherwise,
+ * if the next character is the first character of a simple or compound word,
+ * we insert a `^' before it.
+ */
+ if(lastword){
+ lastword = 0;
+ if(d=='('){
+ advance();
+ strcpy(tok, "( [SUB]");
+ return SUB;
+ }
+ if(wordchr(d) || d=='\'' || d=='`' || d=='$' || d=='"'){
+ strcpy(tok, "^");
+ return '^';
+ }
+ }
+ inquote = 0;
+ skipwhite();
+ switch(c = advance()){
+ case EOF:
+ lastdol = 0;
+ strcpy(tok, "EOF");
+ return EOF;
+ case '$':
+ lastdol = 1;
+ if(nextis('#')){
+ strcpy(tok, "$#");
+ return COUNT;
+ }
+ if(nextis('"')){
+ strcpy(tok, "$\"");
+ return '"';
+ }
+ strcpy(tok, "$");
+ return '$';
+ case '&':
+ lastdol = 0;
+ if(nextis('&')){
+ skipnl();
+ strcpy(tok, "&&");
+ return ANDAND;
+ }
+ strcpy(tok, "&");
+ return '&';
+ case '|':
+ lastdol = 0;
+ if(nextis(c)){
+ skipnl();
+ strcpy(tok, "||");
+ return OROR;
+ }
+ case '<':
+ case '>':
+ lastdol = 0;
+ /*
+ * funny redirection tokens:
+ * redir: arrow | arrow '[' fd ']'
+ * arrow: '<' | '<<' | '>' | '>>' | '|'
+ * fd: digit | digit '=' | digit '=' digit
+ * digit: '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'
+ * some possibilities are nonsensical and get a message.
+ */
+ *w++=c;
+ t = newtree();
+ switch(c){
+ case '|':
+ t->type = PIPE;
+ t->fd0 = 1;
+ t->fd1 = 0;
+ break;
+ case '>':
+ t->type = REDIR;
+ if(nextis(c)){
+ t->rtype = APPEND;
+ *w++=c;
+ }
+ else t->rtype = WRITE;
+ t->fd0 = 1;
+ break;
+ case '<':
+ t->type = REDIR;
+ if(nextis(c)){
+ t->rtype = HERE;
+ *w++=c;
+ } else if (nextis('>')){
+ t->rtype = RDWR;
+ *w++=c;
+ } else t->rtype = READ;
+ t->fd0 = 0;
+ break;
+ }
+ if(nextis('[')){
+ *w++='[';
+ c = advance();
+ *w++=c;
+ if(c<'0' || '9'<c){
+ RedirErr:
+ *w = 0;
+ yyerror(t->type==PIPE?"pipe syntax"
+ :"redirection syntax");
+ return EOF;
+ }
+ t->fd0 = 0;
+ do{
+ t->fd0 = t->fd0*10+c-'0';
+ *w++=c;
+ c = advance();
+ }while('0'<=c && c<='9');
+ if(c=='='){
+ *w++='=';
+ if(t->type==REDIR)
+ t->type = DUP;
+ c = advance();
+ if('0'<=c && c<='9'){
+ t->rtype = DUPFD;
+ t->fd1 = t->fd0;
+ t->fd0 = 0;
+ do{
+ t->fd0 = t->fd0*10+c-'0';
+ *w++=c;
+ c = advance();
+ }while('0'<=c && c<='9');
+ }
+ else{
+ if(t->type==PIPE)
+ goto RedirErr;
+ t->rtype = CLOSE;
+ }
+ }
+ if(c!=']'
+ || t->type==DUP && (t->rtype==HERE || t->rtype==APPEND))
+ goto RedirErr;
+ *w++=']';
+ }
+ *w='\0';
+ yylval.tree = t;
+ if(t->type==PIPE)
+ skipnl();
+ return t->type;
+ case '\'':
+ lastdol = 0;
+ lastword = 1;
+ inquote = 1;
+ for(;;){
+ c = advance();
+ if(c==EOF)
+ break;
+ if(c=='\''){
+ if(nextc()!='\'')
+ break;
+ advance();
+ }
+ w = addutf(w, c);
+ }
+ if(w!=0)
+ *w='\0';
+ t = token(tok, WORD);
+ t->quoted = 1;
+ yylval.tree = t;
+ return t->type;
+ }
+ if(!wordchr(c)){
+ lastdol = 0;
+ tok[0] = c;
+ tok[1]='\0';
+ return c;
+ }
+ for(;;){
+ /* next line should have (char)c==GLOB, but ken's compiler is broken */
+ if(c=='*' || c=='[' || c=='?' || c==(unsigned char)GLOB)
+ w = addtok(w, GLOB);
+ w = addutf(w, c);
+ c = nextc();
+ if(lastdol?!idchr(c):!wordchr(c)) break;
+ advance();
+ }
+
+ lastword = 1;
+ lastdol = 0;
+ if(w!=0)
+ *w='\0';
+ t = klook(tok);
+ if(t->type!=WORD)
+ lastword = 0;
+ t->quoted = 0;
+ yylval.tree = t;
+ return t->type;
+}
diff --git a/sys/src/cmd/rc/mkfile b/sys/src/cmd/rc/mkfile
new file mode 100755
index 000000000..b2ea3c5eb
--- /dev/null
+++ b/sys/src/cmd/rc/mkfile
@@ -0,0 +1,63 @@
+</$objtype/mkfile
+
+TARG=rc
+COMMONOFILES=\
+ code.$O\
+ exec.$O\
+ getflags.$O\
+ glob.$O\
+ here.$O\
+ io.$O\
+ lex.$O\
+ pcmd.$O\
+ pfnc.$O\
+ simple.$O\
+ subr.$O\
+ trap.$O\
+ tree.$O\
+ var.$O\
+ havefork.$O\
+
+PLAN9OFILES=plan9.$O\
+
+UNIXOFILES=unix.$O\
+
+OFILES=$COMMONOFILES $PLAN9OFILES y.tab.$O
+
+HFILES=rc.h\
+ x.tab.h\
+ io.h\
+ exec.h\
+ fns.h\
+ getflags.h\
+
+YFILES=syn.y
+
+BIN=/$objtype/bin
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${COMMONOFILES:%.$O=%.c}\
+ ${UNIXOFILES:%.$O=%.c}\
+ ${PLAN9OFILES:%.$O=%.c}\
+ $YFILES\
+ ${TARG:%=/386/bin/%}\
+
+</sys/src/cmd/mkone
+
+x.tab.h: y.tab.h
+ cmp -s x.tab.h y.tab.h || cp y.tab.h x.tab.h
+
+clean:V:
+ rm -f [$OS].out *.[$OS] [xy].tab.? y.debug $TARG
+
+regress: $O.out
+ cd test
+ mk
+
+unregress:V:
+ for(test in test/*.test) rc $test >$test.out
+
+listing:
+ pr mkfile $HFILES $FILES $FILES9 $FILESUNIX $YFILES|lp -du
diff --git a/sys/src/cmd/rc/pcmd.c b/sys/src/cmd/rc/pcmd.c
new file mode 100755
index 000000000..8caf60a28
--- /dev/null
+++ b/sys/src/cmd/rc/pcmd.c
@@ -0,0 +1,147 @@
+#include "rc.h"
+#include "io.h"
+#include "fns.h"
+char nl='\n'; /* change to semicolon for bourne-proofing */
+#define c0 t->child[0]
+#define c1 t->child[1]
+#define c2 t->child[2]
+
+void
+pdeglob(io *f, char *s)
+{
+ while(*s){
+ if(*s==GLOB)
+ s++;
+ pchr(f, *s++);
+ }
+}
+
+void
+pcmd(io *f, tree *t)
+{
+ if(t==0)
+ return;
+ switch(t->type){
+ default: pfmt(f, "bad %d %p %p %p", t->type, c0, c1, c2);
+ break;
+ case '$': pfmt(f, "$%t", c0);
+ break;
+ case '"': pfmt(f, "$\"%t", c0);
+ break;
+ case '&': pfmt(f, "%t&", c0);
+ break;
+ case '^': pfmt(f, "%t^%t", c0, c1);
+ break;
+ case '`': pfmt(f, "`%t", c0);
+ break;
+ case ANDAND: pfmt(f, "%t && %t", c0, c1);
+ break;
+ case BANG: pfmt(f, "! %t", c0);
+ break;
+ case BRACE: pfmt(f, "{%t}", c0);
+ break;
+ case COUNT: pfmt(f, "$#%t", c0);
+ break;
+ case FN: pfmt(f, "fn %t %t", c0, c1);
+ break;
+ case IF: pfmt(f, "if%t%t", c0, c1);
+ break;
+ case NOT: pfmt(f, "if not %t", c0);
+ break;
+ case OROR: pfmt(f, "%t || %t", c0, c1);
+ break;
+ case PCMD:
+ case PAREN: pfmt(f, "(%t)", c0);
+ break;
+ case SUB: pfmt(f, "$%t(%t)", c0, c1);
+ break;
+ case SIMPLE: pfmt(f, "%t", c0);
+ break;
+ case SUBSHELL: pfmt(f, "@ %t", c0);
+ break;
+ case SWITCH: pfmt(f, "switch %t %t", c0, c1);
+ break;
+ case TWIDDLE: pfmt(f, "~ %t %t", c0, c1);
+ break;
+ case WHILE: pfmt(f, "while %t%t", c0, c1);
+ break;
+ case ARGLIST:
+ if(c0==0)
+ pfmt(f, "%t", c1);
+ else if(c1==0)
+ pfmt(f, "%t", c0);
+ else
+ pfmt(f, "%t %t", c0, c1);
+ break;
+ case ';':
+ if(c0){
+ if(c1)
+ pfmt(f, "%t%c%t", c0, nl, c1);
+ else pfmt(f, "%t", c0);
+ }
+ else pfmt(f, "%t", c1);
+ break;
+ case WORDS:
+ if(c0)
+ pfmt(f, "%t ", c0);
+ pfmt(f, "%t", c1);
+ break;
+ case FOR:
+ pfmt(f, "for(%t", c0);
+ if(c1)
+ pfmt(f, " in %t", c1);
+ pfmt(f, ")%t", c2);
+ break;
+ case WORD:
+ if(t->quoted)
+ pfmt(f, "%Q", t->str);
+ else pdeglob(f, t->str);
+ break;
+ case DUP:
+ if(t->rtype==DUPFD)
+ pfmt(f, ">[%d=%d]", t->fd1, t->fd0); /* yes, fd1, then fd0; read lex.c */
+ else
+ pfmt(f, ">[%d=]", t->fd0);
+ pfmt(f, "%t", c1);
+ break;
+ case PIPEFD:
+ case REDIR:
+ switch(t->rtype){
+ case HERE:
+ pchr(f, '<');
+ case READ:
+ case RDWR:
+ pchr(f, '<');
+ if(t->rtype==RDWR)
+ pchr(f, '>');
+ if(t->fd0!=0)
+ pfmt(f, "[%d]", t->fd0);
+ break;
+ case APPEND:
+ pchr(f, '>');
+ case WRITE:
+ pchr(f, '>');
+ if(t->fd0!=1)
+ pfmt(f, "[%d]", t->fd0);
+ break;
+ }
+ pfmt(f, "%t", c0);
+ if(c1)
+ pfmt(f, " %t", c1);
+ break;
+ case '=':
+ pfmt(f, "%t=%t", c0, c1);
+ if(c2)
+ pfmt(f, " %t", c2);
+ break;
+ case PIPE:
+ pfmt(f, "%t|", c0);
+ if(t->fd1==0){
+ if(t->fd0!=1)
+ pfmt(f, "[%d]", t->fd0);
+ }
+ else pfmt(f, "[%d=%d]", t->fd0, t->fd1);
+ pfmt(f, "%t", c1);
+ break;
+ }
+}
diff --git a/sys/src/cmd/rc/pfnc.c b/sys/src/cmd/rc/pfnc.c
new file mode 100755
index 000000000..3f2b4c98a
--- /dev/null
+++ b/sys/src/cmd/rc/pfnc.c
@@ -0,0 +1,71 @@
+#include "rc.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+struct{
+ void (*f)(void);
+ char *name;
+}fname[] = {
+ Xappend, "Xappend",
+ Xasync, "Xasync",
+ Xbang, "Xbang",
+ Xclose, "Xclose",
+ Xdup, "Xdup",
+ Xeflag, "Xeflag",
+ Xexit, "Xexit",
+ Xfalse, "Xfalse",
+ Xifnot, "Xifnot",
+ Xjump, "Xjump",
+ Xmark, "Xmark",
+ Xpopm, "Xpopm",
+ Xrdwr, "Xrdwr",
+ Xread, "Xread",
+ Xreturn, "Xreturn",
+ Xtrue, "Xtrue",
+ Xif, "Xif",
+ Xwastrue, "Xwastrue",
+ Xword, "Xword",
+ Xwrite, "Xwrite",
+ Xmatch, "Xmatch",
+ Xcase, "Xcase",
+ Xconc, "Xconc",
+ Xassign, "Xassign",
+ Xdol, "Xdol",
+ Xcount, "Xcount",
+ Xlocal, "Xlocal",
+ Xunlocal, "Xunlocal",
+ Xfn, "Xfn",
+ Xdelfn, "Xdelfn",
+ Xpipe, "Xpipe",
+ Xpipewait, "Xpipewait",
+ Xrdcmds, "Xrdcmds",
+ (void (*)(void))Xerror, "Xerror",
+ Xbackq, "Xbackq",
+ Xpipefd, "Xpipefd",
+ Xsubshell, "Xsubshell",
+ Xdelhere, "Xdelhere",
+ Xfor, "Xfor",
+ Xglob, "Xglob",
+ Xrdfn, "Xrdfn",
+ Xsimple, "Xsimple",
+ Xrdfn, "Xrdfn",
+ Xqdol, "Xqdol",
+0};
+
+void
+pfnc(io *fd, thread *t)
+{
+ int i;
+ void (*fn)(void) = t->code[t->pc].f;
+ list *a;
+ pfmt(fd, "pid %d cycle %p %d ", getpid(), t->code, t->pc);
+ for(i = 0;fname[i].f;i++) if(fname[i].f==fn){
+ pstr(fd, fname[i].name);
+ break;
+ }
+ if(!fname[i].f)
+ pfmt(fd, "%p", fn);
+ for(a = t->argv;a;a = a->next) pfmt(fd, " (%v)", a->words);
+ pchr(fd, '\n');
+ flush(fd);
+}
diff --git a/sys/src/cmd/rc/plan9.c b/sys/src/cmd/rc/plan9.c
new file mode 100755
index 000000000..d71ca675c
--- /dev/null
+++ b/sys/src/cmd/rc/plan9.c
@@ -0,0 +1,670 @@
+/*
+ * Plan 9 versions of system-specific functions
+ * By convention, exported routines herein have names beginning with an
+ * upper case letter.
+ */
+#include "rc.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+#include "getflags.h"
+
+enum {
+ Maxenvname = 256, /* undocumented limit */
+};
+
+char *Signame[] = {
+ "sigexit", "sighup", "sigint", "sigquit",
+ "sigalrm", "sigkill", "sigfpe", "sigterm",
+ 0
+};
+char *syssigname[] = {
+ "exit", /* can't happen */
+ "hangup",
+ "interrupt",
+ "quit", /* can't happen */
+ "alarm",
+ "kill",
+ "sys: fp: ",
+ "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)
+{
+ int arg;
+ char *s;
+ switch(count(runq->argv->words)){
+ case 1:
+ arg = RFENVG|RFNAMEG|RFNOTEG;
+ break;
+ case 2:
+ arg = 0;
+ for(s = runq->argv->words->next->word;*s;s++) switch(*s){
+ default:
+ goto Usage;
+ case 'n':
+ arg|=RFNAMEG; break;
+ case 'N':
+ arg|=RFCNAMEG;
+ break;
+ case 'm':
+ arg|=RFNOMNT; break;
+ case 'e':
+ arg|=RFENVG; break;
+ case 'E':
+ arg|=RFCENVG; break;
+ case 's':
+ arg|=RFNOTEG; break;
+ case 'f':
+ arg|=RFFDG; break;
+ case 'F':
+ arg|=RFCFDG; break;
+ }
+ break;
+ default:
+ Usage:
+ pfmt(err, "Usage: %s [fnesFNEm]\n", runq->argv->words->word);
+ setstatus("rfork usage");
+ poplist();
+ return;
+ }
+ if(rfork(arg)==-1){
+ pfmt(err, "rc: %s failed\n", runq->argv->words->word);
+ setstatus("rfork failed");
+ }
+ else
+ setstatus("");
+ poplist();
+}
+
+void
+Vinit(void)
+{
+ int dir, f, len, i, n, nent;
+ char *buf, *s;
+ char envname[Maxenvname];
+ word *val;
+ Dir *ent;
+
+ dir = open("/env", OREAD);
+ if(dir<0){
+ pfmt(err, "rc: can't open /env: %r\n");
+ return;
+ }
+ ent = nil;
+ for(;;){
+ nent = dirread(dir, &ent);
+ if(nent <= 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);
+ efree(buf);
+ }
+ }
+ }
+ free(ent);
+ }
+ close(dir);
+}
+int envdir;
+
+void
+Xrdfn(void)
+{
+ int f, len;
+ Dir *e;
+ char envname[Maxenvname];
+ static Dir *ent, *allocent;
+ static int nent;
+
+ for(;;){
+ if(nent == 0){
+ free(allocent);
+ nent = dirread(envdir, &allocent);
+ ent = allocent;
+ }
+ if(nent <= 0)
+ break;
+ while(nent){
+ e = ent++;
+ nent--;
+ len = e->length;
+ if(len && strncmp(e->name, "fn#", 3)==0){
+ snprint(envname, sizeof envname, "/env/%s", e->name);
+ if((f = open(envname, 0))>=0){
+ execcmds(openfd(f));
+ return;
+ }
+ }
+ }
+ }
+ close(envdir);
+ Xreturn();
+}
+union code rdfns[4];
+
+void
+execfinit(void)
+{
+ static int first = 1;
+ if(first){
+ rdfns[0].i = 1;
+ rdfns[1].f = Xrdfn;
+ rdfns[2].f = Xjump;
+ rdfns[3].i = 1;
+ first = 0;
+ }
+ Xpopm();
+ envdir = open("/env", 0);
+ if(envdir<0){
+ pfmt(err, "rc: can't open /env: %r\n");
+ return;
+ }
+ start(rdfns, 1, runq->local);
+}
+
+int
+Waitfor(int pid, int)
+{
+ thread *p;
+ Waitmsg *w;
+ char errbuf[ERRMAX];
+
+ if(pid >= 0 && !havewaitpid(pid))
+ return 0;
+
+ while((w = wait()) != nil){
+ delwaitpid(w->pid);
+ if(w->pid==pid){
+ setstatus(w->msg);
+ free(w);
+ return 0;
+ }
+ for(p = runq->ret;p;p = p->ret)
+ if(p->pid==w->pid){
+ p->pid=-1;
+ strcpy(p->status, w->msg);
+ }
+ free(w);
+ }
+
+ errstr(errbuf, sizeof errbuf);
+ if(strcmp(errbuf, "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
+addenv(var *v)
+{
+ char envname[Maxenvname];
+ word *w;
+ int f;
+ io *fd;
+ 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);
+ else{
+ for(w = v->val;w;w = w->next)
+ write(f, w->word, strlen(w->word)+1L);
+ close(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);
+ else{
+ if(v->fn){
+ fd = openfd(f);
+ pfmt(fd, "fn %q %s\n", v->name, v->fn[v->pc-1].s);
+ closeio(fd);
+ }
+ close(f);
+ }
+ }
+}
+
+void
+updenvlocal(var *v)
+{
+ if(v){
+ updenvlocal(v->next);
+ addenv(v);
+ }
+}
+
+void
+Updenv(void)
+{
+ var *v, **h;
+ for(h = gvar;h!=&gvar[NVAR];h++)
+ for(v=*h;v;v = v->next)
+ addenv(v);
+ if(runq)
+ updenvlocal(runq->local);
+}
+
+/* not used on plan 9 */
+int
+ForkExecute(char *file, char **argv, int sin, int sout, int serr)
+{
+ int pid;
+
+ if(access(file, 1) != 0)
+ return -1;
+ switch(pid = fork()){
+ case -1:
+ return -1;
+ case 0:
+ if(sin >= 0)
+ dup(sin, 0);
+ else
+ close(0);
+ if(sout >= 0)
+ dup(sout, 1);
+ else
+ close(1);
+ if(serr >= 0)
+ dup(serr, 2);
+ else
+ close(2);
+ exec(file, argv);
+ exits(file);
+ }
+ return pid;
+}
+
+void
+Execute(word *args, word *path)
+{
+ char **argv = mkargv(args);
+ char file[1024], errstr[1024];
+ int nc;
+
+ Updenv();
+ errstr[0] = '\0';
+ for(;path;path = path->next){
+ nc = strlen(path->word);
+ if(nc < sizeof file - 1){ /* 1 for / */
+ strcpy(file, path->word);
+ if(file[0]){
+ strcat(file, "/");
+ nc++;
+ }
+ if(nc + strlen(argv[1]) < sizeof file){
+ strcat(file, argv[1]);
+ exec(file, argv+1);
+ rerrstr(errstr, sizeof errstr);
+ /*
+ * if file exists and is executable, exec should
+ * have worked, unless it's a directory or an
+ * executable for another architecture. in
+ * particular, if it failed due to lack of
+ * swap/vm (e.g., arg. list too long) or other
+ * allocation failure, stop searching and print
+ * the reason for failure.
+ */
+ if (strstr(errstr, " allocat") != nil ||
+ strstr(errstr, " full") != nil)
+ break;
+ }
+ else werrstr("command name too long");
+ }
+ }
+ pfmt(err, "%s: %s\n", argv[1], errstr);
+ efree((char *)argv);
+}
+#define NDIR 256 /* shoud be a better way */
+
+int
+Globsize(char *p)
+{
+ 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;
+}
+#define NFD 50
+
+struct{
+ Dir *dbuf;
+ int i;
+ int n;
+}dir[NFD];
+
+int
+Opendir(char *name)
+{
+ Dir *db;
+ int f;
+ f = open(name, 0);
+ if(f==-1)
+ return f;
+ db = dirfstat(f);
+ if(db!=nil && (db->mode&DMDIR)){
+ if(f<NFD){
+ dir[f].i = 0;
+ dir[f].n = 0;
+ }
+ free(db);
+ return f;
+ }
+ free(db);
+ close(f);
+ return -1;
+}
+
+static int
+trimdirs(Dir *d, int nd)
+{
+ int r, w;
+
+ for(r=w=0; r<nd; r++)
+ if(d[r].mode&DMDIR)
+ d[w++] = d[r];
+ return w;
+}
+
+/*
+ * onlydirs is advisory -- it means you only
+ * need to return the directories. it's okay to
+ * return files too (e.g., on unix where you can't
+ * tell during the readdir), but that just makes
+ * the globber work harder.
+ */
+int
+Readdir(int f, void *p, int onlydirs)
+{
+ 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(n>0){
+ if(onlydirs){
+ n = trimdirs(dir[f].dbuf, n);
+ if(n == 0)
+ goto Again;
+ }
+ dir[f].n = n;
+ }else
+ dir[f].n = 0;
+ dir[f].i = 0;
+ }
+ if(dir[f].i == dir[f].n)
+ return 0;
+ strcpy(p, dir[f].dbuf[dir[f].i].name);
+ dir[f].i++;
+ return 1;
+}
+
+void
+Closedir(int f)
+{
+ if(f>=0 && f<NFD){
+ free(dir[f].dbuf);
+ dir[f].i = 0;
+ dir[f].n = 0;
+ dir[f].dbuf = 0;
+ }
+ close(f);
+}
+int interrupted = 0;
+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:
+ if(strcmp(s, "interrupt")!=0 || trap[i]==0){
+ 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);
+}
+
+void
+Trapinit(void)
+{
+ notify(notifyf);
+}
+
+void
+Unlink(char *name)
+{
+ remove(name);
+}
+
+long
+Write(int fd, void *buf, long cnt)
+{
+ return write(fd, buf, cnt);
+}
+
+long
+Read(int fd, void *buf, long cnt)
+{
+ return read(fd, buf, cnt);
+}
+
+long
+Seek(int fd, long cnt, long whence)
+{
+ return seek(fd, cnt, whence);
+}
+
+int
+Executable(char *file)
+{
+ Dir *statbuf;
+ int ret;
+
+ statbuf = dirstat(file);
+ if(statbuf == nil)
+ return 0;
+ ret = ((statbuf->mode&0111)!=0 && (statbuf->mode&DMDIR)==0);
+ free(statbuf);
+ return ret;
+}
+
+int
+Creat(char *file)
+{
+ return create(file, 1, 0666L);
+}
+
+int
+Dup(int a, int b)
+{
+ return dup(a, b);
+}
+
+int
+Dup1(int)
+{
+ return -1;
+}
+
+void
+Exit(char *stat)
+{
+ Updenv();
+ setstatus(stat);
+ exits(truestatus()?"":getstatus());
+}
+
+int
+Eintr(void)
+{
+ return interrupted;
+}
+
+void
+Noerror(void)
+{
+ interrupted = 0;
+}
+
+int
+Isatty(int fd)
+{
+ char buf[64];
+
+ if(fd2path(fd, buf, sizeof buf) != 0)
+ return 0;
+
+ /* might be #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;
+}
+
+void
+Abort(void)
+{
+ pfmt(err, "aborting\n");
+ flush(err);
+ Exit("aborting");
+}
+
+void
+Memcpy(void *a, void *b, long n)
+{
+ memmove(a, b, n);
+}
+
+void*
+Malloc(ulong n)
+{
+ return malloc(n);
+}
+
+int *waitpids;
+int nwaitpids;
+
+void
+addwaitpid(int pid)
+{
+ waitpids = realloc(waitpids, (nwaitpids+1)*sizeof waitpids[0]);
+ if(waitpids == 0)
+ panic("Can't realloc %d waitpids", nwaitpids+1);
+ 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;
+}
+
+/* avoid loading any floating-point library code */
+int
+_efgfmt(Fmt *)
+{
+ return -1;
+}
diff --git a/sys/src/cmd/rc/rc.h b/sys/src/cmd/rc/rc.h
new file mode 100755
index 000000000..8ccdc344b
--- /dev/null
+++ b/sys/src/cmd/rc/rc.h
@@ -0,0 +1,150 @@
+/*
+ * Plan9 is defined for plan 9
+ * V9 is defined for 9th edition
+ * Sun is defined for sun-os
+ * Please don't litter the code with ifdefs. The three below (and one in
+ * getflags) should be enough.
+ */
+#define Plan9
+#ifdef Plan9
+#include <u.h>
+#include <libc.h>
+#define NSIG 32
+#define SIGINT 2
+#define SIGQUIT 3
+#endif
+#ifdef V9
+#include <signal.h>
+#include <libc.h>
+#endif
+#ifdef Sun
+#include <signal.h>
+#endif
+#define YYMAXDEPTH 500
+#ifndef PAREN
+#include "x.tab.h"
+#endif
+typedef struct tree tree;
+typedef struct word word;
+typedef struct io io;
+typedef union code code;
+typedef struct var var;
+typedef struct list list;
+typedef struct redir redir;
+typedef struct thread thread;
+typedef struct builtin builtin;
+
+#pragma incomplete word
+#pragma incomplete io
+
+struct tree{
+ int type;
+ int rtype, fd0, fd1; /* details of REDIR PIPE DUP tokens */
+ char *str;
+ int quoted;
+ int iskw;
+ tree *child[3];
+ tree *next;
+};
+tree *newtree(void);
+tree *token(char*, int), *klook(char*), *tree1(int, tree*);
+tree *tree2(int, tree*, tree*), *tree3(int, tree*, tree*, tree*);
+tree *mung1(tree*, tree*), *mung2(tree*, tree*, tree*);
+tree *mung3(tree*, tree*, tree*, tree*), *epimung(tree*, tree*);
+tree *simplemung(tree*), *heredoc(tree*);
+void freetree(tree*);
+tree *cmdtree;
+/*
+ * The first word of any code vector is a reference count.
+ * Always create a new reference to a code vector by calling codecopy(.).
+ * Always call codefree(.) when deleting a reference.
+ */
+union code{
+ void (*f)(void);
+ int i;
+ char *s;
+};
+
+char *promptstr;
+int doprompt;
+
+#define NTOK 8192
+
+char tok[NTOK];
+
+#define APPEND 1
+#define WRITE 2
+#define READ 3
+#define HERE 4
+#define DUPFD 5
+#define CLOSE 6
+#define RDWR 7
+
+struct var{
+ char *name; /* ascii name */
+ word *val; /* value */
+ int changed;
+ code *fn; /* pointer to function's code vector */
+ int fnchanged;
+ int pc; /* pc of start of function */
+ var *next; /* next on hash or local list */
+};
+var *vlook(char*), *gvlook(char*), *newvar(char*, var*);
+
+#define NVAR 521
+
+var *gvar[NVAR]; /* hash for globals */
+
+#define new(type) ((type *)emalloc(sizeof(type)))
+
+void *emalloc(long);
+void *Malloc(ulong);
+void efree(void *);
+
+#define NOFILE 128 /* should come from <param.h> */
+
+struct here{
+ tree *tag;
+ char *name;
+ struct here *next;
+};
+int mypid;
+
+/*
+ * Glob character escape in strings:
+ * In a string, GLOB must be followed by *?[ or GLOB.
+ * GLOB* matches any string
+ * GLOB? matches any single character
+ * GLOB[...] matches anything in the brackets
+ * GLOBGLOB matches GLOB
+ */
+#define GLOB ((char)0x01)
+/*
+ * onebyte(c), twobyte(c), threebyte(c)
+ * Is c the first character of a one- two- or three-byte utf sequence?
+ */
+#define onebyte(c) ((c&0x80)==0x00)
+#define twobyte(c) ((c&0xe0)==0xc0)
+#define threebyte(c) ((c&0xf0)==0xe0)
+
+char **argp;
+char **args;
+int nerror; /* number of errors encountered during compilation */
+int doprompt; /* is it time for a prompt? */
+/*
+ * Which fds are the reading/writing end of a pipe?
+ * Unfortunately, this can vary from system to system.
+ * 9th edition Unix doesn't care, the following defines
+ * work on plan 9.
+ */
+#define PRD 0
+#define PWR 1
+char Rcmain[], Fdprefix[];
+/*
+ * How many dot commands have we executed?
+ * Used to ensure that -v flag doesn't print rcmain.
+ */
+int ndot;
+char *getstatus(void);
+int lastc;
+int lastword;
diff --git a/sys/src/cmd/rc/simple.c b/sys/src/cmd/rc/simple.c
new file mode 100755
index 000000000..d4b897cd1
--- /dev/null
+++ b/sys/src/cmd/rc/simple.c
@@ -0,0 +1,517 @@
+/*
+ * Maybe `simple' is a misnomer.
+ */
+#include "rc.h"
+#include "getflags.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+/*
+ * Search through the following code to see if we're just going to exit.
+ */
+int
+exitnext(void){
+ union code *c=&runq->code[runq->pc];
+ while(c->f==Xpopredir) c++;
+ return c->f==Xexit;
+}
+
+void
+Xsimple(void)
+{
+ word *a;
+ thread *p = runq;
+ var *v;
+ struct builtin *bp;
+ int pid;
+ globlist();
+ a = runq->argv->words;
+ if(a==0){
+ Xerror1("empty argument list");
+ return;
+ }
+ if(flag['x'])
+ pfmt(err, "%v\n", p->argv->words); /* wrong, should do redirs */
+ v = gvlook(a->word);
+ if(v->fn)
+ execfunc(v);
+ else{
+ if(strcmp(a->word, "builtin")==0){
+ if(count(a)==1){
+ pfmt(err, "builtin: empty argument list\n");
+ setstatus("empty arg list");
+ poplist();
+ return;
+ }
+ a = a->next;
+ popword();
+ }
+ for(bp = Builtin;bp->name;bp++)
+ if(strcmp(a->word, bp->name)==0){
+ (*bp->fnc)();
+ return;
+ }
+ if(exitnext()){
+ /* fork and wait is redundant */
+ pushword("exec");
+ execexec();
+ Xexit();
+ }
+ else{
+ flush(err);
+ Updenv(); /* necessary so changes don't go out again */
+ if((pid = execforkexec()) < 0){
+ Xerror("try again");
+ return;
+ }
+
+ /* interrupts don't get us out */
+ poplist();
+ while(Waitfor(pid, 1) < 0)
+ ;
+ }
+ }
+}
+struct word nullpath = { "", 0};
+
+void
+doredir(redir *rp)
+{
+ if(rp){
+ doredir(rp->next);
+ switch(rp->type){
+ case ROPEN:
+ if(rp->from!=rp->to){
+ Dup(rp->from, rp->to);
+ close(rp->from);
+ }
+ break;
+ case RDUP:
+ Dup(rp->from, rp->to);
+ break;
+ case RCLOSE:
+ close(rp->from);
+ break;
+ }
+ }
+}
+
+word*
+searchpath(char *w)
+{
+ word *path;
+ if(strncmp(w, "/", 1)==0
+ || strncmp(w, "#", 1)==0
+ || strncmp(w, "./", 2)==0
+ || strncmp(w, "../", 3)==0
+ || (path = vlook("path")->val)==0)
+ path=&nullpath;
+ return path;
+}
+
+void
+execexec(void)
+{
+ popword(); /* "exec" */
+ if(runq->argv->words==0){
+ Xerror1("empty argument list");
+ return;
+ }
+ doredir(runq->redir);
+ Execute(runq->argv->words, searchpath(runq->argv->words->word));
+ poplist();
+}
+
+void
+execfunc(var *func)
+{
+ word *starval;
+ popword();
+ starval = runq->argv->words;
+ runq->argv->words = 0;
+ poplist();
+ start(func->fn, func->pc, runq->local);
+ runq->local = newvar(strdup("*"), runq->local);
+ runq->local->val = starval;
+ runq->local->changed = 1;
+}
+
+int
+dochdir(char *word)
+{
+ /* report to /dev/wdir if it exists and we're interactive */
+ static int wdirfd = -2;
+ if(chdir(word)<0) return -1;
+ if(flag['i']!=0){
+ if(wdirfd==-2) /* try only once */
+ wdirfd = open("/dev/wdir", OWRITE|OCEXEC);
+ if(wdirfd>=0)
+ write(wdirfd, word, strlen(word));
+ }
+ return 1;
+}
+
+void
+execcd(void)
+{
+ word *a = runq->argv->words;
+ word *cdpath;
+ char *dir;
+
+ setstatus("can't cd");
+ cdpath = vlook("cdpath")->val;
+ switch(count(a)){
+ default:
+ pfmt(err, "Usage: cd [directory]\n");
+ break;
+ case 2:
+ if(a->next->word[0]=='/' || cdpath==0)
+ cdpath = &nullpath;
+ for(; cdpath; cdpath = cdpath->next){
+ if(cdpath->word[0] != '\0')
+ dir = smprint("%s/%s", cdpath->word,
+ a->next->word);
+ else
+ dir = strdup(a->next->word);
+
+ if(dochdir(dir) >= 0){
+ if(cdpath->word[0] != '\0' &&
+ strcmp(cdpath->word, ".") != 0)
+ pfmt(err, "%s\n", dir);
+ free(dir);
+ setstatus("");
+ break;
+ }
+ free(dir);
+ }
+ if(cdpath==0)
+ pfmt(err, "Can't cd %s: %r\n", a->next->word);
+ break;
+ case 1:
+ a = vlook("home")->val;
+ if(count(a)>=1){
+ if(dochdir(a->word)>=0)
+ setstatus("");
+ else
+ pfmt(err, "Can't cd %s: %r\n", a->word);
+ }
+ else
+ pfmt(err, "Can't cd -- $home empty\n");
+ break;
+ }
+ poplist();
+}
+
+void
+execexit(void)
+{
+ switch(count(runq->argv->words)){
+ default:
+ pfmt(err, "Usage: exit [status]\nExiting anyway\n");
+ case 2:
+ setstatus(runq->argv->words->next->word);
+ case 1: Xexit();
+ }
+}
+
+void
+execshift(void)
+{
+ int n;
+ word *a;
+ var *star;
+ switch(count(runq->argv->words)){
+ default:
+ pfmt(err, "Usage: shift [n]\n");
+ setstatus("shift usage");
+ poplist();
+ return;
+ case 2:
+ n = atoi(runq->argv->words->next->word);
+ break;
+ case 1:
+ n = 1;
+ break;
+ }
+ star = vlook("*");
+ for(;n && star->val;--n){
+ a = star->val->next;
+ efree(star->val->word);
+ efree((char *)star->val);
+ star->val = a;
+ star->changed = 1;
+ }
+ setstatus("");
+ poplist();
+}
+
+int
+octal(char *s)
+{
+ int n = 0;
+ while(*s==' ' || *s=='\t' || *s=='\n') s++;
+ while('0'<=*s && *s<='7') n = n*8+*s++-'0';
+ return n;
+}
+
+int
+mapfd(int fd)
+{
+ redir *rp;
+ for(rp = runq->redir;rp;rp = rp->next){
+ switch(rp->type){
+ case RCLOSE:
+ if(rp->from==fd)
+ fd=-1;
+ break;
+ case RDUP:
+ case ROPEN:
+ if(rp->to==fd)
+ fd = rp->from;
+ break;
+ }
+ }
+ return fd;
+}
+union code rdcmds[4];
+
+void
+execcmds(io *f)
+{
+ static int first = 1;
+ if(first){
+ rdcmds[0].i = 1;
+ rdcmds[1].f = Xrdcmds;
+ rdcmds[2].f = Xreturn;
+ first = 0;
+ }
+ start(rdcmds, 1, runq->local);
+ runq->cmdfd = f;
+ runq->iflast = 0;
+}
+
+void
+execeval(void)
+{
+ char *cmdline, *s, *t;
+ int len = 0;
+ word *ap;
+ if(count(runq->argv->words)<=1){
+ Xerror1("Usage: eval cmd ...");
+ return;
+ }
+ eflagok = 1;
+ for(ap = runq->argv->words->next;ap;ap = ap->next)
+ len+=1+strlen(ap->word);
+ cmdline = emalloc(len);
+ s = cmdline;
+ for(ap = runq->argv->words->next;ap;ap = ap->next){
+ for(t = ap->word;*t;) *s++=*t++;
+ *s++=' ';
+ }
+ s[-1]='\n';
+ poplist();
+ execcmds(opencore(cmdline, len));
+ efree(cmdline);
+}
+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;
+
+ if(first){
+ dotcmds[0].i = 1;
+ dotcmds[1].f = Xmark;
+ dotcmds[2].f = Xword;
+ dotcmds[3].s="0";
+ dotcmds[4].f = Xlocal;
+ dotcmds[5].f = Xmark;
+ dotcmds[6].f = Xword;
+ dotcmds[7].s="*";
+ dotcmds[8].f = Xlocal;
+ dotcmds[9].f = Xrdcmds;
+ dotcmds[10].f = Xunlocal;
+ dotcmds[11].f = Xunlocal;
+ dotcmds[12].f = Xreturn;
+ first = 0;
+ }
+ else
+ eflagok = 1;
+ popword();
+ if(p->argv->words && strcmp(p->argv->words->word, "-i")==0){
+ iflag = 1;
+ popword();
+ }
+ /* get input file */
+ if(p->argv->words==0){
+ Xerror1("Usage: . [-i] file [arg ...]");
+ return;
+ }
+ zero = strdup(p->argv->words->word);
+ popword();
+ fd = -1;
+ for(path = searchpath(zero); path; path = path->next){
+ if(path->word[0] != '\0')
+ file = smprint("%s/%s", path->word, zero);
+ else
+ file = strdup(zero);
+
+ fd = open(file, 0);
+ free(file);
+ if(fd >= 0)
+ break;
+ if(strcmp(file, "/dev/stdin")==0){ /* for sun & ucb */
+ fd = Dup1(0);
+ if(fd>=0)
+ break;
+ }
+ }
+ if(fd<0){
+ pfmt(err, "%s: ", zero);
+ setstatus("can't open");
+ Xerror(".: can't open");
+ return;
+ }
+ /* set up for a new command loop */
+ start(dotcmds, 1, (struct var *)0);
+ pushredir(RCLOSE, fd, 0);
+ runq->cmdfile = zero;
+ runq->cmdfd = openfd(fd);
+ runq->iflag = iflag;
+ runq->iflast = 0;
+ /* push $* value */
+ pushlist();
+ runq->argv->words = p->argv->words;
+ /* free caller's copy of $* */
+ av = p->argv;
+ p->argv = av->next;
+ efree((char *)av);
+ /* push $0 value */
+ pushlist();
+ pushword(zero);
+ ndot++;
+}
+
+void
+execflag(void)
+{
+ char *letter, *val;
+ switch(count(runq->argv->words)){
+ case 2:
+ setstatus(flag[(uchar)runq->argv->words->next->word[0]]?"":"flag not set");
+ break;
+ case 3:
+ letter = runq->argv->words->next->word;
+ val = runq->argv->words->next->next->word;
+ if(strlen(letter)==1){
+ if(strcmp(val, "+")==0){
+ flag[(uchar)letter[0]] = flagset;
+ break;
+ }
+ if(strcmp(val, "-")==0){
+ flag[(uchar)letter[0]] = 0;
+ break;
+ }
+ }
+ default:
+ Xerror1("Usage: flag [letter] [+-]");
+ return;
+ }
+ poplist();
+}
+
+void
+execwhatis(void){ /* mildly wrong -- should fork before writing */
+ word *a, *b, *path;
+ var *v;
+ struct builtin *bp;
+ char *file;
+ struct io out[1];
+ int found, sep;
+ a = runq->argv->words->next;
+ if(a==0){
+ Xerror1("Usage: whatis name ...");
+ return;
+ }
+ setstatus("");
+ out->fd = mapfd(1);
+ out->bufp = out->buf;
+ out->ebuf = &out->buf[NBUF];
+ out->strp = 0;
+ for(;a;a = a->next){
+ v = vlook(a->word);
+ if(v->val){
+ pfmt(out, "%s=", a->word);
+ if(v->val->next==0)
+ pfmt(out, "%q\n", v->val->word);
+ else{
+ sep='(';
+ for(b = v->val;b && b->word;b = b->next){
+ pfmt(out, "%c%q", sep, b->word);
+ sep=' ';
+ }
+ pfmt(out, ")\n");
+ }
+ found = 1;
+ }
+ else
+ found = 0;
+ v = gvlook(a->word);
+ if(v->fn)
+ pfmt(out, "fn %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->next){
+ if(path->word[0] != '\0')
+ file = smprint("%s/%s",
+ path->word, a->word);
+ else
+ file = strdup(a->word);
+ if(Executable(file)){
+ pfmt(out, "%s\n", file);
+ free(file);
+ break;
+ }
+ free(file);
+ }
+ if(!path && !found){
+ pfmt(err, "%s: not found\n", a->word);
+ setstatus("not found");
+ }
+ }
+ }
+ }
+ poplist();
+ flush(err);
+}
+
+void
+execwait(void)
+{
+ switch(count(runq->argv->words)){
+ default:
+ Xerror1("Usage: wait [pid]");
+ return;
+ case 2:
+ Waitfor(atoi(runq->argv->words->next->word), 0);
+ break;
+ case 1:
+ Waitfor(-1, 0);
+ break;
+ }
+ poplist();
+}
diff --git a/sys/src/cmd/rc/subr.c b/sys/src/cmd/rc/subr.c
new file mode 100755
index 000000000..48e8cb091
--- /dev/null
+++ b/sys/src/cmd/rc/subr.c
@@ -0,0 +1,77 @@
+#include "rc.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+
+void *
+emalloc(long n)
+{
+ void *p = Malloc(n);
+
+ if(p==0)
+ panic("Can't malloc %d bytes", n);
+/* if(err){ pfmt(err, "malloc %d->%p\n", n, p); flush(err); } /**/
+ return p;
+}
+
+void
+efree(void *p)
+{
+/* pfmt(err, "free %p\n", p); flush(err); /**/
+ if(p)
+ free(p);
+ else pfmt(err, "free 0\n");
+}
+extern int lastword, lastdol;
+
+void
+yyerror(char *m)
+{
+ 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));
+}
+char *bp;
+
+static void
+iacvt(int n)
+{
+ if(n<0){
+ *bp++='-';
+ n=-n; /* doesn't work for n==-inf */
+ }
+ if(n/10)
+ iacvt(n/10);
+ *bp++=n%10+'0';
+}
+
+void
+inttoascii(char *s, long n)
+{
+ bp = s;
+ iacvt(n);
+ *bp='\0';
+}
+
+void
+panic(char *s, int n)
+{
+ pfmt(err, "rc: ");
+ pfmt(err, s, n);
+ pchr(err, '\n');
+ flush(err);
+ Abort();
+}
diff --git a/sys/src/cmd/rc/syn.y b/sys/src/cmd/rc/syn.y
new file mode 100755
index 000000000..c7de35313
--- /dev/null
+++ b/sys/src/cmd/rc/syn.y
@@ -0,0 +1,91 @@
+%term FOR IN WHILE IF NOT TWIDDLE BANG SUBSHELL SWITCH FN
+%term WORD REDIR DUP PIPE SUB
+%term SIMPLE ARGLIST WORDS BRACE PAREN PCMD PIPEFD /* not used in syntax */
+/* operator priorities -- lowest first */
+%left IF WHILE FOR SWITCH ')' NOT
+%left ANDAND OROR
+%left BANG SUBSHELL
+%left PIPE
+%left '^'
+%right '$' COUNT '"'
+%left SUB
+%{
+#include "rc.h"
+#include "fns.h"
+%}
+%union{
+ struct tree *tree;
+};
+%type<tree> line paren brace body cmdsa cmdsan assign epilog redir
+%type<tree> cmd simple first word comword keyword words
+%type<tree> NOT FOR IN WHILE IF TWIDDLE BANG SUBSHELL SWITCH FN
+%type<tree> WORD REDIR DUP PIPE
+%%
+rc: { return 1;}
+| line '\n' {return !compile($1);}
+line: cmd
+| cmdsa line {$$=tree2(';', $1, $2);}
+body: cmd
+| cmdsan body {$$=tree2(';', $1, $2);}
+cmdsa: cmd ';'
+| cmd '&' {$$=tree1('&', $1);}
+cmdsan: cmdsa
+| cmd '\n'
+brace: '{' body '}' {$$=tree1(BRACE, $2);}
+paren: '(' body ')' {$$=tree1(PCMD, $2);}
+assign: first '=' word {$$=tree2('=', $1, $3);}
+epilog: {$$=0;}
+| redir epilog {$$=mung2($1, $1->child[0], $2);}
+redir: REDIR word {$$=mung1($1, $1->rtype==HERE?heredoc($2):$2);}
+| DUP
+cmd: {$$=0;}
+| brace epilog {$$=epimung($1, $2);}
+| IF paren {skipnl();} cmd
+ {$$=mung2($1, $2, $4);}
+| IF NOT {skipnl();} cmd {$$=mung1($2, $4);}
+| FOR '(' word IN words ')' {skipnl();} cmd
+ /*
+ * if ``words'' is nil, we need a tree element to distinguish between
+ * for(i in ) and for(i), the former being a loop over the empty set
+ * and the latter being the implicit argument loop. so if $5 is nil
+ * (the empty set), we represent it as "()". don't parenthesize non-nil
+ * functions, to avoid growing parentheses every time we reread the
+ * definition.
+ */
+ {$$=mung3($1, $3, $5 ? $5 : tree1(PAREN, $5), $8);}
+| FOR '(' word ')' {skipnl();} cmd
+ {$$=mung3($1, $3, (struct tree *)0, $6);}
+| WHILE paren {skipnl();} cmd
+ {$$=mung2($1, $2, $4);}
+| SWITCH word {skipnl();} brace
+ {$$=tree2(SWITCH, $2, $4);}
+| simple {$$=simplemung($1);}
+| TWIDDLE word words {$$=mung2($1, $2, $3);}
+| cmd ANDAND cmd {$$=tree2(ANDAND, $1, $3);}
+| cmd OROR cmd {$$=tree2(OROR, $1, $3);}
+| cmd PIPE cmd {$$=mung2($2, $1, $3);}
+| redir cmd %prec BANG {$$=mung2($1, $1->child[0], $2);}
+| assign cmd %prec BANG {$$=mung3($1, $1->child[0], $1->child[1], $2);}
+| BANG cmd {$$=mung1($1, $2);}
+| SUBSHELL cmd {$$=mung1($1, $2);}
+| FN words brace {$$=tree2(FN, $2, $3);}
+| FN words {$$=tree1(FN, $2);}
+simple: first
+| simple word {$$=tree2(ARGLIST, $1, $2);}
+| simple redir {$$=tree2(ARGLIST, $1, $2);}
+first: comword
+| first '^' word {$$=tree2('^', $1, $3);}
+word: keyword {lastword=1; $1->type=WORD;}
+| comword
+| word '^' word {$$=tree2('^', $1, $3);}
+comword: '$' word {$$=tree1('$', $2);}
+| '$' word SUB words ')' {$$=tree2(SUB, $2, $4);}
+| '"' word {$$=tree1('"', $2);}
+| COUNT word {$$=tree1(COUNT, $2);}
+| WORD
+| '`' brace {$$=tree1('`', $2);}
+| '(' words ')' {$$=tree1(PAREN, $2);}
+| REDIR brace {$$=mung1($1, $2); $$->type=PIPEFD;}
+keyword: FOR|IN|WHILE|IF|NOT|TWIDDLE|BANG|SUBSHELL|SWITCH|FN
+words: {$$=(struct tree*)0;}
+| words word {$$=tree2(WORDS, $1, $2);}
diff --git a/sys/src/cmd/rc/trap.c b/sys/src/cmd/rc/trap.c
new file mode 100755
index 000000000..a572cac37
--- /dev/null
+++ b/sys/src/cmd/rc/trap.c
@@ -0,0 +1,37 @@
+#include "rc.h"
+#include "exec.h"
+#include "fns.h"
+#include "io.h"
+extern char *Signame[];
+
+void
+dotrap(void)
+{
+ int i;
+ struct var *trapreq;
+ struct word *starval;
+ starval = vlook("*")->val;
+ while(ntrap) for(i = 0;i!=NSIG;i++) while(trap[i]){
+ --trap[i];
+ --ntrap;
+ if(getpid()!=mypid) Exit(getstatus());
+ trapreq = vlook(Signame[i]);
+ if(trapreq->fn){
+ start(trapreq->fn, trapreq->pc, (struct var *)0);
+ runq->local = newvar(strdup("*"), runq->local);
+ runq->local->val = copywords(starval, (struct word *)0);
+ runq->local->changed = 1;
+ runq->redir = runq->startredir = 0;
+ }
+ else if(i==SIGINT || i==SIGQUIT){
+ /*
+ * run the stack down until we uncover the
+ * command reading loop. Xreturn will exit
+ * if there is none (i.e. if this is not
+ * an interactive rc.)
+ */
+ while(!runq->iflag) Xreturn();
+ }
+ else Exit(getstatus());
+ }
+}
diff --git a/sys/src/cmd/rc/tree.c b/sys/src/cmd/rc/tree.c
new file mode 100755
index 000000000..a2cd1af69
--- /dev/null
+++ b/sys/src/cmd/rc/tree.c
@@ -0,0 +1,148 @@
+#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.
+ */
+
+tree*
+newtree(void)
+{
+ tree *t = new(tree);
+ t->iskw = 0;
+ t->str = 0;
+ t->child[0] = t->child[1] = t->child[2] = 0;
+ t->next = treenodes;
+ treenodes = t;
+ return t;
+}
+
+void
+freenodes(void)
+{
+ tree *t, *u;
+ for(t = treenodes;t;t = u){
+ u = t->next;
+ if(t->str)
+ efree(t->str);
+ efree((char *)t);
+ }
+ treenodes = 0;
+}
+
+tree*
+tree1(int type, tree *c0)
+{
+ return tree3(type, c0, (tree *)0, (tree *)0);
+}
+
+tree*
+tree2(int type, tree *c0, tree *c1)
+{
+ return tree3(type, c0, c1, (tree *)0);
+}
+
+tree*
+tree3(int type, tree *c0, tree *c1, tree *c2)
+{
+ tree *t;
+ if(type==';'){
+ if(c0==0)
+ return c1;
+ if(c1==0)
+ return c0;
+ }
+ t = newtree();
+ t->type = type;
+ t->child[0] = c0;
+ t->child[1] = c1;
+ t->child[2] = c2;
+ return t;
+}
+
+tree*
+mung1(tree *t, tree *c0)
+{
+ t->child[0] = c0;
+ return t;
+}
+
+tree*
+mung2(tree *t, tree *c0, tree *c1)
+{
+ t->child[0] = c0;
+ t->child[1] = c1;
+ return t;
+}
+
+tree*
+mung3(tree *t, tree *c0, tree *c1, tree *c2)
+{
+ t->child[0] = c0;
+ t->child[1] = c1;
+ t->child[2] = c2;
+ return t;
+}
+
+tree*
+epimung(tree *comp, tree *epi)
+{
+ tree *p;
+ if(epi==0)
+ return comp;
+ for(p = epi;p->child[1];p = p->child[1]);
+ p->child[1] = comp;
+ return epi;
+}
+/*
+ * Add a SIMPLE node at the root of t and percolate all the redirections
+ * up to the root.
+ */
+
+tree*
+simplemung(tree *t)
+{
+ tree *u;
+ struct io *s;
+
+ t = tree1(SIMPLE, t);
+ s = openstr();
+ pfmt(s, "%t", t);
+ t->str = strdup((char *)s->strp);
+ closeio(s);
+ for(u = t->child[0];u->type==ARGLIST;u = u->child[0]){
+ if(u->child[1]->type==DUP
+ || u->child[1]->type==REDIR){
+ u->child[1]->child[1] = t;
+ t = u->child[1];
+ u->child[1] = 0;
+ }
+ }
+ return t;
+}
+
+tree*
+token(char *str, int type)
+{
+ tree *t = newtree();
+
+ t->type = type;
+ t->str = strdup(str);
+ return t;
+}
+
+void
+freetree(tree *p)
+{
+ if(p==0)
+ return;
+ freetree(p->child[0]);
+ freetree(p->child[1]);
+ freetree(p->child[2]);
+ if(p->str)
+ efree(p->str);
+ efree((char *)p);
+}
diff --git a/sys/src/cmd/rc/unix.c b/sys/src/cmd/rc/unix.c
new file mode 100755
index 000000000..3be938148
--- /dev/null
+++ b/sys/src/cmd/rc/unix.c
@@ -0,0 +1,469 @@
+/*
+ * Unix versions of system-specific functions
+ * By convention, exported routines herein have names beginning with an
+ * upper case letter.
+ */
+#include "rc.h"
+#include "exec.h"
+#include <errno.h>
+char Rcmain[]="/usr/lib/rcmain";
+char Fdprefix[]="/dev/fd/";
+int execumask(), execfinit();
+struct builtin Builtin[] = {
+ "cd", execcd,
+ "whatis", execwhatis,
+ "eval", execeval,
+ "exec", execexec, /* but with popword first */
+ "exit", execexit,
+ "shift", execshift,
+ "wait", execwait,
+ "umask", execumask,
+ ".", execdot,
+ "finit", execfinit,
+ "flag", execflag,
+ 0
+};
+#define SEP '\1'
+char **environp;
+
+struct word*
+enval(s)
+register char *s;
+{
+ char *t, c;
+ struct word *v;
+ for(t = s;*t && *t!=SEP;t++);
+ c=*t;
+ *t='\0';
+ v = newword(s, c=='\0'?(struct word *)0:enval(t+1));
+ *t = c;
+ return v;
+}
+Vinit(){
+ extern char **environ;
+ char *s;
+ char **env = environ;
+ environp = env;
+ for(;*env;env++){
+ for(s=*env;*s && *s!='(' && *s!='=';s++);
+ switch(*s){
+ case '\0':
+ pfmt(err, "environment %q?\n", *env);
+ break;
+ case '=':
+ *s='\0';
+ setvar(*env, enval(s+1));
+ *s='=';
+ break;
+ case '(': /* ignore functions for now */
+ break;
+ }
+ }
+}
+char **envp;
+Xrdfn(){
+ char *s;
+ int len;
+ for(;*envp;envp++){
+ for(s=*envp;*s && *s!='(' && *s!='=';s++);
+ switch(*s){
+ case '\0':
+ pfmt(err, "environment %q?\n", *envp);
+ break;
+ case '=': /* ignore variables */
+ break;
+ case '(': /* Bourne again */
+ s=*envp+3;
+ envp++;
+ len = strlen(s);
+ s[len]='\n';
+ execcmds(opencore(s, len+1));
+ s[len]='\0';
+ return;
+ }
+ }
+ Xreturn();
+}
+union code rdfns[4];
+execfinit(){
+ static int first = 1;
+ if(first){
+ rdfns[0].i = 1;
+ rdfns[1].f = Xrdfn;
+ rdfns[2].f = Xjump;
+ rdfns[3].i = 1;
+ first = 0;
+ }
+ Xpopm();
+ envp = environp;
+ start(rdfns, 1, runq->local);
+}
+cmpenv(a, b)
+char **a, **b;
+{
+ return strcmp(*a, *b);
+}
+
+char*
+*mkenv()
+{
+ char **env, **ep, *p, *q;
+ struct var **h, *v;
+ struct word *a;
+ int nvar = 0, nchr = 0, sep;
+ /*
+ * Slightly kludgy loops look at locals then globals
+ */
+ for(h = var-1;h!=&var[NVAR];h++) for(v = h>=var?*h:runq->local;v;v = v->next){
+ if((v==vlook(v->name)) && v->val){
+ nvar++;
+ nchr+=strlen(v->name)+1;
+ for(a = v->val;a;a = a->next)
+ nchr+=strlen(a->word)+1;
+ }
+ if(v->fn){
+ nvar++;
+ nchr+=strlen(v->name)+strlen(v->fn[v->pc-1].s)+8;
+ }
+ }
+ env = (char **)emalloc((nvar+1)*sizeof(char *)+nchr);
+ ep = env;
+ p = (char *)&env[nvar+1];
+ for(h = var-1;h!=&var[NVAR];h++) for(v = h>=var?*h:runq->local;v;v = v->next){
+ if((v==vlook(v->name)) && v->val){
+ *ep++=p;
+ q = v->name;
+ while(*q) *p++=*q++;
+ sep='=';
+ for(a = v->val;a;a = a->next){
+ *p++=sep;
+ sep = SEP;
+ q = a->word;
+ while(*q) *p++=*q++;
+ }
+ *p++='\0';
+ }
+ if(v->fn){
+ *ep++=p;
+ *p++='#'; *p++='('; *p++=')'; /* to fool Bourne */
+ *p++='f'; *p++='n'; *p++=' ';
+ q = v->name;
+ while(*q) *p++=*q++;
+ *p++=' ';
+ q = v->fn[v->pc-1].s;
+ while(*q) *p++=*q++;
+ *p++='\0';
+ }
+ }
+ *ep = 0;
+ qsort((char *)env, nvar, sizeof ep[0], cmpenv);
+ return env;
+}
+char *sigmsg[] = {
+/* 0 normal */ 0,
+/* 1 SIGHUP */ "Hangup",
+/* 2 SIGINT */ 0,
+/* 3 SIGQUIT */ "Quit",
+/* 4 SIGILL */ "Illegal instruction",
+/* 5 SIGTRAP */ "Trace/BPT trap",
+/* 6 SIGIOT */ "abort",
+/* 7 SIGEMT */ "EMT trap",
+/* 8 SIGFPE */ "Floating exception",
+/* 9 SIGKILL */ "Killed",
+/* 10 SIGBUS */ "Bus error",
+/* 11 SIGSEGV */ "Memory fault",
+/* 12 SIGSYS */ "Bad system call",
+/* 13 SIGPIPE */ 0,
+/* 14 SIGALRM */ "Alarm call",
+/* 15 SIGTERM */ "Terminated",
+/* 16 unused */ "signal 16",
+/* 17 SIGSTOP */ "Process stopped",
+/* 18 unused */ "signal 18",
+/* 19 SIGCONT */ "Process continued",
+/* 20 SIGCHLD */ "Child death",
+};
+Waitfor(pid, persist){
+ int wpid, sig;
+ struct thread *p;
+ int wstat;
+ char wstatstr[12];
+ for(;;){
+ errno = 0;
+ wpid = wait(&wstat);
+ if(errno==EINTR && persist)
+ continue;
+ if(wpid==-1)
+ break;
+ sig = wstat&0177;
+ if(sig==0177){
+ pfmt(err, "trace: ");
+ sig = (wstat>>8)&0177;
+ }
+ if(sig>(sizeof sigmsg/sizeof sigmsg[0]) || sigmsg[sig]){
+ if(pid!=wpid)
+ pfmt(err, "%d: ", wpid);
+ if(sig<=(sizeof sigmsg/sizeof sigmsg[0]))
+ pfmt(err, "%s", sigmsg[sig]);
+ else if(sig==0177) pfmt(err, "stopped by ptrace");
+ else pfmt(err, "signal %d", sig);
+ if(wstat&0200)pfmt(err, " -- core dumped");
+ pfmt(err, "\n");
+ }
+ wstat = sig?sig+1000:(wstat>>8)&0xFF;
+ if(wpid==pid){
+ inttoascii(wstatstr, wstat);
+ setstatus(wstatstr);
+ break;
+ }
+ else{
+ for(p = runq->ret;p;p = p->ret)
+ if(p->pid==wpid){
+ p->pid=-1;
+ inttoascii(p->status, wstat);
+ break;
+ }
+ }
+ }
+}
+
+char*
+*mkargv(a)
+register struct 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;
+}
+Updenv(){}
+Execute(args, path)
+register struct word *args, *path;
+{
+ char *msg="not found";
+ int txtbusy = 0;
+ char **env = mkenv();
+ char **argv = mkargv(args);
+ char file[512];
+ for(;path;path = path->next){
+ strcpy(file, path->word);
+ if(file[0])
+ strcat(file, "/");
+ strcat(file, argv[1]);
+ ReExec:
+ execve(file, argv+1, env);
+ switch(errno){
+ case ENOEXEC:
+ pfmt(err, "%s: Bourne again\n", argv[1]);
+ argv[0]="sh";
+ argv[1] = strdup(file);
+ execve("/bin/sh", argv, env);
+ goto Bad;
+ case ETXTBSY:
+ if(++txtbusy!=5){
+ sleep(txtbusy);
+ goto ReExec;
+ }
+ msg="text busy"; goto Bad;
+ case EACCES:
+ msg="no access";
+ break;
+ case ENOMEM:
+ msg="not enough memory"; goto Bad;
+ case E2BIG:
+ msg="too big"; goto Bad;
+ }
+ }
+Bad:
+ pfmt(err, "%s: %s\n", argv[1], msg);
+ efree((char *)env);
+ efree((char *)argv);
+}
+#define NDIR 14 /* should get this from param.h */
+Globsize(p)
+register char *p;
+{
+ 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;
+}
+#include <sys/types.h>
+#include <ndir.h>
+#define NDIRLIST 50
+DIR *dirlist[NDIRLIST];
+Opendir(name)
+char *name;
+{
+ DIR **dp;
+ for(dp = dirlist;dp!=&dirlist[NDIRLIST];dp++)
+ if(*dp==0){
+ *dp = opendir(name);
+ return *dp?dp-dirlist:-1;
+ }
+ return -1;
+}
+Readdir(f, p, onlydirs)
+int f;
+void *p;
+int onlydirs; /* ignored, just advisory */
+{
+ struct direct *dp = readdir(dirlist[f]);
+ if(dp==0)
+ return 0;
+ strcpy(p, dp->d_name);
+ return 1;
+}
+Closedir(f){
+ closedir(dirlist[f]);
+ dirlist[f] = 0;
+}
+char *Signame[] = {
+ "sigexit", "sighup", "sigint", "sigquit",
+ "sigill", "sigtrap", "sigiot", "sigemt",
+ "sigfpe", "sigkill", "sigbus", "sigsegv",
+ "sigsys", "sigpipe", "sigalrm", "sigterm",
+ "sig16", "sigstop", "sigtstp", "sigcont",
+ "sigchld", "sigttin", "sigttou", "sigtint",
+ "sigxcpu", "sigxfsz", "sig26", "sig27",
+ "sig28", "sig29", "sig30", "sig31",
+ 0,
+};
+
+int
+gettrap(sig)
+{
+ signal(sig, gettrap);
+ trap[sig]++;
+ ntrap++;
+ if(ntrap>=NSIG){
+ pfmt(err, "rc: Too many traps (trap %d), dumping core\n", sig);
+ signal(SIGIOT, (int (*)())0);
+ kill(getpid(), SIGIOT);
+ }
+}
+Trapinit(){
+ int i;
+ int (*sig)();
+ if(1 || flag['d']){ /* wrong!!! */
+ sig = signal(SIGINT, gettrap);
+ if(sig==SIG_IGN)
+ signal(SIGINT, SIG_IGN);
+ }
+ else{
+ for(i = 1;i<=NSIG;i++) if(i!=SIGCHLD){
+ sig = signal(i, gettrap);
+ if(sig==SIG_IGN)
+ signal(i, SIG_IGN);
+ }
+ }
+}
+Unlink(name)
+char *name;
+{
+ return unlink(name);
+}
+Write(fd, buf, cnt)
+void *buf;
+{
+ return write(fd, buf, cnt);
+}
+Read(fd, buf, cnt)
+void *buf;
+{
+ return read(fd, buf, cnt);
+}
+Seek(fd, cnt, whence)
+long cnt;
+{
+ return lseek(fd, cnt, whence);
+}
+Executable(file)
+char *file;
+{
+ return(access(file, 01)==0);
+}
+Creat(file)
+char *file;
+{
+ return creat(file, 0666);
+}
+Dup(a, b){
+ return dup2(a, b);
+}
+Dup1(a){
+ return dup(a);
+}
+/*
+ * Wrong: should go through components of a|b|c and return the maximum.
+ */
+Exit(stat)
+register char *stat;
+{
+ int n = 0;
+ while(*stat){
+ if(*stat!='|'){
+ if(*stat<'0' || '9'<*stat)
+ exit(1);
+ else n = n*10+*stat-'0';
+ }
+ stat++;
+ }
+ exit(n);
+}
+Eintr(){
+ return errno==EINTR;
+}
+Noerror(){
+ errno = 0;
+}
+Isatty(fd){
+ return isatty(fd);
+}
+Abort(){
+ abort();
+}
+execumask(){ /* wrong -- should fork before writing */
+ int m;
+ struct io out[1];
+ switch(count(runq->argv->words)){
+ default:
+ pfmt(err, "Usage: umask [umask]\n");
+ setstatus("umask usage");
+ poplist();
+ return;
+ case 2:
+ umask(octal(runq->argv->words->next->word));
+ break;
+ case 1:
+ umask(m = umask(0));
+ out->fd = mapfd(1);
+ out->bufp = out->buf;
+ out->ebuf=&out->buf[NBUF];
+ out->strp = 0;
+ pfmt(out, "%o\n", m);
+ break;
+ }
+ setstatus("");
+ poplist();
+}
+Memcpy(a, b, n)
+void *a, *b;
+long n;
+{
+ memmove(a, b, n);
+}
+
+void*
+Malloc(n)
+{
+ return (void *)malloc(n);
+}
diff --git a/sys/src/cmd/rc/var.c b/sys/src/cmd/rc/var.c
new file mode 100755
index 000000000..a96af06d6
--- /dev/null
+++ b/sys/src/cmd/rc/var.c
@@ -0,0 +1,86 @@
+#include "rc.h"
+#include "exec.h"
+#include "fns.h"
+
+int
+hash(char *s, int n)
+{
+ int h = 0, i = 1;
+ while(*s) h+=*s++*i++;
+ h%=n;
+ return h<0?h+n:h;
+}
+#define NKW 30
+struct kw{
+ char *name;
+ int type;
+ struct kw *next;
+}*kw[NKW];
+
+void
+kenter(int type, char *name)
+{
+ int h = hash(name, NKW);
+ struct kw *p = new(struct kw);
+ p->type = type;
+ p->name = name;
+ p->next = kw[h];
+ kw[h] = p;
+}
+
+void
+kinit(void)
+{
+ kenter(FOR, "for");
+ kenter(IN, "in");
+ kenter(WHILE, "while");
+ kenter(IF, "if");
+ kenter(NOT, "not");
+ kenter(TWIDDLE, "~");
+ kenter(BANG, "!");
+ kenter(SUBSHELL, "@");
+ kenter(SWITCH, "switch");
+ kenter(FN, "fn");
+}
+
+tree*
+klook(char *name)
+{
+ struct kw *p;
+ tree *t = token(name, WORD);
+ for(p = kw[hash(name, NKW)];p;p = p->next)
+ if(strcmp(p->name, name)==0){
+ t->type = p->type;
+ t->iskw = 1;
+ break;
+ }
+ return t;
+}
+
+var*
+gvlook(char *name)
+{
+ int h = hash(name, NVAR);
+ var *v;
+ for(v = gvar[h];v;v = v->next) if(strcmp(v->name, name)==0) return v;
+ return gvar[h] = newvar(strdup(name), gvar[h]);
+}
+
+var*
+vlook(char *name)
+{
+ var *v;
+ if(runq)
+ for(v = runq->local;v;v = v->next)
+ if(strcmp(v->name, name)==0) return v;
+ return gvlook(name);
+}
+
+void
+setvar(char *name, word *val)
+{
+ struct var *v = vlook(name);
+ freewords(v->val);
+ v->val = val;
+ v->changed = 1;
+}
diff --git a/sys/src/cmd/rc/win32.c b/sys/src/cmd/rc/win32.c
new file mode 100755
index 000000000..b7fcece61
--- /dev/null
+++ b/sys/src/cmd/rc/win32.c
@@ -0,0 +1,560 @@
+/*
+ * Plan 9 versions of system-specific functions
+ * By convention, exported routines herein have names beginning with an
+ * upper case letter.
+ */
+#include "rc.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+#include "getflags.h"
+char *Signame[] = {
+ "sigexit", "sighup", "sigint", "sigquit",
+ "sigalrm", "sigkill", "sigfpe", "sigterm",
+ 0
+};
+char *syssigname[] = {
+ "exit", /* can't happen */
+ "hangup",
+ "interrupt",
+ "quit", /* can't happen */
+ "alarm",
+ "kill",
+ "sys: fp: ",
+ "term",
+ 0
+};
+char Rcmain[]="/rc/lib/rcmain";
+char Fdprefix[]="/fd/";
+void execfinit(void);
+void execbind(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,
+ 0
+};
+
+void
+Vinit(void)
+{
+ int dir, f, len;
+ word *val;
+ char *buf, *s;
+ Dir *ent;
+ int i, nent;
+ char envname[256];
+ dir = open("/env", OREAD);
+ if(dir<0){
+ pfmt(err, "rc: can't open /env: %r\n");
+ return;
+ }
+ ent = nil;
+ for(;;){
+ nent = dirread(dir, &ent);
+ if(nent <= 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((int)len+1);
+ read(f, buf, (long)len);
+ 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);
+ efree(buf);
+ }
+ }
+ }
+ free(ent);
+ }
+ close(dir);
+}
+int envdir;
+
+void
+Xrdfn(void)
+{
+ int f, len;
+ static Dir *ent, *allocent;
+ static int nent;
+ Dir *e;
+ char envname[256];
+
+ for(;;){
+ if(nent == 0){
+ free(allocent);
+ nent = dirread(envdir, &allocent);
+ ent = allocent;
+ }
+ if(nent <= 0)
+ break;
+ while(nent){
+ e = ent++;
+ nent--;
+ len = e->length;
+ if(len && strncmp(e->name, "fn#", 3)==0){
+ snprint(envname, sizeof envname, "/env/%s", e->name);
+ if((f = open(envname, 0))>=0){
+ execcmds(openfd(f));
+ return;
+ }
+ }
+ }
+ }
+ close(envdir);
+ Xreturn();
+}
+union code rdfns[4];
+
+void
+execfinit(void)
+{
+ static int first = 1;
+ if(first){
+ rdfns[0].i = 1;
+ rdfns[1].f = Xrdfn;
+ rdfns[2].f = Xjump;
+ rdfns[3].i = 1;
+ first = 0;
+ }
+ Xpopm();
+ envdir = open("/env", 0);
+ if(envdir<0){
+ pfmt(err, "rc: can't open /env: %r\n");
+ return;
+ }
+ start(rdfns, 1, runq->local);
+}
+
+int
+Waitfor(int pid, int persist)
+{
+ thread *p;
+ Waitmsg *w;
+ char errbuf[ERRMAX];
+
+ while((w = wait()) != nil){
+ if(w->pid==pid){
+ setstatus(w->msg);
+ free(w);
+ return 0;
+ }
+ for(p = runq->ret;p;p = p->ret)
+ if(p->pid==w->pid){
+ p->pid=-1;
+ strcpy(p->status, w->msg);
+ }
+ free(w);
+ }
+
+ errstr(errbuf, sizeof errbuf);
+ if(strcmp(errbuf, "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
+addenv(var *v)
+{
+ char envname[256];
+ word *w;
+ int f;
+ io *fd;
+ 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);
+ else{
+ for(w = v->val;w;w = w->next)
+ write(f, w->word, strlen(w->word)+1L);
+ close(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);
+ else{
+ if(v->fn){
+ fd = openfd(f);
+ pfmt(fd, "fn %q %s\n", v->name, v->fn[v->pc-1].s);
+ closeio(fd);
+ }
+ close(f);
+ }
+ }
+}
+
+void
+updenvlocal(var *v)
+{
+ if(v){
+ updenvlocal(v->next);
+ addenv(v);
+ }
+}
+
+void
+Updenv(void)
+{
+ var *v, **h;
+ for(h = gvar;h!=&gvar[NVAR];h++)
+ for(v=*h;v;v = v->next)
+ addenv(v);
+ if(runq)
+ updenvlocal(runq->local);
+}
+
+int
+ForkExecute(char *file, char **argv, int sin, int sout, int serr)
+{
+ int pid;
+
+{int i;
+fprint(2, "forkexec %s", file);
+for(i = 0; argv[i]; i++)fprint(2, " %s", argv[i]);
+fprint(2, " %d %d %d\n", sin, sout, serr);
+}
+ if(access(file, 1) != 0)
+ return -1;
+fprint(2, "forking\n");
+ switch(pid = fork()){
+ case -1:
+ return -1;
+ case 0:
+ if(sin >= 0)
+ dup(sin, 0);
+ else
+ close(0);
+ if(sout >= 0)
+ dup(sout, 1);
+ else
+ close(1);
+ if(serr >= 0)
+ dup(serr, 2);
+ else
+ close(2);
+fprint(2, "execing\n");
+ exec(file, argv);
+fprint(2, "exec: %r\n");
+ exits(file);
+ }
+ return pid;
+}
+
+void
+Execute(word *args, word *path)
+{
+ char **argv = mkargv(args);
+ char file[1024];
+ int nc;
+ Updenv();
+ for(;path;path = path->next){
+ nc = strlen(path->word);
+ if(nc<1024){
+ strcpy(file, path->word);
+ if(file[0]){
+ strcat(file, "/");
+ nc++;
+ }
+ if(nc+strlen(argv[1])<1024){
+ strcat(file, argv[1]);
+ exec(file, argv+1);
+ }
+ else werrstr("command name too long");
+ }
+ }
+ rerrstr(file, sizeof file);
+ pfmt(err, "%s: %s\n", argv[1], file);
+ efree((char *)argv);
+}
+#define NDIR 256 /* shoud be a better way */
+
+int
+Globsize(char *p)
+{
+ 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;
+}
+#define NFD 50
+#define NDBUF 32
+struct{
+ Dir *dbuf;
+ int i;
+ int n;
+}dir[NFD];
+
+int
+Opendir(char *name)
+{
+ Dir *db;
+ int f;
+ f = open(name, 0);
+ if(f==-1)
+ return f;
+ db = dirfstat(f);
+ if(db!=nil && (db->mode&DMDIR)){
+ if(f<NFD){
+ dir[f].i = 0;
+ dir[f].n = 0;
+ }
+ free(db);
+ return f;
+ }
+ free(db);
+ close(f);
+ return -1;
+}
+
+static int
+trimdirs(Dir *d, int nd)
+{
+ int r, w;
+
+ for(r=w=0; r<nd; r++)
+ if(d[r].mode&DMDIR)
+ d[w++] = d[r];
+ return w;
+}
+
+/*
+ * onlydirs is advisory -- it means you only
+ * need to return the directories. it's okay to
+ * return files too (e.g., on unix where you can't
+ * tell during the readdir), but that just makes
+ * the globber work harder.
+ */
+int
+Readdir(int f, void *p, int onlydirs)
+{
+ 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(n>0){
+ if(onlydirs){
+ n = trimdirs(dir[f].dbuf, n);
+ if(n == 0)
+ goto Again;
+ }
+ dir[f].n = n;
+ }else
+ dir[f].n = 0;
+ dir[f].i = 0;
+ }
+ if(dir[f].i == dir[f].n)
+ return 0;
+ strcpy(p, dir[f].dbuf[dir[f].i].name);
+ dir[f].i++;
+ return 1;
+}
+
+void
+Closedir(int f)
+{
+ if(f>=0 && f<NFD){
+ free(dir[f].dbuf);
+ dir[f].i = 0;
+ dir[f].n = 0;
+ dir[f].dbuf = 0;
+ }
+ close(f);
+}
+int interrupted = 0;
+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:
+ if(strcmp(s, "interrupt")!=0 || trap[i]==0){
+ 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);
+}
+
+void
+Trapinit(void)
+{
+ notify(notifyf);
+}
+
+void
+Unlink(char *name)
+{
+ remove(name);
+}
+
+long
+Write(int fd, void *buf, long cnt)
+{
+ return write(fd, buf, (long)cnt);
+}
+
+long
+Read(int fd, void *buf, long cnt)
+{
+ return read(fd, buf, cnt);
+}
+
+long
+Seek(int fd, long cnt, long whence)
+{
+ return seek(fd, cnt, whence);
+}
+
+int
+Executable(char *file)
+{
+ Dir *statbuf;
+ int ret;
+
+ statbuf = dirstat(file);
+ if(statbuf == nil)
+ return 0;
+ ret = ((statbuf->mode&0111)!=0 && (statbuf->mode&DMDIR)==0);
+ free(statbuf);
+ return ret;
+}
+
+int
+Creat(char *file)
+{
+ return create(file, 1, 0666L);
+}
+
+int
+Dup(int a, int b)
+{
+ return dup(a, b);
+}
+
+int
+Dup1(int)
+{
+ return -1;
+}
+
+void
+Exit(char *stat)
+{
+ Updenv();
+ setstatus(stat);
+ exits(truestatus()?"":getstatus());
+}
+
+int
+Eintr(void)
+{
+ return interrupted;
+}
+
+void
+Noerror(void)
+{
+ interrupted = 0;
+}
+
+int
+Isatty(int fd)
+{
+ Dir *d1, *d2;
+ int ret;
+
+ d1 = dirfstat(fd);
+ if(d1 == nil)
+ return 0;
+ if(strncmp(d1->name, "ptty", 4)==0){ /* fwd complaints to philw */
+ free(d1);
+ return 1;
+ }
+ d2 = dirstat("/dev/cons");
+ if(d2 == nil){
+ free(d1);
+ return 0;
+ }
+ ret = (d1->type==d2->type&&d1->dev==d2->dev&&d1->qid.path==d2->qid.path);
+ free(d1);
+ free(d2);
+ return ret;
+}
+
+void
+Abort(void)
+{
+ pfmt(err, "aborting\n");
+ flush(err);
+ Exit("aborting");
+}
+
+void
+Memcpy(void *a, void *b, long n)
+{
+ memmove(a, b, n);
+}
+
+void*
+Malloc(ulong n)
+{
+ return malloc(n);
+}