xref: /openbsd/bin/ksh/c_test.c (revision 990e11ad)
1*990e11adSop /*	$OpenBSD: c_test.c,v 1.28 2023/06/10 07:24:21 op Exp $	*/
27cb960a2Sdownsj 
37cb960a2Sdownsj /*
47cb960a2Sdownsj  * test(1); version 7-like  --  author Erik Baalbergen
57cb960a2Sdownsj  * modified by Eric Gisin to be used as built-in.
67cb960a2Sdownsj  * modified by Arnold Robbins to add SVR3 compatibility
77cb960a2Sdownsj  * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket).
87cb960a2Sdownsj  * modified by Michael Rendell to add Korn's [[ .. ]] expressions.
97cb960a2Sdownsj  * modified by J.T. Conklin to add POSIX compatibility.
107cb960a2Sdownsj  */
117cb960a2Sdownsj 
1269b9f96bSmillert #include <sys/stat.h>
13b608f594Smmcc 
1456018212Smmcc #include <string.h>
154a010e0cStb #include <unistd.h>
1656018212Smmcc 
17b608f594Smmcc #include "sh.h"
187cb960a2Sdownsj #include "c_test.h"
197cb960a2Sdownsj 
207cb960a2Sdownsj /* test(1) accepts the following grammar:
217cb960a2Sdownsj 	oexpr	::= aexpr | aexpr "-o" oexpr ;
227cb960a2Sdownsj 	aexpr	::= nexpr | nexpr "-a" aexpr ;
237cb960a2Sdownsj 	nexpr	::= primary | "!" nexpr ;
247cb960a2Sdownsj 	primary	::= unary-operator operand
257cb960a2Sdownsj 		| operand binary-operator operand
267cb960a2Sdownsj 		| operand
277cb960a2Sdownsj 		| "(" oexpr ")"
287cb960a2Sdownsj 		;
297cb960a2Sdownsj 
307cb960a2Sdownsj 	unary-operator ::= "-a"|"-r"|"-w"|"-x"|"-e"|"-f"|"-d"|"-c"|"-b"|"-p"|
317cb960a2Sdownsj 			   "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|
327cb960a2Sdownsj 			   "-L"|"-h"|"-S"|"-H";
337cb960a2Sdownsj 
34dcacb757Sdownsj 	binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
355b199efbSmillert 			    "-nt"|"-ot"|"-ef"|"<"|">"
367cb960a2Sdownsj 			    ;
377cb960a2Sdownsj 	operand ::= <any thing>
387cb960a2Sdownsj */
397cb960a2Sdownsj 
407cb960a2Sdownsj #define T_ERR_EXIT	2	/* POSIX says > 1 for errors */
417cb960a2Sdownsj 
427cb960a2Sdownsj struct t_op {
437cb960a2Sdownsj 	char	op_text[4];
447cb960a2Sdownsj 	Test_op	op_num;
457cb960a2Sdownsj };
467cb960a2Sdownsj static const struct t_op u_ops [] = {
477cb960a2Sdownsj 	{"-a",	TO_FILAXST },
487cb960a2Sdownsj 	{"-b",	TO_FILBDEV },
497cb960a2Sdownsj 	{"-c",	TO_FILCDEV },
507cb960a2Sdownsj 	{"-d",	TO_FILID },
517cb960a2Sdownsj 	{"-e",	TO_FILEXST },
527cb960a2Sdownsj 	{"-f",	TO_FILREG },
537cb960a2Sdownsj 	{"-G",	TO_FILGID },
547cb960a2Sdownsj 	{"-g",	TO_FILSETG },
557cb960a2Sdownsj 	{"-h",	TO_FILSYM },
567cb960a2Sdownsj 	{"-H",	TO_FILCDF },
577cb960a2Sdownsj 	{"-k",	TO_FILSTCK },
587cb960a2Sdownsj 	{"-L",	TO_FILSYM },
597cb960a2Sdownsj 	{"-n",	TO_STNZE },
607cb960a2Sdownsj 	{"-O",	TO_FILUID },
617cb960a2Sdownsj 	{"-o",	TO_OPTION },
627cb960a2Sdownsj 	{"-p",	TO_FILFIFO },
637cb960a2Sdownsj 	{"-r",	TO_FILRD },
647cb960a2Sdownsj 	{"-s",	TO_FILGZ },
657cb960a2Sdownsj 	{"-S",	TO_FILSOCK },
667cb960a2Sdownsj 	{"-t",	TO_FILTT },
677cb960a2Sdownsj 	{"-u",	TO_FILSETU },
687cb960a2Sdownsj 	{"-w",	TO_FILWR },
697cb960a2Sdownsj 	{"-x",	TO_FILEX },
707cb960a2Sdownsj 	{"-z",	TO_STZER },
717cb960a2Sdownsj 	{"",	TO_NONOP }
727cb960a2Sdownsj };
737cb960a2Sdownsj static const struct t_op b_ops [] = {
747cb960a2Sdownsj 	{"=",	TO_STEQL },
75dcacb757Sdownsj 	{"==",	TO_STEQL },
767cb960a2Sdownsj 	{"!=",	TO_STNEQ },
777cb960a2Sdownsj 	{"<",	TO_STLT },
787cb960a2Sdownsj 	{">",	TO_STGT },
797cb960a2Sdownsj 	{"-eq",	TO_INTEQ },
807cb960a2Sdownsj 	{"-ne",	TO_INTNE },
817cb960a2Sdownsj 	{"-gt",	TO_INTGT },
827cb960a2Sdownsj 	{"-ge",	TO_INTGE },
837cb960a2Sdownsj 	{"-lt",	TO_INTLT },
847cb960a2Sdownsj 	{"-le",	TO_INTLE },
857cb960a2Sdownsj 	{"-ef",	TO_FILEQ },
867cb960a2Sdownsj 	{"-nt",	TO_FILNT },
877cb960a2Sdownsj 	{"-ot",	TO_FILOT },
887cb960a2Sdownsj 	{"",	TO_NONOP }
897cb960a2Sdownsj };
907cb960a2Sdownsj 
91c5d5393cSotto static int	test_eaccess(const char *, int);
92c5d5393cSotto static int	test_oexpr(Test_env *, int);
93c5d5393cSotto static int	test_aexpr(Test_env *, int);
94c5d5393cSotto static int	test_nexpr(Test_env *, int);
95c5d5393cSotto static int	test_primary(Test_env *, int);
96c5d5393cSotto static int	ptest_isa(Test_env *, Test_meta);
97c5d5393cSotto static const char *ptest_getopnd(Test_env *, Test_op, int);
98c5d5393cSotto static int	ptest_eval(Test_env *, Test_op, const char *,
99c5d5393cSotto 		    const char *, int);
100c5d5393cSotto static void	ptest_error(Test_env *, int, const char *);
1017cb960a2Sdownsj 
1027cb960a2Sdownsj int
c_test(char ** wp)103c5d5393cSotto c_test(char **wp)
1047cb960a2Sdownsj {
1057cb960a2Sdownsj 	int argc;
1067cb960a2Sdownsj 	int res;
1077cb960a2Sdownsj 	Test_env te;
1087cb960a2Sdownsj 
1097cb960a2Sdownsj 	te.flags = 0;
1107cb960a2Sdownsj 	te.isa = ptest_isa;
1117cb960a2Sdownsj 	te.getopnd = ptest_getopnd;
1127cb960a2Sdownsj 	te.eval = ptest_eval;
1137cb960a2Sdownsj 	te.error = ptest_error;
1147cb960a2Sdownsj 
1157cb960a2Sdownsj 	for (argc = 0; wp[argc]; argc++)
1167cb960a2Sdownsj 		;
1177cb960a2Sdownsj 
1187cb960a2Sdownsj 	if (strcmp(wp[0], "[") == 0) {
1197cb960a2Sdownsj 		if (strcmp(wp[--argc], "]") != 0) {
1207cb960a2Sdownsj 			bi_errorf("missing ]");
1217cb960a2Sdownsj 			return T_ERR_EXIT;
1227cb960a2Sdownsj 		}
1237cb960a2Sdownsj 	}
1247cb960a2Sdownsj 
1257cb960a2Sdownsj 	te.pos.wp = wp + 1;
1267cb960a2Sdownsj 	te.wp_end = wp + argc;
1277cb960a2Sdownsj 
1287cb960a2Sdownsj 	/*
1297cb960a2Sdownsj 	 * Handle the special cases from POSIX.2, section 4.62.4.
1307cb960a2Sdownsj 	 * Implementation of all the rules isn't necessary since
13112e7fb2dSjmc 	 * our parser does the right thing for the omitted steps.
1327cb960a2Sdownsj 	 */
1337cb960a2Sdownsj 	if (argc <= 5) {
1347cb960a2Sdownsj 		char **owp = wp;
1357cb960a2Sdownsj 		int invert = 0;
1367cb960a2Sdownsj 		Test_op	op;
1377cb960a2Sdownsj 		const char *opnd1, *opnd2;
1387cb960a2Sdownsj 
1397cb960a2Sdownsj 		while (--argc >= 0) {
1407cb960a2Sdownsj 			if ((*te.isa)(&te, TM_END))
1417cb960a2Sdownsj 				return !0;
1427cb960a2Sdownsj 			if (argc == 3) {
1437cb960a2Sdownsj 				opnd1 = (*te.getopnd)(&te, TO_NONOP, 1);
1447cb960a2Sdownsj 				if ((op = (Test_op) (*te.isa)(&te, TM_BINOP))) {
1457cb960a2Sdownsj 					opnd2 = (*te.getopnd)(&te, op, 1);
1467a8124d8Sderaadt 					res = (*te.eval)(&te, op, opnd1,
1477a8124d8Sderaadt 					    opnd2, 1);
1487cb960a2Sdownsj 					if (te.flags & TEF_ERROR)
1497cb960a2Sdownsj 						return T_ERR_EXIT;
1507cb960a2Sdownsj 					if (invert & 1)
1517cb960a2Sdownsj 						res = !res;
1527cb960a2Sdownsj 					return !res;
1537cb960a2Sdownsj 				}
1547cb960a2Sdownsj 				/* back up to opnd1 */
1557cb960a2Sdownsj 				te.pos.wp--;
1567cb960a2Sdownsj 			}
1577cb960a2Sdownsj 			if (argc == 1) {
1587cb960a2Sdownsj 				opnd1 = (*te.getopnd)(&te, TO_NONOP, 1);
1594c69a2c1Skstailey 				res = (*te.eval)(&te, TO_STNZE, opnd1,
160355ffa75Stedu 				    NULL, 1);
1617cb960a2Sdownsj 				if (invert & 1)
1627cb960a2Sdownsj 					res = !res;
1637cb960a2Sdownsj 				return !res;
1647cb960a2Sdownsj 			}
1657cb960a2Sdownsj 			if ((*te.isa)(&te, TM_NOT)) {
1667cb960a2Sdownsj 				invert++;
1677cb960a2Sdownsj 			} else
1687cb960a2Sdownsj 				break;
1697cb960a2Sdownsj 		}
1707cb960a2Sdownsj 		te.pos.wp = owp + 1;
1717cb960a2Sdownsj 	}
1727cb960a2Sdownsj 
1737cb960a2Sdownsj 	return test_parse(&te);
1747cb960a2Sdownsj }
1757cb960a2Sdownsj 
1767cb960a2Sdownsj /*
1777cb960a2Sdownsj  * Generic test routines.
1787cb960a2Sdownsj  */
1797cb960a2Sdownsj 
1807cb960a2Sdownsj Test_op
test_isop(Test_env * te,Test_meta meta,const char * s)181c5d5393cSotto test_isop(Test_env *te, Test_meta meta, const char *s)
1827cb960a2Sdownsj {
1837cb960a2Sdownsj 	char sc1;
1847cb960a2Sdownsj 	const struct t_op *otab;
1857cb960a2Sdownsj 
1867cb960a2Sdownsj 	otab = meta == TM_UNOP ? u_ops : b_ops;
1877cb960a2Sdownsj 	if (*s) {
1887cb960a2Sdownsj 		sc1 = s[1];
1897cb960a2Sdownsj 		for (; otab->op_text[0]; otab++)
1907a8124d8Sderaadt 			if (sc1 == otab->op_text[1] &&
1915b199efbSmillert 			    strcmp(s, otab->op_text) == 0)
1927cb960a2Sdownsj 				return otab->op_num;
1937cb960a2Sdownsj 	}
1947cb960a2Sdownsj 	return TO_NONOP;
1957cb960a2Sdownsj }
1967cb960a2Sdownsj 
1977cb960a2Sdownsj int
test_eval(Test_env * te,Test_op op,const char * opnd1,const char * opnd2,int do_eval)198c5d5393cSotto test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2,
199c5d5393cSotto     int do_eval)
2007cb960a2Sdownsj {
2017cb960a2Sdownsj 	int res;
2027cb960a2Sdownsj 	int not;
2037cb960a2Sdownsj 	struct stat b1, b2;
2047cb960a2Sdownsj 
2057cb960a2Sdownsj 	if (!do_eval)
2067cb960a2Sdownsj 		return 0;
2077cb960a2Sdownsj 
2087cb960a2Sdownsj 	switch ((int) op) {
2097cb960a2Sdownsj 	/*
2107cb960a2Sdownsj 	 * Unary Operators
2117cb960a2Sdownsj 	 */
2127cb960a2Sdownsj 	case TO_STNZE: /* -n */
2137cb960a2Sdownsj 		return *opnd1 != '\0';
2147cb960a2Sdownsj 	case TO_STZER: /* -z */
2157cb960a2Sdownsj 		return *opnd1 == '\0';
2167cb960a2Sdownsj 	case TO_OPTION: /* -o */
2177cb960a2Sdownsj 		if ((not = *opnd1 == '!'))
2187cb960a2Sdownsj 			opnd1++;
2197cb960a2Sdownsj 		if ((res = option(opnd1)) < 0)
2207cb960a2Sdownsj 			res = 0;
2217cb960a2Sdownsj 		else {
2227cb960a2Sdownsj 			res = Flag(res);
2237cb960a2Sdownsj 			if (not)
2247cb960a2Sdownsj 				res = !res;
2257cb960a2Sdownsj 		}
2267cb960a2Sdownsj 		return res;
2277cb960a2Sdownsj 	case TO_FILRD: /* -r */
2287cb960a2Sdownsj 		return test_eaccess(opnd1, R_OK) == 0;
2297cb960a2Sdownsj 	case TO_FILWR: /* -w */
2307cb960a2Sdownsj 		return test_eaccess(opnd1, W_OK) == 0;
2317cb960a2Sdownsj 	case TO_FILEX: /* -x */
2327cb960a2Sdownsj 		return test_eaccess(opnd1, X_OK) == 0;
2337cb960a2Sdownsj 	case TO_FILAXST: /* -a */
234855754a1Smillert 		return stat(opnd1, &b1) == 0;
2357cb960a2Sdownsj 	case TO_FILEXST: /* -e */
2367cb960a2Sdownsj 		/* at&t ksh does not appear to do the /dev/fd/ thing for
2377cb960a2Sdownsj 		 * this (unless the os itself handles it)
2387cb960a2Sdownsj 		 */
2397cb960a2Sdownsj 		return stat(opnd1, &b1) == 0;
2407cb960a2Sdownsj 	case TO_FILREG: /* -r */
241855754a1Smillert 		return stat(opnd1, &b1) == 0 && S_ISREG(b1.st_mode);
2427cb960a2Sdownsj 	case TO_FILID: /* -d */
243855754a1Smillert 		return stat(opnd1, &b1) == 0 && S_ISDIR(b1.st_mode);
2447cb960a2Sdownsj 	case TO_FILCDEV: /* -c */
245855754a1Smillert 		return stat(opnd1, &b1) == 0 && S_ISCHR(b1.st_mode);
2467cb960a2Sdownsj 	case TO_FILBDEV: /* -b */
247855754a1Smillert 		return stat(opnd1, &b1) == 0 && S_ISBLK(b1.st_mode);
2487cb960a2Sdownsj 	case TO_FILFIFO: /* -p */
249855754a1Smillert 		return stat(opnd1, &b1) == 0 && S_ISFIFO(b1.st_mode);
2507cb960a2Sdownsj 	case TO_FILSYM: /* -h -L */
2517cb960a2Sdownsj 		return lstat(opnd1, &b1) == 0 && S_ISLNK(b1.st_mode);
2527cb960a2Sdownsj 	case TO_FILSOCK: /* -S */
253855754a1Smillert 		return stat(opnd1, &b1) == 0 && S_ISSOCK(b1.st_mode);
2547cb960a2Sdownsj 	case TO_FILCDF:/* -H HP context dependent files (directories) */
2557cb960a2Sdownsj 		return 0;
2567cb960a2Sdownsj 	case TO_FILSETU: /* -u */
257855754a1Smillert 		return stat(opnd1, &b1) == 0 &&
2587a8124d8Sderaadt 		    (b1.st_mode & S_ISUID) == S_ISUID;
2597cb960a2Sdownsj 	case TO_FILSETG: /* -g */
260855754a1Smillert 		return stat(opnd1, &b1) == 0 &&
2617a8124d8Sderaadt 		    (b1.st_mode & S_ISGID) == S_ISGID;
2627cb960a2Sdownsj 	case TO_FILSTCK: /* -k */
263855754a1Smillert 		return stat(opnd1, &b1) == 0 &&
2647a8124d8Sderaadt 		    (b1.st_mode & S_ISVTX) == S_ISVTX;
2657cb960a2Sdownsj 	case TO_FILGZ: /* -s */
266855754a1Smillert 		return stat(opnd1, &b1) == 0 && b1.st_size > 0L;
2677cb960a2Sdownsj 	case TO_FILTT: /* -t */
268*990e11adSop 		if (!bi_getn(opnd1, &res)) {
2697cb960a2Sdownsj 			te->flags |= TEF_ERROR;
270*990e11adSop 			return 0;
2713b015934Smillert 		}
272*990e11adSop 		return isatty(res);
2737cb960a2Sdownsj 	case TO_FILUID: /* -O */
274855754a1Smillert 		return stat(opnd1, &b1) == 0 && b1.st_uid == ksheuid;
2757cb960a2Sdownsj 	case TO_FILGID: /* -G */
276855754a1Smillert 		return stat(opnd1, &b1) == 0 && b1.st_gid == getegid();
2777cb960a2Sdownsj 	/*
2787cb960a2Sdownsj 	 * Binary Operators
2797cb960a2Sdownsj 	 */
2807cb960a2Sdownsj 	case TO_STEQL: /* = */
2817cb960a2Sdownsj 		if (te->flags & TEF_DBRACKET)
2820e7d3a01Smillert 			return gmatch(opnd1, opnd2, false);
2837cb960a2Sdownsj 		return strcmp(opnd1, opnd2) == 0;
2847cb960a2Sdownsj 	case TO_STNEQ: /* != */
2857cb960a2Sdownsj 		if (te->flags & TEF_DBRACKET)
2860e7d3a01Smillert 			return !gmatch(opnd1, opnd2, false);
2877cb960a2Sdownsj 		return strcmp(opnd1, opnd2) != 0;
2887cb960a2Sdownsj 	case TO_STLT: /* < */
2897cb960a2Sdownsj 		return strcmp(opnd1, opnd2) < 0;
2907cb960a2Sdownsj 	case TO_STGT: /* > */
2917cb960a2Sdownsj 		return strcmp(opnd1, opnd2) > 0;
2927cb960a2Sdownsj 	case TO_INTEQ: /* -eq */
2937cb960a2Sdownsj 	case TO_INTNE: /* -ne */
2947cb960a2Sdownsj 	case TO_INTGE: /* -ge */
2957cb960a2Sdownsj 	case TO_INTGT: /* -gt */
2967cb960a2Sdownsj 	case TO_INTLE: /* -le */
2977cb960a2Sdownsj 	case TO_INTLT: /* -lt */
2987cb960a2Sdownsj 		{
299517d3880Stobias 			int64_t v1, v2;
3007cb960a2Sdownsj 
3017a8124d8Sderaadt 			if (!evaluate(opnd1, &v1, KSH_RETURN_ERROR, false) ||
3027a8124d8Sderaadt 			    !evaluate(opnd2, &v2, KSH_RETURN_ERROR, false)) {
3037cb960a2Sdownsj 				/* error already printed.. */
3047cb960a2Sdownsj 				te->flags |= TEF_ERROR;
3057cb960a2Sdownsj 				return 1;
3067cb960a2Sdownsj 			}
3077cb960a2Sdownsj 			switch ((int) op) {
3087cb960a2Sdownsj 			case TO_INTEQ:
3097cb960a2Sdownsj 				return v1 == v2;
3107cb960a2Sdownsj 			case TO_INTNE:
3117cb960a2Sdownsj 				return v1 != v2;
3127cb960a2Sdownsj 			case TO_INTGE:
3137cb960a2Sdownsj 				return v1 >= v2;
3147cb960a2Sdownsj 			case TO_INTGT:
3157cb960a2Sdownsj 				return v1 > v2;
3167cb960a2Sdownsj 			case TO_INTLE:
3177cb960a2Sdownsj 				return v1 <= v2;
3187cb960a2Sdownsj 			case TO_INTLT:
3197cb960a2Sdownsj 				return v1 < v2;
3207cb960a2Sdownsj 			}
3217cb960a2Sdownsj 		}
3227cb960a2Sdownsj 	case TO_FILNT: /* -nt */
3232dee7088Smillert 		{
3242dee7088Smillert 			int s2;
3252dee7088Smillert 			/* ksh88/ksh93 succeed if file2 can't be stated
3262dee7088Smillert 			 * (subtly different from `does not exist').
3272dee7088Smillert 			 */
3287a8124d8Sderaadt 			return stat(opnd1, &b1) == 0 &&
3297a8124d8Sderaadt 			    (((s2 = stat(opnd2, &b2)) == 0 &&
3307a8124d8Sderaadt 			    b1.st_mtime > b2.st_mtime) || s2 < 0);
3312dee7088Smillert 		}
3327cb960a2Sdownsj 	case TO_FILOT: /* -ot */
3332dee7088Smillert 		{
3342dee7088Smillert 			int s1;
3352dee7088Smillert 			/* ksh88/ksh93 succeed if file1 can't be stated
3362dee7088Smillert 			 * (subtly different from `does not exist').
3372dee7088Smillert 			 */
3387a8124d8Sderaadt 			return stat(opnd2, &b2) == 0 &&
3397a8124d8Sderaadt 			    (((s1 = stat(opnd1, &b1)) == 0 &&
3407a8124d8Sderaadt 			    b1.st_mtime < b2.st_mtime) || s1 < 0);
3412dee7088Smillert 		}
3427cb960a2Sdownsj 	case TO_FILEQ: /* -ef */
3437a8124d8Sderaadt 		return stat (opnd1, &b1) == 0 && stat (opnd2, &b2) == 0 &&
3447a8124d8Sderaadt 		    b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino;
3457cb960a2Sdownsj 	}
3467cb960a2Sdownsj 	(*te->error)(te, 0, "internal error: unknown op");
3477cb960a2Sdownsj 	return 1;
3487cb960a2Sdownsj }
3497cb960a2Sdownsj 
350855754a1Smillert /* Routine to deal with X_OK on non-directories when running as root.
3513b015934Smillert  */
3527cb960a2Sdownsj static int
test_eaccess(const char * path,int amode)353855754a1Smillert test_eaccess(const char *path, int amode)
3547cb960a2Sdownsj {
3553b015934Smillert 	int res;
3563b015934Smillert 
357855754a1Smillert 	res = access(path, amode);
3588b1c8262Smillert 	/*
3598b1c8262Smillert 	 * On most (all?) unixes, access() says everything is executable for
3603b015934Smillert 	 * root - avoid this on files by using stat().
3613b015934Smillert 	 */
362855754a1Smillert 	if (res == 0 && ksheuid == 0 && (amode & X_OK)) {
3633b015934Smillert 		struct stat statb;
3643b015934Smillert 
3653aaa63ebSderaadt 		if (stat(path, &statb) == -1)
3663b015934Smillert 			res = -1;
3673b015934Smillert 		else if (S_ISDIR(statb.st_mode))
3683b015934Smillert 			res = 0;
3693b015934Smillert 		else
3707a8124d8Sderaadt 			res = (statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) ?
3717a8124d8Sderaadt 			    0 : -1;
3728b1c8262Smillert 	}
3733b015934Smillert 
3743b015934Smillert 	return res;
3757cb960a2Sdownsj }
3767cb960a2Sdownsj 
3777cb960a2Sdownsj int
test_parse(Test_env * te)378c5d5393cSotto test_parse(Test_env *te)
3797cb960a2Sdownsj {
3807cb960a2Sdownsj 	int res;
3817cb960a2Sdownsj 
3827cb960a2Sdownsj 	res = test_oexpr(te, 1);
3837cb960a2Sdownsj 
3847cb960a2Sdownsj 	if (!(te->flags & TEF_ERROR) && !(*te->isa)(te, TM_END))
3857cb960a2Sdownsj 		(*te->error)(te, 0, "unexpected operator/operand");
3867cb960a2Sdownsj 
3877cb960a2Sdownsj 	return (te->flags & TEF_ERROR) ? T_ERR_EXIT : !res;
3887cb960a2Sdownsj }
3897cb960a2Sdownsj 
3907cb960a2Sdownsj static int
test_oexpr(Test_env * te,int do_eval)391c5d5393cSotto test_oexpr(Test_env *te, int do_eval)
3927cb960a2Sdownsj {
3937cb960a2Sdownsj 	int res;
3947cb960a2Sdownsj 
3957cb960a2Sdownsj 	res = test_aexpr(te, do_eval);
3967cb960a2Sdownsj 	if (res)
3977cb960a2Sdownsj 		do_eval = 0;
3987cb960a2Sdownsj 	if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_OR))
3997cb960a2Sdownsj 		return test_oexpr(te, do_eval) || res;
4007cb960a2Sdownsj 	return res;
4017cb960a2Sdownsj }
4027cb960a2Sdownsj 
4037cb960a2Sdownsj static int
test_aexpr(Test_env * te,int do_eval)404c5d5393cSotto test_aexpr(Test_env *te, int do_eval)
4057cb960a2Sdownsj {
4067cb960a2Sdownsj 	int res;
4077cb960a2Sdownsj 
4087cb960a2Sdownsj 	res = test_nexpr(te, do_eval);
4097cb960a2Sdownsj 	if (!res)
4107cb960a2Sdownsj 		do_eval = 0;
4117cb960a2Sdownsj 	if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_AND))
4127cb960a2Sdownsj 		return test_aexpr(te, do_eval) && res;
4137cb960a2Sdownsj 	return res;
4147cb960a2Sdownsj }
4157cb960a2Sdownsj 
4167cb960a2Sdownsj static int
test_nexpr(Test_env * te,int do_eval)417c5d5393cSotto test_nexpr(Test_env *te, int do_eval)
4187cb960a2Sdownsj {
4197cb960a2Sdownsj 	if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_NOT))
4207cb960a2Sdownsj 		return !test_nexpr(te, do_eval);
4217cb960a2Sdownsj 	return test_primary(te, do_eval);
4227cb960a2Sdownsj }
4237cb960a2Sdownsj 
4247cb960a2Sdownsj static int
test_primary(Test_env * te,int do_eval)425c5d5393cSotto test_primary(Test_env *te, int do_eval)
4267cb960a2Sdownsj {
4277cb960a2Sdownsj 	const char *opnd1, *opnd2;
4287cb960a2Sdownsj 	int res;
4297cb960a2Sdownsj 	Test_op op;
4307cb960a2Sdownsj 
4317cb960a2Sdownsj 	if (te->flags & TEF_ERROR)
4327cb960a2Sdownsj 		return 0;
4337cb960a2Sdownsj 	if ((*te->isa)(te, TM_OPAREN)) {
4347cb960a2Sdownsj 		res = test_oexpr(te, do_eval);
4357cb960a2Sdownsj 		if (te->flags & TEF_ERROR)
4367cb960a2Sdownsj 			return 0;
4377cb960a2Sdownsj 		if (!(*te->isa)(te, TM_CPAREN)) {
4387cb960a2Sdownsj 			(*te->error)(te, 0, "missing closing paren");
4397cb960a2Sdownsj 			return 0;
4407cb960a2Sdownsj 		}
4417cb960a2Sdownsj 		return res;
4427cb960a2Sdownsj 	}
44315e13106Sotto 	/*
44415e13106Sotto 	 * Binary should have precedence over unary in this case
44515e13106Sotto 	 * so that something like test \( -f = -f \) is accepted
44615e13106Sotto 	 */
44715e13106Sotto 	if ((te->flags & TEF_DBRACKET) || (&te->pos.wp[1] < te->wp_end &&
44815e13106Sotto 	    !test_isop(te, TM_BINOP, te->pos.wp[1]))) {
4497cb960a2Sdownsj 		if ((op = (Test_op) (*te->isa)(te, TM_UNOP))) {
4507cb960a2Sdownsj 			/* unary expression */
4517cb960a2Sdownsj 			opnd1 = (*te->getopnd)(te, op, do_eval);
4527cb960a2Sdownsj 			if (!opnd1) {
4537cb960a2Sdownsj 				(*te->error)(te, -1, "missing argument");
4547cb960a2Sdownsj 				return 0;
4557cb960a2Sdownsj 			}
4567cb960a2Sdownsj 
457f7654a50Snicm 			return (*te->eval)(te, op, opnd1, NULL,
45815e13106Sotto 			    do_eval);
45915e13106Sotto 		}
4607cb960a2Sdownsj 	}
4617cb960a2Sdownsj 	opnd1 = (*te->getopnd)(te, TO_NONOP, do_eval);
4627cb960a2Sdownsj 	if (!opnd1) {
4637cb960a2Sdownsj 		(*te->error)(te, 0, "expression expected");
4647cb960a2Sdownsj 		return 0;
4657cb960a2Sdownsj 	}
4667cb960a2Sdownsj 	if ((op = (Test_op) (*te->isa)(te, TM_BINOP))) {
4677cb960a2Sdownsj 		/* binary expression */
4687cb960a2Sdownsj 		opnd2 = (*te->getopnd)(te, op, do_eval);
4697cb960a2Sdownsj 		if (!opnd2) {
4707cb960a2Sdownsj 			(*te->error)(te, -1, "missing second argument");
4717cb960a2Sdownsj 			return 0;
4727cb960a2Sdownsj 		}
4737cb960a2Sdownsj 
4747cb960a2Sdownsj 		return (*te->eval)(te, op, opnd1, opnd2, do_eval);
4757cb960a2Sdownsj 	}
4767cb960a2Sdownsj 	if (te->flags & TEF_DBRACKET) {
4777cb960a2Sdownsj 		(*te->error)(te, -1, "missing expression operator");
4787cb960a2Sdownsj 		return 0;
4797cb960a2Sdownsj 	}
480f7654a50Snicm 	return (*te->eval)(te, TO_STNZE, opnd1, NULL, do_eval);
4817cb960a2Sdownsj }
4827cb960a2Sdownsj 
4837cb960a2Sdownsj /*
4847cb960a2Sdownsj  * Plain test (test and [ .. ]) specific routines.
4857cb960a2Sdownsj  */
4867cb960a2Sdownsj 
4877cb960a2Sdownsj /* Test if the current token is a whatever.  Accepts the current token if
4887cb960a2Sdownsj  * it is.  Returns 0 if it is not, non-zero if it is (in the case of
4897cb960a2Sdownsj  * TM_UNOP and TM_BINOP, the returned value is a Test_op).
4907cb960a2Sdownsj  */
4917cb960a2Sdownsj static int
ptest_isa(Test_env * te,Test_meta meta)492c5d5393cSotto ptest_isa(Test_env *te, Test_meta meta)
4937cb960a2Sdownsj {
4947cb960a2Sdownsj 	/* Order important - indexed by Test_meta values */
4957cb960a2Sdownsj 	static const char *const tokens[] = {
4967cb960a2Sdownsj 		"-o", "-a", "!", "(", ")"
4977cb960a2Sdownsj 	};
4987cb960a2Sdownsj 	int ret;
4997cb960a2Sdownsj 
5007cb960a2Sdownsj 	if (te->pos.wp >= te->wp_end)
5017cb960a2Sdownsj 		return meta == TM_END;
5027cb960a2Sdownsj 
5037cb960a2Sdownsj 	if (meta == TM_UNOP || meta == TM_BINOP)
5047cb960a2Sdownsj 		ret = (int) test_isop(te, meta, *te->pos.wp);
5057cb960a2Sdownsj 	else if (meta == TM_END)
5067cb960a2Sdownsj 		ret = 0;
5077cb960a2Sdownsj 	else
5087cb960a2Sdownsj 		ret = strcmp(*te->pos.wp, tokens[(int) meta]) == 0;
5097cb960a2Sdownsj 
5107cb960a2Sdownsj 	/* Accept the token? */
5117cb960a2Sdownsj 	if (ret)
5127cb960a2Sdownsj 		te->pos.wp++;
5137cb960a2Sdownsj 
5147cb960a2Sdownsj 	return ret;
5157cb960a2Sdownsj }
5167cb960a2Sdownsj 
5177cb960a2Sdownsj static const char *
ptest_getopnd(Test_env * te,Test_op op,int do_eval)518c5d5393cSotto ptest_getopnd(Test_env *te, Test_op op, int do_eval)
5197cb960a2Sdownsj {
5207cb960a2Sdownsj 	if (te->pos.wp >= te->wp_end)
521*990e11adSop 		return NULL;
5227cb960a2Sdownsj 	return *te->pos.wp++;
5237cb960a2Sdownsj }
5247cb960a2Sdownsj 
5257cb960a2Sdownsj static int
ptest_eval(Test_env * te,Test_op op,const char * opnd1,const char * opnd2,int do_eval)526c5d5393cSotto ptest_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2,
527c5d5393cSotto     int do_eval)
5287cb960a2Sdownsj {
5297cb960a2Sdownsj 	return test_eval(te, op, opnd1, opnd2, do_eval);
5307cb960a2Sdownsj }
5317cb960a2Sdownsj 
5327cb960a2Sdownsj static void
ptest_error(Test_env * te,int offset,const char * msg)533c5d5393cSotto ptest_error(Test_env *te, int offset, const char *msg)
5347cb960a2Sdownsj {
5357cb960a2Sdownsj 	const char *op = te->pos.wp + offset >= te->wp_end ?
536f7654a50Snicm 	    NULL : te->pos.wp[offset];
5377cb960a2Sdownsj 
5387cb960a2Sdownsj 	te->flags |= TEF_ERROR;
5397cb960a2Sdownsj 	if (op)
5407cb960a2Sdownsj 		bi_errorf("%s: %s", op, msg);
5417cb960a2Sdownsj 	else
5427cb960a2Sdownsj 		bi_errorf("%s", msg);
5437cb960a2Sdownsj }
544