1 /* $NetBSD: test.c,v 1.21 1999/04/05 09:48:38 kleink Exp $ */ 2 3 /* 4 * test(1); version 7-like -- author Erik Baalbergen 5 * modified by Eric Gisin to be used as built-in. 6 * modified by Arnold Robbins to add SVR3 compatibility 7 * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). 8 * modified by J.T. Conklin for NetBSD. 9 * 10 * This program is in the Public Domain. 11 */ 12 13 #include <sys/cdefs.h> 14 #ifndef lint 15 __RCSID("$NetBSD: test.c,v 1.21 1999/04/05 09:48:38 kleink Exp $"); 16 #endif 17 18 #include <sys/types.h> 19 #include <sys/stat.h> 20 #include <unistd.h> 21 #include <ctype.h> 22 #include <errno.h> 23 #include <stdio.h> 24 #include <stdlib.h> 25 #include <string.h> 26 #include <err.h> 27 28 /* test(1) accepts the following grammar: 29 oexpr ::= aexpr | aexpr "-o" oexpr ; 30 aexpr ::= nexpr | nexpr "-a" aexpr ; 31 nexpr ::= primary | "!" primary 32 primary ::= unary-operator operand 33 | operand binary-operator operand 34 | operand 35 | "(" oexpr ")" 36 ; 37 unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| 38 "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; 39 40 binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| 41 "-nt"|"-ot"|"-ef"; 42 operand ::= <any legal UNIX file name> 43 */ 44 45 enum token { 46 EOI, 47 FILRD, 48 FILWR, 49 FILEX, 50 FILEXIST, 51 FILREG, 52 FILDIR, 53 FILCDEV, 54 FILBDEV, 55 FILFIFO, 56 FILSOCK, 57 FILSYM, 58 FILGZ, 59 FILTT, 60 FILSUID, 61 FILSGID, 62 FILSTCK, 63 FILNT, 64 FILOT, 65 FILEQ, 66 FILUID, 67 FILGID, 68 STREZ, 69 STRNZ, 70 STREQ, 71 STRNE, 72 STRLT, 73 STRGT, 74 INTEQ, 75 INTNE, 76 INTGE, 77 INTGT, 78 INTLE, 79 INTLT, 80 UNOT, 81 BAND, 82 BOR, 83 LPAREN, 84 RPAREN, 85 OPERAND 86 }; 87 88 enum token_types { 89 UNOP, 90 BINOP, 91 BUNOP, 92 BBINOP, 93 PAREN 94 }; 95 96 struct t_op { 97 const char *op_text; 98 short op_num, op_type; 99 } const ops [] = { 100 {"-r", FILRD, UNOP}, 101 {"-w", FILWR, UNOP}, 102 {"-x", FILEX, UNOP}, 103 {"-e", FILEXIST,UNOP}, 104 {"-f", FILREG, UNOP}, 105 {"-d", FILDIR, UNOP}, 106 {"-c", FILCDEV,UNOP}, 107 {"-b", FILBDEV,UNOP}, 108 {"-p", FILFIFO,UNOP}, 109 {"-u", FILSUID,UNOP}, 110 {"-g", FILSGID,UNOP}, 111 {"-k", FILSTCK,UNOP}, 112 {"-s", FILGZ, UNOP}, 113 {"-t", FILTT, UNOP}, 114 {"-z", STREZ, UNOP}, 115 {"-n", STRNZ, UNOP}, 116 {"-h", FILSYM, UNOP}, /* for backwards compat */ 117 {"-O", FILUID, UNOP}, 118 {"-G", FILGID, UNOP}, 119 {"-L", FILSYM, UNOP}, 120 {"-S", FILSOCK,UNOP}, 121 {"=", STREQ, BINOP}, 122 {"!=", STRNE, BINOP}, 123 {"<", STRLT, BINOP}, 124 {">", STRGT, BINOP}, 125 {"-eq", INTEQ, BINOP}, 126 {"-ne", INTNE, BINOP}, 127 {"-ge", INTGE, BINOP}, 128 {"-gt", INTGT, BINOP}, 129 {"-le", INTLE, BINOP}, 130 {"-lt", INTLT, BINOP}, 131 {"-nt", FILNT, BINOP}, 132 {"-ot", FILOT, BINOP}, 133 {"-ef", FILEQ, BINOP}, 134 {"!", UNOT, BUNOP}, 135 {"-a", BAND, BBINOP}, 136 {"-o", BOR, BBINOP}, 137 {"(", LPAREN, PAREN}, 138 {")", RPAREN, PAREN}, 139 {0, 0, 0} 140 }; 141 142 char **t_wp; 143 struct t_op const *t_wp_op; 144 145 static void syntax __P((const char *, const char *)); 146 static int oexpr __P((enum token)); 147 static int aexpr __P((enum token)); 148 static int nexpr __P((enum token)); 149 static int primary __P((enum token)); 150 static int binop __P((void)); 151 static int filstat __P((char *, enum token)); 152 static enum token t_lex __P((char *)); 153 static int isoperand __P((void)); 154 static int getn __P((const char *)); 155 static int newerf __P((const char *, const char *)); 156 static int olderf __P((const char *, const char *)); 157 static int equalf __P((const char *, const char *)); 158 159 int main __P((int, char **)); 160 161 int 162 main(argc, argv) 163 int argc; 164 char **argv; 165 { 166 int res; 167 168 if (strcmp(argv[0], "[") == 0) { 169 if (strcmp(argv[--argc], "]")) 170 errx(2, "missing ]"); 171 argv[argc] = NULL; 172 } 173 174 t_wp = &argv[1]; 175 res = !oexpr(t_lex(*t_wp)); 176 177 if (*t_wp != NULL && *++t_wp != NULL) 178 syntax(*t_wp, "unexpected operator"); 179 180 return res; 181 } 182 183 static void 184 syntax(op, msg) 185 const char *op; 186 const char *msg; 187 { 188 if (op && *op) 189 errx(2, "%s: %s", op, msg); 190 else 191 errx(2, "%s", msg); 192 } 193 194 static int 195 oexpr(n) 196 enum token n; 197 { 198 int res; 199 200 res = aexpr(n); 201 if (t_lex(*++t_wp) == BOR) 202 return oexpr(t_lex(*++t_wp)) || res; 203 t_wp--; 204 return res; 205 } 206 207 static int 208 aexpr(n) 209 enum token n; 210 { 211 int res; 212 213 res = nexpr(n); 214 if (t_lex(*++t_wp) == BAND) 215 return aexpr(t_lex(*++t_wp)) && res; 216 t_wp--; 217 return res; 218 } 219 220 static int 221 nexpr(n) 222 enum token n; /* token */ 223 { 224 if (n == UNOT) 225 return !nexpr(t_lex(*++t_wp)); 226 return primary(n); 227 } 228 229 static int 230 primary(n) 231 enum token n; 232 { 233 enum token nn; 234 int res; 235 236 if (n == EOI) 237 return 0; /* missing expression */ 238 if (n == LPAREN) { 239 if ((nn = t_lex(*++t_wp)) == RPAREN) 240 return 0; /* missing expression */ 241 res = oexpr(nn); 242 if (t_lex(*++t_wp) != RPAREN) 243 syntax(NULL, "closing paren expected"); 244 return res; 245 } 246 if (t_wp_op && t_wp_op->op_type == UNOP) { 247 /* unary expression */ 248 if (*++t_wp == NULL) 249 syntax(t_wp_op->op_text, "argument expected"); 250 switch (n) { 251 case STREZ: 252 return strlen(*t_wp) == 0; 253 case STRNZ: 254 return strlen(*t_wp) != 0; 255 case FILTT: 256 return isatty(getn(*t_wp)); 257 default: 258 return filstat(*t_wp, n); 259 } 260 } 261 262 if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) { 263 return binop(); 264 } 265 266 return strlen(*t_wp) > 0; 267 } 268 269 static int 270 binop() 271 { 272 const char *opnd1, *opnd2; 273 struct t_op const *op; 274 275 opnd1 = *t_wp; 276 (void) t_lex(*++t_wp); 277 op = t_wp_op; 278 279 if ((opnd2 = *++t_wp) == (char *)0) 280 syntax(op->op_text, "argument expected"); 281 282 switch (op->op_num) { 283 case STREQ: 284 return strcmp(opnd1, opnd2) == 0; 285 case STRNE: 286 return strcmp(opnd1, opnd2) != 0; 287 case STRLT: 288 return strcmp(opnd1, opnd2) < 0; 289 case STRGT: 290 return strcmp(opnd1, opnd2) > 0; 291 case INTEQ: 292 return getn(opnd1) == getn(opnd2); 293 case INTNE: 294 return getn(opnd1) != getn(opnd2); 295 case INTGE: 296 return getn(opnd1) >= getn(opnd2); 297 case INTGT: 298 return getn(opnd1) > getn(opnd2); 299 case INTLE: 300 return getn(opnd1) <= getn(opnd2); 301 case INTLT: 302 return getn(opnd1) < getn(opnd2); 303 case FILNT: 304 return newerf (opnd1, opnd2); 305 case FILOT: 306 return olderf (opnd1, opnd2); 307 case FILEQ: 308 return equalf (opnd1, opnd2); 309 default: 310 abort(); 311 /* NOTREACHED */ 312 } 313 } 314 315 static int 316 filstat(nm, mode) 317 char *nm; 318 enum token mode; 319 { 320 struct stat s; 321 322 if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) 323 return 0; 324 325 switch (mode) { 326 case FILRD: 327 return access(nm, R_OK) == 0; 328 case FILWR: 329 return access(nm, W_OK) == 0; 330 case FILEX: 331 return access(nm, X_OK) == 0; 332 case FILEXIST: 333 return access(nm, F_OK) == 0; 334 case FILREG: 335 return S_ISREG(s.st_mode); 336 case FILDIR: 337 return S_ISDIR(s.st_mode); 338 case FILCDEV: 339 return S_ISCHR(s.st_mode); 340 case FILBDEV: 341 return S_ISBLK(s.st_mode); 342 case FILFIFO: 343 return S_ISFIFO(s.st_mode); 344 case FILSOCK: 345 return S_ISSOCK(s.st_mode); 346 case FILSYM: 347 return S_ISLNK(s.st_mode); 348 case FILSUID: 349 return (s.st_mode & S_ISUID) != 0; 350 case FILSGID: 351 return (s.st_mode & S_ISGID) != 0; 352 case FILSTCK: 353 return (s.st_mode & S_ISVTX) != 0; 354 case FILGZ: 355 return s.st_size > (off_t)0; 356 case FILUID: 357 return s.st_uid == geteuid(); 358 case FILGID: 359 return s.st_gid == getegid(); 360 default: 361 return 1; 362 } 363 } 364 365 static enum token 366 t_lex(s) 367 char *s; 368 { 369 struct t_op const *op = ops; 370 371 if (s == 0) { 372 t_wp_op = (struct t_op *)0; 373 return EOI; 374 } 375 while (op->op_text) { 376 if (strcmp(s, op->op_text) == 0) { 377 if ((op->op_type == UNOP && isoperand()) || 378 (op->op_num == LPAREN && *(t_wp+1) == 0)) 379 break; 380 t_wp_op = op; 381 return op->op_num; 382 } 383 op++; 384 } 385 t_wp_op = (struct t_op *)0; 386 return OPERAND; 387 } 388 389 static int 390 isoperand() 391 { 392 struct t_op const *op = ops; 393 char *s; 394 char *t; 395 396 if ((s = *(t_wp+1)) == 0) 397 return 1; 398 if ((t = *(t_wp+2)) == 0) 399 return 0; 400 while (op->op_text) { 401 if (strcmp(s, op->op_text) == 0) 402 return op->op_type == BINOP && 403 (t[0] != ')' || t[1] != '\0'); 404 op++; 405 } 406 return 0; 407 } 408 409 /* atoi with error detection */ 410 static int 411 getn(s) 412 const char *s; 413 { 414 char *p; 415 long r; 416 417 errno = 0; 418 r = strtol(s, &p, 10); 419 420 if (errno != 0) 421 errx(2, "%s: out of range", s); 422 423 while (isspace((unsigned char)*p)) 424 p++; 425 426 if (*p) 427 errx(2, "%s: bad number", s); 428 429 return (int) r; 430 } 431 432 static int 433 newerf (f1, f2) 434 const char *f1, *f2; 435 { 436 struct stat b1, b2; 437 438 return (stat (f1, &b1) == 0 && 439 stat (f2, &b2) == 0 && 440 b1.st_mtime > b2.st_mtime); 441 } 442 443 static int 444 olderf (f1, f2) 445 const char *f1, *f2; 446 { 447 struct stat b1, b2; 448 449 return (stat (f1, &b1) == 0 && 450 stat (f2, &b2) == 0 && 451 b1.st_mtime < b2.st_mtime); 452 } 453 454 static int 455 equalf (f1, f2) 456 const char *f1, *f2; 457 { 458 struct stat b1, b2; 459 460 return (stat (f1, &b1) == 0 && 461 stat (f2, &b2) == 0 && 462 b1.st_dev == b2.st_dev && 463 b1.st_ino == b2.st_ino); 464 } 465