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