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 __FBSDID("$FreeBSD$"); 15 16 #include <sys/types.h> 17 #include <sys/stat.h> 18 19 #include <ctype.h> 20 #include <err.h> 21 #include <errno.h> 22 #include <inttypes.h> 23 #include <limits.h> 24 #include <stdarg.h> 25 #include <stdio.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <unistd.h> 29 30 #ifdef SHELL 31 #define main testcmd 32 #include "bltin/bltin.h" 33 #else 34 #include <locale.h> 35 36 static void error(const char *, ...) __dead2 __printf0like(1, 2); 37 38 static void 39 error(const char *msg, ...) 40 { 41 va_list ap; 42 va_start(ap, msg); 43 verrx(2, msg, ap); 44 /*NOTREACHED*/ 45 va_end(ap); 46 } 47 #endif 48 49 /* test(1) accepts the following grammar: 50 oexpr ::= aexpr | aexpr "-o" oexpr ; 51 aexpr ::= nexpr | nexpr "-a" aexpr ; 52 nexpr ::= primary | "!" primary 53 primary ::= unary-operator operand 54 | operand binary-operator operand 55 | operand 56 | "(" oexpr ")" 57 ; 58 unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| 59 "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; 60 61 binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| 62 "-nt"|"-ot"|"-ef"; 63 operand ::= <any legal UNIX file name> 64 */ 65 66 enum token { 67 EOI, 68 FILRD, 69 FILWR, 70 FILEX, 71 FILEXIST, 72 FILREG, 73 FILDIR, 74 FILCDEV, 75 FILBDEV, 76 FILFIFO, 77 FILSOCK, 78 FILSYM, 79 FILGZ, 80 FILTT, 81 FILSUID, 82 FILSGID, 83 FILSTCK, 84 FILNT, 85 FILOT, 86 FILEQ, 87 FILUID, 88 FILGID, 89 STREZ, 90 STRNZ, 91 STREQ, 92 STRNE, 93 STRLT, 94 STRGT, 95 INTEQ, 96 INTNE, 97 INTGE, 98 INTGT, 99 INTLE, 100 INTLT, 101 UNOT, 102 BAND, 103 BOR, 104 LPAREN, 105 RPAREN, 106 OPERAND 107 }; 108 109 enum token_types { 110 UNOP, 111 BINOP, 112 BUNOP, 113 BBINOP, 114 PAREN 115 }; 116 117 struct t_op { 118 const char *op_text; 119 short op_num, op_type; 120 } const ops [] = { 121 {"-r", FILRD, UNOP}, 122 {"-w", FILWR, UNOP}, 123 {"-x", FILEX, UNOP}, 124 {"-e", FILEXIST,UNOP}, 125 {"-f", FILREG, UNOP}, 126 {"-d", FILDIR, UNOP}, 127 {"-c", FILCDEV,UNOP}, 128 {"-b", FILBDEV,UNOP}, 129 {"-p", FILFIFO,UNOP}, 130 {"-u", FILSUID,UNOP}, 131 {"-g", FILSGID,UNOP}, 132 {"-k", FILSTCK,UNOP}, 133 {"-s", FILGZ, UNOP}, 134 {"-t", FILTT, UNOP}, 135 {"-z", STREZ, UNOP}, 136 {"-n", STRNZ, UNOP}, 137 {"-h", FILSYM, UNOP}, /* for backwards compat */ 138 {"-O", FILUID, UNOP}, 139 {"-G", FILGID, UNOP}, 140 {"-L", FILSYM, UNOP}, 141 {"-S", FILSOCK,UNOP}, 142 {"=", STREQ, BINOP}, 143 {"!=", STRNE, BINOP}, 144 {"<", STRLT, BINOP}, 145 {">", STRGT, BINOP}, 146 {"-eq", INTEQ, BINOP}, 147 {"-ne", INTNE, BINOP}, 148 {"-ge", INTGE, BINOP}, 149 {"-gt", INTGT, BINOP}, 150 {"-le", INTLE, BINOP}, 151 {"-lt", INTLT, BINOP}, 152 {"-nt", FILNT, BINOP}, 153 {"-ot", FILOT, BINOP}, 154 {"-ef", FILEQ, BINOP}, 155 {"!", UNOT, BUNOP}, 156 {"-a", BAND, BBINOP}, 157 {"-o", BOR, BBINOP}, 158 {"(", LPAREN, PAREN}, 159 {")", RPAREN, PAREN}, 160 {0, 0, 0} 161 }; 162 163 struct t_op const *t_wp_op; 164 int nargc; 165 char **t_wp; 166 int parenlevel; 167 168 static int aexpr(enum token); 169 static int binop(void); 170 static int equalf(const char *, const char *); 171 static int filstat(char *, enum token); 172 static int getn(const char *); 173 static intmax_t getq(const char *); 174 static int intcmp(const char *, const char *); 175 static int isunopoperand(void); 176 static int islparenoperand(void); 177 static int isrparenoperand(void); 178 static int newerf(const char *, const char *); 179 static int nexpr(enum token); 180 static int oexpr(enum token); 181 static int olderf(const char *, const char *); 182 static int primary(enum token); 183 static void syntax(const char *, const char *); 184 static enum token t_lex(char *); 185 186 int 187 main(int argc, char **argv) 188 { 189 int res; 190 char *p; 191 192 if ((p = rindex(argv[0], '/')) == NULL) 193 p = argv[0]; 194 else 195 p++; 196 if (strcmp(p, "[") == 0) { 197 if (strcmp(argv[--argc], "]") != 0) 198 error("missing ]"); 199 argv[argc] = NULL; 200 } 201 202 /* no expression => false */ 203 if (--argc <= 0) 204 return 1; 205 206 #ifndef SHELL 207 (void)setlocale(LC_CTYPE, ""); 208 #endif 209 nargc = argc; 210 t_wp = &argv[1]; 211 parenlevel = 0; 212 if (nargc == 4 && strcmp(*t_wp, "!") == 0) { 213 /* Things like ! "" -o x do not fit in the normal grammar. */ 214 --nargc; 215 ++t_wp; 216 res = oexpr(t_lex(*t_wp)); 217 } else 218 res = !oexpr(t_lex(*t_wp)); 219 220 if (--nargc > 0) 221 syntax(*t_wp, "unexpected operator"); 222 223 return res; 224 } 225 226 static void 227 syntax(const char *op, const char *msg) 228 { 229 230 if (op && *op) 231 error("%s: %s", op, msg); 232 else 233 error("%s", msg); 234 } 235 236 static int 237 oexpr(enum token n) 238 { 239 int res; 240 241 res = aexpr(n); 242 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BOR) 243 return oexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) || 244 res; 245 t_wp--; 246 nargc++; 247 return res; 248 } 249 250 static int 251 aexpr(enum token n) 252 { 253 int res; 254 255 res = nexpr(n); 256 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BAND) 257 return aexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) && 258 res; 259 t_wp--; 260 nargc++; 261 return res; 262 } 263 264 static int 265 nexpr(enum token n) 266 { 267 if (n == UNOT) 268 return !nexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)); 269 return primary(n); 270 } 271 272 static int 273 primary(enum token n) 274 { 275 enum token nn; 276 int res; 277 278 if (n == EOI) 279 return 0; /* missing expression */ 280 if (n == LPAREN) { 281 parenlevel++; 282 if ((nn = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) == 283 RPAREN) { 284 parenlevel--; 285 return 0; /* missing expression */ 286 } 287 res = oexpr(nn); 288 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) != RPAREN) 289 syntax(NULL, "closing paren expected"); 290 parenlevel--; 291 return res; 292 } 293 if (t_wp_op && t_wp_op->op_type == UNOP) { 294 /* unary expression */ 295 if (--nargc == 0) 296 syntax(t_wp_op->op_text, "argument expected"); 297 switch (n) { 298 case STREZ: 299 return strlen(*++t_wp) == 0; 300 case STRNZ: 301 return strlen(*++t_wp) != 0; 302 case FILTT: 303 return isatty(getn(*++t_wp)); 304 default: 305 return filstat(*++t_wp, n); 306 } 307 } 308 309 if (t_lex(nargc > 0 ? t_wp[1] : NULL), t_wp_op && t_wp_op->op_type == 310 BINOP) { 311 return binop(); 312 } 313 314 return strlen(*t_wp) > 0; 315 } 316 317 static int 318 binop(void) 319 { 320 const char *opnd1, *opnd2; 321 struct t_op const *op; 322 323 opnd1 = *t_wp; 324 (void) t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL); 325 op = t_wp_op; 326 327 if ((opnd2 = nargc > 0 ? (--nargc, *++t_wp) : NULL) == NULL) 328 syntax(op->op_text, "argument expected"); 329 330 switch (op->op_num) { 331 case STREQ: 332 return strcmp(opnd1, opnd2) == 0; 333 case STRNE: 334 return strcmp(opnd1, opnd2) != 0; 335 case STRLT: 336 return strcmp(opnd1, opnd2) < 0; 337 case STRGT: 338 return strcmp(opnd1, opnd2) > 0; 339 case INTEQ: 340 return intcmp(opnd1, opnd2) == 0; 341 case INTNE: 342 return intcmp(opnd1, opnd2) != 0; 343 case INTGE: 344 return intcmp(opnd1, opnd2) >= 0; 345 case INTGT: 346 return intcmp(opnd1, opnd2) > 0; 347 case INTLE: 348 return intcmp(opnd1, opnd2) <= 0; 349 case INTLT: 350 return intcmp(opnd1, opnd2) < 0; 351 case FILNT: 352 return newerf (opnd1, opnd2); 353 case FILOT: 354 return olderf (opnd1, opnd2); 355 case FILEQ: 356 return equalf (opnd1, opnd2); 357 default: 358 abort(); 359 /* NOTREACHED */ 360 } 361 } 362 363 static int 364 filstat(char *nm, enum token mode) 365 { 366 struct stat s; 367 368 if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) 369 return 0; 370 371 switch (mode) { 372 case FILRD: 373 return (eaccess(nm, R_OK) == 0); 374 case FILWR: 375 return (eaccess(nm, W_OK) == 0); 376 case FILEX: 377 /* XXX work around eaccess(2) false positives for superuser */ 378 if (eaccess(nm, X_OK) != 0) 379 return 0; 380 if (S_ISDIR(s.st_mode) || geteuid() != 0) 381 return 1; 382 return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0; 383 case FILEXIST: 384 return (eaccess(nm, F_OK) == 0); 385 case FILREG: 386 return S_ISREG(s.st_mode); 387 case FILDIR: 388 return S_ISDIR(s.st_mode); 389 case FILCDEV: 390 return S_ISCHR(s.st_mode); 391 case FILBDEV: 392 return S_ISBLK(s.st_mode); 393 case FILFIFO: 394 return S_ISFIFO(s.st_mode); 395 case FILSOCK: 396 return S_ISSOCK(s.st_mode); 397 case FILSYM: 398 return S_ISLNK(s.st_mode); 399 case FILSUID: 400 return (s.st_mode & S_ISUID) != 0; 401 case FILSGID: 402 return (s.st_mode & S_ISGID) != 0; 403 case FILSTCK: 404 return (s.st_mode & S_ISVTX) != 0; 405 case FILGZ: 406 return s.st_size > (off_t)0; 407 case FILUID: 408 return s.st_uid == geteuid(); 409 case FILGID: 410 return s.st_gid == getegid(); 411 default: 412 return 1; 413 } 414 } 415 416 static enum token 417 t_lex(char *s) 418 { 419 struct t_op const *op = ops; 420 421 if (s == 0) { 422 t_wp_op = NULL; 423 return EOI; 424 } 425 while (op->op_text) { 426 if (strcmp(s, op->op_text) == 0) { 427 if (((op->op_type == UNOP || op->op_type == BUNOP) 428 && isunopoperand()) || 429 (op->op_num == LPAREN && islparenoperand()) || 430 (op->op_num == RPAREN && isrparenoperand())) 431 break; 432 t_wp_op = op; 433 return op->op_num; 434 } 435 op++; 436 } 437 t_wp_op = NULL; 438 return OPERAND; 439 } 440 441 static int 442 isunopoperand(void) 443 { 444 struct t_op const *op = ops; 445 char *s; 446 char *t; 447 448 if (nargc == 1) 449 return 1; 450 s = *(t_wp + 1); 451 if (nargc == 2) 452 return parenlevel == 1 && strcmp(s, ")") == 0; 453 t = *(t_wp + 2); 454 while (op->op_text) { 455 if (strcmp(s, op->op_text) == 0) 456 return op->op_type == BINOP && 457 (parenlevel == 0 || t[0] != ')' || t[1] != '\0'); 458 op++; 459 } 460 return 0; 461 } 462 463 static int 464 islparenoperand(void) 465 { 466 struct t_op const *op = ops; 467 char *s; 468 469 if (nargc == 1) 470 return 1; 471 s = *(t_wp + 1); 472 if (nargc == 2) 473 return parenlevel == 1 && strcmp(s, ")") == 0; 474 if (nargc != 3) 475 return 0; 476 while (op->op_text) { 477 if (strcmp(s, op->op_text) == 0) 478 return op->op_type == BINOP; 479 op++; 480 } 481 return 0; 482 } 483 484 static int 485 isrparenoperand(void) 486 { 487 char *s; 488 489 if (nargc == 1) 490 return 0; 491 s = *(t_wp + 1); 492 if (nargc == 2) 493 return parenlevel == 1 && strcmp(s, ")") == 0; 494 return 0; 495 } 496 497 /* atoi with error detection */ 498 static int 499 getn(const char *s) 500 { 501 char *p; 502 long r; 503 504 errno = 0; 505 r = strtol(s, &p, 10); 506 507 if (s == p) 508 error("%s: bad number", s); 509 510 if (errno != 0) 511 error((errno == EINVAL) ? "%s: bad number" : 512 "%s: out of range", s); 513 514 while (isspace((unsigned char)*p)) 515 p++; 516 517 if (*p) 518 error("%s: bad number", s); 519 520 return (int) r; 521 } 522 523 /* atoi with error detection and 64 bit range */ 524 static intmax_t 525 getq(const char *s) 526 { 527 char *p; 528 intmax_t r; 529 530 errno = 0; 531 r = strtoimax(s, &p, 10); 532 533 if (s == p) 534 error("%s: bad number", s); 535 536 if (errno != 0) 537 error((errno == EINVAL) ? "%s: bad number" : 538 "%s: out of range", s); 539 540 while (isspace((unsigned char)*p)) 541 p++; 542 543 if (*p) 544 error("%s: bad number", s); 545 546 return r; 547 } 548 549 static int 550 intcmp (const char *s1, const char *s2) 551 { 552 intmax_t q1, q2; 553 554 555 q1 = getq(s1); 556 q2 = getq(s2); 557 558 if (q1 > q2) 559 return 1; 560 561 if (q1 < q2) 562 return -1; 563 564 return 0; 565 } 566 567 static int 568 newerf (const char *f1, const char *f2) 569 { 570 struct stat b1, b2; 571 572 if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0) 573 return 0; 574 575 if (b1.st_mtim.tv_sec > b2.st_mtim.tv_sec) 576 return 1; 577 if (b1.st_mtim.tv_sec < b2.st_mtim.tv_sec) 578 return 0; 579 580 return (b1.st_mtim.tv_nsec > b2.st_mtim.tv_nsec); 581 } 582 583 static int 584 olderf (const char *f1, const char *f2) 585 { 586 return (newerf(f2, f1)); 587 } 588 589 static int 590 equalf (const char *f1, const char *f2) 591 { 592 struct stat b1, b2; 593 594 return (stat (f1, &b1) == 0 && 595 stat (f2, &b2) == 0 && 596 b1.st_dev == b2.st_dev && 597 b1.st_ino == b2.st_ino); 598 } 599