1 /* $NetBSD: c_test.c,v 1.2 1997/01/12 19:11:40 tls 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 res = (*te.eval)(&te, TO_STNZE, opnd1, 161 (char *) 0, 1); 162 if (invert & 1) 163 res = !res; 164 return !res; 165 } 166 if ((*te.isa)(&te, TM_NOT)) { 167 invert++; 168 } else 169 break; 170 } 171 te.pos.wp = owp + 1; 172 } 173 174 return test_parse(&te); 175 } 176 177 /* 178 * Generic test routines. 179 */ 180 181 Test_op 182 test_isop(te, meta, s) 183 Test_env *te; 184 Test_meta meta; 185 const char *s; 186 { 187 char sc1; 188 const struct t_op *otab; 189 190 otab = meta == TM_UNOP ? u_ops : b_ops; 191 if (*s) { 192 sc1 = s[1]; 193 for (; otab->op_text[0]; otab++) 194 if (sc1 == otab->op_text[1] 195 && strcmp(s, otab->op_text) == 0 196 && ((te->flags & TEF_DBRACKET) 197 || (otab->op_num != TO_STLT 198 && otab->op_num != TO_STGT))) 199 return otab->op_num; 200 } 201 return TO_NONOP; 202 } 203 204 int 205 test_eval(te, op, opnd1, opnd2, do_eval) 206 Test_env *te; 207 Test_op op; 208 const char *opnd1; 209 const char *opnd2; 210 int do_eval; 211 { 212 int res; 213 int not; 214 struct stat b1, b2; 215 216 if (!do_eval) 217 return 0; 218 219 switch ((int) op) { 220 /* 221 * Unary Operators 222 */ 223 case TO_STNZE: /* -n */ 224 return *opnd1 != '\0'; 225 case TO_STZER: /* -z */ 226 return *opnd1 == '\0'; 227 case TO_OPTION: /* -o */ 228 if ((not = *opnd1 == '!')) 229 opnd1++; 230 if ((res = option(opnd1)) < 0) 231 res = 0; 232 else { 233 res = Flag(res); 234 if (not) 235 res = !res; 236 } 237 return res; 238 case TO_FILRD: /* -r */ 239 return test_eaccess(opnd1, R_OK) == 0; 240 case TO_FILWR: /* -w */ 241 return test_eaccess(opnd1, W_OK) == 0; 242 case TO_FILEX: /* -x */ 243 return test_eaccess(opnd1, X_OK) == 0; 244 case TO_FILAXST: /* -a */ 245 return test_stat(opnd1, &b1) == 0; 246 case TO_FILEXST: /* -e */ 247 /* at&t ksh does not appear to do the /dev/fd/ thing for 248 * this (unless the os itself handles it) 249 */ 250 return stat(opnd1, &b1) == 0; 251 case TO_FILREG: /* -r */ 252 return test_stat(opnd1, &b1) == 0 && S_ISREG(b1.st_mode); 253 case TO_FILID: /* -d */ 254 return test_stat(opnd1, &b1) == 0 && S_ISDIR(b1.st_mode); 255 case TO_FILCDEV: /* -c */ 256 #ifdef S_ISCHR 257 return test_stat(opnd1, &b1) == 0 && S_ISCHR(b1.st_mode); 258 #else 259 return 0; 260 #endif 261 case TO_FILBDEV: /* -b */ 262 #ifdef S_ISBLK 263 return test_stat(opnd1, &b1) == 0 && S_ISBLK(b1.st_mode); 264 #else 265 return 0; 266 #endif 267 case TO_FILFIFO: /* -p */ 268 #ifdef S_ISFIFO 269 return test_stat(opnd1, &b1) == 0 && S_ISFIFO(b1.st_mode); 270 #else 271 return 0; 272 #endif 273 case TO_FILSYM: /* -h -L */ 274 #ifdef S_ISLNK 275 return lstat(opnd1, &b1) == 0 && S_ISLNK(b1.st_mode); 276 #else 277 return 0; 278 #endif 279 case TO_FILSOCK: /* -S */ 280 #ifdef S_ISSOCK 281 return test_stat(opnd1, &b1) == 0 && S_ISSOCK(b1.st_mode); 282 #else 283 return 0; 284 #endif 285 case TO_FILCDF:/* -H HP context dependent files (directories) */ 286 #ifdef S_ISCDF 287 { 288 /* Append a + to filename and check to see if result is a 289 * setuid directory. CDF stuff in general is hookey, since 290 * it breaks for the following sequence: echo hi > foo+; 291 * mkdir foo; echo bye > foo/default; chmod u+s foo 292 * (foo+ refers to the file with hi in it, there is no way 293 * to get at the file with bye in it - please correct me if 294 * I'm wrong about this). 295 */ 296 int len = strlen(opnd1); 297 char *p = str_nsave(opnd1, len + 1, ATEMP); 298 299 p[len++] = '+'; 300 p[len] = '\0'; 301 return stat(p, &b1) == 0 && S_ISCDF(b1.st_mode); 302 } 303 #else 304 return 0; 305 #endif 306 case TO_FILSETU: /* -u */ 307 #ifdef S_ISUID 308 return test_stat(opnd1, &b1) == 0 309 && (b1.st_mode & S_ISUID) == S_ISUID; 310 #else 311 return 0; 312 #endif 313 case TO_FILSETG: /* -g */ 314 #ifdef S_ISGID 315 return test_stat(opnd1, &b1) == 0 316 && (b1.st_mode & S_ISGID) == S_ISGID; 317 #else 318 return 0; 319 #endif 320 case TO_FILSTCK: /* -k */ 321 return test_stat(opnd1, &b1) == 0 322 && (b1.st_mode & S_ISVTX) == S_ISVTX; 323 case TO_FILGZ: /* -s */ 324 return test_stat(opnd1, &b1) == 0 && b1.st_size > 0L; 325 case TO_FILTT: /* -t */ 326 if (opnd1 && !bi_getn(opnd1, &res)) { 327 te->flags |= TEF_ERROR; 328 res = 0; 329 } else 330 res = isatty(opnd1 ? res : 0); 331 return res; 332 case TO_FILUID: /* -O */ 333 return test_stat(opnd1, &b1) == 0 && b1.st_uid == geteuid(); 334 case TO_FILGID: /* -G */ 335 return test_stat(opnd1, &b1) == 0 && b1.st_gid == getegid(); 336 /* 337 * Binary Operators 338 */ 339 case TO_STEQL: /* = */ 340 if (te->flags & TEF_DBRACKET) 341 return gmatch(opnd1, opnd2, FALSE); 342 return strcmp(opnd1, opnd2) == 0; 343 case TO_STNEQ: /* != */ 344 if (te->flags & TEF_DBRACKET) 345 return !gmatch(opnd1, opnd2, FALSE); 346 return strcmp(opnd1, opnd2) != 0; 347 case TO_STLT: /* < */ 348 return strcmp(opnd1, opnd2) < 0; 349 case TO_STGT: /* > */ 350 return strcmp(opnd1, opnd2) > 0; 351 case TO_INTEQ: /* -eq */ 352 case TO_INTNE: /* -ne */ 353 case TO_INTGE: /* -ge */ 354 case TO_INTGT: /* -gt */ 355 case TO_INTLE: /* -le */ 356 case TO_INTLT: /* -lt */ 357 { 358 long v1, v2; 359 360 if (!evaluate(opnd1, &v1, TRUE) 361 || !evaluate(opnd2, &v2, TRUE)) 362 { 363 /* error already printed.. */ 364 te->flags |= TEF_ERROR; 365 return 1; 366 } 367 switch ((int) op) { 368 case TO_INTEQ: 369 return v1 == v2; 370 case TO_INTNE: 371 return v1 != v2; 372 case TO_INTGE: 373 return v1 >= v2; 374 case TO_INTGT: 375 return v1 > v2; 376 case TO_INTLE: 377 return v1 <= v2; 378 case TO_INTLT: 379 return v1 < v2; 380 } 381 } 382 case TO_FILNT: /* -nt */ 383 return stat (opnd1, &b1) == 0 && stat (opnd2, &b2) == 0 384 && b1.st_mtime > b2.st_mtime; 385 case TO_FILOT: /* -ot */ 386 return stat (opnd1, &b1) == 0 && stat (opnd2, &b2) == 0 387 && b1.st_mtime < b2.st_mtime; 388 case TO_FILEQ: /* -ef */ 389 return stat (opnd1, &b1) == 0 && stat (opnd2, &b2) == 0 390 && b1.st_dev == b2.st_dev 391 && b1.st_ino == b2.st_ino; 392 } 393 (*te->error)(te, 0, "internal error: unknown op"); 394 return 1; 395 } 396 397 /* Nasty kludge to handle Korn's bizarre /dev/fd hack */ 398 static int 399 test_stat(path, statb) 400 const char *path; 401 struct stat *statb; 402 { 403 #if !defined(HAVE_DEV_FD) 404 int fd; 405 406 if (strncmp(path, "/dev/fd/", 8) == 0 && getn(path + 8, &fd)) 407 return fstat(fd, statb); 408 #endif /* !HAVE_DEV_FD */ 409 410 return stat(path, statb); 411 } 412 413 /* Another nasty kludge to handle Korn's bizarre /dev/fd hack */ 414 static int 415 test_eaccess(path, mode) 416 const char *path; 417 int mode; 418 { 419 #if !defined(HAVE_DEV_FD) 420 int fd; 421 422 if (strncmp(path, "/dev/fd/", 8) == 0 && getn(path + 8, &fd)) { 423 int flags; 424 425 if ((flags = fcntl(fd, F_GETFL, 0)) < 0 426 || (mode & X_OK) 427 || ((mode & W_OK) && (flags & O_ACCMODE) == O_RDONLY) 428 || ((mode & R_OK) && (flags & O_ACCMODE) == O_WRONLY)) 429 return -1; 430 return 0; 431 } 432 #endif /* !HAVE_DEV_FD */ 433 434 return eaccess(path, mode); 435 } 436 437 int 438 test_parse(te) 439 Test_env *te; 440 { 441 int res; 442 443 res = test_oexpr(te, 1); 444 445 if (!(te->flags & TEF_ERROR) && !(*te->isa)(te, TM_END)) 446 (*te->error)(te, 0, "unexpected operator/operand"); 447 448 return (te->flags & TEF_ERROR) ? T_ERR_EXIT : !res; 449 } 450 451 static int 452 test_oexpr(te, do_eval) 453 Test_env *te; 454 int do_eval; 455 { 456 int res; 457 458 res = test_aexpr(te, do_eval); 459 if (res) 460 do_eval = 0; 461 if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_OR)) 462 return test_oexpr(te, do_eval) || res; 463 return res; 464 } 465 466 static int 467 test_aexpr(te, do_eval) 468 Test_env *te; 469 int do_eval; 470 { 471 int res; 472 473 res = test_nexpr(te, do_eval); 474 if (!res) 475 do_eval = 0; 476 if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_AND)) 477 return test_aexpr(te, do_eval) && res; 478 return res; 479 } 480 481 static int 482 test_nexpr(te, do_eval) 483 Test_env *te; 484 int do_eval; 485 { 486 if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_NOT)) 487 return !test_nexpr(te, do_eval); 488 return test_primary(te, do_eval); 489 } 490 491 static int 492 test_primary(te, do_eval) 493 Test_env *te; 494 int do_eval; 495 { 496 const char *opnd1, *opnd2; 497 int res; 498 Test_op op; 499 500 if (te->flags & TEF_ERROR) 501 return 0; 502 if ((*te->isa)(te, TM_OPAREN)) { 503 res = test_oexpr(te, do_eval); 504 if (te->flags & TEF_ERROR) 505 return 0; 506 if (!(*te->isa)(te, TM_CPAREN)) { 507 (*te->error)(te, 0, "missing closing paren"); 508 return 0; 509 } 510 return res; 511 } 512 if ((op = (Test_op) (*te->isa)(te, TM_UNOP))) { 513 /* unary expression */ 514 opnd1 = (*te->getopnd)(te, op, do_eval); 515 if (!opnd1) { 516 (*te->error)(te, -1, "missing argument"); 517 return 0; 518 } 519 520 return (*te->eval)(te, op, opnd1, (const char *) 0, do_eval); 521 } 522 opnd1 = (*te->getopnd)(te, TO_NONOP, do_eval); 523 if (!opnd1) { 524 (*te->error)(te, 0, "expression expected"); 525 return 0; 526 } 527 if ((op = (Test_op) (*te->isa)(te, TM_BINOP))) { 528 /* binary expression */ 529 opnd2 = (*te->getopnd)(te, op, do_eval); 530 if (!opnd2) { 531 (*te->error)(te, -1, "missing second argument"); 532 return 0; 533 } 534 535 return (*te->eval)(te, op, opnd1, opnd2, do_eval); 536 } 537 if (te->flags & TEF_DBRACKET) { 538 (*te->error)(te, -1, "missing expression operator"); 539 return 0; 540 } 541 return (*te->eval)(te, TO_STNZE, opnd1, (const char *) 0, do_eval); 542 } 543 544 /* 545 * Plain test (test and [ .. ]) specific routines. 546 */ 547 548 /* Test if the current token is a whatever. Accepts the current token if 549 * it is. Returns 0 if it is not, non-zero if it is (in the case of 550 * TM_UNOP and TM_BINOP, the returned value is a Test_op). 551 */ 552 static int 553 ptest_isa(te, meta) 554 Test_env *te; 555 Test_meta meta; 556 { 557 /* Order important - indexed by Test_meta values */ 558 static const char *const tokens[] = { 559 "-o", "-a", "!", "(", ")" 560 }; 561 int ret; 562 563 if (te->pos.wp >= te->wp_end) 564 return meta == TM_END; 565 566 if (meta == TM_UNOP || meta == TM_BINOP) 567 ret = (int) test_isop(te, meta, *te->pos.wp); 568 else if (meta == TM_END) 569 ret = 0; 570 else 571 ret = strcmp(*te->pos.wp, tokens[(int) meta]) == 0; 572 573 /* Accept the token? */ 574 if (ret) 575 te->pos.wp++; 576 577 return ret; 578 } 579 580 static const char * 581 ptest_getopnd(te, op, do_eval) 582 Test_env *te; 583 Test_op op; 584 int do_eval; 585 { 586 if (te->pos.wp >= te->wp_end) 587 return op == TO_FILTT ? "1" : (const char *) 0; 588 return *te->pos.wp++; 589 } 590 591 static int 592 ptest_eval(te, op, opnd1, opnd2, do_eval) 593 Test_env *te; 594 Test_op op; 595 const char *opnd1; 596 const char *opnd2; 597 int do_eval; 598 { 599 return test_eval(te, op, opnd1, opnd2, do_eval); 600 } 601 602 static void 603 ptest_error(te, offset, msg) 604 Test_env *te; 605 int offset; 606 const char *msg; 607 { 608 const char *op = te->pos.wp + offset >= te->wp_end ? 609 (const char *) 0 : te->pos.wp[offset]; 610 611 te->flags |= TEF_ERROR; 612 if (op) 613 bi_errorf("%s: %s", op, msg); 614 else 615 bi_errorf("%s", msg); 616 } 617