PWB1/sys/source/s2/sh.c

Compare this file to the similar file:
Show the results in this format:

/*
 * PWB/UNIX shell
 * copyright 1973 Bell Telephone Laboratries inc.
 * 2.44 of 5/26/77
 */

#include	"errnos.h"
#include	"stat.h"
#define NOFILE	15	/* max open files per process */
static char SCCSID[] "@(#)sh.c 2.44";

#define	INTR	2
#define	QUIT	3
#define	SIGPIPE	13
#define LINSIZ 1000
#define ARGSIZ 50
#define TRESIZ 100

#define QUOTE 0200
#define FAND 1
#define FCAT 2
#define FPIN 4
#define FPOU 8
#define FPAR 16
#define FINT 32
#define FPRS 64

#define TCOM 1
#define TPAR 2
#define TFIL 3
#define TLST 4
#define TOR  5
#define TAND 6

#define DTYP 0
#define DFLG 1
#define DLEF 2
#define DRIT 3
#define DSPR 4
#define DCOM 5
#define N 'n'-'a'
#define P 'p'-'a'
#define R 'r'-'a'
#define S 's'-'a'
#define T 't'-'a'
#define W 'w'-'a'
#define Z 'z'-'a'
#define DOLREPL 1
#define DOLREPQ	2

extern errno;
/*	BSIZ = buffer for tty, BSIZFIL = size for file, 1 = size for pipe */
#define BSIZ 64
#define BSIZFIL	512
struct {
	long start;	/* absolute file addr of char in buf[0] */
	long linloc;	/* loc of begin of line for while processing */
	int gotten;	/* number of bytes read in last actual read */
	int nleft;
	char *nextp;
} b;
char	bstate;	/* primary state var for control of buffering */
		/* 0 ==> BNOW right; real I/O ptr set at BEND (internal I/O) */
		/* 1 ==> real ptr has been set from BNOW (pre-exec) */
		/* 2 ==> post-exec - internals must be reset */
		/* pre-exec: 0->1; post-exec: 1->2; int cmd: 2->0 */
		/* pump begin: 1->0 */
char	b1char;	/* input buffer: 1 at a time */
char	*bbuf	&b1char;	/* addr of actual buffer */
#define	BNOW	(b.start + (b.gotten - b.nleft))
#define	BEND	(b.start + b.gotten)
int bnread BSIZ;	/* must change to 1 if found to be in pipe */

#define ACNAME	"/etc/sha"

char	*dolp;
int	idolp;
int	oldfil0;	/* fildes for original file 0 */
char	pidp[6], argstr[6];
int	wide 5; /* glitch for 5char pid # */
char	devtty[10];
int	ldivr;
char	**dolv;
int	dolc;
char	*promp, *opromp, *optpromp;
char	*linep;
char	*elinep;
char	**argp;
char	**eargp;
int	*treep;
int	*treeend;
char	peekc;
char	gflg;
char	error;
char	acctf;	/*	<= 0 ==> no acctg at all, >0 ==> acctg */
char	acctfi;	/*	= 0 ==> no acctg for internal cmds */
char	uid;
char	setintr;
char	*arginp;
char	onelflg;
char	rflg;
int	exitcode;
char	exitstr[6] ;
char	*seta[26];
char	*endcore, *endptr;
int	oldintr;	/* save INTR state existing at start */
int	wasintr;
char	gointr[32];
int	catchintr();
char	proflag;	/* 1 ==> inside .profile */
			/* 2 ==> interrupt in .profile */
char	redirf;	/* 1 ==> I/O redirection of input; controls pump rebuffering */
char	optfv;		/* 0 ==> +v, 1 ==> -v; -v ==> print commands */


struct statb sb;

/*	commands performed internally */
char	*comint[] {
	"chdir",	/* 0 */
	"shift",	/* 1 */
	"login",	/* 2 */
	"newgrp",	/* 3 */
	"wait",		/* 4 */
	":",		/* 5 */
	"onintr",	/* 6 */
	"=",		/* 7 */
	"next",		/* 8 */
	"if",		/* 9 */
	"else",		/* 10 */
	"endif",	/* 11 */
	"goto",		/* 12 */
	"test",		/* 13 */
	"switch",	/* 14 */
	"break",	/* 15 */
	"endsw",	/* 16 */
	"exit",		/* 17 */
	"while",	/* 18 */
	"end",		/* 19 */
	"continue",	/* 20 */
	"breaksw",	/* 21 */
	"opt",		/* 22 */
	"cd",		/* 23, another name for chdir */
	0
};

/* defines for internal commands */
#define	ZCHDIR	0
#define	ZSHIFT	1
#define	ZLOGIN	2
#define	ZNEWGRP	3
#define	ZWAIT	4
#define	ZLABEL	5
#define	ZONINTR	6
#define	ZEQUALS	7
#define	ZNEXT	8
#define	ZIF	9
#define	ZELSE	10
#define	ZENDIF	11
#define	ZGOTO	12
#define	ZTEST	13
#define	ZSWITCH	14
#define	ZBREAK	15
#define	ZENDSW	16
#define	ZEXIT	17
#define	ZWHILE	18
#define	ZEND	19
#define ZCONTINUE	20
#define ZBREAKSW	21
#define ZOPT	22
#define ZCD	23
#define	ONINTR	comint[ZONINTR]
#define	IF	comint[ZIF]
#define	ENDIF	comint[ZENDIF]
#define	ENDSW	comint[ZENDSW]
#define	END	comint[ZEND]

/*	other keywords */
char	THEN[]	"then";
char	*ARG0, *ARG1;	/* for diagnostics & internal commands */
int	COMTYPE;	/* code for command, -1, or one of Z* */

char	*mesg[] {
	0,
	"Hangup",
	0,
	"Quit",
	"Illegal instruction",
	"Trace/BPT trap",
	"IOT trap",
	"EMT trap",
	"Floating exception",
	"Killed",
	"Bus error",
	"Memory fault",
	"Bad system call",
	"Broken pipe",
	"Alarm clock",
	"Terminated",
	0,
	0,
	0,
	0,
};

/*	messages */
char	*SYNTAX	"syntax error: ";
char	*MISS	"missing ";
char	*MISSL	"missing label: ";
char	*NONUM	"non-numeric arg: ";

char	*ARGCNT	"arg count";
char	*ARGLNG "arg list too long";
char	*BADARG	"bad arg: ";
char	*CANTEX	"cannot execute";
char	*CANTOP	"cannot open: ";
char	*NOTPER	"not permitted";
char	*NOTWHI	"used outside loop";
char	*EQERR	"`=' error";

struct stime {
	long procu, procs, childu, childs, curtim;
} timeb;


struct {
	char cname[8];
	char lname[6];
	char shtty;
	char tuid;
	long datet;
	long realt;
	long bcput;
	long bsyst;
} tbuf;

long tell();
char *logtty(), *nxtarg(), *sname();

/*	following items implement while -- end stack of WDEEP levels */
#define	WDEEP	3
#define INWHILE	(wtop < WDEEP)
struct {
	long sloc;	/* starting loc = addr of while */
	long eloc;	/* ending loc = addr of line AFTER end */
} wstk[WDEEP];
int	wtop	WDEEP;	/* top of stack */

main(c, av)
int c;
char **av;
{
	register f;
	register char *p, **v;
	copy(itoa(getpid()), pidp);
	copy("/dev/tty",devtty);
	setxcod(0);

	if (c >= 2 && (eq(av[1], "-v") || eq(av[1], "-x"))) {
		optfv = 1;
		av[1] = av[0];
		av++;
		--c;
	}
	f = c-2;
	f = f<0 ? 0 : f;
	copy(itoa(f), argstr); seta[N] = argstr;
	seta[T] = logtty();
	copyn(logname(), tbuf.lname, sizeof tbuf.lname);
	tbuf.shtty = devtty[8] = *seta[T];
	v = av;
	p = sname(*v);
	if (eq(p,"-rsh") || eq(p,"rsh"))
				rflg++;
	if (*p == '-')
		proflag++;
	for (f = 3; f < NOFILE; f++)
		close(f);
	oldfil0 = dup(0);	/* save for pipe into, .profile */
	promp = "% ";
	if(c > 1) {
		promp = 0;
		tbuf.shtty = 'x';
		signal(INTR, oldintr = signal(INTR, 1));
		if (*v[1]=='-') {
			*p = '-';
			if (v[1][1]=='c' && c>2)
				arginp = v[2];
			else if (v[1][1]=='t')
				onelflg = 2;
		} else {
			close(0);
			f = open(v[1], 0);
			if(f < 0) {
				prs(CANTOP);
				err(v[1]);
				exit(1);
			}
			bnread = BSIZFIL;
		}
	}

	if (onelflg || arginp || (seek(0, 0, 1) == -1 && errno == ESPIPE))
		bnread = 1;	/* no lookahead, no buffering */
	else
		bbuf = sbrk(bnread);
	endptr = endcore = sbrk(0);

	seta[S] = logdir();
	setwhere();
	if(((uid = getuid())&0377) == 0) {
		if (promp)
			promp = "# ";
	}
	pexinit();
	initacct();
	if (proflag) {
		if (uid != 0 && (f = open(".profile",0)) >= 0) {
			close(0);
			dup(f);
			close(f);
			promp = 0;
			signal(INTR, catchintr);
		} else
			proflag = 0;
	}
	if(*p == '-' && !proflag) {
		setintr++;
		signal(QUIT, 1);
		signal(INTR, 1);
	}
	dolv = v+1;
	dolc = c-1;

loop:
	if (promp)
		prs(optpromp ? optpromp : promp);
	main1();
	if(wasintr) {
		if(eq(gointr, "-")) goto loop;
		wasintr = 0;
		if (proflag > 1) {
			b.nleft = 0;
			goto loop;	/* will cause faked `next' */
		}
		wtop = WDEEP;		/* clear out while stack */
		ARG0 = ONINTR;
		ARG1 = gointr;
		search(COMTYPE = ZGOTO, 0);
		setxcod(1);
		signal(INTR, 0);
	}
	goto loop;
}


char line[LINSIZ];

main1()
{
	char *args[ARGSIZ];
	int trebuf[TRESIZ];
	register char c, *cp;
	register *t;

	argp = args;
	eargp = args+ARGSIZ-5;
	linep = line;
	elinep = line+LINSIZ-5;
	error = 0;
	gflg = 0;
	b.linloc = BNOW;	/* find where we are, in case while */
	COMTYPE = -1;	/* avoid bad diagnostic from readc */
	do {
		cp = linep;
		word();
		if (optfv) {
			putc(' ');
			prs(cp);
		}
	} while(*cp != '\n');

	treep = trebuf;
	treeend = &trebuf[TRESIZ];
	if(gflg == 0) {
		if(error == 0) {
			setexit();
			if (error)
				return;
			t = syntax(args, argp);
		}
		if(error != 0)  {
			err("syntax error"); 
		} else {
			redirf = 0;	/* no redirect of fildes 0 yet */
			execute(t);
			bsynch(0);	/* adjust internal to real */
		}
	}
}


word()
{
	register char c, c1;
	register dolflag;

	*argp++ = linep;

loop:
	switch(c = getc(DOLREPL)) {

	case ' ':
	case '\t':
		goto loop;

	case '\'':	/* '...' : what you see is what you get */
	case '"':	/* "..." : \", \$, $ substitution */
		c1 = c;
		dolflag = (c == '"' && !dolp) ? DOLREPQ : !DOLREPL;
		while((c=getc(dolflag)) != c1) {
			if(c == '\n') {
				error++;
				peekc = c;
				return;
			}
			if (c1 == '"' && c == '\\' &&
				((peekc = getc(!DOLREPL)) == '$' ||
				peekc == '"')) {
					c = peekc;
					peekc = 0;
			}
			*linep++ = c|QUOTE;
		}
		goto pack;

	case '&':
	case '|':
		*linep++ = c;
		if((peekc=getc(DOLREPL)) == c)
			peekc = 0;
		else
			linep--;
	case ';':
	case '<':
	case '>':
	case '(':
	case ')':
	case '^':
	case '\n':
		*linep++ = c;
		*linep++ = '\0';
		return;
	case '\\':
		if ((c=getc(!DOLREPL))=='\n') goto loop;
		else {
			c =| QUOTE;
			break;
		}
	}

	peekc = c;

pack:
	for(;;) {
		if ((c = getc(DOLREPL))=='\\') {
			if ((c=getc(!DOLREPL))=='\n') c = ' ';
			else c =| QUOTE;
		}
		if(any(c, " '\"\t;&<>()|^\n")) {
			peekc = c;
			if(any(c, "\"'"))
				goto loop;
			*linep++ = '\0';
			return;
		}
		*linep++ = c;
	}
}


tree(n)
int n;
{
	register *t;

	t = treep;
	treep =+ n;
	if (treep>treeend) {
		prs("Command line overflow\n");
		error++;
		reset();
	}
	return(t);
}

char	subchar	'$';	/* variable marker, may be changed by pump */

/*	flag: !DOLREPL ==> no substitution, DOLREPL ==> substitute,
	DOLREPQ ==> quoted substitution: "$1" = value of $1 for sure */
getc(flag)
register flag;
{
	register char c;

	if(peekc) {
		c = peekc;
		peekc = 0;
		return(c);
	}
	if(argp > eargp) {
		argp =- 10;
		while((c=getc(!DOLREPL)) != '\n');
		argp =+ 10;
		err("Too many args");
		gflg++;
		return(c);
	}
	if(linep > elinep) {
		linep =- 10;
		while((c=getc(!DOLREPL)) != '\n');
		linep =+ 10;
		err("Too many characters");
		gflg++;
		return(c);
	}
getd:
	if(dolp) {
		if (c = *dolp++) {
			if (flag == DOLREPQ)
				c =| QUOTE;
			return c;
		}
		if (idolp && ++idolp < dolc) {
			dolp = dolv[idolp];
			return(' ');
		}
		dolp = 0;
	}
	c = readc();
	if(c == subchar && flag) {
		c = readc();
		if(c>='0' && c<='9') {
			if(c-'0' < dolc)
				dolp = dolv[c-'0'];
			goto getd;
		}
		else if(c>='a' && c<='z') {
			dolp = seta[c-'a'];
			goto getd;
		}
		else if(c == '$') {
			dolp = pidp;
			goto getd;
		}
		/* $* = $1 $2 .... */
		else if (c == '*') {
			if (dolc > 1) {
				idolp = 1;
				dolp = dolv[1];
			}
			goto getd;
		}
		else
			if(c != '\n')  c = readc();
	}
	return(c&0177);
}



/*
 * syntax
 *	empty
 *	syn1
 */

syntax(p1, p2)
register char **p1, **p2;
{

	while(p1 != p2) {
		if(any(**p1, ";&\n"))
			p1++;
		else
			return(syn1(p1, p2));
	}
	return(0);
}


/*
 * syn1
 *	syn1a
 *	syn1a & syntax
 *	syn1a ; syntax
 */

syn1(p1, p2)
char **p1, **p2;
{
	register char **p;
	register *t, *t1;
	int l;

	l = 0;
	for(p=p1; p!=p2; p++)
	switch(**p) {

	case '(':
		l++;
		continue;

	case ')':
		l--;
		if(l < 0)
			error++;
		continue;

	case '&':
		if((*p)[1] == '&')
			continue;	/* and, not asynch */
	case ';':
	case '\n':
		if(l == 0) {
			l = **p;
			t = tree(4);
			t[DTYP] = TLST;
			t[DLEF] = syn1a(p1, p);
			t[DFLG] = 0;
			if(l == '&') {
				t1 = t[DLEF];
				t1[DFLG] =| FAND|FPRS|FINT;
			}
			t[DRIT] = syntax(p+1, p2);
			return(t);
		}
	}
	if(l == 0)
		return(syn1a(p1, p2));
	error++;
}


/*
 * syn1a
 *	syn1b
 *	syn1b || syn1a
 */

syn1a(p1,p2)
char **p1, **p2;
{
	register char **p;
	register int l, *t;

	l = 0;
	for(p=p1; p!=p2; p++)
	switch(**p) {

	case '(':
		l++;
		continue;

	case ')':
		l--;
		continue;

	case '|':
		if((*p)[1] == '\0')
			continue;	/* a pipe not an or */
		if(l == 0) {
			t = tree(4);
			t[DTYP] = TOR;
			t[DLEF] = syn1b(p1, p);
			t[DRIT] = syn1a(p+1, p2);
			t[DFLG] = 0;
			return(t);
		}
	}
	return(syn1b(p1, p2));
}


/*
 * syn1b
 *	syn2
 *	syn2 && syn1b
 */

syn1b(p1,p2)
char **p1, **p2;
{
	register char **p;
	register int l, *t;

	l = 0;
	for(p=p1; p!=p2; p++)
	switch(**p) {

	case '(':
		l++;
		continue;

	case ')':
		l--;
		continue;

	case '&':
		if(l == 0) {
			t = tree(4);
			t[DTYP] = TAND;
			t[DLEF] = syn2(p1, p);
			t[DRIT] = syn1b(p+1, p2);
			t[DFLG] = 0;
			return(t);
		}
	}
	return(syn2(p1, p2));
}


/*
 * syn2
 *	syn3
 *	syn3 | syn2
 */

syn2(p1, p2)
char **p1, **p2;
{
	register char **p;
	register int l, *t;

	l = 0;
	for(p=p1; p!=p2; p++)
	switch(**p) {

	case '(':
		l++;
		continue;

	case ')':
		l--;
		continue;

	case '|':
	case '^':
		if(l == 0) {
			t = tree(4);
			t[DTYP] = TFIL;
			t[DLEF] = syn3(p1, p);
			t[DRIT] = syn2(p+1, p2);
			t[DFLG] = 0;
			return(t);
		}
	}
	return(syn3(p1, p2));
}


/*
 * syn3
 *	( syn1 ) [ < in  ] [ > out ]
 *	word word* [ < in ] [ > out ]
 */

syn3(p1, p2)
char **p1, **p2;
{
	register char **p;
	char **lp, **rp;
	register *t;
	int n, l, i, o, c, flg;

	flg = 0;
	if(**p2 == ')')
		flg =| FPAR;
	lp = 0;
	rp = 0;
	i = 0;
	o = 0;
	n = 0;
	l = 0;
	for(p=p1; p!=p2; p++)
	switch(c = **p) {

	case '(':
		if(l == 0) {
			if(lp != 0)
				error++;
			lp = p+1;
		}
		l++;
		continue;

	case ')':
		l--;
		if(l == 0)
			rp = p;
		continue;

	case '>':
		p++;
		if (p1 != p2 && **p == '>') {
			if (l == 0)
				flg =| FCAT;
		} else p--;

	case '<':
		if(l == 0) {
			p++;
			if(p == p2) {
				error++;
				p--;
			}
			if(any(**p, "<>("))
				error++;
			if(c == '<') {
				if(i != 0)
					error++;
				i = *p;
				continue;
			}
			if(o != 0)
				error++;
			o = *p;
		}
		continue;

	default:
		if(l == 0)
			p1[n++] = *p;
	}
	if(lp != 0) {
		if(n != 0)
			error++;
		t = tree(5);
		t[DTYP] = TPAR;
		t[DSPR] = syn1(lp, rp);
	} else {
		if(n == 0)
			error++;
		p1[n++] = 0;
		t = tree(n+5);
		t[DTYP] = TCOM;
		for(l=0; l<n; l++)
			t[l+DCOM] = p1[l];
	}
	t[DFLG] = flg;
	t[DLEF] = i;
	t[DRIT] = o;
	return(t);
}


scan(at, f)
int *at;
int (*f)();
{
	register char *p;
	register *t;

	t = at+DCOM;
	while(p = *t++)
		(*f)(p);
}


tglob(s)
char *s;
{
	register char *p, c;

	for (p=s; c = *p++;)
		if(any(c, "[?*"))
			gflg = 1;
}


trim(s)
char *s;
{
	register char *p;

	for (p=s; *p++ =& 0177; );
	return(s);
}
int	ap, ac;		/* arg pointer & count for if & related cmds */
char	**av;		/* av[0] = t[DCOM] */
int	*savdlef;	/* for cmd piped into, has &t for cmd on other end */

execute(t, pf1, pf2)
int *t, *pf1, *pf2;
{
	int i, f, pv[2], wt;
	register *t1;
	register char *cp1, *cp2;

tryagain:	/* if expr command may come back here to do command */
	if(t != 0)
	switch(t[DTYP]) {

	case TCOM:
		ARG0 = cp1 = t[DCOM];
		ARG1 = cp2 = t[DCOM+1];
		if((COMTYPE = lookup(cp1)) < 0)
			goto notinternal;
		if (acctfi) {
			times(&timeb);
			time(&timeb.curtim);
		}
		setxcod(0);	/* assume will be good */
		av = &t[DCOM];
		ap = 1;
		for(ac = 1; av[ac]; ac++);
		if (cp2)
			trim(cp2);
		if (COMTYPE != ZEQUALS)	/* = takes care of self,can't do now */
			bsynch(0);	/* make sure internal ptr right */
		switch (COMTYPE) {
		case ZCHDIR:
		case ZCD:
			if (rflg) {
				die(NOTPER, 0);
				break;
			}
			if(cp2 == 0)
				die(ARGCNT, 0);
			else if(chdir(cp2) < 0)
				die("bad directory", 0);
			break;

		case ZSHIFT:
			if(dolc < 1) {
				die(ARGCNT, 0);
				break;
			}
			if(cp2==0) {
				dolv[1] = dolv[0];
				dolv++;
				dolc--;
				break;
			}
			if((i = atoi(cp2)) < 0 || i > dolc) {
				die(BADARG, cp2);
				break;
			}
			for(; i < dolc; i++)
				dolv[i] = dolv[i+1];
			dolc--;
			break;

		case ZLOGIN:
			if(promp != 0) {
				fclean();
				execv("/bin/login", t+DCOM);
			}
			die(CANTEX, 0);
			break;

		case ZNEWGRP:
			if(promp != 0) {
				fclean();
				execv("/bin/newgrp", t+DCOM);
			}
			die(CANTEX, 0);
			break;

		case ZWAIT:
			pwait(-1, 0);
			break;

		case ZLABEL:
		case ZENDIF:
		case ZENDSW:
			break;

		case ZONINTR:
			/* suppress onintr if interactive shell, or if
			 * noninteractive fired up immune to INTR.
			 */
			if (promp || oldintr)
				break;
			if(!cp2) {
				signal(INTR, 0);
				break;
			}
			copy(cp2, gointr);
			wasintr = 0;
			signal(INTR, catchintr);
			break;

		case ZEQUALS:
			if(t[DFLG]&FPIN) close(pf1[1]);
			i = *cp2 - 'a';
			if(t[DCOM+3] != 0 && eq(t[DCOM+2], "")) {
				t[DCOM+2] = t[DCOM+3];
				setxcod(1);
			}
				/* 3rd exists & null 2nd ==> use 3rd instead */
			if(i>25 || i<0 || (i==P && rflg))
				err(EQERR);
			else {
				f = t[DFLG]&FPIN;
				if ((seta[i] = rdval((f ? pf1[0] : 0),
					t[DLEF], t[DCOM+2]))==0)
					err(EQERR);
				if (f) {	/* piped, must assure synch */
					pwait(savdlef[DSPR], savdlef);
					bsynch(2);
				}
			}
			break;

		case ZNEXT:
			opromp = promp;
			if(!cp2) {
				f = dup(oldfil0);
				promp = uid? "% ": "# ";
				bnread = BSIZ;
				proflag = 0;
			}
			else {
				if (rflg) {
					die(NOTPER, 0);
					break;
				}
				promp = 0;
				f = open(cp2, 0);
			}
			if (f<0) {
				promp = opromp;
				die(CANTOP, cp2);
			}
			else {
				close(0);
				dup(f);
				close(f);
				signal(INTR, promp?1:0);
				signal(QUIT, promp?1:0);
				setintr = promp?1:0;
			}
			b.nleft = b.gotten = 0;
			b.start = 0;
			for (wtop = 0; INWHILE; wtop++) {
				wstk[wtop].sloc = 0;
				wstk[wtop].eloc = 0;
			}
			break;

		case ZEXIT:
			bflush();
			setxcod(cp2 ? atoi(cp2) : 0);
			break;

		case ZGOTO:
			/* following takes care of (unlikely) case in which
			 * goto exits from loop(s) whose end(s) are as
			 * yet unlocated. although not efficient, it is
			 * rigged to act in the intuitively proper way.
			 */
			i = wtop;
			while (INWHILE) {
				if (wstk[wtop].eloc == 0) {
					search(ZBREAK, 0);	/* find end */
					wstk[wtop].eloc = BNOW;
				} else bseek(wstk[wtop].eloc);
				wtop++;
			}
			wtop = i;

			search(ZGOTO, 0);

			b.linloc = BNOW;	/* effect of goto on while */
			while (INWHILE &&
				(b.linloc < wstk[wtop].sloc ||
				b.linloc > wstk[wtop].eloc))
				wtop++;	/* pop */
			break;

		case ZELSE:
			search(ZELSE, eq(cp2, IF) && eq(av[ac-1], THEN));
			break;

		case ZBREAK:
			if (INWHILE)
				toend();
			else die(NOTWHI, 0);
			break;

		case ZBREAKSW:
			ARG1 = 0;	/* ignore possible arg */

		case ZSWITCH:
			search(COMTYPE, 0);	/* ZBREAKSW or ZSWITCH */
			break;

		case ZTEST:
			i = exp();
			if(nxtarg() != 0)
				die(SYNTAX, av[ap-1]);
			else
				setxcod(!i);
			break;

		case ZIF:
			if (exp()) {
				if (eq(nxtarg(), THEN))
					break;
				--ap;	/*	ap -> new cmd */
				/* shift args to eliminate if */
				for(i = DSPR; i >= 0; i--)
					av[--ap] = t[i];
				t = &av[ap];
				enacct(cp1, 1);	/* acct for if part */
				goto tryagain;
			} else if (eq(nxtarg(), THEN))
				search(ZIF, 0);
			break;

		case ZWHILE:
			if (ac > 2) { /* expr like that of if */
				i = exp();
				if (cp1 = nxtarg()) {
					die(SYNTAX, cp1);
					break;
				}
			} else /* 0 args or 1 null arg = false, else true*/
				i = cp2 != 0 && *cp2 != '\0';
			if (wtop >= WDEEP || wstk[wtop].sloc != b.linloc) {
				/* not already in loop */
				if (--wtop >= 0) {
					if (wstk[wtop].sloc != b.linloc) {
						wstk[wtop].sloc = b.linloc;
						wstk[wtop].eloc = 0;
					}
				} else {
					die(">3 levels", 0);
					break;
				}
			}

			if (i)
				break;
			toend();	/* condition nomatch */
			break;
		case ZCONTINUE:
			if (INWHILE)
				bseek(wstk[wtop].sloc);
			else die(NOTWHI, 0);
			break;

		case ZEND:
			if (INWHILE) { /* while active */
				wstk[wtop].eloc = BNOW;
				bseek(wstk[wtop].sloc);
			} else die(NOTWHI, 0);
			break;

		case ZOPT:
			for(i = DCOM+1; cp2 = t[i]; i++) {
				if (eq(cp2, "-v") || eq(cp2, "-x"))
					optfv = 1;
				else if (eq(cp2, "+v") || eq(cp2, "+x"))
					optfv = 0;
				else if (eq(cp2, "-p")) {
					if (cp2 = t[++i])
						optpromp = rdval(0, 0, cp2);
					else
						optpromp = "";
				}
			}
			break;
		}
		enacct(cp1, 1);		/* internal commands */
		return;

	notinternal:
	case TPAR:
		bsynch(1);	/* set real ptr = BNOW */
		f = t[DFLG];
		i = 0;
		if((f&FPAR) == 0)
			if ((i = dofork()) == -1)
				return;	/* couldn't fork */
		if(i != 0) { /* parent */
			if((f&FPIN) != 0) {
				close(pf1[0]);
				close(pf1[1]);
				t[DLEF] = savdlef;	/* link back for pwait*/
			} else
				t[DLEF] = 0;	/* 1st or only in pipeline */
			t[DSPR] = i;	/* save proc-num for pwait */
			if ((f & FPRS) && promp) {
				prs(itoa(i));
				prs("\n");
			}
			if ((f & (FAND | FPOU)) == 0)
				pwait(i, t);
			bsynch(2);	/* mark possible read by cmd */
			return;
		}
		/*
		 * ignore interrupts for asynchronous cmds [code needed
		 * for sh proc only; ignore also for onintr -
		 */
		if (f & FPRS) {
			signal(INTR, 1);
			signal(QUIT, 1);
		}
		if (eq(gointr, "-"))
			signal(INTR, 1);
		if(t[DLEF] != 0) {
			close(0);
			/*	following for pipe into sh */
			trim(t[DLEF]);
			if (eq(t[DLEF], "--"))
				dup(oldfil0);
			else {
				i = open(t[DLEF], 0);
				if(i < 0)
					xdie(CANTOP, t[DLEF]);
			}
			redirf++;
		}
		if(t[DRIT] != 0) {
			if (rflg)
				xdie(">: ",NOTPER);
			if((f&FCAT) != 0) {
				i = open(trim(t[DRIT]), 1);
				if(i >= 0) {
					seek(i, 0, 2);
					goto f1;
				}
			}
			i = creat(trim(t[DRIT]), 0666);
			if(i < 0)
				xdie("cannot create: ",t[DRIT]);
		f1:
			close(1);
			dup(i);
			close(i);
		}
		if((f&FPIN) != 0) {
			close(0);
			dup(pf1[0]);
			close(pf1[0]);
			close(pf1[1]);
			redirf++;
		}
		if((f&FPOU) != 0) {
			close(1);
			dup(pf2[1]);
			close(pf2[0]);
			close(pf2[1]);
		}
		if((f&FINT)!=0 && t[DLEF]==0 && (f&FPIN)==0) {
			close(0);
			open("/dev/null", 0);
		}
		if((f&FINT) == 0 && setintr) {
			signal(INTR, 0);
			signal(QUIT, 0);
		}
		if(t[DTYP] == TPAR) {
			if(t1 = t[DSPR])
				t1[DFLG] =| f&FINT;
			execute(t1);
			exit(exitcode);
		}
		fclean();

		if (rflg && any('/', trim(t[DCOM])))
			xdie(NOTPER, 0);	/* no / in rsh commands */
		gflg = 0;
		scan(t, &tglob);
		if (gflg && t[DCOM+1]) {
			/* old glob stuff
			t[DSPR] = seta[P];
			execv("/etc/glob", t+DSPR);
			xdie("glob: ",CANTEX);
			*/
			etcglob(t + DCOM);
		}
		scan(t, &trim);

		if (eq(cp1, "pump"))
			dopump(&t[DCOM+1], redirf);
		texec(t[DCOM], t);

	case TFIL:
		f = t[DFLG];
		pipe(pv);
		t1 = t[DLEF];
		t1[DFLG] =| FPOU | (f&(FPIN|FINT|FPRS));
		execute(t1, pf1, pv);
		t1 = t[DRIT];
		t1[DFLG] =| FPIN | (f&(FPOU|FINT|FAND|FPRS));
		savdlef = t[DLEF];	/* save so can link pipe together */
		execute(t1, pv, pf2);
		return;

	case TLST:
		f = t[DFLG]&FINT;
		if(t1 = t[DLEF])
			t1[DFLG] =| f;
		execute(t1);
		if(t1 = t[DRIT])
			t1[DFLG] =| f;
		execute(t1);
		return;


	case TOR:
	case TAND:
		f = t[DFLG]&FINT;
		t1 = t[DLEF];
		t1[DFLG] =| f;
		execute(t1);
		i = atoi(seta[R]);
		if ((i == 0) == (t[DTYP] == TAND)) {
			t1 = t[DRIT];
			t1[DFLG] =| f;
			execute(t1);
		}
		return;

	}
}

toend()
{
	if (wstk[wtop].eloc == 0) { /* need to find end */
		ARG1 = 0;
		search(ZBREAK, 0);
	} else bseek(wstk[wtop].eloc);
	wtop++;		/* pop 1 level */
	return;
}


lookup(p)
register char *p;
{
	register char *q;
	register i;
	for (i = 0; q = comint[i]; i++)
		if (eq(p, q))
			return(i);
	return -1;
}

dofork()
{
	register wt, i;
	for(wt = 10;; wt =+ 10) {
		if ((i = fork()) != -1)
			break;
		if (promp == 0 && wt < 60)
			sleep(wt);
		else {
			err("cannot fork;try again");
			break;
		}
	}
	return i;
}

fclean()
{
	if (acctf)
		close(acctf);
	if (oldfil0)
		close(oldfil0);
	return;
}

texec(f, t)
register *t;
{
	extern errno;
	register char *cp;
	char tline[48];
	char txe2big, txeacces;
	int txtbsy;	/* kludge cntr for ETXTBSY fix */

	txeacces = txe2big = 0;
	txtbsy = 0;
	cp = seta[P];	/* normal case -- search */
	if (any('/', f)) {
		if (rflg)
			xdie(NOTPER, 0);	/* no / by rsh */
		cp = "";	/* sh: exec only cmd name as given */
	}
	do {
		cp = pcat(cp, f, tline, sizeof tline);
	retry:
		execv(tline, t+DCOM);
		switch (errno) {
		case ENOEXEC:
			t[DCOM] = tline;
			t[DSPR] = seta[Z];
			execv(t[DSPR], t+DSPR);
			xdie(seta[Z], " No shell!");
		case EACCES:
			txeacces++;	/* file there, missing x (probably) */
			break;
		case ENOMEM:
			xdie("too large", 0);
		case E2BIG:
			txe2big++;
			break;
		case ETXTBSY:
			if ((txtbsy =+ 10) > 60)
				xdie("text busy", 0);
			sleep(txtbsy);
			goto retry;
		}
	} while(cp);
	if (txe2big)
		xdie(ARGLNG,0);
	if (txeacces)
		xdie("file not executable", 0);
	xdie("not found", 0);
}

char	pipebomb;	/* 1 ==> SIGPIPE caught */
catchpipe()
{
	if (promp != 0)
		exit(1);
	pipebomb++;
	return;
}

/*	dopump: pump command:
 *	pump [+] [-[subchar]] [eofstr]
 *	default is to substitute using $
 *	-	suppresses substitution
 *	-subchar uses subchar instead of $ for substitution
 *	+	causes leading tabs in input to be thrown away
 *	eofstr defaults to !
 *	eofstr may not begin with `+', which is reserved for future flags.
 *	acts like filter, although actually copy of sh
 *	in input, \subchar = subchar, no other escaping performed
 *	if interactive, ignores interrupts & quits, but dies if other end
 *	of pipe dies; if non-interactive, reads to eofstr or real eof
 *	even if other end does die.
 *	NOTE: line is used as workarea, both for eofstr and input line
 *	eofstr is safe because initial arg can't be at beginning
 */

dopump(t)
register char **t;
{
#define	eofstr	line
#define	pumpwk	(line+96)
	register char c, *p;
	char tabeat;
	tabeat = 0;
	if (redirf) {
		b.nleft = b.gotten = 0;	/* clear buffer values */
		bnread = 1;	/* be careful with pipes & files */
	}
	else {
		if (promp == 0)
			bsynch(0);	/* use existing b., if any */
		else {
			signal(INTR, 1);	/* interactive - ignore */
			signal(QUIT, 1);	/* for sake of pump -|ed */
		}
	}
	if (proflag) {
		signal(INTR, 0);
		proflag = 0;
	}
	signal(SIGPIPE, catchpipe);
	setxcod(0);		/* in case real eof */
	copy("!", eofstr);
	for(; *t; t++)
		switch (**t) {
		case '-':
			subchar = *(*t + 1);	/* \0 turns subst off */
			break;
		case '+':
			tabeat++;
			break;
		default:
			copy(*t, eofstr);
		}

	while(1) {	/* real eof will cause exit in readc */
		p = pumpwk;
		if (tabeat) {
			while((c = getc(DOLREPL)) == '\t');
			peekc = c;
		}
		do {
			c = getc(DOLREPL);
			if (c == '\\' && (peekc = getc(!DOLREPL)) == subchar) {
				c = peekc;
				peekc = 0;
			}
			*p++ = c;
		} while (c != '\n');
		*--p = 0;		/* put 0 for \n */
		if (eq(pumpwk, eofstr))
			break;
		*p = '\n';
		if (!pipebomb)
			write(1, pumpwk, p - pumpwk + 1);
	}
	bsynch(1);	/* in case file */
	exit(pipebomb);
}

atoi(s)
char *s;
{

	register char *sp;
	register i, neg;

	sp = s;
	i = neg = 0;

	if(*sp == '-') {
		++neg;
		++sp;
	}

	while(*sp) {
		if(*sp < '0' || *sp > '9') {
			die(NONUM, s);
			return 0;
		}
		i = i * 10 + (*sp - '0');
		++sp;
	}
	return neg? -i: i;
}

xdie(str1, str2)
char *str1, *str2;
{
	die(str1, str2);
	exit(1);
}

die(str1, str2)
char *str1, *str2;
{
	prs(ARG0);
	prs(": ");
	prs(str1);
	err(str2);
	return;
}

/*
 *	err: emit error message, flush input by seeking to EOF (but only
 *	if reading from 0 in unrestricted way), exit.
 */
err(s)
char *s;
{

	prs(s);
	prs("\n");
	if (onelflg == 0 && arginp == 0)
		bflush();
	setxcod(1);
}


prs(s)
register char *s;
{

	if (s == 0)
		return;
	while(*s)
		putc(*s++);
}


putc(c)
{

	write(2, &c, 1);
}


any(c, s)
register char c, *s;
{

	while(*s)
		if(*s++ == c)
			return(1);
	return(0);
}


eq(s1, s2)
register char *s1, *s2;
{

	if (s1 == 0 || s2 == 0)
		return 0;
	while(*s1++ == *s2)
		if(*s2++ == '\0')
			return(1);
	return(0);
}


/*	initacct: initialize acctg according to state of ACNAME file:
	if cannot open ACNAME, no acctg done (acctf == 0)
	group permissions of ACNAME are used otherwise:
	0	cmd level, externals only	(acctf > 0, acctfi == 0)
	4	cmd level, add internal cmds	(acctf > 0, acctfi > 0)
	2	shell proc, externals only	(acctf > 0, acctfi == 0)
	1	shell proc, add internal cmds	(acctf > 0, acctfi > 0)
	NOTE: THIS IS KLUDGE INTENDED FOR OBLIVION AFTER EITHER NEW
	MH ACCTG GETS INSTALLED AND/OR INTERRUPT-HANDLING CHANGE
	PERMITS BETTER INTERNAL CMD HANDLING.
 */

initacct()
{
	register f;
	if ((acctf = open(ACNAME, 1)) < 0)
		acctf = 0;	/* no acctg at all */
	else {
		fstat(acctf, &sb);
		f = sb.i_mode;
		if (tbuf.shtty == 'x') {	/* shell proc */
			if (f & 020) {
				if (f & 010)
					acctfi++;	/* ext + int */
			} else {
				close(acctf);	/* no acct at all */
				acctf = 0;
			}
		} else if (f & 040)
			acctfi++;	/* cmd level, ext + int */
	}
	return;
}

pwait(i, t)
int i, *t;
{
	register p, e, *t1;
	int nprocs;
	int s;

	nprocs = (t1 = t) ? 0 : 1;
	while(t1) {	/* count number of procs in current pipeline */
		if (t1[DSPR])
			nprocs++;
		t1 = t1[DLEF];
	}

	if(i != 0)
	do {
		if (acctf) {
			times(&timeb);
			time(&timeb.curtim);
		}
		p = wait(&s);
		if(wasintr) p = wait(&s);
		if(p == -1)
			break;
		e = s&0177;
		exitcode = (s>>8)&0377;
		if(e==INTR && wasintr)  /* process returned intr */
			e = 0;
		if(mesg[e] != 0) {
			if(p != i) {
				prs(itoa(p));
				prs(": ");
			}
			prs(mesg[e]);
			if(s&0200)
				prs(" -- Core dumped");
		}
		if (p == i)
			setxcod(exitcode);
		if(e != 0)
			err("");
		for(t1 = t; t1; t1 = t1[DLEF]) { /* hunt proc; elims most **gok*/
			if (t1[DSPR] == p) {
				nprocs--;
				break;
			}
		}
		acct(t1);
	} while (nprocs);
}


acct(t)
int *t;
{
	if(t == 0)
		enacct("**gok", 0);
	else if(*t == TPAR)
		enacct("()", 0);
	else
		enacct(t[DCOM], 0);
}


enacct(as, acctype)
char *as;
int acctype;	/* 0 ==> child process, 1 ==> internal cmd */
{
	struct stime timbuf;
	register i;
	register char *np;

	if (uid == 0)
		return;
	if (acctf <= 0)	/* if no accounting active, elim sys calls */
		return;
	if (acctype && acctfi <= 0)
		return;	/* internal cmd, but no internal acctg */
	times(&timbuf);
	time(&timbuf.curtim);
	tbuf.realt = timbuf.curtim - timeb.curtim;
	if (acctype) {
		tbuf.bcput = timbuf.procu - timeb.procu;
		tbuf.bsyst = timbuf.procs - timeb.procs;
	} else {
		tbuf.bcput = timbuf.childu - timeb.childu;
		tbuf.bsyst = timbuf.childs - timeb.childs;
	}
	np = sname(as);
	for (i=0; i<8; i++) {
		tbuf.cname[i] = *np;
		if (*np)
			np++;
	}
	tbuf.datet = timbuf.curtim;
	tbuf.tuid = uid;
	seek(acctf, 0, 2);
	write(acctf, &tbuf, sizeof(tbuf));
}


rdval(pipef, lef, na)
char *na;
{
	register char *st, *np;
	char c;

	st = endptr;
	np = na;
	if(!pipef && lef) {
		pipef = eq(lef, "--") ? dup(oldfil0) : open(lef, 0);
		if(pipef<0)  return 0;
	}
	for(;;) {
		if(endptr >= endcore-10)
			if((endcore=sbrk(64))<0) return 0;
		if(!na) {
			if(read(pipef, &c, 1) <= 0) {
				setxcod(1);	/* EOF indicator */
				break;
			}
		} else c = *np++ & 0177;
		*endptr++ = c;
		if(c=='\n' || c=='\0') break;
	}
	if(c=='\n') --endptr;
	*endptr++ = '\0';
	if(pipef || lef) close(pipef);
	return st;
}


catchintr() {
	if (proflag)
		proflag++;	/* in .profile, make sure come out ok */
	wasintr++;
	signal(INTR, 1);
}


pcat(so1, so2, si, sz)
register char *so1, *so2;
char *si;
int sz;
{
	register char *s;

	s = si;
	while(*so1 != ':' && *so1 != '\0' && --sz) *s++ = *so1++;
	if(si != s && --sz > 0) *s++ = '/';
	while(*so2 && --sz > 0) *s++ = *so2++;
	if (--sz < 0) {
			*si = '\0';
			die("cmd line overflow", 0);
	}
	else *s = '\0';
	return *so1 ? ++so1 : 0;
}


setxcod(code)
int code;
{
	copy(itoa(code), exitstr);
	seta[R] = exitstr;
	return;
}

copy(source, sink)
register char *source, *sink;
{
	 while(*sink++ = *source++ & 0177);
}

/*
 *	copyn: copy at most n bytes from source to sink.
 */
copyn(source, sink, n)
register char *source, *sink;
int	n;
{
	register i;
	for (i = 0; i < n; i++)
		if (!(*sink++ = *source++))
			break;
}

itoa(n) {
	register i, j;
	register char *cp;
	static char *str[12];

	j = n;
	for(cp = &str[4]; ;--cp) {
		if(wide) --wide;
		j = ldiv(0, j, 10);
		*cp = ldivr+'0';
		if((j | wide) == 0)
			return cp;
	}
}

char *nxtarg()
{
	register iap;

	if ((iap = ap++) > ac || av[iap] == 0)
		return(0);
	return trim(av[iap]);
}

exp() {
	int p1;

	p1 = e1();
	if (eq(nxtarg(), "-o")) return(p1 | exp());
	ap--;
	return(p1);
}

e1() {
	int p1;

	p1 = e2();
	if (eq(nxtarg(), "-a")) return (p1 & e1());
	ap--;
	return(p1);
}

e2() {
	if (eq(nxtarg(), "!"))
		return(!e3());
	ap--;
	return(e3());
}

e3() {
	int ccode;
	int nap;
	int int1, int2;
	register char *a, *p1, *p2;

	ccode = 0;
	a = nxtarg();
	if(eq(a, "(")) {
		ccode = exp();
		if(!eq(nxtarg(), ")")) goto erre3;
		return(ccode);
	}

	if(eq(a, "{")) { /* execute a command for exit code */
		nap = ap;	/*	save 1st arg ptr */
		while (ap < ac && !eq(av[ap], "}")) trim(av[ap++]);
		if (ap >= ac || nap == ap) {
			die(SYNTAX, av[nap]);
			return 0;
		}
		av[ap++] = 0;	/*	change } to 0 for pexec */
		if ((int1 = dofork()) > 0)
			wait(&ccode);	/* parent */
		else if (int1 == 0)	/* child */
			texec(av[nap], &av[nap-DCOM]);
		return(ccode? 0 : 1);
	}

	p1 = nxtarg();

	/* file predicates */
	if(eq(a, "-r"))
		return(tio(p1, 0));

	if(eq(a, "-w"))
		return(tio(p1, 1));
	if (eq(a, "-f")) {
		if (stat(p1, &sb) == -1)
			return 0;
		return ((sb.i_mode & IFMT) == 0);
	}
	if (eq(a, "-d")) {
		if (stat(p1, &sb) == -1)
			return 0;
		return ((sb.i_mode & IFMT) == IFDIR);
	}
	if (eq(a, "-s")) {
		if (stat(p1, &sb) == -1)
			return 0;
		return (sb.i_size0 || sb.i_size1);
	}

	/* string predicates */
	if (eq(a, "-n"))
		return(p1 && *p1 != '\0');
	if (eq(a, "-z"))
		return(p1 == 0 || *p1 == '\0');
	if ((p2 = nxtarg()) == 0)
		goto erre3;
	if(eq(p1, "="))
		return(eq(a, p2));

	if(eq(p1, "!="))
		return(!eq(a, p2));

	int1 = atoi(a);
	int2 = atoi(p2);
	if(eq(p1, "-eq"))
		return( int1 == int2 );
	if(eq(p1, "-ne"))
		return( int1 != int2 );
	if(eq(p1, "-gt"))
		return( int1 > int2 );
	if(eq(p1, "-lt"))
		return( int1 < int2 );
	if(eq(p1, "-ge"))
		return( int1 >= int2 );
	if(eq(p1, "-le"))
		return( int1 <= int2 );

erre3:
	die(SYNTAX, p1);
}

tio(a, f)
char *a; int f;
{
	register int fil;

	if ((fil = open(a, f)) >= 0) {
		close(fil);
		return(1);
	}
	return(0);
}


/*	search: forward search for required token, type =
 *	ZGOTO: : label
 *	ZSWITCH: : label, : default, or unmatched ENDSW
 *	ZBREAKSW: unmatched endsw
 *	ZIF: unmatched else or endif (or }) (this is failed if-then)
 *	ZELSE: unmatched endif (or })
 *	ZBREAK: unmatched end (break, failed while or end)
 *	levinit = 0, except when called from else if ... then, when it is 1
 */
search(type, levinit)
int type, levinit;
{
	register int level, t;
	register char *aword;
	char fifel, fbr, fswex;
	char wordbuf[128];
	if (type == ZGOTO)
		bseek(0L);
	fifel = type == ZIF || type == ZELSE;
	fbr = type == ZBREAK;
	fswex = type == ZSWITCH || type == ZBREAKSW;
	aword = wordbuf;
	level = levinit;

	do {
		*aword = '\0';
		getword(aword);
		if ((t = lookup(aword)) >= 0)
			switch(t) {
			case ZELSE:
				if (level == 0 && type == ZIF)
					return;	/* leave rest of else line */
				continue;
			case ZIF:
				while(getword(aword));
				if (fifel && eq(aword, THEN))
					level++;
				break;
			case ZENDIF:
				if (fifel)
					level--;
				break;
			case ZWHILE:
				if (fbr)
					level++;
				break;
			case ZEND:
				if (fbr)
					level--;
				break;
			case ZSWITCH:
				if (fswex)
					level++;
				break;
			case ZENDSW:
				if (fswex)
					level--;
				break;
			case ZLABEL:
				if (getword(aword)) {
					if (type == ZGOTO) {
						if (eq(aword, ARG1))
							level = -1;
					} else if (type == ZSWITCH &&
						level == 0 &&
						(eq(aword, "default") ||
						match(ARG1, aword)))
						level = -1;
				}
				break;
			}
		getword(0);	/* gobble rest of line, if any */
	} while (level >= 0 && proflag < 2);
	return;
}

/*	getword: return next word from input (if any):
	aword != 0 : get next word (if any, return 1),
			if none, return 0, do not distturb *aword.
	aword == 0 : consume rest of line thru (nl).
	return 1 if word, 0 if only newline left.
*/

getword(aword)
char *aword;
{
	register int found;	/* 1 ==> found word, 0 ==> not */
	register char c, *wp;
	wp = aword;
	found = 0;
	c = peekc ? peekc : readc();
	peekc = '\0';

	do {
		while(c == ' ' || c == '\t')
			c = readc();
		if (c == '\n') {
			if (wp)
				break;
			else
				return 0;	/* newline only thing left */
		}
		found++;
		do {
			if (wp)
				*wp++ = c;
			c = readc();
			if (c == '\\' && (c = readc()) == '\n')
				c = ' ';
		} while (c != ' ' && c != '\t' && c != '\n');
	} while (!wp);
	peekc = c;
	if (found)
		*wp = '\0';
	return(found);
}

readc()
{
	register c;

	if (arginp) {
		if (arginp == 1)
			goto ONLYEXIT;
		if ((c = *arginp++) == 0) {
			arginp = 1;
			c = '\n';
		}
		return(c);
	}
	if (onelflg==1)
		goto ONLYEXIT;
	if (b.nleft == 0) {
		b.nextp = bbuf;
		b.start = b.start + b.gotten;	/* increm by last gotten */
		if ((b.nleft = read(0, b.nextp, bnread)) == 0 || proflag > 1) {
			if (proflag) {	/* still inside .profile */
				b.nleft = 5;	/* fake next */
				b.nextp = "next\n";
			} else {
				eoferr();
			ONLYEXIT:
				exit(atoi(seta[R]));
			}
		}
		b.gotten = b.nleft;
	}
	b.nleft--;
	c = *b.nextp++;
	if (c=='\n' && onelflg)
		onelflg--;
	return(c);
}

/*	eoferr: issue error message if cmd was in middle of search */
eoferr()
{
	switch (COMTYPE) {
	case ZGOTO:
		die(MISSL, ARG1);
		break;
	case ZIF:
	case ZELSE:
		die(MISS, ENDIF);
		break;
	case ZSWITCH:
	case ZBREAKSW:
		die(MISS, ENDSW);
		break;
	case ZWHILE:
	case ZBREAK:
		die(MISS, END);
		break;
	}
	bsynch(1);
	return;
}

/*	bflush: complete input flush */
bflush()
{
	seek(0, 0, 2);
	b.nleft = b.gotten = 0;
	return;
}

/*	bsynch: synchronize internal buffering & outside world */
/*	btarg gives nominal target value of bstate */
bsynch(btarg)
register btarg;
{
	register obstate;
	long f;
	if (btarg == bstate || promp || bnread == 1 || redirf)
		return;	/* no seeking in any of these cases */
	obstate = bstate;
	bstate = btarg;
	if (btarg == 2)
		return;	/* just remember this; do nothing now */
	switch (obstate) {
	case 0:		/* 0 --> 1 only possible */
		if (b.nleft > 0)
			lseek(0, BNOW, 0);	/* set real = internal */
		break;
	case 1:		/* 1 --> 0 (pump begin) */
		lseek(0, BEND, 0);	/* ptr where expected for read */
		break;
	case 2:		/* 2 --> 0; 2 --> 1, leave as 2, since 1 dominates */
		if (btarg == 0) {
			f = tell(0);
			if (f >= 0)
				bseek(f);
		} else
			bstate = 2;
		break;
	}
}

/*	bseek: seek, staying within current input buffer b, if possible */
bseek(where)
long where;
{
	long bend;
	if (promp || bnread == 1 || redirf)
		return;
	bend = BEND;
	if (where >= b.start && where < bend) {
		b.nleft = bend - where;	/* 1 <= b.nleft <= b.gotten */
		b.nextp = bbuf + b.gotten - b.nleft;
		lseek(0, bend, 0);	/* really at end ofbuffer */
	} else {
		b.nleft = b.gotten = 0;
		b.start = where;
		lseek(0, where, 0);
	}
	return;
}

/*	sname: simple name (last component) of file name */
char *sname(s)
register char *s;
{
	register char *p;
	for(p = s; *p;)
		if (*p++ == '/' && *p != '\0')
			s = p;
	return(s);
}

/*	setwhere: set up wherev for $w (1st component of pathname) */
char	wherev[6];
setwhere()
{
	register char *s, *w;
	register i;
	s = seta[S];
	seta[W] = w = wherev;
	i = 0;
	do {
		*w++ = *s++;
	} while (++i < 5 && *s != '/');
}

/*
 *	pexinit: fills in pathstr (seta[P]) and shellnam (seta[Z]).
 *	may be invoked before fork to avoid unnecessary .path opening.
 *	returns 0 if OK, -1 if any error.
 */
pexinit()
{
	char pathbuf[128 + 16];
	register n, f;
	char *newpath, *newshell;
	char *p;

	newshell = "/bin/sh";
	pcat(seta[S], ".path", pathbuf, sizeof pathbuf);
	if ((f = open(pathbuf, 0)) < 0)
		newpath = uid ? ":/bin:/usr/bin" : "/bin:/etc:/";
	else {
		n = read(f, pathbuf, sizeof pathbuf);
		close(f);
		if (n <= 0) {
			prs("cannot read .path"); prs("\n");
			return(-1);
		}
		if (pexline(pathbuf, pathbuf + n, 128, &newpath, &p)
		|| pexline(p, pathbuf + n, 16, &newshell, &p))
			return(-1);
	}
	seta[P] = rdval(0, 0, newpath);
	seta[Z] = rdval(0, 0, newshell);
	return(0);
}

/*
 *	pexline: scan for a line (if any) beginning at ptr,
 *	ending at ptrlim -1, for line up to psize bytes long.
 *	convert it to string, return beginning addr in pret
 *	and addr of next line in pnext
 *	return 0 if OK (or not present), -1 if runs off end in middle of line
 *	or if line too long.
 */
pexline(ptr, ptrlim, psize, pret, pnext)
register char *ptr, *ptrlim;
int psize;
char **pret, **pnext;
{
	if (ptr >= ptrlim)
		return(0);
	*pret = ptr;
	if (ptrlim > ptr + psize)
		ptrlim = ptr + psize;
	for (; ptr < ptrlim; ptr++)
		if (*ptr == '\n') {
			*ptr++ = '\0';
			*pnext = ptr;
			return(0);
		}
	prs(".path too long"); prs("\n");
	return(-1);
}
/*
 *	sh internal version of /etc/glob
 *	(from here thru function cat)
 *
 *	"*" in params matches r.e ".*"
 *	"?" in params matches r.e. "."
 *	"[...]" in params matches character class
 *	"[...a-z...]" in params matches a through z.
 *	if param does not contain "*", "[", or "?", use it as is
 *	if it does, find all files in current directory
 *	which match the param, sort them, and use them
 */

#define	STRSIZ	5300
char	**avx;
char	*string;
char	*ablimit;
int	ncoll;

etcglob(argv)
char *argv[];
{
	char	ab[STRSIZ];		/* generated characters */
	char	*avxa[500+DCOM];		/* generated arguments */

	avx = &avxa[DCOM];
	string = ab;
	ablimit = &ab[STRSIZ];
	*avx++ = *argv++;
	while (*argv)
		expand(*argv++);
	if (ncoll == 0)
		gdie("No match");
	texec(avxa[DCOM], avxa);
}

expand(as)
char *as;
{
	register char *s, *cs;
	register int dirf;
	char **oavx;
	static struct {
		int	ino;
		char	name[16];
	} entry;

	s = cs = as;
	while (*cs!='*' && *cs!='?' && *cs!='[') {
		if (*cs++ == 0) {
			*avx++ = cat(s, "");
			return;
		}
	}
	for (;;) {
		if (cs==s) {
			dirf = open(".", 0);
			s = "";
			break;
		}
		if (*--cs == '/') {
			*cs = 0;
			dirf = open(s==cs? "/": s, 0);
			*cs++ = 0200;
			break;
		}
	}
	if (dirf < 0)
		gdie("No directory");
	oavx = avx;
	while (read(dirf, &entry, 16) == 16) {
		if (entry.ino==0)
			continue;
		if (match(entry.name, cs)) {
			*avx++ = cat(s, entry.name);
			ncoll++;
		}
	}
	close(dirf);
	sort(oavx);
}

sort(oavx)
char **oavx;
{
	register char **p1, **p2, **c;

	p1 = oavx;
	while (p1 < avx-1) {
		p2 = p1;
		while(++p2 < avx) {
			if (compar(*p1, *p2) > 0) {
				c = *p1;
				*p1 = *p2;
				*p2 = c;
			}
		}
		p1++;
	}
}


match(s, p)
char *s, *p;
{
	if (*s=='.' && *p!='.')
		return(0);
	return(amatch(s, p));
}

amatch(as, ap)
char *as, *ap;
{
	register char *s, *p;
	register scc;
	int c, cc, ok, lc;

	s = as;
	p = ap;
	if (scc = *s++)
		if ((scc =& 0177) == 0)
			scc = 0200;
	switch (c = *p++) {

	case '[':
		ok = 0;
		lc = 077777;
		while (cc = *p++) {
			if (cc==']') {
				if (ok)
					return(amatch(s, p));
				else
					return(0);
			} else if (cc=='-') {
				if (lc<=scc && scc<=(c = *p++))
					ok++;
			} else
				if (scc == (lc=cc))
					ok++;
		}
		return(0);

	default:
		if (c!=scc)
			return(0);

	case '?':
		if (scc)
			return(amatch(s, p));
		return(0);

	case '*':
		return(umatch(--s, p));

	case '\0':
		return(!scc);
	}
}

umatch(s, p)
char *s, *p;
{
	if(*p==0)
		return(1);
	while(*s)
		if (amatch(s++,p))
			return(1);
	return(0);
}

compar(as1, as2)
char *as1, *as2;
{
	register char *s1, *s2;

	s1 = as1;
	s2 = as2;
	while (*s1++ ==  *s2)
		if (*s2++ == 0)
			return(0);
	return (*--s1 - *s2);
}

cat(as1, as2)
char *as1, *as2;
{
	register char *s1, *s2;
	register int c;

	s2 = string;
	s1 = as1;
	while (c = *s1++) {
		if (s2 > ablimit)
			gdie(ARGLNG);
		c =& 0177;
		if (c==0) {
			*s2++ = '/';
			break;
		}
		*s2++ = c;
	}
	s1 = as2;
	do {
		if (s2 > ablimit)
			gdie(ARGLNG);
		*s2++ = c = *s1++;
	} while (c);
	s1 = string;
	string = s2;
	return(s1);
}

/*
 *	gdie: terminate with error message, but no seek to EOF
 *	purpose is to make internal glob work same as external one.
 *	something should be done about match/nomatch actions.
 */
gdie(str)
char *str;
{
	prs(ARG0); prs(": ");
	prs(str); prs("\n");
	exit(1);
}