1 /* $OpenBSD: test.c,v 1.19 2018/04/02 06:47:43 tobias Exp $ */ 2 /* $NetBSD: test.c,v 1.15 1995/03/21 07:04:06 cgd Exp $ */ 3 4 /* 5 * test(1); version 7-like -- author Erik Baalbergen 6 * modified by Eric Gisin to be used as built-in. 7 * modified by Arnold Robbins to add SVR3 compatibility 8 * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). 9 * modified by J.T. Conklin for NetBSD. 10 * 11 * This program is in the Public Domain. 12 */ 13 14 #include <sys/types.h> 15 #include <sys/stat.h> 16 #include <unistd.h> 17 #include <ctype.h> 18 #include <errno.h> 19 #include <limits.h> 20 #include <stdio.h> 21 #include <stdlib.h> 22 #include <string.h> 23 #include <err.h> 24 25 /* test(1) accepts the following grammar: 26 oexpr ::= aexpr | aexpr "-o" oexpr ; 27 aexpr ::= nexpr | nexpr "-a" aexpr ; 28 nexpr ::= primary | "!" primary 29 primary ::= unary-operator operand 30 | operand binary-operator operand 31 | operand 32 | "(" oexpr ")" 33 ; 34 unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| 35 "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; 36 37 binary-operator ::= "="|"!="|"<"|">"|"-eq"|"-ne"|"-ge"|"-gt"| 38 "-le"|"-lt"|"-nt"|"-ot"|"-ef"; 39 operand ::= <any legal UNIX file name> 40 */ 41 42 enum token { 43 EOI, 44 FILRD, 45 FILWR, 46 FILEX, 47 FILEXIST, 48 FILREG, 49 FILDIR, 50 FILCDEV, 51 FILBDEV, 52 FILFIFO, 53 FILSOCK, 54 FILSYM, 55 FILGZ, 56 FILTT, 57 FILSUID, 58 FILSGID, 59 FILSTCK, 60 FILNT, 61 FILOT, 62 FILEQ, 63 FILUID, 64 FILGID, 65 STREZ, 66 STRNZ, 67 STREQ, 68 STRNE, 69 STRLT, 70 STRGT, 71 INTEQ, 72 INTNE, 73 INTGE, 74 INTGT, 75 INTLE, 76 INTLT, 77 UNOT, 78 BAND, 79 BOR, 80 LPAREN, 81 RPAREN, 82 OPERAND 83 }; 84 85 enum token_types { 86 UNOP, 87 BINOP, 88 BUNOP, 89 BBINOP, 90 PAREN 91 }; 92 93 struct t_op { 94 const char *op_text; 95 short op_num, op_type; 96 } const ops [] = { 97 {"-r", FILRD, UNOP}, 98 {"-w", FILWR, UNOP}, 99 {"-x", FILEX, UNOP}, 100 {"-e", FILEXIST,UNOP}, 101 {"-f", FILREG, UNOP}, 102 {"-d", FILDIR, UNOP}, 103 {"-c", FILCDEV,UNOP}, 104 {"-b", FILBDEV,UNOP}, 105 {"-p", FILFIFO,UNOP}, 106 {"-u", FILSUID,UNOP}, 107 {"-g", FILSGID,UNOP}, 108 {"-k", FILSTCK,UNOP}, 109 {"-s", FILGZ, UNOP}, 110 {"-t", FILTT, UNOP}, 111 {"-z", STREZ, UNOP}, 112 {"-n", STRNZ, UNOP}, 113 {"-h", FILSYM, UNOP}, /* for backwards compat */ 114 {"-O", FILUID, UNOP}, 115 {"-G", FILGID, UNOP}, 116 {"-L", FILSYM, UNOP}, 117 {"-S", FILSOCK,UNOP}, 118 {"=", STREQ, BINOP}, 119 {"!=", STRNE, BINOP}, 120 {"<", STRLT, BINOP}, 121 {">", STRGT, BINOP}, 122 {"-eq", INTEQ, BINOP}, 123 {"-ne", INTNE, BINOP}, 124 {"-ge", INTGE, BINOP}, 125 {"-gt", INTGT, BINOP}, 126 {"-le", INTLE, BINOP}, 127 {"-lt", INTLT, BINOP}, 128 {"-nt", FILNT, BINOP}, 129 {"-ot", FILOT, BINOP}, 130 {"-ef", FILEQ, BINOP}, 131 {"!", UNOT, BUNOP}, 132 {"-a", BAND, BBINOP}, 133 {"-o", BOR, BBINOP}, 134 {"(", LPAREN, PAREN}, 135 {")", RPAREN, PAREN}, 136 {0, 0, 0} 137 }; 138 139 char **t_wp; 140 struct t_op const *t_wp_op; 141 142 static enum token t_lex(char *); 143 static enum token t_lex_type(char *); 144 static int oexpr(enum token n); 145 static int aexpr(enum token n); 146 static int nexpr(enum token n); 147 static int binop(void); 148 static int primary(enum token n); 149 static const char *getnstr(const char *, int *, size_t *); 150 static int intcmp(const char *, const char *); 151 static int filstat(char *nm, enum token mode); 152 static int getn(const char *s); 153 static int newerf(const char *, const char *); 154 static int olderf(const char *, const char *); 155 static int equalf(const char *, const char *); 156 static __dead void syntax(const char *op, char *msg); 157 158 int 159 main(int argc, char *argv[]) 160 { 161 extern char *__progname; 162 int res; 163 164 if (pledge("stdio rpath", NULL) == -1) 165 err(2, "pledge"); 166 167 if (strcmp(__progname, "[") == 0) { 168 if (strcmp(argv[--argc], "]")) 169 errx(2, "missing ]"); 170 argv[argc] = NULL; 171 } 172 173 /* Implement special cases from POSIX.2, section 4.62.4 */ 174 switch (argc) { 175 case 1: 176 return 1; 177 case 2: 178 return (*argv[1] == '\0'); 179 case 3: 180 if (argv[1][0] == '!' && argv[1][1] == '\0') { 181 return !(*argv[2] == '\0'); 182 } 183 break; 184 case 4: 185 if (argv[1][0] != '!' || argv[1][1] != '\0') { 186 if (t_lex(argv[2]), 187 t_wp_op && t_wp_op->op_type == BINOP) { 188 t_wp = &argv[1]; 189 return (binop() == 0); 190 } 191 } 192 break; 193 case 5: 194 if (argv[1][0] == '!' && argv[1][1] == '\0') { 195 if (t_lex(argv[3]), 196 t_wp_op && t_wp_op->op_type == BINOP) { 197 t_wp = &argv[2]; 198 return !(binop() == 0); 199 } 200 } 201 break; 202 } 203 204 t_wp = &argv[1]; 205 res = !oexpr(t_lex(*t_wp)); 206 207 if (*t_wp != NULL && *++t_wp != NULL) 208 syntax(*t_wp, "unknown operand"); 209 210 return res; 211 } 212 213 static __dead void 214 syntax(const char *op, char *msg) 215 { 216 if (op && *op) 217 errx(2, "%s: %s", op, msg); 218 else 219 errx(2, "%s", msg); 220 } 221 222 static int 223 oexpr(enum token n) 224 { 225 int res; 226 227 res = aexpr(n); 228 if (t_lex(*++t_wp) == BOR) 229 return oexpr(t_lex(*++t_wp)) || res; 230 t_wp--; 231 return res; 232 } 233 234 static int 235 aexpr(enum token n) 236 { 237 int res; 238 239 res = nexpr(n); 240 if (t_lex(*++t_wp) == BAND) 241 return aexpr(t_lex(*++t_wp)) && res; 242 t_wp--; 243 return res; 244 } 245 246 static int 247 nexpr(enum token n) 248 { 249 if (n == UNOT) 250 return !nexpr(t_lex(*++t_wp)); 251 return primary(n); 252 } 253 254 static int 255 primary(enum token n) 256 { 257 int res; 258 259 if (n == EOI) 260 syntax(NULL, "argument expected"); 261 if (n == LPAREN) { 262 res = oexpr(t_lex(*++t_wp)); 263 if (t_lex(*++t_wp) != RPAREN) 264 syntax(NULL, "closing paren expected"); 265 return res; 266 } 267 /* 268 * We need this, if not binary operations with more than 4 269 * arguments will always fall into unary. 270 */ 271 if(t_lex_type(t_wp[1]) == BINOP) { 272 t_lex(t_wp[1]); 273 if (t_wp_op && t_wp_op->op_type == BINOP) 274 return binop(); 275 } 276 277 if (t_wp_op && t_wp_op->op_type == UNOP) { 278 /* unary expression */ 279 if (*++t_wp == NULL) 280 syntax(t_wp_op->op_text, "argument expected"); 281 switch (n) { 282 case STREZ: 283 return strlen(*t_wp) == 0; 284 case STRNZ: 285 return strlen(*t_wp) != 0; 286 case FILTT: 287 return isatty(getn(*t_wp)); 288 default: 289 return filstat(*t_wp, n); 290 } 291 } 292 293 return strlen(*t_wp) > 0; 294 } 295 296 static const char * 297 getnstr(const char *s, int *signum, size_t *len) 298 { 299 const char *p, *start; 300 301 /* skip leading whitespaces */ 302 p = s; 303 while (isspace((unsigned char)*p)) 304 p++; 305 306 /* accept optional sign */ 307 if (*p == '-') { 308 *signum = -1; 309 p++; 310 } else { 311 *signum = 1; 312 if (*p == '+') 313 p++; 314 } 315 316 /* skip leading zeros */ 317 while (*p == '0' && isdigit((unsigned char)p[1])) 318 p++; 319 320 /* turn 0 always positive */ 321 if (*p == '0') 322 *signum = 1; 323 324 start = p; 325 while (isdigit((unsigned char)*p)) 326 p++; 327 *len = p - start; 328 329 /* allow trailing whitespaces */ 330 while (isspace((unsigned char)*p)) 331 p++; 332 333 /* validate number */ 334 if (*p != '\0' || *start == '\0') 335 errx(2, "%s: invalid", s); 336 337 return start; 338 } 339 340 static int 341 intcmp(const char *opnd1, const char *opnd2) 342 { 343 const char *p1, *p2; 344 size_t len1, len2; 345 int c, sig1, sig2; 346 347 p1 = getnstr(opnd1, &sig1, &len1); 348 p2 = getnstr(opnd2, &sig2, &len2); 349 350 if (sig1 != sig2) 351 c = sig1; 352 else if (len1 != len2) 353 c = (len1 < len2) ? -sig1 : sig1; 354 else 355 c = strncmp(p1, p2, len1) * sig1; 356 357 return c; 358 } 359 360 static int 361 binop(void) 362 { 363 const char *opnd1, *opnd2; 364 struct t_op const *op; 365 366 opnd1 = *t_wp; 367 (void) t_lex(*++t_wp); 368 op = t_wp_op; 369 370 if ((opnd2 = *++t_wp) == NULL) 371 syntax(op->op_text, "argument expected"); 372 373 switch (op->op_num) { 374 case STREQ: 375 return strcmp(opnd1, opnd2) == 0; 376 case STRNE: 377 return strcmp(opnd1, opnd2) != 0; 378 case STRLT: 379 return strcmp(opnd1, opnd2) < 0; 380 case STRGT: 381 return strcmp(opnd1, opnd2) > 0; 382 case INTEQ: 383 return intcmp(opnd1, opnd2) == 0; 384 case INTNE: 385 return intcmp(opnd1, opnd2) != 0; 386 case INTGE: 387 return intcmp(opnd1, opnd2) >= 0; 388 case INTGT: 389 return intcmp(opnd1, opnd2) > 0; 390 case INTLE: 391 return intcmp(opnd1, opnd2) <= 0; 392 case INTLT: 393 return intcmp(opnd1, opnd2) < 0; 394 case FILNT: 395 return newerf(opnd1, opnd2); 396 case FILOT: 397 return olderf(opnd1, opnd2); 398 case FILEQ: 399 return equalf(opnd1, opnd2); 400 } 401 402 syntax(op->op_text, "not a binary operator"); 403 } 404 405 static enum token 406 t_lex_type(char *s) 407 { 408 struct t_op const *op = ops; 409 410 if (s == NULL) 411 return -1; 412 413 while (op->op_text) { 414 if (strcmp(s, op->op_text) == 0) 415 return op->op_type; 416 op++; 417 } 418 return -1; 419 } 420 421 static int 422 filstat(char *nm, enum token mode) 423 { 424 struct stat s; 425 mode_t i; 426 427 if (mode == FILSYM) { 428 #ifdef S_IFLNK 429 if (lstat(nm, &s) == 0) { 430 i = S_IFLNK; 431 goto filetype; 432 } 433 #endif 434 return 0; 435 } 436 437 if (stat(nm, &s) != 0) 438 return 0; 439 440 switch (mode) { 441 case FILRD: 442 return access(nm, R_OK) == 0; 443 case FILWR: 444 return access(nm, W_OK) == 0; 445 case FILEX: 446 return access(nm, X_OK) == 0; 447 case FILEXIST: 448 return access(nm, F_OK) == 0; 449 case FILREG: 450 i = S_IFREG; 451 goto filetype; 452 case FILDIR: 453 i = S_IFDIR; 454 goto filetype; 455 case FILCDEV: 456 i = S_IFCHR; 457 goto filetype; 458 case FILBDEV: 459 i = S_IFBLK; 460 goto filetype; 461 case FILFIFO: 462 #ifdef S_IFIFO 463 i = S_IFIFO; 464 goto filetype; 465 #else 466 return 0; 467 #endif 468 case FILSOCK: 469 #ifdef S_IFSOCK 470 i = S_IFSOCK; 471 goto filetype; 472 #else 473 return 0; 474 #endif 475 case FILSUID: 476 i = S_ISUID; 477 goto filebit; 478 case FILSGID: 479 i = S_ISGID; 480 goto filebit; 481 case FILSTCK: 482 i = S_ISVTX; 483 goto filebit; 484 case FILGZ: 485 return s.st_size > 0L; 486 case FILUID: 487 return s.st_uid == geteuid(); 488 case FILGID: 489 return s.st_gid == getegid(); 490 default: 491 return 1; 492 } 493 494 filetype: 495 return ((s.st_mode & S_IFMT) == i); 496 497 filebit: 498 return ((s.st_mode & i) != 0); 499 } 500 501 static enum token 502 t_lex(char *s) 503 { 504 struct t_op const *op = ops; 505 506 if (s == 0) { 507 t_wp_op = NULL; 508 return EOI; 509 } 510 while (op->op_text) { 511 if (strcmp(s, op->op_text) == 0) { 512 t_wp_op = op; 513 return op->op_num; 514 } 515 op++; 516 } 517 t_wp_op = NULL; 518 return OPERAND; 519 } 520 521 /* atoi with error detection */ 522 static int 523 getn(const char *s) 524 { 525 char buf[32]; 526 const char *errstr, *p; 527 size_t len; 528 int r, sig; 529 530 p = getnstr(s, &sig, &len); 531 if (sig != 1) 532 errstr = "too small"; 533 else if (len >= sizeof(buf)) 534 errstr = "too large"; 535 else { 536 strlcpy(buf, p, sizeof(buf)); 537 buf[len] = '\0'; 538 r = strtonum(buf, 0, INT_MAX, &errstr); 539 } 540 541 if (errstr != NULL) 542 errx(2, "%s: %s", s, errstr); 543 544 return r; 545 } 546 547 static int 548 newerf(const char *f1, const char *f2) 549 { 550 struct stat b1, b2; 551 552 return (stat(f1, &b1) == 0 && 553 stat(f2, &b2) == 0 && 554 b1.st_mtime > b2.st_mtime); 555 } 556 557 static int 558 olderf(const char *f1, const char *f2) 559 { 560 struct stat b1, b2; 561 562 return (stat(f1, &b1) == 0 && 563 stat(f2, &b2) == 0 && 564 b1.st_mtime < b2.st_mtime); 565 } 566 567 static int 568 equalf(const char *f1, const char *f2) 569 { 570 struct stat b1, b2; 571 572 return (stat(f1, &b1) == 0 && 573 stat(f2, &b2) == 0 && 574 b1.st_dev == b2.st_dev && 575 b1.st_ino == b2.st_ino); 576 } 577