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