| #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; | 
 | 	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, "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()); | 
 | 	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(); | 
 | 	} | 
 | 	return 0;  /* not reached; silence OS X Lion gcc */ | 
 | } | 
 | /* | 
 |  * 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(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* | 
 | 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); | 
 | 	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(); | 
 | } | 
 |  | 
 | 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(); | 
 | } |