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