1/* test.c - evaluate expression 2 * 3 * Copyright 2018 Rob Landley <rob@landley.net> 4 * 5 * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html 6 7USE_TEST(NEWTOY(test, 0, TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_NOHELP|TOYFLAG_MAYFORK)) 8USE_TEST(OLDTOY([, test, TOYFLAG_NOFORK|TOYFLAG_NOHELP)) 9 10config TEST 11 bool "test" 12 default y 13 help 14 usage: test [-bcdefghLPrSsuwx PATH] [-nz STRING] [-t FD] [X ?? Y] 15 16 Return true or false by performing tests. (With no arguments return false.) 17 18 --- Tests with a single argument (after the option): 19 PATH is/has: 20 -b block device -f regular file -p fifo -u setuid bit 21 -c char device -g setgid -r read bit -w write bit 22 -d directory -h symlink -S socket -x execute bit 23 -e exists -L symlink -s nonzero size 24 STRING is: 25 -n nonzero size -z zero size (STRING by itself implies -n) 26 FD (integer file descriptor) is: 27 -t a TTY 28 29 --- Tests with one argument on each side of an operator: 30 Two strings: 31 = are identical != differ 32 Two integers: 33 -eq equal -gt first > second -lt first < second 34 -ne not equal -ge first >= second -le first <= second 35 36 --- Modify or combine tests: 37 ! EXPR not (swap true/false) EXPR -a EXPR and (are both true) 38 ( EXPR ) evaluate this first EXPR -o EXPR or (is either true) 39*/ 40 41#include "toys.h" 42 43// Consume 3, 2, or 1 argument test, returning result and *count used. 44int do_test(char **args, int *count) 45{ 46 char c, *s; 47 int i; 48 49 if (*count>=3) { 50 *count = 3; 51 char *s = args[1], *ss = "eqnegtgeltle"; 52 if (!strcmp(s, "=") || !strcmp(s, "==")) return !strcmp(args[0], args[2]); 53 if (!strcmp(s, "!=")) return strcmp(args[0], args[2]); 54 if (*s=='-' && strlen(s)==3 && (s = strstr(ss, s+1)) && !((i = s-ss)&1)) { 55 long long a = atolx(args[0]), b = atolx(args[2]); 56 57 if (!i) return a == b; 58 if (i==2) return a != b; 59 if (i==4) return a > b; 60 if (i==6) return a >= b; 61 if (i==8) return a < b; 62 if (i==10) return a<= b; 63 } 64 } 65 s = *args; 66 if (*count>=2 && *s == '-' && s[1] && !s[2]) { 67 *count = 2; 68 c = s[1]; 69 if (-1 != (i = stridx("hLbcdefgpSusxwr", c))) { 70 struct stat st; 71 72 // stat or lstat, then handle rwx and s 73 if (-1 == ((i<2) ? lstat : stat)(args[1], &st)) return 0; 74 if (i>=12) return !!(st.st_mode&(0x111<<(i-12))); 75 if (c == 's') return !!st.st_size; // otherwise 1<<32 == 0 76 77 // handle file type checking and SUID/SGID 78 if ((i = (unsigned short []){80,80,48,16,32,0,64,2,8,96,4}[i]<<9)>=4096) 79 return (st.st_mode&S_IFMT) == i; 80 else return (st.st_mode & i) == i; 81 } else if (c == 'z') return !*args[1]; 82 else if (c == 'n') return *args[1]; 83 else if (c == 't') return isatty(atolx(args[1])); 84 } 85 return *count = 0; 86} 87 88#define NOT 1 // Most recent test had an odd number of preceding ! 89#define AND 2 // test before -a failed since -o or ( so force false 90#define OR 4 // test before -o succeeded since ( so force true 91void test_main(void) 92{ 93 char *s; 94 int pos, paren, pstack, result = 0; 95 96 toys.exitval = 2; 97 if (!strcmp("[", toys.which->name)) 98 if (!toys.optc || strcmp("]", toys.optargs[--toys.optc])) 99 error_exit("Missing ']'"); 100 101 // loop through command line arguments 102 if (toys.optc) for (pos = paren = pstack = 0; ; pos++) { 103 int len = toys.optc-pos; 104 105 if (!toys.optargs[pos]) perror_exit("need arg @%d", pos); 106 107 // Evaluate next test 108 result = do_test(toys.optargs+pos, &len); 109 pos += len; 110 // Single argument could be ! ( or nonempty 111 if (!len) { 112 if (toys.optargs[pos+1]) { 113 if (!strcmp("!", toys.optargs[pos])) { 114 pstack ^= NOT; 115 continue; 116 } 117 if (!strcmp("(", toys.optargs[pos])) { 118 if (++paren>9) perror_exit("bad ("); 119 pstack <<= 3; 120 continue; 121 } 122 } 123 result = *toys.optargs[pos++]; 124 } 125 s = toys.optargs[pos]; 126 for (;;) { 127 128 // Handle pending ! -a -o (the else means -o beats -a) 129 if (pstack&NOT) result = !result; 130 pstack &= ~NOT; 131 if (pstack&OR) result = 1; 132 else if (pstack&AND) result = 0; 133 134 // Do it again for every ) 135 if (!paren || !s || strcmp(")", s)) break; 136 paren--; 137 pstack >>= 3; 138 s = toys.optargs[++pos]; 139 } 140 141 // Out of arguments? 142 if (!s) { 143 if (paren) perror_exit("need )"); 144 break; 145 } 146 147 // are we followed by -a or -o? 148 149 if (!strcmp("-a", s)) { 150 if (!result) pstack |= AND; 151 } else if (!strcmp("-o", s)) { 152 // -o flushes -a even if previous test was false 153 pstack &=~AND; 154 if (result) pstack |= OR; 155 } else error_exit("too many arguments"); 156 } 157 158 // Invert C logic to get shell logic 159 toys.exitval = !result; 160} 161