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"|"-ot"|"-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 FILNT, 89 FILOT, 90 FILEQ, 91 FILUID, 92 FILGID, 93 STREZ, 94 STRNZ, 95 STREQ, 96 STRNE, 97 STRLT, 98 STRGT, 99 INTEQ, 100 INTNE, 101 INTGE, 102 INTGT, 103 INTLE, 104 INTLT, 105 UNOT, 106 BAND, 107 BOR, 108 LPAREN, 109 RPAREN, 110 OPERAND 111 }; 112 113 enum token_types { 114 UNOP, 115 BINOP, 116 BUNOP, 117 BBINOP, 118 PAREN 119 }; 120 121 struct t_op { 122 const char *op_text; 123 short op_num, op_type; 124 } const ops [] = { 125 {"-r", FILRD, UNOP}, 126 {"-w", FILWR, UNOP}, 127 {"-x", FILEX, UNOP}, 128 {"-e", FILEXIST,UNOP}, 129 {"-f", FILREG, UNOP}, 130 {"-d", FILDIR, UNOP}, 131 {"-c", FILCDEV,UNOP}, 132 {"-b", FILBDEV,UNOP}, 133 {"-p", FILFIFO,UNOP}, 134 {"-u", FILSUID,UNOP}, 135 {"-g", FILSGID,UNOP}, 136 {"-k", FILSTCK,UNOP}, 137 {"-s", FILGZ, UNOP}, 138 {"-t", FILTT, UNOP}, 139 {"-z", STREZ, UNOP}, 140 {"-n", STRNZ, UNOP}, 141 {"-h", FILSYM, UNOP}, /* for backwards compat */ 142 {"-O", FILUID, UNOP}, 143 {"-G", FILGID, UNOP}, 144 {"-L", FILSYM, UNOP}, 145 {"-S", FILSOCK,UNOP}, 146 {"=", STREQ, BINOP}, 147 {"!=", STRNE, BINOP}, 148 {"<", STRLT, BINOP}, 149 {">", STRGT, BINOP}, 150 {"-eq", INTEQ, BINOP}, 151 {"-ne", INTNE, BINOP}, 152 {"-ge", INTGE, BINOP}, 153 {"-gt", INTGT, BINOP}, 154 {"-le", INTLE, BINOP}, 155 {"-lt", INTLT, BINOP}, 156 {"-nt", FILNT, BINOP}, 157 {"-ot", FILOT, BINOP}, 158 {"-ef", FILEQ, BINOP}, 159 {"!", UNOT, BUNOP}, 160 {"-a", BAND, BBINOP}, 161 {"-o", BOR, BBINOP}, 162 {"(", LPAREN, PAREN}, 163 {")", RPAREN, PAREN}, 164 {0, 0, 0} 165 }; 166 167 struct t_op const *t_wp_op; 168 int nargc; 169 char **t_wp; 170 int parenlevel; 171 172 static int aexpr(enum token); 173 static int binop(void); 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 = rindex(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 (t_wp_op && t_wp_op->op_type == UNOP) { 298 /* unary expression */ 299 if (--nargc == 0) 300 syntax(t_wp_op->op_text, "argument expected"); 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 if (t_lex(nargc > 0 ? t_wp[1] : NULL), t_wp_op && t_wp_op->op_type == 314 BINOP) { 315 return binop(); 316 } 317 318 return strlen(*t_wp) > 0; 319 } 320 321 static int 322 binop(void) 323 { 324 const char *opnd1, *opnd2; 325 struct t_op const *op; 326 327 opnd1 = *t_wp; 328 (void) t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL); 329 op = t_wp_op; 330 331 if ((opnd2 = nargc > 0 ? (--nargc, *++t_wp) : NULL) == NULL) 332 syntax(op->op_text, "argument expected"); 333 334 switch (op->op_num) { 335 case STREQ: 336 return strcmp(opnd1, opnd2) == 0; 337 case STRNE: 338 return strcmp(opnd1, opnd2) != 0; 339 case STRLT: 340 return strcmp(opnd1, opnd2) < 0; 341 case STRGT: 342 return strcmp(opnd1, opnd2) > 0; 343 case INTEQ: 344 return intcmp(opnd1, opnd2) == 0; 345 case INTNE: 346 return intcmp(opnd1, opnd2) != 0; 347 case INTGE: 348 return intcmp(opnd1, opnd2) >= 0; 349 case INTGT: 350 return intcmp(opnd1, opnd2) > 0; 351 case INTLE: 352 return intcmp(opnd1, opnd2) <= 0; 353 case INTLT: 354 return intcmp(opnd1, opnd2) < 0; 355 case FILNT: 356 return newerf (opnd1, opnd2); 357 case FILOT: 358 return olderf (opnd1, opnd2); 359 case FILEQ: 360 return equalf (opnd1, opnd2); 361 default: 362 abort(); 363 /* NOTREACHED */ 364 } 365 } 366 367 static int 368 filstat(char *nm, enum token mode) 369 { 370 struct stat s; 371 372 if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) 373 return 0; 374 375 switch (mode) { 376 case FILRD: 377 return (eaccess(nm, R_OK) == 0); 378 case FILWR: 379 return (eaccess(nm, W_OK) == 0); 380 case FILEX: 381 /* XXX work around eaccess(2) false positives for superuser */ 382 if (eaccess(nm, X_OK) != 0) 383 return 0; 384 if (S_ISDIR(s.st_mode) || geteuid() != 0) 385 return 1; 386 return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0; 387 case FILEXIST: 388 return (eaccess(nm, F_OK) == 0); 389 case FILREG: 390 return S_ISREG(s.st_mode); 391 case FILDIR: 392 return S_ISDIR(s.st_mode); 393 case FILCDEV: 394 return S_ISCHR(s.st_mode); 395 case FILBDEV: 396 return S_ISBLK(s.st_mode); 397 case FILFIFO: 398 return S_ISFIFO(s.st_mode); 399 case FILSOCK: 400 return S_ISSOCK(s.st_mode); 401 case FILSYM: 402 return S_ISLNK(s.st_mode); 403 case FILSUID: 404 return (s.st_mode & S_ISUID) != 0; 405 case FILSGID: 406 return (s.st_mode & S_ISGID) != 0; 407 case FILSTCK: 408 return (s.st_mode & S_ISVTX) != 0; 409 case FILGZ: 410 return s.st_size > (off_t)0; 411 case FILUID: 412 return s.st_uid == geteuid(); 413 case FILGID: 414 return s.st_gid == getegid(); 415 default: 416 return 1; 417 } 418 } 419 420 static enum token 421 t_lex(char *s) 422 { 423 struct t_op const *op = ops; 424 425 if (s == 0) { 426 t_wp_op = NULL; 427 return EOI; 428 } 429 while (op->op_text) { 430 if (strcmp(s, op->op_text) == 0) { 431 if (((op->op_type == UNOP || op->op_type == BUNOP) 432 && isunopoperand()) || 433 (op->op_num == LPAREN && islparenoperand()) || 434 (op->op_num == RPAREN && isrparenoperand())) 435 break; 436 t_wp_op = op; 437 return op->op_num; 438 } 439 op++; 440 } 441 t_wp_op = NULL; 442 return OPERAND; 443 } 444 445 static int 446 isunopoperand(void) 447 { 448 struct t_op const *op = ops; 449 char *s; 450 char *t; 451 452 if (nargc == 1) 453 return 1; 454 s = *(t_wp + 1); 455 if (nargc == 2) 456 return parenlevel == 1 && strcmp(s, ")") == 0; 457 t = *(t_wp + 2); 458 while (op->op_text) { 459 if (strcmp(s, op->op_text) == 0) 460 return op->op_type == BINOP && 461 (parenlevel == 0 || t[0] != ')' || t[1] != '\0'); 462 op++; 463 } 464 return 0; 465 } 466 467 static int 468 islparenoperand(void) 469 { 470 struct t_op const *op = ops; 471 char *s; 472 473 if (nargc == 1) 474 return 1; 475 s = *(t_wp + 1); 476 if (nargc == 2) 477 return parenlevel == 1 && strcmp(s, ")") == 0; 478 if (nargc != 3) 479 return 0; 480 while (op->op_text) { 481 if (strcmp(s, op->op_text) == 0) 482 return op->op_type == BINOP; 483 op++; 484 } 485 return 0; 486 } 487 488 static int 489 isrparenoperand(void) 490 { 491 char *s; 492 493 if (nargc == 1) 494 return 0; 495 s = *(t_wp + 1); 496 if (nargc == 2) 497 return parenlevel == 1 && strcmp(s, ")") == 0; 498 return 0; 499 } 500 501 /* atoi with error detection */ 502 static int 503 getn(const char *s) 504 { 505 char *p; 506 long r; 507 508 errno = 0; 509 r = strtol(s, &p, 10); 510 511 if (s == p) 512 error("%s: bad number", s); 513 514 if (errno != 0) 515 error((errno == EINVAL) ? "%s: bad number" : 516 "%s: out of range", s); 517 518 while (isspace((unsigned char)*p)) 519 p++; 520 521 if (*p) 522 error("%s: bad number", s); 523 524 return (int) r; 525 } 526 527 /* atoi with error detection and 64 bit range */ 528 static intmax_t 529 getq(const char *s) 530 { 531 char *p; 532 intmax_t r; 533 534 errno = 0; 535 r = strtoimax(s, &p, 10); 536 537 if (s == p) 538 error("%s: bad number", s); 539 540 if (errno != 0) 541 error((errno == EINVAL) ? "%s: bad number" : 542 "%s: out of range", s); 543 544 while (isspace((unsigned char)*p)) 545 p++; 546 547 if (*p) 548 error("%s: bad number", s); 549 550 return r; 551 } 552 553 static int 554 intcmp (const char *s1, const char *s2) 555 { 556 intmax_t q1, q2; 557 558 559 q1 = getq(s1); 560 q2 = getq(s2); 561 562 if (q1 > q2) 563 return 1; 564 565 if (q1 < q2) 566 return -1; 567 568 return 0; 569 } 570 571 static int 572 newerf (const char *f1, const char *f2) 573 { 574 struct stat b1, b2; 575 576 if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0) 577 return 0; 578 579 if (b1.st_mtim.tv_sec > b2.st_mtim.tv_sec) 580 return 1; 581 if (b1.st_mtim.tv_sec < b2.st_mtim.tv_sec) 582 return 0; 583 584 return (b1.st_mtim.tv_nsec > b2.st_mtim.tv_nsec); 585 } 586 587 static int 588 olderf (const char *f1, const char *f2) 589 { 590 return (newerf(f2, f1)); 591 } 592 593 static int 594 equalf (const char *f1, const char *f2) 595 { 596 struct stat b1, b2; 597 598 return (stat (f1, &b1) == 0 && 599 stat (f2, &b2) == 0 && 600 b1.st_dev == b2.st_dev && 601 b1.st_ino == b2.st_ino); 602 } 603