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