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