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