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