test.c revision 87961
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 14static const char rcsid[] = 15 "$FreeBSD: head/bin/test/test.c 87961 2001-12-14 23:20:54Z ache $"; 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 35static void error(const char *, ...) __attribute__((__noreturn__)); 36 37static void 38#ifdef __STDC__ 39error(const char *msg, ...) 40#else 41error(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 77enum 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 120enum token_types { 121 UNOP, 122 BINOP, 123 BUNOP, 124 BBINOP, 125 PAREN 126}; 127 128struct 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 174struct t_op const *t_wp_op; 175char **t_wp; 176 177static int aexpr __P((enum token)); 178static int binop __P((void)); 179static int equalf __P((const char *, const char *)); 180static int filstat __P((char *, enum token)); 181static int getn __P((const char *)); 182static quad_t getq __P((const char *)); 183static int intcmp __P((const char *, const char *)); 184static int isoperand __P((void)); 185int main __P((int, char **)); 186static int newerf __P((const char *, const char *)); 187static int nexpr __P((enum token)); 188static int oexpr __P((enum token)); 189static int olderf __P((const char *, const char *)); 190static int primary __P((enum token)); 191static void syntax __P((const char *, const char *)); 192static enum token t_lex __P((char *)); 193 194int 195main(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 229static void 230syntax(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 241static int 242oexpr(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 254static int 255aexpr(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 267static int 268nexpr(n) 269 enum token n; /* token */ 270{ 271 if (n == UNOT) 272 return !nexpr(t_lex(*++t_wp)); 273 return primary(n); 274} 275 276static int 277primary(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 316static int 317binop() 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 362static int 363filstat(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 417static enum token 418t_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 441static int 442isoperand() 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 */ 462static int 463getn(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((errno == EINVAL) ? "%s: bad number" : 474 "%s: out of range", s); 475 476 while (isspace((unsigned char)*p)) 477 p++; 478 479 if (*p) 480 error("%s: bad number", s); 481 482 return (int) r; 483} 484 485/* atoi with error detection and 64 bit range */ 486static quad_t 487getq(s) 488 const char *s; 489{ 490 char *p; 491 quad_t r; 492 493 errno = 0; 494 r = strtoq(s, &p, 10); 495 496 if (errno != 0) 497 error((errno == EINVAL) ? "%s: bad number" : 498 "%s: out of range", s); 499 500 while (isspace((unsigned char)*p)) 501 p++; 502 503 if (*p) 504 error("%s: bad number", s); 505 506 return r; 507} 508 509static int 510intcmp (s1, s2) 511 const char *s1, *s2; 512{ 513 quad_t q1, q2; 514 515 516 q1 = getq(s1); 517 q2 = getq(s2); 518 519 if (q1 > q2) 520 return 1; 521 522 if (q1 < q2) 523 return -1; 524 525 return 0; 526} 527 528static int 529newerf (f1, f2) 530 const char *f1, *f2; 531{ 532 struct stat b1, b2; 533 534 return (stat (f1, &b1) == 0 && 535 stat (f2, &b2) == 0 && 536 b1.st_mtime > b2.st_mtime); 537} 538 539static int 540olderf (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 550static int 551equalf (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_dev == b2.st_dev && 559 b1.st_ino == b2.st_ino); 560} 561