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