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