#include <u.h>
#include <signal.h>
#if defined(PLAN9PORT) && defined(__sun__)
#	define BSD_COMP	/* sigh.  for TIOCNOTTY */
#endif
#include <sys/ioctl.h>
#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->pid=-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;
	v->changefn = 0;
	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
 */
int
main(int argc, char *argv[])
{
	code bootstrap[32];
	char num[12], *rcmain;
	int i;
	
	/* needed for rcmain later */
	putenv("PLAN9", unsharp("#9"));

	argc=getflags(argc, argv, "srdiIlxepvVc: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();
	itoa(num, mypid=getpid());
	pathinit();
	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
 * 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 Xasync(void){
	int null=open("/dev/null", 0);
	int tty;
	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:
		/*
		 * I don't know what the right thing to do here is,
		 * so this is all experimentally determined.
		 * If we just dup /dev/null onto 0, then running
		 * ssh foo & will reopen /dev/tty, try to read a password,
		 * get a signal, and repeat, in a tight loop, forever.
		 * Arguably this is a bug in ssh (it behaves the same
		 * way under bash as under rc) but I'm fixing it here 
		 * anyway.  If we dissociate the process from the tty,
		 * then it won't be able to open /dev/tty ever again.
		 * The SIG_IGN on SIGTTOU makes writing the tty
		 * (via fd 1 or 2, for example) succeed even though 
		 * our pgrp is not the terminal's controlling pgrp.
		 */
		if((tty=open("/dev/tty", OREAD)) >= 0){
			/*
			 * Should make reads of tty fail, writes succeed.
			 */
			signal(SIGTTIN, SIG_IGN);
			signal(SIGTTOU, SIG_IGN);
			ioctl(tty, TIOCNOTTY);
			close(tty);
		}
		if(isatty(0))
			pushredir(ROPEN, null, 0);
		else
			close(null);
		start(runq->code, runq->pc+1, runq->local);
		runq->ret=0;
		break;
	default:
		close(null);
		runq->pc=runq->code[runq->pc].i;
		itoa(npid, pid);
		setvar("apid", newword(npid, (word *)0));
		break;
	}
}
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 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, int c){
	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++=c;
	}
	if(s==value) *s='\0';
	else s[-1]='\0';
	return value;
}
char *list2str(word *words){
	return _list2str(words, ' ');
}
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(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;
	if(v->changefn)
		v->changefn(v);
	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 *subwords(word *val, int len, word *sub, word *a)
{
	int n;
	char *s;
	if(!sub) return a;
	a=subwords(val, len, sub->next, a);
	s=sub->word;
	deglob(s);
	n=0;
	while('0'<=*s && *s<='9') n=n*10+ *s++ -'0';
	if(n<1 || len<n) return a;
	for(;n!=1;--n) val=val->next;
	return newword(val->word, a);
}
void 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;
		itoa(num, count(a));
	}
	else{
		a=vlook("*")->val;
		itoa(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);
	runq->local->val=copywords(runq->argv->next->words, (word *)0);
	runq->local->changed=1;
	poplist();
	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;
	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();
}
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:
		start(p->code, pc+2, runq->local);
		runq->ret=0;
		close(pfd[PRD]);
		pushredir(ROPEN, pfd[PWR], lfd);
		break;
	default:
		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;
	}
}
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 Xbackq(void){
	char wd[8193];
	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;
	}
	switch(pid=fork()){
	case -1: Xerror("try again");
		close(pfd[PRD]);
		close(pfd[PWR]);
		return;
	case 0:
		close(pfd[PRD]);
		start(runq->code, runq->pc+1, runq->local);
		pushredir(ROPEN, pfd[PWR], 1);
		return;
	default:
		close(pfd[PWR]);
		f=openfd(pfd[PRD]);
		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, 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;
	}
}
/*
 * Who should wait for the exit from the fork?
 */
void Xpipefd(void){
	struct thread *p=runq;
	int pc=p->pc;
	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(fork()){
	case -1:
		Xerror("try again");
		break;
	case 0:
		start(p->code, pc+2, runq->local);
		close(mainfd);
		pushredir(ROPEN, sidefd, p->code[pc].i==READ?1:0);
		runq->ret=0;
		break;
	default:
		close(sidefd);
		pushredir(ROPEN, mainfd, mainfd);	/* isn't this a noop? */
		strcpy(name, Fdprefix);
		itoa(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:
		start(runq->code, runq->pc+1, runq->local);
		runq->ret=0;
		break;
	default:
		Waitfor(pid, 1);
		runq->pc=runq->code[runq->pc].i;
		break;
	}
}
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();
}