1 /* $OpenBSD: c_test.c,v 1.7 1999/07/14 13:37:23 millert 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 Michael Rendell to add Korn's [[ .. ]] expressions. 9 * modified by J.T. Conklin to add POSIX compatibility. 10 */ 11 12 #include "sh.h" 13 #include "ksh_stat.h" 14 #include "c_test.h" 15 16 /* test(1) accepts the following grammar: 17 oexpr ::= aexpr | aexpr "-o" oexpr ; 18 aexpr ::= nexpr | nexpr "-a" aexpr ; 19 nexpr ::= primary | "!" nexpr ; 20 primary ::= unary-operator operand 21 | operand binary-operator operand 22 | operand 23 | "(" oexpr ")" 24 ; 25 26 unary-operator ::= "-a"|"-r"|"-w"|"-x"|"-e"|"-f"|"-d"|"-c"|"-b"|"-p"| 27 "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"| 28 "-L"|"-h"|"-S"|"-H"; 29 30 binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| 31 "-nt"|"-ot"|"-ef"| 32 "<"|">" # rules used for [[ .. ]] expressions 33 ; 34 operand ::= <any thing> 35 */ 36 37 #define T_ERR_EXIT 2 /* POSIX says > 1 for errors */ 38 39 struct t_op { 40 char op_text[4]; 41 Test_op op_num; 42 }; 43 static const struct t_op u_ops [] = { 44 {"-a", TO_FILAXST }, 45 {"-b", TO_FILBDEV }, 46 {"-c", TO_FILCDEV }, 47 {"-d", TO_FILID }, 48 {"-e", TO_FILEXST }, 49 {"-f", TO_FILREG }, 50 {"-G", TO_FILGID }, 51 {"-g", TO_FILSETG }, 52 {"-h", TO_FILSYM }, 53 {"-H", TO_FILCDF }, 54 {"-k", TO_FILSTCK }, 55 {"-L", TO_FILSYM }, 56 {"-n", TO_STNZE }, 57 {"-O", TO_FILUID }, 58 {"-o", TO_OPTION }, 59 {"-p", TO_FILFIFO }, 60 {"-r", TO_FILRD }, 61 {"-s", TO_FILGZ }, 62 {"-S", TO_FILSOCK }, 63 {"-t", TO_FILTT }, 64 {"-u", TO_FILSETU }, 65 {"-w", TO_FILWR }, 66 {"-x", TO_FILEX }, 67 {"-z", TO_STZER }, 68 {"", TO_NONOP } 69 }; 70 static const struct t_op b_ops [] = { 71 {"=", TO_STEQL }, 72 #ifdef KSH 73 {"==", TO_STEQL }, 74 #endif /* KSH */ 75 {"!=", TO_STNEQ }, 76 {"<", TO_STLT }, 77 {">", TO_STGT }, 78 {"-eq", TO_INTEQ }, 79 {"-ne", TO_INTNE }, 80 {"-gt", TO_INTGT }, 81 {"-ge", TO_INTGE }, 82 {"-lt", TO_INTLT }, 83 {"-le", TO_INTLE }, 84 {"-ef", TO_FILEQ }, 85 {"-nt", TO_FILNT }, 86 {"-ot", TO_FILOT }, 87 {"", TO_NONOP } 88 }; 89 90 static int test_stat ARGS((const char *path, struct stat *statb)); 91 static int test_eaccess ARGS((const char *path, int mode)); 92 static int test_oexpr ARGS((Test_env *te, int do_eval)); 93 static int test_aexpr ARGS((Test_env *te, int do_eval)); 94 static int test_nexpr ARGS((Test_env *te, int do_eval)); 95 static int test_primary ARGS((Test_env *te, int do_eval)); 96 static int ptest_isa ARGS((Test_env *te, Test_meta meta)); 97 static const char *ptest_getopnd ARGS((Test_env *te, Test_op op, int do_eval)); 98 static int ptest_eval ARGS((Test_env *te, Test_op op, const char *opnd1, 99 const char *opnd2, int do_eval)); 100 static void ptest_error ARGS((Test_env *te, int offset, const char *msg)); 101 102 int 103 c_test(wp) 104 char **wp; 105 { 106 int argc; 107 int res; 108 Test_env te; 109 110 te.flags = 0; 111 te.isa = ptest_isa; 112 te.getopnd = ptest_getopnd; 113 te.eval = ptest_eval; 114 te.error = ptest_error; 115 116 for (argc = 0; wp[argc]; argc++) 117 ; 118 119 if (strcmp(wp[0], "[") == 0) { 120 if (strcmp(wp[--argc], "]") != 0) { 121 bi_errorf("missing ]"); 122 return T_ERR_EXIT; 123 } 124 } 125 126 te.pos.wp = wp + 1; 127 te.wp_end = wp + argc; 128 129 /* 130 * Handle the special cases from POSIX.2, section 4.62.4. 131 * Implementation of all the rules isn't necessary since 132 * our parser does the right thing for the ommited steps. 133 */ 134 if (argc <= 5) { 135 char **owp = wp; 136 int invert = 0; 137 Test_op op; 138 const char *opnd1, *opnd2; 139 140 while (--argc >= 0) { 141 if ((*te.isa)(&te, TM_END)) 142 return !0; 143 if (argc == 3) { 144 opnd1 = (*te.getopnd)(&te, TO_NONOP, 1); 145 if ((op = (Test_op) (*te.isa)(&te, TM_BINOP))) { 146 opnd2 = (*te.getopnd)(&te, op, 1); 147 res = (*te.eval)(&te, op, opnd1, opnd2, 148 1); 149 if (te.flags & TEF_ERROR) 150 return T_ERR_EXIT; 151 if (invert & 1) 152 res = !res; 153 return !res; 154 } 155 /* back up to opnd1 */ 156 te.pos.wp--; 157 } 158 if (argc == 1) { 159 opnd1 = (*te.getopnd)(&te, TO_NONOP, 1); 160 /* Historically, -t by itself test if fd 1 161 * is a file descriptor, but POSIX says its 162 * a string test... 163 */ 164 if (!Flag(FPOSIX) && strcmp(opnd1, "-t") == 0) 165 break; 166 res = (*te.eval)(&te, TO_STNZE, opnd1, 167 (char *) 0, 1); 168 if (invert & 1) 169 res = !res; 170 return !res; 171 } 172 if ((*te.isa)(&te, TM_NOT)) { 173 invert++; 174 } else 175 break; 176 } 177 te.pos.wp = owp + 1; 178 } 179 180 return test_parse(&te); 181 } 182 183 /* 184 * Generic test routines. 185 */ 186 187 Test_op 188 test_isop(te, meta, s) 189 Test_env *te; 190 Test_meta meta; 191 const char *s; 192 { 193 char sc1; 194 const struct t_op *otab; 195 196 otab = meta == TM_UNOP ? u_ops : b_ops; 197 if (*s) { 198 sc1 = s[1]; 199 for (; otab->op_text[0]; otab++) 200 if (sc1 == otab->op_text[1] 201 && strcmp(s, otab->op_text) == 0 202 && ((te->flags & TEF_DBRACKET) 203 || (otab->op_num != TO_STLT 204 && otab->op_num != TO_STGT))) 205 return otab->op_num; 206 } 207 return TO_NONOP; 208 } 209 210 int 211 test_eval(te, op, opnd1, opnd2, do_eval) 212 Test_env *te; 213 Test_op op; 214 const char *opnd1; 215 const char *opnd2; 216 int do_eval; 217 { 218 int res; 219 int not; 220 struct stat b1, b2; 221 222 if (!do_eval) 223 return 0; 224 225 switch ((int) op) { 226 /* 227 * Unary Operators 228 */ 229 case TO_STNZE: /* -n */ 230 return *opnd1 != '\0'; 231 case TO_STZER: /* -z */ 232 return *opnd1 == '\0'; 233 case TO_OPTION: /* -o */ 234 if ((not = *opnd1 == '!')) 235 opnd1++; 236 if ((res = option(opnd1)) < 0) 237 res = 0; 238 else { 239 res = Flag(res); 240 if (not) 241 res = !res; 242 } 243 return res; 244 case TO_FILRD: /* -r */ 245 return test_eaccess(opnd1, R_OK) == 0; 246 case TO_FILWR: /* -w */ 247 return test_eaccess(opnd1, W_OK) == 0; 248 case TO_FILEX: /* -x */ 249 return test_eaccess(opnd1, X_OK) == 0; 250 case TO_FILAXST: /* -a */ 251 return test_stat(opnd1, &b1) == 0; 252 case TO_FILEXST: /* -e */ 253 /* at&t ksh does not appear to do the /dev/fd/ thing for 254 * this (unless the os itself handles it) 255 */ 256 return stat(opnd1, &b1) == 0; 257 case TO_FILREG: /* -r */ 258 return test_stat(opnd1, &b1) == 0 && S_ISREG(b1.st_mode); 259 case TO_FILID: /* -d */ 260 return test_stat(opnd1, &b1) == 0 && S_ISDIR(b1.st_mode); 261 case TO_FILCDEV: /* -c */ 262 #ifdef S_ISCHR 263 return test_stat(opnd1, &b1) == 0 && S_ISCHR(b1.st_mode); 264 #else 265 return 0; 266 #endif 267 case TO_FILBDEV: /* -b */ 268 #ifdef S_ISBLK 269 return test_stat(opnd1, &b1) == 0 && S_ISBLK(b1.st_mode); 270 #else 271 return 0; 272 #endif 273 case TO_FILFIFO: /* -p */ 274 #ifdef S_ISFIFO 275 return test_stat(opnd1, &b1) == 0 && S_ISFIFO(b1.st_mode); 276 #else 277 return 0; 278 #endif 279 case TO_FILSYM: /* -h -L */ 280 #ifdef S_ISLNK 281 return lstat(opnd1, &b1) == 0 && S_ISLNK(b1.st_mode); 282 #else 283 return 0; 284 #endif 285 case TO_FILSOCK: /* -S */ 286 #ifdef S_ISSOCK 287 return test_stat(opnd1, &b1) == 0 && S_ISSOCK(b1.st_mode); 288 #else 289 return 0; 290 #endif 291 case TO_FILCDF:/* -H HP context dependent files (directories) */ 292 #ifdef S_ISCDF 293 { 294 /* Append a + to filename and check to see if result is a 295 * setuid directory. CDF stuff in general is hookey, since 296 * it breaks for the following sequence: echo hi > foo+; 297 * mkdir foo; echo bye > foo/default; chmod u+s foo 298 * (foo+ refers to the file with hi in it, there is no way 299 * to get at the file with bye in it - please correct me if 300 * I'm wrong about this). 301 */ 302 int len = strlen(opnd1); 303 char *p = str_nsave(opnd1, len + 1, ATEMP); 304 305 p[len++] = '+'; 306 p[len] = '\0'; 307 return stat(p, &b1) == 0 && S_ISCDF(b1.st_mode); 308 } 309 #else 310 return 0; 311 #endif 312 case TO_FILSETU: /* -u */ 313 #ifdef S_ISUID 314 return test_stat(opnd1, &b1) == 0 315 && (b1.st_mode & S_ISUID) == S_ISUID; 316 #else 317 return 0; 318 #endif 319 case TO_FILSETG: /* -g */ 320 #ifdef S_ISGID 321 return test_stat(opnd1, &b1) == 0 322 && (b1.st_mode & S_ISGID) == S_ISGID; 323 #else 324 return 0; 325 #endif 326 case TO_FILSTCK: /* -k */ 327 return test_stat(opnd1, &b1) == 0 328 && (b1.st_mode & S_ISVTX) == S_ISVTX; 329 case TO_FILGZ: /* -s */ 330 return test_stat(opnd1, &b1) == 0 && b1.st_size > 0L; 331 case TO_FILTT: /* -t */ 332 if (opnd1 && !bi_getn(opnd1, &res)) { 333 te->flags |= TEF_ERROR; 334 res = 0; 335 } else { 336 /* generate error if in FPOSIX mode? */ 337 res = isatty(opnd1 ? res : 0); 338 } 339 return res; 340 case TO_FILUID: /* -O */ 341 return test_stat(opnd1, &b1) == 0 && b1.st_uid == ksheuid; 342 case TO_FILGID: /* -G */ 343 return test_stat(opnd1, &b1) == 0 && b1.st_gid == getegid(); 344 /* 345 * Binary Operators 346 */ 347 case TO_STEQL: /* = */ 348 if (te->flags & TEF_DBRACKET) 349 return gmatch(opnd1, opnd2, FALSE); 350 return strcmp(opnd1, opnd2) == 0; 351 case TO_STNEQ: /* != */ 352 if (te->flags & TEF_DBRACKET) 353 return !gmatch(opnd1, opnd2, FALSE); 354 return strcmp(opnd1, opnd2) != 0; 355 case TO_STLT: /* < */ 356 return strcmp(opnd1, opnd2) < 0; 357 case TO_STGT: /* > */ 358 return strcmp(opnd1, opnd2) > 0; 359 case TO_INTEQ: /* -eq */ 360 case TO_INTNE: /* -ne */ 361 case TO_INTGE: /* -ge */ 362 case TO_INTGT: /* -gt */ 363 case TO_INTLE: /* -le */ 364 case TO_INTLT: /* -lt */ 365 { 366 long v1, v2; 367 368 if (!evaluate(opnd1, &v1, KSH_RETURN_ERROR) 369 || !evaluate(opnd2, &v2, KSH_RETURN_ERROR)) 370 { 371 /* error already printed.. */ 372 te->flags |= TEF_ERROR; 373 return 1; 374 } 375 switch ((int) op) { 376 case TO_INTEQ: 377 return v1 == v2; 378 case TO_INTNE: 379 return v1 != v2; 380 case TO_INTGE: 381 return v1 >= v2; 382 case TO_INTGT: 383 return v1 > v2; 384 case TO_INTLE: 385 return v1 <= v2; 386 case TO_INTLT: 387 return v1 < v2; 388 } 389 } 390 case TO_FILNT: /* -nt */ 391 { 392 int s2; 393 /* ksh88/ksh93 succeed if file2 can't be stated 394 * (subtly different from `does not exist'). 395 */ 396 return stat(opnd1, &b1) == 0 397 && (((s2 = stat(opnd2, &b2)) == 0 398 && b1.st_mtime > b2.st_mtime) || s2 < 0); 399 } 400 case TO_FILOT: /* -ot */ 401 { 402 int s1; 403 /* ksh88/ksh93 succeed if file1 can't be stated 404 * (subtly different from `does not exist'). 405 */ 406 return stat(opnd2, &b2) == 0 407 && (((s1 = stat(opnd1, &b1)) == 0 408 && b1.st_mtime < b2.st_mtime) || s1 < 0); 409 } 410 case TO_FILEQ: /* -ef */ 411 return stat (opnd1, &b1) == 0 && stat (opnd2, &b2) == 0 412 && b1.st_dev == b2.st_dev 413 && b1.st_ino == b2.st_ino; 414 } 415 (*te->error)(te, 0, "internal error: unknown op"); 416 return 1; 417 } 418 419 /* Nasty kludge to handle Korn's bizarre /dev/fd hack */ 420 static int 421 test_stat(path, statb) 422 const char *path; 423 struct stat *statb; 424 { 425 #if !defined(HAVE_DEV_FD) 426 int fd; 427 428 if (strncmp(path, "/dev/fd/", 8) == 0 && getn(path + 8, &fd)) 429 return fstat(fd, statb); 430 #endif /* !HAVE_DEV_FD */ 431 432 return stat(path, statb); 433 } 434 435 /* Routine to handle Korn's /dev/fd hack, and to deal with X_OK on 436 * non-directories when running as root. 437 */ 438 static int 439 test_eaccess(path, mode) 440 const char *path; 441 int mode; 442 { 443 int res; 444 445 #if !defined(HAVE_DEV_FD) 446 int fd; 447 448 /* Note: doesn't handle //dev/fd, etc.. (this is ok) */ 449 if (strncmp(path, "/dev/fd/", 8) == 0 && getn(path + 8, &fd)) { 450 int flags; 451 452 if ((flags = fcntl(fd, F_GETFL, 0)) < 0 453 || (mode & X_OK) 454 || ((mode & W_OK) && (flags & O_ACCMODE) == O_RDONLY) 455 || ((mode & R_OK) && (flags & O_ACCMODE) == O_WRONLY)) 456 return -1; 457 return 0; 458 } 459 #endif /* !HAVE_DEV_FD */ 460 461 /* On most (all?) unixes, access() says everything is executable for 462 * root - avoid this on files by using stat(). 463 */ 464 if ((mode & X_OK) && ksheuid == 0) { 465 struct stat statb; 466 467 if (stat(path, &statb) < 0) 468 res = -1; 469 else if (S_ISDIR(statb.st_mode)) 470 res = 0; 471 else 472 res = (statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) 473 ? 0 : -1; 474 /* Need to check other permissions? If so, use access() as 475 * this will deal with root on NFS. 476 */ 477 if (res == 0 && (mode & (R_OK|W_OK))) 478 res = eaccess(path, mode); 479 } else 480 res = eaccess(path, mode); 481 482 return res; 483 } 484 485 int 486 test_parse(te) 487 Test_env *te; 488 { 489 int res; 490 491 res = test_oexpr(te, 1); 492 493 if (!(te->flags & TEF_ERROR) && !(*te->isa)(te, TM_END)) 494 (*te->error)(te, 0, "unexpected operator/operand"); 495 496 return (te->flags & TEF_ERROR) ? T_ERR_EXIT : !res; 497 } 498 499 static int 500 test_oexpr(te, do_eval) 501 Test_env *te; 502 int do_eval; 503 { 504 int res; 505 506 res = test_aexpr(te, do_eval); 507 if (res) 508 do_eval = 0; 509 if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_OR)) 510 return test_oexpr(te, do_eval) || res; 511 return res; 512 } 513 514 static int 515 test_aexpr(te, do_eval) 516 Test_env *te; 517 int do_eval; 518 { 519 int res; 520 521 res = test_nexpr(te, do_eval); 522 if (!res) 523 do_eval = 0; 524 if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_AND)) 525 return test_aexpr(te, do_eval) && res; 526 return res; 527 } 528 529 static int 530 test_nexpr(te, do_eval) 531 Test_env *te; 532 int do_eval; 533 { 534 if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_NOT)) 535 return !test_nexpr(te, do_eval); 536 return test_primary(te, do_eval); 537 } 538 539 static int 540 test_primary(te, do_eval) 541 Test_env *te; 542 int do_eval; 543 { 544 const char *opnd1, *opnd2; 545 int res; 546 Test_op op; 547 548 if (te->flags & TEF_ERROR) 549 return 0; 550 if ((*te->isa)(te, TM_OPAREN)) { 551 res = test_oexpr(te, do_eval); 552 if (te->flags & TEF_ERROR) 553 return 0; 554 if (!(*te->isa)(te, TM_CPAREN)) { 555 (*te->error)(te, 0, "missing closing paren"); 556 return 0; 557 } 558 return res; 559 } 560 if ((op = (Test_op) (*te->isa)(te, TM_UNOP))) { 561 /* unary expression */ 562 opnd1 = (*te->getopnd)(te, op, do_eval); 563 if (!opnd1) { 564 (*te->error)(te, -1, "missing argument"); 565 return 0; 566 } 567 568 return (*te->eval)(te, op, opnd1, (const char *) 0, do_eval); 569 } 570 opnd1 = (*te->getopnd)(te, TO_NONOP, do_eval); 571 if (!opnd1) { 572 (*te->error)(te, 0, "expression expected"); 573 return 0; 574 } 575 if ((op = (Test_op) (*te->isa)(te, TM_BINOP))) { 576 /* binary expression */ 577 opnd2 = (*te->getopnd)(te, op, do_eval); 578 if (!opnd2) { 579 (*te->error)(te, -1, "missing second argument"); 580 return 0; 581 } 582 583 return (*te->eval)(te, op, opnd1, opnd2, do_eval); 584 } 585 if (te->flags & TEF_DBRACKET) { 586 (*te->error)(te, -1, "missing expression operator"); 587 return 0; 588 } 589 return (*te->eval)(te, TO_STNZE, opnd1, (const char *) 0, do_eval); 590 } 591 592 /* 593 * Plain test (test and [ .. ]) specific routines. 594 */ 595 596 /* Test if the current token is a whatever. Accepts the current token if 597 * it is. Returns 0 if it is not, non-zero if it is (in the case of 598 * TM_UNOP and TM_BINOP, the returned value is a Test_op). 599 */ 600 static int 601 ptest_isa(te, meta) 602 Test_env *te; 603 Test_meta meta; 604 { 605 /* Order important - indexed by Test_meta values */ 606 static const char *const tokens[] = { 607 "-o", "-a", "!", "(", ")" 608 }; 609 int ret; 610 611 if (te->pos.wp >= te->wp_end) 612 return meta == TM_END; 613 614 if (meta == TM_UNOP || meta == TM_BINOP) 615 ret = (int) test_isop(te, meta, *te->pos.wp); 616 else if (meta == TM_END) 617 ret = 0; 618 else 619 ret = strcmp(*te->pos.wp, tokens[(int) meta]) == 0; 620 621 /* Accept the token? */ 622 if (ret) 623 te->pos.wp++; 624 625 return ret; 626 } 627 628 static const char * 629 ptest_getopnd(te, op, do_eval) 630 Test_env *te; 631 Test_op op; 632 int do_eval; 633 { 634 if (te->pos.wp >= te->wp_end) 635 return op == TO_FILTT ? "1" : (const char *) 0; 636 return *te->pos.wp++; 637 } 638 639 static int 640 ptest_eval(te, op, opnd1, opnd2, do_eval) 641 Test_env *te; 642 Test_op op; 643 const char *opnd1; 644 const char *opnd2; 645 int do_eval; 646 { 647 return test_eval(te, op, opnd1, opnd2, do_eval); 648 } 649 650 static void 651 ptest_error(te, offset, msg) 652 Test_env *te; 653 int offset; 654 const char *msg; 655 { 656 const char *op = te->pos.wp + offset >= te->wp_end ? 657 (const char *) 0 : te->pos.wp[offset]; 658 659 te->flags |= TEF_ERROR; 660 if (op) 661 bi_errorf("%s: %s", op, msg); 662 else 663 bi_errorf("%s", msg); 664 } 665