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