| /* | 
 |  * POSIX standard | 
 |  *	test expression | 
 |  *	[ expression ] | 
 |  * | 
 |  * Plan 9 additions: | 
 |  *	-A file exists and is append-only | 
 |  *	-L file exists and is exclusive-use | 
 |  *	-T file exists and is temporary | 
 |  */ | 
 |  | 
 | #include <u.h> | 
 | #include <libc.h> | 
 |  | 
 | #define isatty plan9_isatty | 
 |  | 
 | #define EQ(a,b)	((tmp=a)==0?0:(strcmp(tmp,b)==0)) | 
 |  | 
 | int	ap; | 
 | int	ac; | 
 | char	**av; | 
 | char	*tmp; | 
 |  | 
 | void	synbad(char *, char *); | 
 | int	fsizep(char *); | 
 | int	isdir(char *); | 
 | int	isreg(char *); | 
 | int	isatty(int); | 
 | int	isint(char *, int *); | 
 | int	isolder(char *, char *); | 
 | int	isolderthan(char *, char *); | 
 | int	isnewerthan(char *, char *); | 
 | int	hasmode(char *, ulong); | 
 | int	tio(char *, int); | 
 | int	e(void), e1(void), e2(void), e3(void); | 
 | char	*nxtarg(int); | 
 |  | 
 | void | 
 | main(int argc, char *argv[]) | 
 | { | 
 | 	int r; | 
 | 	char *c; | 
 |  | 
 | 	ac = argc; av = argv; ap = 1; | 
 | 	if(EQ(argv[0],"[")) { | 
 | 		if(!EQ(argv[--ac],"]")) | 
 | 			synbad("] missing",""); | 
 | 	} | 
 | 	argv[ac] = 0; | 
 | 	if (ac<=1) | 
 | 		exits("usage"); | 
 | 	r = e(); | 
 | 	/* | 
 | 	 * nice idea but short-circuit -o and -a operators may have | 
 | 	 * not consumed their right-hand sides. | 
 | 	 */ | 
 | 	if(0 && (c = nxtarg(1)) != nil) | 
 | 		synbad("unexpected operator/operand: ", c); | 
 | 	exits(r?0:"false"); | 
 | } | 
 |  | 
 | char * | 
 | nxtarg(int mt) | 
 | { | 
 | 	if(ap>=ac){ | 
 | 		if(mt){ | 
 | 			ap++; | 
 | 			return(0); | 
 | 		} | 
 | 		synbad("argument expected",""); | 
 | 	} | 
 | 	return(av[ap++]); | 
 | } | 
 |  | 
 | int | 
 | nxtintarg(int *pans) | 
 | { | 
 | 	if(ap<ac && isint(av[ap], pans)){ | 
 | 		ap++; | 
 | 		return 1; | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 | int | 
 | e(void) | 
 | { | 
 | 	int p1; | 
 |  | 
 | 	p1 = e1(); | 
 | 	if (EQ(nxtarg(1), "-o")) | 
 | 		return(p1 || e()); | 
 | 	ap--; | 
 | 	return(p1); | 
 | } | 
 |  | 
 | int | 
 | e1(void) | 
 | { | 
 | 	int p1; | 
 |  | 
 | 	p1 = e2(); | 
 | 	if (EQ(nxtarg(1), "-a")) | 
 | 		return (p1 && e1()); | 
 | 	ap--; | 
 | 	return(p1); | 
 | } | 
 |  | 
 | int | 
 | e2(void) | 
 | { | 
 | 	if (EQ(nxtarg(0), "!")) | 
 | 		return(!e2()); | 
 | 	ap--; | 
 | 	return(e3()); | 
 | } | 
 |  | 
 | int | 
 | e3(void) | 
 | { | 
 | 	int p1, int1, int2; | 
 | 	char *a, *p2; | 
 |  | 
 | 	a = nxtarg(0); | 
 | 	if(EQ(a, "(")) { | 
 | 		p1 = e(); | 
 | 		if(!EQ(nxtarg(0), ")")) | 
 | 			synbad(") expected",""); | 
 | 		return(p1); | 
 | 	} | 
 |  | 
 | 	if(EQ(a, "-A")) | 
 | 		return(hasmode(nxtarg(0), DMAPPEND)); | 
 |  | 
 | 	if(EQ(a, "-L")) | 
 | 		return(hasmode(nxtarg(0), DMEXCL)); | 
 |  | 
 | 	if(EQ(a, "-T")) | 
 | 		return(hasmode(nxtarg(0), DMTMP)); | 
 |  | 
 | 	if(EQ(a, "-f")) | 
 | 		return(isreg(nxtarg(0))); | 
 |  | 
 | 	if(EQ(a, "-d")) | 
 | 		return(isdir(nxtarg(0))); | 
 |  | 
 | 	if(EQ(a, "-r")) | 
 | 		return(tio(nxtarg(0), 4)); | 
 |  | 
 | 	if(EQ(a, "-w")) | 
 | 		return(tio(nxtarg(0), 2)); | 
 |  | 
 | 	if(EQ(a, "-x")) | 
 | 		return(tio(nxtarg(0), 1)); | 
 |  | 
 | 	if(EQ(a, "-e")) | 
 | 		return(tio(nxtarg(0), 0)); | 
 |  | 
 | 	if(EQ(a, "-c")) | 
 | 		return(0); | 
 |  | 
 | 	if(EQ(a, "-b")) | 
 | 		return(0); | 
 |  | 
 | 	if(EQ(a, "-u")) | 
 | 		return(0); | 
 |  | 
 | 	if(EQ(a, "-g")) | 
 | 		return(0); | 
 |  | 
 | 	if(EQ(a, "-s")) | 
 | 		return(fsizep(nxtarg(0))); | 
 |  | 
 | 	if(EQ(a, "-t")) | 
 | 		if(ap>=ac) | 
 | 			return(isatty(1)); | 
 | 		else if(nxtintarg(&int1)) | 
 | 			return(isatty(int1)); | 
 | 		else | 
 | 			synbad("not a valid file descriptor number ", ""); | 
 |  | 
 | 	if(EQ(a, "-n")) | 
 | 		return(!EQ(nxtarg(0), "")); | 
 | 	if(EQ(a, "-z")) | 
 | 		return(EQ(nxtarg(0), "")); | 
 |  | 
 | 	p2 = nxtarg(1); | 
 | 	if (p2==0) | 
 | 		return(!EQ(a,"")); | 
 | 	if(EQ(p2, "=")) | 
 | 		return(EQ(nxtarg(0), a)); | 
 |  | 
 | 	if(EQ(p2, "!=")) | 
 | 		return(!EQ(nxtarg(0), a)); | 
 |  | 
 | 	if(EQ(p2, "-older")) | 
 | 		return(isolder(nxtarg(0), a)); | 
 |  | 
 | 	if(EQ(p2, "-ot")) | 
 | 		return(isolderthan(nxtarg(0), a)); | 
 |  | 
 | 	if(EQ(p2, "-nt")) | 
 | 		return(isnewerthan(nxtarg(0), a)); | 
 |  | 
 | 	if(!isint(a, &int1)) | 
 | 		synbad("unexpected operator/operand: ", p2); | 
 |  | 
 | 	if(nxtintarg(&int2)){ | 
 | 		if(EQ(p2, "-eq")) | 
 | 			return(int1==int2); | 
 | 		if(EQ(p2, "-ne")) | 
 | 			return(int1!=int2); | 
 | 		if(EQ(p2, "-gt")) | 
 | 			return(int1>int2); | 
 | 		if(EQ(p2, "-lt")) | 
 | 			return(int1<int2); | 
 | 		if(EQ(p2, "-ge")) | 
 | 			return(int1>=int2); | 
 | 		if(EQ(p2, "-le")) | 
 | 			return(int1<=int2); | 
 | 	} | 
 |  | 
 | 	synbad("unknown operator ",p2); | 
 | 	return 0;		/* to shut ken up */ | 
 | } | 
 |  | 
 | int | 
 | tio(char *a, int f) | 
 | { | 
 | 	return access (a, f) >= 0; | 
 | } | 
 |  | 
 | /* | 
 |  * note that the name strings pointed to by Dir members are | 
 |  * allocated with the Dir itself (by the same call to malloc), | 
 |  * but are not included in sizeof(Dir), so copying a Dir won't | 
 |  * copy the strings it points to. | 
 |  */ | 
 |  | 
 | int | 
 | hasmode(char *f, ulong m) | 
 | { | 
 | 	int r; | 
 | 	Dir *dir; | 
 |  | 
 | 	dir = dirstat(f); | 
 | 	if (dir == nil) | 
 | 		return 0; | 
 | 	r = (dir->mode & m) != 0; | 
 | 	free(dir); | 
 | 	return r; | 
 | } | 
 |  | 
 | int | 
 | isdir(char *f) | 
 | { | 
 | 	return hasmode(f, DMDIR); | 
 | } | 
 |  | 
 | int | 
 | isreg(char *f) | 
 | { | 
 | 	int r; | 
 | 	Dir *dir; | 
 |  | 
 | 	dir = dirstat(f); | 
 | 	if (dir == nil) | 
 | 		return 0; | 
 | 	r = (dir->mode & DMDIR) == 0; | 
 | 	free(dir); | 
 | 	return r; | 
 | } | 
 |  | 
 | int | 
 | isatty(int fd) | 
 | { | 
 | 	int r; | 
 | 	Dir *d1, *d2; | 
 |  | 
 | 	d1 = dirfstat(fd); | 
 | 	d2 = dirstat("/dev/cons"); | 
 | 	if (d1 == nil || d2 == nil) | 
 | 		r = 0; | 
 | 	else | 
 | 		r = d1->type == d2->type && d1->dev == d2->dev && | 
 | 			d1->qid.path == d2->qid.path; | 
 | 	free(d1); | 
 | 	free(d2); | 
 | 	return r; | 
 | } | 
 |  | 
 | int | 
 | fsizep(char *f) | 
 | { | 
 | 	int r; | 
 | 	Dir *dir; | 
 |  | 
 | 	dir = dirstat(f); | 
 | 	if (dir == nil) | 
 | 		return 0; | 
 | 	r = dir->length > 0; | 
 | 	free(dir); | 
 | 	return r; | 
 | } | 
 |  | 
 | void | 
 | synbad(char *s1, char *s2) | 
 | { | 
 | 	int len; | 
 |  | 
 | 	write(2, "test: ", 6); | 
 | 	if ((len = strlen(s1)) != 0) | 
 | 		write(2, s1, len); | 
 | 	if ((len = strlen(s2)) != 0) | 
 | 		write(2, s2, len); | 
 | 	write(2, "\n", 1); | 
 | 	exits("bad syntax"); | 
 | } | 
 |  | 
 | int | 
 | isint(char *s, int *pans) | 
 | { | 
 | 	char *ep; | 
 |  | 
 | 	*pans = strtol(s, &ep, 0); | 
 | 	return (*ep == 0); | 
 | } | 
 |  | 
 | int | 
 | isolder(char *pin, char *f) | 
 | { | 
 | 	int r; | 
 | 	ulong n, m; | 
 | 	char *p = pin; | 
 | 	Dir *dir; | 
 |  | 
 | 	dir = dirstat(f); | 
 | 	if (dir == nil) | 
 | 		return 0; | 
 |  | 
 | 	/* parse time */ | 
 | 	n = 0; | 
 | 	while(*p){ | 
 | 		m = strtoul(p, &p, 0); | 
 | 		switch(*p){ | 
 | 		case 0: | 
 | 			n = m; | 
 | 			break; | 
 | 		case 'y': | 
 | 			m *= 12; | 
 | 			/* fall through */ | 
 | 		case 'M': | 
 | 			m *= 30; | 
 | 			/* fall through */ | 
 | 		case 'd': | 
 | 			m *= 24; | 
 | 			/* fall through */ | 
 | 		case 'h': | 
 | 			m *= 60; | 
 | 			/* fall through */ | 
 | 		case 'm': | 
 | 			m *= 60; | 
 | 			/* fall through */ | 
 | 		case 's': | 
 | 			n += m; | 
 | 			p++; | 
 | 			break; | 
 | 		default: | 
 | 			synbad("bad time syntax, ", pin); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	r = dir->mtime + n < time(0); | 
 | 	free(dir); | 
 | 	return r; | 
 | } | 
 |  | 
 | int | 
 | isolderthan(char *a, char *b) | 
 | { | 
 | 	int r; | 
 | 	Dir *ad, *bd; | 
 |  | 
 | 	ad = dirstat(a); | 
 | 	bd = dirstat(b); | 
 | 	if (ad == nil || bd == nil) | 
 | 		r = 0; | 
 | 	else | 
 | 		r = ad->mtime > bd->mtime; | 
 | 	free(ad); | 
 | 	free(bd); | 
 | 	return r; | 
 | } | 
 |  | 
 | int | 
 | isnewerthan(char *a, char *b) | 
 | { | 
 | 	int r; | 
 | 	Dir *ad, *bd; | 
 |  | 
 | 	ad = dirstat(a); | 
 | 	bd = dirstat(b); | 
 | 	if (ad == nil || bd == nil) | 
 | 		r = 0; | 
 | 	else | 
 | 		r = ad->mtime < bd->mtime; | 
 | 	free(ad); | 
 | 	free(bd); | 
 | 	return r; | 
 | } |