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