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.4 2003/09/28 14:39:15 hmp 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 (enum token); 177 static int binop (void); 178 static int equalf (const char *, const char *); 179 static int filstat (char *, enum token); 180 static int getn (const char *); 181 static quad_t getq (const char *); 182 static int intcmp (const char *, const char *); 183 static int isoperand (void); 184 static int newerf (const char *, const char *); 185 static int nexpr (enum token); 186 static int oexpr (enum token); 187 static int olderf (const char *, const char *); 188 static int primary (enum token); 189 static void syntax (const char *, const char *); 190 static enum token t_lex (char *); 191 192 int 193 main(int argc, char **argv) 194 { 195 gid_t egid, gid; 196 uid_t euid, uid; 197 int res; 198 char *p; 199 200 if ((p = rindex(argv[0], '/')) == NULL) 201 p = argv[0]; 202 else 203 p++; 204 if (strcmp(p, "[") == 0) { 205 if (strcmp(argv[--argc], "]") != 0) 206 error("missing ]"); 207 argv[argc] = NULL; 208 } 209 210 /* no expression => false */ 211 if (--argc <= 0) 212 return 1; 213 214 /* XXX work around the absence of an eaccess(2) syscall */ 215 egid = getegid(); 216 euid = geteuid(); 217 gid = getgid(); 218 uid = getuid(); 219 (void)setregid(egid, gid); 220 (void)setreuid(euid, uid); 221 222 nargc = argc; 223 t_wp = &argv[1]; 224 res = !oexpr(t_lex(*t_wp)); 225 226 if (--nargc > 0) 227 syntax(*t_wp, "unexpected operator"); 228 (void)setregid(gid, egid); 229 (void)setreuid(uid, euid); 230 231 return res; 232 } 233 234 static void 235 syntax(const char *op, const char *msg) 236 { 237 238 if (op && *op) 239 error("%s: %s", op, msg); 240 else 241 error("%s", msg); 242 } 243 244 static int 245 oexpr(enum token n) 246 { 247 int res; 248 249 res = aexpr(n); 250 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BOR) 251 return oexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) || 252 res; 253 t_wp--; 254 nargc++; 255 return res; 256 } 257 258 static int 259 aexpr(enum token n) 260 { 261 int res; 262 263 res = nexpr(n); 264 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BAND) 265 return aexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) && 266 res; 267 t_wp--; 268 nargc++; 269 return res; 270 } 271 272 static int 273 nexpr(enum token n) 274 { 275 if (n == UNOT) 276 return !nexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)); 277 return primary(n); 278 } 279 280 static int 281 primary(enum token n) 282 { 283 enum token nn; 284 int res; 285 286 if (n == EOI) 287 return 0; /* missing expression */ 288 if (n == LPAREN) { 289 if ((nn = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) == 290 RPAREN) 291 return 0; /* missing expression */ 292 res = oexpr(nn); 293 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) != RPAREN) 294 syntax(NULL, "closing paren expected"); 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 access(nm, R_OK) == 0; 378 case FILWR: 379 return access(nm, W_OK) == 0; 380 case FILEX: 381 /* XXX work around access(2) false positives for superuser */ 382 if (access(nm, X_OK) != 0) 383 return 0; 384 if (S_ISDIR(s.st_mode) || getuid() != 0) 385 return 1; 386 return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0; 387 case FILEXIST: 388 return access(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 && isoperand()) || 432 (op->op_num == LPAREN && nargc == 1)) 433 break; 434 t_wp_op = op; 435 return op->op_num; 436 } 437 op++; 438 } 439 t_wp_op = NULL; 440 return OPERAND; 441 } 442 443 static int 444 isoperand(void) 445 { 446 struct t_op const *op = ops; 447 char *s; 448 char *t; 449 450 if (nargc == 1) 451 return 1; 452 if (nargc == 2) 453 return 0; 454 s = *(t_wp + 1); 455 t = *(t_wp + 2); 456 while (op->op_text) { 457 if (strcmp(s, op->op_text) == 0) 458 return op->op_type == BINOP && 459 (t[0] != ')' || t[1] != '\0'); 460 op++; 461 } 462 return 0; 463 } 464 465 /* atoi with error detection */ 466 static int 467 getn(const char *s) 468 { 469 char *p; 470 long r; 471 472 errno = 0; 473 r = strtol(s, &p, 10); 474 475 if (errno != 0) 476 error((errno == EINVAL) ? "%s: bad number" : 477 "%s: out of range", s); 478 479 while (isspace((unsigned char)*p)) 480 p++; 481 482 if (*p) 483 error("%s: bad number", s); 484 485 return (int) r; 486 } 487 488 /* atoi with error detection and 64 bit range */ 489 static quad_t 490 getq(const char *s) 491 { 492 char *p; 493 quad_t r; 494 495 errno = 0; 496 r = strtoq(s, &p, 10); 497 498 if (errno != 0) 499 error((errno == EINVAL) ? "%s: bad number" : 500 "%s: out of range", s); 501 502 while (isspace((unsigned char)*p)) 503 p++; 504 505 if (*p) 506 error("%s: bad number", s); 507 508 return r; 509 } 510 511 static int 512 intcmp (const char *s1, const char *s2) 513 { 514 quad_t q1, q2; 515 516 517 q1 = getq(s1); 518 q2 = getq(s2); 519 520 if (q1 > q2) 521 return 1; 522 523 if (q1 < q2) 524 return -1; 525 526 return 0; 527 } 528 529 static int 530 newerf (const char *f1, const char *f2) 531 { 532 struct stat b1, b2; 533 534 if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0) 535 return 0; 536 537 if (b1.st_mtimespec.tv_sec > b2.st_mtimespec.tv_sec) 538 return 1; 539 if (b1.st_mtimespec.tv_sec < b2.st_mtimespec.tv_sec) 540 return 0; 541 542 return (b1.st_mtimespec.tv_nsec > b2.st_mtimespec.tv_nsec); 543 } 544 545 static int 546 olderf (const char *f1, const char *f2) 547 { 548 return (newerf(f2, f1)); 549 } 550 551 static int 552 equalf (const char *f1, const char *f2) 553 { 554 struct stat b1, b2; 555 556 return (stat (f1, &b1) == 0 && 557 stat (f2, &b2) == 0 && 558 b1.st_dev == b2.st_dev && 559 b1.st_ino == b2.st_ino); 560 } 561