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 int nargc; 165 char **t_wp; 166 167 static int aexpr(enum token); 168 static int binop(void); 169 static int equalf(const char *, const char *); 170 static int filstat(char *, enum token); 171 static int getn(const char *); 172 static intmax_t getq(const char *); 173 static int intcmp(const char *, const char *); 174 static int isoperand(void); 175 static int newerf(const char *, const char *); 176 static int nexpr(enum token); 177 static int oexpr(enum token); 178 static int olderf(const char *, const char *); 179 static int primary(enum token); 180 static void syntax(const char *, const char *); 181 static enum token t_lex(char *); 182 183 int 184 main(int argc, char **argv) 185 { 186 int res; 187 char *p; 188 189 if ((p = rindex(argv[0], '/')) == NULL) 190 p = argv[0]; 191 else 192 p++; 193 if (strcmp(p, "[") == 0) { 194 if (strcmp(argv[--argc], "]") != 0) 195 error("missing ]"); 196 argv[argc] = NULL; 197 } 198 199 /* no expression => false */ 200 if (--argc <= 0) 201 return 1; 202 203 #ifndef SHELL 204 (void)setlocale(LC_CTYPE, ""); 205 #endif 206 nargc = argc; 207 t_wp = &argv[1]; 208 res = !oexpr(t_lex(*t_wp)); 209 210 if (--nargc > 0) 211 syntax(*t_wp, "unexpected operator"); 212 213 return res; 214 } 215 216 static void 217 syntax(const char *op, const char *msg) 218 { 219 220 if (op && *op) 221 error("%s: %s", op, msg); 222 else 223 error("%s", msg); 224 } 225 226 static int 227 oexpr(enum token n) 228 { 229 int res; 230 231 res = aexpr(n); 232 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BOR) 233 return oexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) || 234 res; 235 t_wp--; 236 nargc++; 237 return res; 238 } 239 240 static int 241 aexpr(enum token n) 242 { 243 int res; 244 245 res = nexpr(n); 246 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BAND) 247 return aexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) && 248 res; 249 t_wp--; 250 nargc++; 251 return res; 252 } 253 254 static int 255 nexpr(enum token n) 256 { 257 if (n == UNOT) 258 return !nexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)); 259 return primary(n); 260 } 261 262 static int 263 primary(enum token n) 264 { 265 enum token nn; 266 int res; 267 268 if (n == EOI) 269 return 0; /* missing expression */ 270 if (n == LPAREN) { 271 if ((nn = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) == 272 RPAREN) 273 return 0; /* missing expression */ 274 res = oexpr(nn); 275 if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) != RPAREN) 276 syntax(NULL, "closing paren expected"); 277 return res; 278 } 279 if (t_wp_op && t_wp_op->op_type == UNOP) { 280 /* unary expression */ 281 if (--nargc == 0) 282 syntax(t_wp_op->op_text, "argument expected"); 283 switch (n) { 284 case STREZ: 285 return strlen(*++t_wp) == 0; 286 case STRNZ: 287 return strlen(*++t_wp) != 0; 288 case FILTT: 289 return isatty(getn(*++t_wp)); 290 default: 291 return filstat(*++t_wp, n); 292 } 293 } 294 295 if (t_lex(nargc > 0 ? t_wp[1] : NULL), t_wp_op && t_wp_op->op_type == 296 BINOP) { 297 return binop(); 298 } 299 300 return strlen(*t_wp) > 0; 301 } 302 303 static int 304 binop(void) 305 { 306 const char *opnd1, *opnd2; 307 struct t_op const *op; 308 309 opnd1 = *t_wp; 310 (void) t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL); 311 op = t_wp_op; 312 313 if ((opnd2 = nargc > 0 ? (--nargc, *++t_wp) : NULL) == NULL) 314 syntax(op->op_text, "argument expected"); 315 316 switch (op->op_num) { 317 case STREQ: 318 return strcmp(opnd1, opnd2) == 0; 319 case STRNE: 320 return strcmp(opnd1, opnd2) != 0; 321 case STRLT: 322 return strcmp(opnd1, opnd2) < 0; 323 case STRGT: 324 return strcmp(opnd1, opnd2) > 0; 325 case INTEQ: 326 return intcmp(opnd1, opnd2) == 0; 327 case INTNE: 328 return intcmp(opnd1, opnd2) != 0; 329 case INTGE: 330 return intcmp(opnd1, opnd2) >= 0; 331 case INTGT: 332 return intcmp(opnd1, opnd2) > 0; 333 case INTLE: 334 return intcmp(opnd1, opnd2) <= 0; 335 case INTLT: 336 return intcmp(opnd1, opnd2) < 0; 337 case FILNT: 338 return newerf (opnd1, opnd2); 339 case FILOT: 340 return olderf (opnd1, opnd2); 341 case FILEQ: 342 return equalf (opnd1, opnd2); 343 default: 344 abort(); 345 /* NOTREACHED */ 346 } 347 } 348 349 static int 350 filstat(char *nm, enum token mode) 351 { 352 struct stat s; 353 354 if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) 355 return 0; 356 357 switch (mode) { 358 case FILRD: 359 return (eaccess(nm, R_OK) == 0); 360 case FILWR: 361 return (eaccess(nm, W_OK) == 0); 362 case FILEX: 363 /* XXX work around eaccess(2) false positives for superuser */ 364 if (eaccess(nm, X_OK) != 0) 365 return 0; 366 if (S_ISDIR(s.st_mode) || geteuid() != 0) 367 return 1; 368 return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0; 369 case FILEXIST: 370 return (eaccess(nm, F_OK) == 0); 371 case FILREG: 372 return S_ISREG(s.st_mode); 373 case FILDIR: 374 return S_ISDIR(s.st_mode); 375 case FILCDEV: 376 return S_ISCHR(s.st_mode); 377 case FILBDEV: 378 return S_ISBLK(s.st_mode); 379 case FILFIFO: 380 return S_ISFIFO(s.st_mode); 381 case FILSOCK: 382 return S_ISSOCK(s.st_mode); 383 case FILSYM: 384 return S_ISLNK(s.st_mode); 385 case FILSUID: 386 return (s.st_mode & S_ISUID) != 0; 387 case FILSGID: 388 return (s.st_mode & S_ISGID) != 0; 389 case FILSTCK: 390 return (s.st_mode & S_ISVTX) != 0; 391 case FILGZ: 392 return s.st_size > (off_t)0; 393 case FILUID: 394 return s.st_uid == geteuid(); 395 case FILGID: 396 return s.st_gid == getegid(); 397 default: 398 return 1; 399 } 400 } 401 402 static enum token 403 t_lex(char *s) 404 { 405 struct t_op const *op = ops; 406 407 if (s == 0) { 408 t_wp_op = NULL; 409 return EOI; 410 } 411 while (op->op_text) { 412 if (strcmp(s, op->op_text) == 0) { 413 if ((op->op_type == UNOP && isoperand()) || 414 (op->op_num == LPAREN && nargc == 1)) 415 break; 416 t_wp_op = op; 417 return op->op_num; 418 } 419 op++; 420 } 421 t_wp_op = NULL; 422 return OPERAND; 423 } 424 425 static int 426 isoperand(void) 427 { 428 struct t_op const *op = ops; 429 char *s; 430 char *t; 431 432 if (nargc == 1) 433 return 1; 434 if (nargc == 2) 435 return 0; 436 s = *(t_wp + 1); 437 t = *(t_wp + 2); 438 while (op->op_text) { 439 if (strcmp(s, op->op_text) == 0) 440 return op->op_type == BINOP && 441 (t[0] != ')' || t[1] != '\0'); 442 op++; 443 } 444 return 0; 445 } 446 447 /* atoi with error detection */ 448 static int 449 getn(const char *s) 450 { 451 char *p; 452 long r; 453 454 errno = 0; 455 r = strtol(s, &p, 10); 456 457 if (s == p) 458 error("%s: bad number", s); 459 460 if (errno != 0) 461 error((errno == EINVAL) ? "%s: bad number" : 462 "%s: out of range", s); 463 464 while (isspace((unsigned char)*p)) 465 p++; 466 467 if (*p) 468 error("%s: bad number", s); 469 470 return (int) r; 471 } 472 473 /* atoi with error detection and 64 bit range */ 474 static intmax_t 475 getq(const char *s) 476 { 477 char *p; 478 intmax_t r; 479 480 errno = 0; 481 r = strtoimax(s, &p, 10); 482 483 if (s == p) 484 error("%s: bad number", s); 485 486 if (errno != 0) 487 error((errno == EINVAL) ? "%s: bad number" : 488 "%s: out of range", s); 489 490 while (isspace((unsigned char)*p)) 491 p++; 492 493 if (*p) 494 error("%s: bad number", s); 495 496 return r; 497 } 498 499 static int 500 intcmp (const char *s1, const char *s2) 501 { 502 intmax_t q1, q2; 503 504 505 q1 = getq(s1); 506 q2 = getq(s2); 507 508 if (q1 > q2) 509 return 1; 510 511 if (q1 < q2) 512 return -1; 513 514 return 0; 515 } 516 517 static int 518 newerf (const char *f1, const char *f2) 519 { 520 struct stat b1, b2; 521 522 if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0) 523 return 0; 524 525 if (b1.st_mtimespec.tv_sec > b2.st_mtimespec.tv_sec) 526 return 1; 527 if (b1.st_mtimespec.tv_sec < b2.st_mtimespec.tv_sec) 528 return 0; 529 530 return (b1.st_mtimespec.tv_nsec > b2.st_mtimespec.tv_nsec); 531 } 532 533 static int 534 olderf (const char *f1, const char *f2) 535 { 536 return (newerf(f2, f1)); 537 } 538 539 static int 540 equalf (const char *f1, const char *f2) 541 { 542 struct stat b1, b2; 543 544 return (stat (f1, &b1) == 0 && 545 stat (f2, &b2) == 0 && 546 b1.st_dev == b2.st_dev && 547 b1.st_ino == b2.st_ino); 548 } 549