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 * Important: This file is used both as a standalone program /bin/test and 14 * as a builtin for /bin/sh (#define SHELL). 15 */ 16 17 #include <sys/cdefs.h> 18 __FBSDID("$FreeBSD$"); 19 20 #include <sys/types.h> 21 #include <sys/stat.h> 22 23 #include <ctype.h> 24 #include <err.h> 25 #include <errno.h> 26 #include <inttypes.h> 27 #include <limits.h> 28 #include <stdarg.h> 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <string.h> 32 #include <unistd.h> 33 34 #ifdef SHELL 35 #define main testcmd 36 #include "bltin/bltin.h" 37 #else 38 #include <locale.h> 39 40 static void error(const char *, ...) __dead2 __printf0like(1, 2); 41 42 static void 43 error(const char *msg, ...) 44 { 45 va_list ap; 46 va_start(ap, msg); 47 verrx(2, msg, ap); 48 /*NOTREACHED*/ 49 va_end(ap); 50 } 51 #endif 52 53 /* test(1) accepts the following grammar: 54 oexpr ::= aexpr | aexpr "-o" oexpr ; 55 aexpr ::= nexpr | nexpr "-a" aexpr ; 56 nexpr ::= primary | "!" primary 57 primary ::= unary-operator operand 58 | operand binary-operator operand 59 | operand 60 | "(" oexpr ")" 61 ; 62 unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| 63 "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; 64 65 binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| 66 "-nt"|"-nt[abcm][abcm]"|"-ot"|"-ot[abcm][abcm])"|"-ef"; 67 operand ::= <any legal UNIX file name> 68 */ 69 70 enum token { 71 EOI, 72 FILRD, 73 FILWR, 74 FILEX, 75 FILEXIST, 76 FILREG, 77 FILDIR, 78 FILCDEV, 79 FILBDEV, 80 FILFIFO, 81 FILSOCK, 82 FILSYM, 83 FILGZ, 84 FILTT, 85 FILSUID, 86 FILSGID, 87 FILSTCK, 88 FILNTAA, 89 FILNTAB, 90 FILNTAC, 91 FILNTAM, 92 FILNTBA, 93 FILNTBB, 94 FILNTBC, 95 FILNTBM, 96 FILNTCA, 97 FILNTCB, 98 FILNTCC, 99 FILNTCM, 100 FILNTMA, 101 FILNTMB, 102 FILNTMC, 103 FILNTMM, 104 FILOTAA, 105 FILOTAB, 106 FILOTAC, 107 FILOTAM, 108 FILOTBA, 109 FILOTBB, 110 FILOTBC, 111 FILOTBM, 112 FILOTCA, 113 FILOTCB, 114 FILOTCC, 115 FILOTCM, 116 FILOTMA, 117 FILOTMB, 118 FILOTMC, 119 FILOTMM, 120 FILEQ, 121 FILUID, 122 FILGID, 123 STREZ, 124 STRNZ, 125 STREQ, 126 STRNE, 127 STRLT, 128 STRGT, 129 INTEQ, 130 INTNE, 131 INTGE, 132 INTGT, 133 INTLE, 134 INTLT, 135 UNOT, 136 BAND, 137 BOR, 138 LPAREN, 139 RPAREN, 140 OPERAND 141 }; 142 143 enum token_types { 144 UNOP, 145 BINOP, 146 BUNOP, 147 BBINOP, 148 PAREN 149 }; 150 151 enum time_types { 152 ATIME, 153 BTIME, 154 CTIME, 155 MTIME 156 }; 157 158 static struct t_op { 159 char op_text[6]; 160 char op_num, op_type; 161 } const ops [] = { 162 {"-r", FILRD, UNOP}, 163 {"-w", FILWR, UNOP}, 164 {"-x", FILEX, UNOP}, 165 {"-e", FILEXIST,UNOP}, 166 {"-f", FILREG, UNOP}, 167 {"-d", FILDIR, UNOP}, 168 {"-c", FILCDEV,UNOP}, 169 {"-b", FILBDEV,UNOP}, 170 {"-p", FILFIFO,UNOP}, 171 {"-u", FILSUID,UNOP}, 172 {"-g", FILSGID,UNOP}, 173 {"-k", FILSTCK,UNOP}, 174 {"-s", FILGZ, UNOP}, 175 {"-t", FILTT, UNOP}, 176 {"-z", STREZ, UNOP}, 177 {"-n", STRNZ, UNOP}, 178 {"-h", FILSYM, UNOP}, /* for backwards compat */ 179 {"-O", FILUID, UNOP}, 180 {"-G", FILGID, UNOP}, 181 {"-L", FILSYM, UNOP}, 182 {"-S", FILSOCK,UNOP}, 183 {"=", STREQ, BINOP}, 184 {"==", STREQ, BINOP}, 185 {"!=", STRNE, BINOP}, 186 {"<", STRLT, BINOP}, 187 {">", STRGT, BINOP}, 188 {"-eq", INTEQ, BINOP}, 189 {"-ne", INTNE, BINOP}, 190 {"-ge", INTGE, BINOP}, 191 {"-gt", INTGT, BINOP}, 192 {"-le", INTLE, BINOP}, 193 {"-lt", INTLT, BINOP}, 194 {"-nt", FILNTMM, BINOP}, 195 {"-ntaa", FILNTAA, BINOP}, 196 {"-ntab", FILNTAB, BINOP}, 197 {"-ntac", FILNTAC, BINOP}, 198 {"-ntam", FILNTAM, BINOP}, 199 {"-ntba", FILNTBA, BINOP}, 200 {"-ntbb", FILNTBB, BINOP}, 201 {"-ntbc", FILNTBC, BINOP}, 202 {"-ntbm", FILNTBM, BINOP}, 203 {"-ntca", FILNTCA, BINOP}, 204 {"-ntcb", FILNTCB, BINOP}, 205 {"-ntcc", FILNTCC, BINOP}, 206 {"-ntcm", FILNTCM, BINOP}, 207 {"-ntma", FILNTMA, BINOP}, 208 {"-ntmb", FILNTMB, BINOP}, 209 {"-ntmc", FILNTMC, BINOP}, 210 {"-ntmm", FILNTMM, BINOP}, 211 {"-ot", FILOTMM, BINOP}, 212 {"-otaa", FILOTAA, BINOP}, 213 {"-otab", FILOTBB, BINOP}, 214 {"-otac", FILOTAC, BINOP}, 215 {"-otam", FILOTAM, BINOP}, 216 {"-otba", FILOTBA, BINOP}, 217 {"-otbb", FILOTBB, BINOP}, 218 {"-otbc", FILOTBC, BINOP}, 219 {"-otbm", FILOTBM, BINOP}, 220 {"-otca", FILOTCA, BINOP}, 221 {"-otcb", FILOTCB, BINOP}, 222 {"-otcc", FILOTCC, BINOP}, 223 {"-otcm", FILOTCM, BINOP}, 224 {"-otma", FILOTMA, BINOP}, 225 {"-otmb", FILOTMB, BINOP}, 226 {"-otmc", FILOTMC, BINOP}, 227 {"-otmm", FILOTMM, BINOP}, 228 {"-ef", FILEQ, BINOP}, 229 {"!", UNOT, BUNOP}, 230 {"-a", BAND, BBINOP}, 231 {"-o", BOR, BBINOP}, 232 {"(", LPAREN, PAREN}, 233 {")", RPAREN, PAREN}, 234 {"", 0, 0} 235 }; 236 237 static struct t_op const *t_wp_op; 238 static int nargc; 239 static char **t_wp; 240 static int parenlevel; 241 242 static int aexpr(enum token); 243 static int binop(void); 244 static int equalf(const char *, const char *); 245 static int filstat(char *, enum token); 246 static int getn(const char *); 247 static intmax_t getq(const char *); 248 static int intcmp(const char *, const char *); 249 static int isunopoperand(void); 250 static int islparenoperand(void); 251 static int isrparenoperand(void); 252 static int newerf(const char *, const char *, enum time_types, 253 enum time_types); 254 static int nexpr(enum token); 255 static int oexpr(enum token); 256 static int primary(enum token); 257 static void syntax(const char *, const char *); 258 static enum token t_lex(char *); 259 260 int 261 main(int argc, char **argv) 262 { 263 int res; 264 char *p; 265 266 if ((p = strrchr(argv[0], '/')) == NULL) 267 p = argv[0]; 268 else 269 p++; 270 if (strcmp(p, "[") == 0) { 271 if (strcmp(argv[--argc], "]") != 0) 272 error("missing ]"); 273 argv[argc] = NULL; 274 } 275 276 /* no expression => false */ 277 if (--argc <= 0) 278 return 1; 279 280 #ifndef SHELL 281 (void)setlocale(LC_CTYPE, ""); 282 #endif 283 nargc = argc; 284 t_wp = &argv[1]; 285 parenlevel = 0; 286 if (nargc == 4 && strcmp(*t_wp, "!") == 0) { 287 /* Things like ! "" -o x do not fit in the normal grammar. */ 288 --nargc; 289 ++t_wp; 290 res = oexpr(t_lex(*t_wp)); 291 } else 292 res = !oexpr(t_lex(*t_wp)); 293 294 if (--nargc > 0) 295 syntax(*t_wp, "unexpected operator"); 296 297 return res; 298 } 299 300 static void 301 syntax(const char *op, const char *msg) 302 { 303 304 if (op && *op) 305 error("%s: %s", op, msg); 306 else 307 error("%s", msg); 308 } 309 310 static int 311 oexpr(enum token n) 312 { 313 int res; 314 315 res = aexpr(n); 316 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BOR) 317 return oexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) || 318 res; 319 t_wp--; 320 nargc++; 321 return res; 322 } 323 324 static int 325 aexpr(enum token n) 326 { 327 int res; 328 329 res = nexpr(n); 330 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BAND) 331 return aexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) && 332 res; 333 t_wp--; 334 nargc++; 335 return res; 336 } 337 338 static int 339 nexpr(enum token n) 340 { 341 if (n == UNOT) 342 return !nexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)); 343 return primary(n); 344 } 345 346 static int 347 primary(enum token n) 348 { 349 enum token nn; 350 int res; 351 352 if (n == EOI) 353 return 0; /* missing expression */ 354 if (n == LPAREN) { 355 parenlevel++; 356 if ((nn = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) == 357 RPAREN) { 358 parenlevel--; 359 return 0; /* missing expression */ 360 } 361 res = oexpr(nn); 362 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) != RPAREN) 363 syntax(NULL, "closing paren expected"); 364 parenlevel--; 365 return res; 366 } 367 if (t_wp_op && t_wp_op->op_type == UNOP) { 368 /* unary expression */ 369 if (--nargc == 0) 370 syntax(t_wp_op->op_text, "argument expected"); 371 switch (n) { 372 case STREZ: 373 return strlen(*++t_wp) == 0; 374 case STRNZ: 375 return strlen(*++t_wp) != 0; 376 case FILTT: 377 return isatty(getn(*++t_wp)); 378 default: 379 return filstat(*++t_wp, n); 380 } 381 } 382 383 if (t_lex(nargc > 0 ? t_wp[1] : NULL), t_wp_op && t_wp_op->op_type == 384 BINOP) { 385 return binop(); 386 } 387 388 return strlen(*t_wp) > 0; 389 } 390 391 static int 392 binop(void) 393 { 394 const char *opnd1, *opnd2; 395 struct t_op const *op; 396 397 opnd1 = *t_wp; 398 (void) t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL); 399 op = t_wp_op; 400 401 if ((opnd2 = nargc > 0 ? (--nargc, *++t_wp) : NULL) == NULL) 402 syntax(op->op_text, "argument expected"); 403 404 switch (op->op_num) { 405 case STREQ: 406 return strcmp(opnd1, opnd2) == 0; 407 case STRNE: 408 return strcmp(opnd1, opnd2) != 0; 409 case STRLT: 410 return strcmp(opnd1, opnd2) < 0; 411 case STRGT: 412 return strcmp(opnd1, opnd2) > 0; 413 case INTEQ: 414 return intcmp(opnd1, opnd2) == 0; 415 case INTNE: 416 return intcmp(opnd1, opnd2) != 0; 417 case INTGE: 418 return intcmp(opnd1, opnd2) >= 0; 419 case INTGT: 420 return intcmp(opnd1, opnd2) > 0; 421 case INTLE: 422 return intcmp(opnd1, opnd2) <= 0; 423 case INTLT: 424 return intcmp(opnd1, opnd2) < 0; 425 case FILNTAA: 426 return newerf(opnd1, opnd2, ATIME, ATIME); 427 case FILNTAB: 428 return newerf(opnd1, opnd2, ATIME, BTIME); 429 case FILNTAC: 430 return newerf(opnd1, opnd2, ATIME, CTIME); 431 case FILNTAM: 432 return newerf(opnd1, opnd2, ATIME, MTIME); 433 case FILNTBA: 434 return newerf(opnd1, opnd2, BTIME, ATIME); 435 case FILNTBB: 436 return newerf(opnd1, opnd2, BTIME, BTIME); 437 case FILNTBC: 438 return newerf(opnd1, opnd2, BTIME, CTIME); 439 case FILNTBM: 440 return newerf(opnd1, opnd2, BTIME, MTIME); 441 case FILNTCA: 442 return newerf(opnd1, opnd2, CTIME, ATIME); 443 case FILNTCB: 444 return newerf(opnd1, opnd2, CTIME, BTIME); 445 case FILNTCC: 446 return newerf(opnd1, opnd2, CTIME, CTIME); 447 case FILNTCM: 448 return newerf(opnd1, opnd2, CTIME, MTIME); 449 case FILNTMA: 450 return newerf(opnd1, opnd2, MTIME, ATIME); 451 case FILNTMB: 452 return newerf(opnd1, opnd2, MTIME, BTIME); 453 case FILNTMC: 454 return newerf(opnd1, opnd2, MTIME, CTIME); 455 case FILNTMM: 456 return newerf(opnd1, opnd2, MTIME, MTIME); 457 case FILOTAA: 458 return newerf(opnd2, opnd1, ATIME, ATIME); 459 case FILOTAB: 460 return newerf(opnd2, opnd1, BTIME, ATIME); 461 case FILOTAC: 462 return newerf(opnd2, opnd1, CTIME, ATIME); 463 case FILOTAM: 464 return newerf(opnd2, opnd1, MTIME, ATIME); 465 case FILOTBA: 466 return newerf(opnd2, opnd1, ATIME, BTIME); 467 case FILOTBB: 468 return newerf(opnd2, opnd1, BTIME, BTIME); 469 case FILOTBC: 470 return newerf(opnd2, opnd1, CTIME, BTIME); 471 case FILOTBM: 472 return newerf(opnd2, opnd1, MTIME, BTIME); 473 case FILOTCA: 474 return newerf(opnd2, opnd1, ATIME, CTIME); 475 case FILOTCB: 476 return newerf(opnd2, opnd1, BTIME, CTIME); 477 case FILOTCC: 478 return newerf(opnd2, opnd1, CTIME, CTIME); 479 case FILOTCM: 480 return newerf(opnd2, opnd1, MTIME, CTIME); 481 case FILOTMA: 482 return newerf(opnd2, opnd1, ATIME, MTIME); 483 case FILOTMB: 484 return newerf(opnd2, opnd1, BTIME, MTIME); 485 case FILOTMC: 486 return newerf(opnd2, opnd1, CTIME, MTIME); 487 case FILOTMM: 488 return newerf(opnd2, opnd1, MTIME, MTIME); 489 case FILEQ: 490 return equalf (opnd1, opnd2); 491 default: 492 abort(); 493 /* NOTREACHED */ 494 } 495 } 496 497 static int 498 filstat(char *nm, enum token mode) 499 { 500 struct stat s; 501 502 if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) 503 return 0; 504 505 switch (mode) { 506 case FILRD: 507 return (eaccess(nm, R_OK) == 0); 508 case FILWR: 509 return (eaccess(nm, W_OK) == 0); 510 case FILEX: 511 /* XXX work around eaccess(2) false positives for superuser */ 512 if (eaccess(nm, X_OK) != 0) 513 return 0; 514 if (S_ISDIR(s.st_mode) || geteuid() != 0) 515 return 1; 516 return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0; 517 case FILEXIST: 518 return (eaccess(nm, F_OK) == 0); 519 case FILREG: 520 return S_ISREG(s.st_mode); 521 case FILDIR: 522 return S_ISDIR(s.st_mode); 523 case FILCDEV: 524 return S_ISCHR(s.st_mode); 525 case FILBDEV: 526 return S_ISBLK(s.st_mode); 527 case FILFIFO: 528 return S_ISFIFO(s.st_mode); 529 case FILSOCK: 530 return S_ISSOCK(s.st_mode); 531 case FILSYM: 532 return S_ISLNK(s.st_mode); 533 case FILSUID: 534 return (s.st_mode & S_ISUID) != 0; 535 case FILSGID: 536 return (s.st_mode & S_ISGID) != 0; 537 case FILSTCK: 538 return (s.st_mode & S_ISVTX) != 0; 539 case FILGZ: 540 return s.st_size > (off_t)0; 541 case FILUID: 542 return s.st_uid == geteuid(); 543 case FILGID: 544 return s.st_gid == getegid(); 545 default: 546 return 1; 547 } 548 } 549 550 static enum token 551 t_lex(char *s) 552 { 553 struct t_op const *op = ops; 554 555 if (s == 0) { 556 t_wp_op = NULL; 557 return EOI; 558 } 559 while (*op->op_text) { 560 if (strcmp(s, op->op_text) == 0) { 561 if (((op->op_type == UNOP || op->op_type == BUNOP) 562 && isunopoperand()) || 563 (op->op_num == LPAREN && islparenoperand()) || 564 (op->op_num == RPAREN && isrparenoperand())) 565 break; 566 t_wp_op = op; 567 return op->op_num; 568 } 569 op++; 570 } 571 t_wp_op = NULL; 572 return OPERAND; 573 } 574 575 static int 576 isunopoperand(void) 577 { 578 struct t_op const *op = ops; 579 char *s; 580 char *t; 581 582 if (nargc == 1) 583 return 1; 584 s = *(t_wp + 1); 585 if (nargc == 2) 586 return parenlevel == 1 && strcmp(s, ")") == 0; 587 t = *(t_wp + 2); 588 while (*op->op_text) { 589 if (strcmp(s, op->op_text) == 0) 590 return op->op_type == BINOP && 591 (parenlevel == 0 || t[0] != ')' || t[1] != '\0'); 592 op++; 593 } 594 return 0; 595 } 596 597 static int 598 islparenoperand(void) 599 { 600 struct t_op const *op = ops; 601 char *s; 602 603 if (nargc == 1) 604 return 1; 605 s = *(t_wp + 1); 606 if (nargc == 2) 607 return parenlevel == 1 && strcmp(s, ")") == 0; 608 if (nargc != 3) 609 return 0; 610 while (*op->op_text) { 611 if (strcmp(s, op->op_text) == 0) 612 return op->op_type == BINOP; 613 op++; 614 } 615 return 0; 616 } 617 618 static int 619 isrparenoperand(void) 620 { 621 char *s; 622 623 if (nargc == 1) 624 return 0; 625 s = *(t_wp + 1); 626 if (nargc == 2) 627 return parenlevel == 1 && strcmp(s, ")") == 0; 628 return 0; 629 } 630 631 /* atoi with error detection */ 632 static int 633 getn(const char *s) 634 { 635 char *p; 636 long r; 637 638 errno = 0; 639 r = strtol(s, &p, 10); 640 641 if (s == p) 642 error("%s: bad number", s); 643 644 if (errno != 0) 645 error((errno == EINVAL) ? "%s: bad number" : 646 "%s: out of range", s); 647 648 while (isspace((unsigned char)*p)) 649 p++; 650 651 if (*p) 652 error("%s: bad number", s); 653 654 return (int) r; 655 } 656 657 /* atoi with error detection and 64 bit range */ 658 static intmax_t 659 getq(const char *s) 660 { 661 char *p; 662 intmax_t r; 663 664 errno = 0; 665 r = strtoimax(s, &p, 10); 666 667 if (s == p) 668 error("%s: bad number", s); 669 670 if (errno != 0) 671 error((errno == EINVAL) ? "%s: bad number" : 672 "%s: out of range", s); 673 674 while (isspace((unsigned char)*p)) 675 p++; 676 677 if (*p) 678 error("%s: bad number", s); 679 680 return r; 681 } 682 683 static int 684 intcmp (const char *s1, const char *s2) 685 { 686 intmax_t q1, q2; 687 688 689 q1 = getq(s1); 690 q2 = getq(s2); 691 692 if (q1 > q2) 693 return 1; 694 695 if (q1 < q2) 696 return -1; 697 698 return 0; 699 } 700 701 static int 702 newerf (const char *f1, const char *f2, enum time_types t1, enum time_types t2) 703 { 704 struct stat b1, b2; 705 struct timespec *ts1, *ts2; 706 707 if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0) 708 return 0; 709 710 switch (t1) { 711 case ATIME: ts1 = &b1.st_atim; break; 712 case BTIME: ts1 = &b1.st_birthtim; break; 713 case CTIME: ts1 = &b1.st_ctim; break; 714 default: ts1 = &b1.st_mtim; break; 715 } 716 717 switch (t2) { 718 case ATIME: ts2 = &b2.st_atim; break; 719 case BTIME: ts2 = &b2.st_birthtim; break; 720 case CTIME: ts2 = &b2.st_ctim; break; 721 default: ts2 = &b2.st_mtim; break; 722 } 723 724 if (ts1->tv_sec > ts2->tv_sec) 725 return 1; 726 if (ts1->tv_sec < ts2->tv_sec) 727 return 0; 728 729 return (ts1->tv_nsec > ts2->tv_nsec); 730 } 731 732 static int 733 equalf (const char *f1, const char *f2) 734 { 735 struct stat b1, b2; 736 737 return (stat (f1, &b1) == 0 && 738 stat (f2, &b2) == 0 && 739 b1.st_dev == b2.st_dev && 740 b1.st_ino == b2.st_ino); 741 } 742