1 /*- 2 * Copyright (c) 1991, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * This code is derived from software contributed to Berkeley by 6 * Kenneth Almquist. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. Neither the name of the University nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33 #ifndef lint 34 #endif /* not lint */ 35 #include <sys/cdefs.h> 36 #include <sys/types.h> 37 #include <sys/stat.h> 38 #include <unistd.h> 39 #include <fcntl.h> 40 #include <errno.h> 41 #include <paths.h> 42 #include <stdbool.h> 43 #include <stdlib.h> 44 45 /* 46 * When commands are first encountered, they are entered in a hash table. 47 * This ensures that a full path search will not have to be done for them 48 * on each invocation. 49 * 50 * We should investigate converting to a linear search, even though that 51 * would make the command name "hash" a misnomer. 52 */ 53 54 #include "shell.h" 55 #include "main.h" 56 #include "nodes.h" 57 #include "parser.h" 58 #include "redir.h" 59 #include "eval.h" 60 #include "exec.h" 61 #include "builtins.h" 62 #include "var.h" 63 #include "options.h" 64 #include "input.h" 65 #include "output.h" 66 #include "syntax.h" 67 #include "memalloc.h" 68 #include "error.h" 69 #include "mystring.h" 70 #include "show.h" 71 #include "jobs.h" 72 #include "alias.h" 73 74 75 #define CMDTABLESIZE 31 /* should be prime */ 76 77 78 79 struct tblentry { 80 struct tblentry *next; /* next entry in hash chain */ 81 union param param; /* definition of builtin function */ 82 int special; /* flag for special builtin commands */ 83 signed char cmdtype; /* index identifying command */ 84 char cmdname[]; /* name of command */ 85 }; 86 87 88 static struct tblentry *cmdtable[CMDTABLESIZE]; 89 static int cmdtable_cd = 0; /* cmdtable contains cd-dependent entries */ 90 91 92 static void tryexec(char *, char **, char **); 93 static void printentry(struct tblentry *, int); 94 static struct tblentry *cmdlookup(const char *, int); 95 static void delete_cmd_entry(void); 96 static void addcmdentry(const char *, struct cmdentry *); 97 98 99 100 /* 101 * Exec a program. Never returns. If you change this routine, you may 102 * have to change the find_command routine as well. 103 * 104 * The argv array may be changed and element argv[-1] should be writable. 105 */ 106 107 void 108 shellexec(char **argv, char **envp, const char *path, int idx) 109 { 110 char *cmdname; 111 const char *opt; 112 int e; 113 114 if (strchr(argv[0], '/') != NULL) { 115 tryexec(argv[0], argv, envp); 116 e = errno; 117 } else { 118 e = ENOENT; 119 while ((cmdname = padvance(&path, &opt, argv[0])) != NULL) { 120 if (--idx < 0 && opt == NULL) { 121 tryexec(cmdname, argv, envp); 122 if (errno != ENOENT && errno != ENOTDIR) 123 e = errno; 124 if (e == ENOEXEC) 125 break; 126 } 127 stunalloc(cmdname); 128 } 129 } 130 131 /* Map to POSIX errors */ 132 if (e == ENOENT || e == ENOTDIR) 133 errorwithstatus(127, "%s: not found", argv[0]); 134 else 135 errorwithstatus(126, "%s: %s", argv[0], strerror(e)); 136 } 137 138 139 static bool 140 isbinary(const char *data, size_t len) 141 { 142 const char *nul, *p; 143 bool hasletter; 144 145 nul = memchr(data, '\0', len); 146 if (nul == NULL) 147 return false; 148 /* 149 * POSIX says we shall allow execution if the initial part intended 150 * to be parsed by the shell consists of characters and does not 151 * contain the NUL character. This allows concatenating a shell 152 * script (ending with exec or exit) and a binary payload. 153 * 154 * In order to reject common binary files such as PNG images, check 155 * that there is a lowercase letter or expansion before the last 156 * newline before the NUL character, in addition to the check for 157 * the newline character suggested by POSIX. 158 */ 159 hasletter = false; 160 for (p = data; *p != '\0'; p++) { 161 if ((*p >= 'a' && *p <= 'z') || *p == '$' || *p == '`') 162 hasletter = true; 163 if (hasletter && *p == '\n') 164 return false; 165 } 166 return true; 167 } 168 169 170 static void 171 tryexec(char *cmd, char **argv, char **envp) 172 { 173 int e, in; 174 ssize_t n; 175 char buf[256]; 176 177 execve(cmd, argv, envp); 178 e = errno; 179 if (e == ENOEXEC) { 180 INTOFF; 181 in = open(cmd, O_RDONLY | O_NONBLOCK); 182 if (in != -1) { 183 n = pread(in, buf, sizeof buf, 0); 184 close(in); 185 if (n > 0 && isbinary(buf, n)) { 186 errno = ENOEXEC; 187 return; 188 } 189 } 190 *argv = cmd; 191 *--argv = __DECONST(char *, _PATH_BSHELL); 192 execve(_PATH_BSHELL, argv, envp); 193 } 194 errno = e; 195 } 196 197 /* 198 * Do a path search. The variable path (passed by reference) should be 199 * set to the start of the path before the first call; padvance will update 200 * this value as it proceeds. Successive calls to padvance will return 201 * the possible path expansions in sequence. If popt is not NULL, options 202 * are processed: if an option (indicated by a percent sign) appears in 203 * the path entry then *popt will be set to point to it; else *popt will be 204 * set to NULL. If popt is NULL, percent signs are not special. 205 */ 206 207 char * 208 padvance(const char **path, const char **popt, const char *name) 209 { 210 const char *p, *start; 211 char *q; 212 size_t len, namelen; 213 214 if (*path == NULL) 215 return NULL; 216 start = *path; 217 if (popt != NULL) 218 for (p = start; *p && *p != ':' && *p != '%'; p++) 219 ; /* nothing */ 220 else 221 for (p = start; *p && *p != ':'; p++) 222 ; /* nothing */ 223 namelen = strlen(name); 224 len = p - start + namelen + 2; /* "2" is for '/' and '\0' */ 225 STARTSTACKSTR(q); 226 CHECKSTRSPACE(len, q); 227 if (p != start) { 228 memcpy(q, start, p - start); 229 q += p - start; 230 *q++ = '/'; 231 } 232 memcpy(q, name, namelen + 1); 233 if (popt != NULL) { 234 if (*p == '%') { 235 *popt = ++p; 236 while (*p && *p != ':') p++; 237 } else 238 *popt = NULL; 239 } 240 if (*p == ':') 241 *path = p + 1; 242 else 243 *path = NULL; 244 return stalloc(len); 245 } 246 247 248 249 /*** Command hashing code ***/ 250 251 252 int 253 hashcmd(int argc __unused, char **argv __unused) 254 { 255 struct tblentry **pp; 256 struct tblentry *cmdp; 257 int c; 258 int verbose; 259 struct cmdentry entry; 260 char *name; 261 int errors; 262 263 errors = 0; 264 verbose = 0; 265 while ((c = nextopt("rv")) != '\0') { 266 if (c == 'r') { 267 clearcmdentry(); 268 } else if (c == 'v') { 269 verbose++; 270 } 271 } 272 if (*argptr == NULL) { 273 for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) { 274 for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { 275 if (cmdp->cmdtype == CMDNORMAL) 276 printentry(cmdp, verbose); 277 } 278 } 279 return 0; 280 } 281 while ((name = *argptr) != NULL) { 282 if ((cmdp = cmdlookup(name, 0)) != NULL 283 && cmdp->cmdtype == CMDNORMAL) 284 delete_cmd_entry(); 285 find_command(name, &entry, DO_ERR, pathval()); 286 if (entry.cmdtype == CMDUNKNOWN) 287 errors = 1; 288 else if (verbose) { 289 cmdp = cmdlookup(name, 0); 290 if (cmdp != NULL) 291 printentry(cmdp, verbose); 292 else { 293 outfmt(out2, "%s: not found\n", name); 294 errors = 1; 295 } 296 flushall(); 297 } 298 argptr++; 299 } 300 return errors; 301 } 302 303 304 static void 305 printentry(struct tblentry *cmdp, int verbose) 306 { 307 int idx; 308 const char *path, *opt; 309 char *name; 310 311 if (cmdp->cmdtype == CMDNORMAL) { 312 idx = cmdp->param.index; 313 path = pathval(); 314 do { 315 name = padvance(&path, &opt, cmdp->cmdname); 316 stunalloc(name); 317 } while (--idx >= 0); 318 out1str(name); 319 } else if (cmdp->cmdtype == CMDBUILTIN) { 320 out1fmt("builtin %s", cmdp->cmdname); 321 } else if (cmdp->cmdtype == CMDFUNCTION) { 322 out1fmt("function %s", cmdp->cmdname); 323 if (verbose) { 324 INTOFF; 325 name = commandtext(getfuncnode(cmdp->param.func)); 326 out1c(' '); 327 out1str(name); 328 ckfree(name); 329 INTON; 330 } 331 #ifdef DEBUG 332 } else { 333 error("internal error: cmdtype %d", cmdp->cmdtype); 334 #endif 335 } 336 out1c('\n'); 337 } 338 339 340 341 /* 342 * Resolve a command name. If you change this routine, you may have to 343 * change the shellexec routine as well. 344 */ 345 346 void 347 find_command(const char *name, struct cmdentry *entry, int act, 348 const char *path) 349 { 350 struct tblentry *cmdp, loc_cmd; 351 int idx; 352 const char *opt; 353 char *fullname; 354 struct stat statb; 355 int e; 356 int i; 357 int spec; 358 int cd; 359 360 /* If name contains a slash, don't use the hash table */ 361 if (strchr(name, '/') != NULL) { 362 entry->cmdtype = CMDNORMAL; 363 entry->u.index = 0; 364 entry->special = 0; 365 return; 366 } 367 368 cd = 0; 369 370 /* If name is in the table, we're done */ 371 if ((cmdp = cmdlookup(name, 0)) != NULL) { 372 if (cmdp->cmdtype == CMDFUNCTION && act & DO_NOFUNC) 373 cmdp = NULL; 374 else 375 goto success; 376 } 377 378 /* Check for builtin next */ 379 if ((i = find_builtin(name, &spec)) >= 0) { 380 INTOFF; 381 cmdp = cmdlookup(name, 1); 382 if (cmdp->cmdtype == CMDFUNCTION) 383 cmdp = &loc_cmd; 384 cmdp->cmdtype = CMDBUILTIN; 385 cmdp->param.index = i; 386 cmdp->special = spec; 387 INTON; 388 goto success; 389 } 390 391 /* We have to search path. */ 392 393 e = ENOENT; 394 idx = -1; 395 for (;(fullname = padvance(&path, &opt, name)) != NULL; 396 stunalloc(fullname)) { 397 idx++; 398 if (opt) { 399 if (strncmp(opt, "func", 4) == 0) { 400 /* handled below */ 401 } else { 402 continue; /* ignore unimplemented options */ 403 } 404 } 405 if (fullname[0] != '/') 406 cd = 1; 407 if (stat(fullname, &statb) < 0) { 408 if (errno != ENOENT && errno != ENOTDIR) 409 e = errno; 410 continue; 411 } 412 e = EACCES; /* if we fail, this will be the error */ 413 if (!S_ISREG(statb.st_mode)) 414 continue; 415 if (opt) { /* this is a %func directory */ 416 readcmdfile(fullname, -1 /* verify */); 417 if ((cmdp = cmdlookup(name, 0)) == NULL || cmdp->cmdtype != CMDFUNCTION) 418 error("%s not defined in %s", name, fullname); 419 stunalloc(fullname); 420 goto success; 421 } 422 #ifdef notdef 423 if (statb.st_uid == geteuid()) { 424 if ((statb.st_mode & 0100) == 0) 425 goto loop; 426 } else if (statb.st_gid == getegid()) { 427 if ((statb.st_mode & 010) == 0) 428 goto loop; 429 } else { 430 if ((statb.st_mode & 01) == 0) 431 goto loop; 432 } 433 #endif 434 TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname)); 435 INTOFF; 436 stunalloc(fullname); 437 cmdp = cmdlookup(name, 1); 438 if (cmdp->cmdtype == CMDFUNCTION) 439 cmdp = &loc_cmd; 440 cmdp->cmdtype = CMDNORMAL; 441 cmdp->param.index = idx; 442 cmdp->special = 0; 443 INTON; 444 goto success; 445 } 446 447 if (act & DO_ERR) { 448 if (e == ENOENT || e == ENOTDIR) 449 outfmt(out2, "%s: not found\n", name); 450 else 451 outfmt(out2, "%s: %s\n", name, strerror(e)); 452 } 453 entry->cmdtype = CMDUNKNOWN; 454 entry->u.index = 0; 455 entry->special = 0; 456 return; 457 458 success: 459 if (cd) 460 cmdtable_cd = 1; 461 entry->cmdtype = cmdp->cmdtype; 462 entry->u = cmdp->param; 463 entry->special = cmdp->special; 464 } 465 466 467 468 /* 469 * Search the table of builtin commands. 470 */ 471 472 int 473 find_builtin(const char *name, int *special) 474 { 475 const unsigned char *bp; 476 size_t len; 477 478 len = strlen(name); 479 for (bp = builtincmd ; *bp ; bp += 2 + bp[0]) { 480 if (bp[0] == len && memcmp(bp + 2, name, len) == 0) { 481 *special = (bp[1] & BUILTIN_SPECIAL) != 0; 482 return bp[1] & ~BUILTIN_SPECIAL; 483 } 484 } 485 return -1; 486 } 487 488 489 490 /* 491 * Called when a cd is done. If any entry in cmdtable depends on the current 492 * directory, simply clear cmdtable completely. 493 */ 494 495 void 496 hashcd(void) 497 { 498 if (cmdtable_cd) 499 clearcmdentry(); 500 } 501 502 503 504 /* 505 * Called before PATH is changed. The argument is the new value of PATH; 506 * pathval() still returns the old value at this point. Called with 507 * interrupts off. 508 */ 509 510 void 511 changepath(const char *newval __unused) 512 { 513 clearcmdentry(); 514 } 515 516 517 /* 518 * Clear out cached utility locations. 519 */ 520 521 void 522 clearcmdentry(void) 523 { 524 struct tblentry **tblp; 525 struct tblentry **pp; 526 struct tblentry *cmdp; 527 528 INTOFF; 529 for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) { 530 pp = tblp; 531 while ((cmdp = *pp) != NULL) { 532 if (cmdp->cmdtype == CMDNORMAL) { 533 *pp = cmdp->next; 534 ckfree(cmdp); 535 } else { 536 pp = &cmdp->next; 537 } 538 } 539 } 540 cmdtable_cd = 0; 541 INTON; 542 } 543 544 545 static unsigned int 546 hashname(const char *p) 547 { 548 unsigned int hashval; 549 550 hashval = (unsigned char)*p << 4; 551 while (*p) 552 hashval += *p++; 553 554 return (hashval % CMDTABLESIZE); 555 } 556 557 558 /* 559 * Locate a command in the command hash table. If "add" is nonzero, 560 * add the command to the table if it is not already present. The 561 * variable "lastcmdentry" is set to point to the address of the link 562 * pointing to the entry, so that delete_cmd_entry can delete the 563 * entry. 564 */ 565 566 static struct tblentry **lastcmdentry; 567 568 569 static struct tblentry * 570 cmdlookup(const char *name, int add) 571 { 572 struct tblentry *cmdp; 573 struct tblentry **pp; 574 size_t len; 575 576 pp = &cmdtable[hashname(name)]; 577 for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { 578 if (equal(cmdp->cmdname, name)) 579 break; 580 pp = &cmdp->next; 581 } 582 if (add && cmdp == NULL) { 583 INTOFF; 584 len = strlen(name); 585 cmdp = *pp = ckmalloc(sizeof (struct tblentry) + len + 1); 586 cmdp->next = NULL; 587 cmdp->cmdtype = CMDUNKNOWN; 588 memcpy(cmdp->cmdname, name, len + 1); 589 INTON; 590 } 591 lastcmdentry = pp; 592 return cmdp; 593 } 594 595 const void * 596 itercmd(const void *entry, struct cmdentry *result) 597 { 598 const struct tblentry *e = entry; 599 size_t i = 0; 600 601 if (e != NULL) { 602 if (e->next != NULL) { 603 e = e->next; 604 goto success; 605 } 606 i = hashname(e->cmdname) + 1; 607 } 608 for (; i < CMDTABLESIZE; i++) 609 if ((e = cmdtable[i]) != NULL) 610 goto success; 611 612 return (NULL); 613 success: 614 result->cmdtype = e->cmdtype; 615 result->cmdname = e->cmdname; 616 617 return (e); 618 } 619 620 /* 621 * Delete the command entry returned on the last lookup. 622 */ 623 624 static void 625 delete_cmd_entry(void) 626 { 627 struct tblentry *cmdp; 628 629 INTOFF; 630 cmdp = *lastcmdentry; 631 *lastcmdentry = cmdp->next; 632 ckfree(cmdp); 633 INTON; 634 } 635 636 637 638 /* 639 * Add a new command entry, replacing any existing command entry for 640 * the same name. 641 */ 642 643 static void 644 addcmdentry(const char *name, struct cmdentry *entry) 645 { 646 struct tblentry *cmdp; 647 648 INTOFF; 649 cmdp = cmdlookup(name, 1); 650 if (cmdp->cmdtype == CMDFUNCTION) { 651 unreffunc(cmdp->param.func); 652 } 653 cmdp->cmdtype = entry->cmdtype; 654 cmdp->param = entry->u; 655 cmdp->special = entry->special; 656 INTON; 657 } 658 659 660 /* 661 * Define a shell function. 662 */ 663 664 void 665 defun(const char *name, union node *func) 666 { 667 struct cmdentry entry; 668 669 INTOFF; 670 entry.cmdtype = CMDFUNCTION; 671 entry.u.func = copyfunc(func); 672 entry.special = 0; 673 addcmdentry(name, &entry); 674 INTON; 675 } 676 677 678 /* 679 * Delete a function if it exists. 680 * Called with interrupts off. 681 */ 682 683 int 684 unsetfunc(const char *name) 685 { 686 struct tblentry *cmdp; 687 688 if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->cmdtype == CMDFUNCTION) { 689 unreffunc(cmdp->param.func); 690 delete_cmd_entry(); 691 return (0); 692 } 693 return (0); 694 } 695 696 697 /* 698 * Check if a function by a certain name exists. 699 */ 700 int 701 isfunc(const char *name) 702 { 703 struct tblentry *cmdp; 704 cmdp = cmdlookup(name, 0); 705 return (cmdp != NULL && cmdp->cmdtype == CMDFUNCTION); 706 } 707 708 709 static void 710 print_absolute_path(const char *name) 711 { 712 const char *pwd; 713 714 if (*name != '/' && (pwd = lookupvar("PWD")) != NULL && *pwd != '\0') { 715 out1str(pwd); 716 if (strcmp(pwd, "/") != 0) 717 outcslow('/', out1); 718 } 719 out1str(name); 720 outcslow('\n', out1); 721 } 722 723 724 /* 725 * Shared code for the following builtin commands: 726 * type, command -v, command -V 727 */ 728 729 int 730 typecmd_impl(int argc, char **argv, int cmd, const char *path) 731 { 732 struct cmdentry entry; 733 struct tblentry *cmdp; 734 const char *const *pp; 735 struct alias *ap; 736 int i; 737 int error1 = 0; 738 739 if (path != pathval()) 740 clearcmdentry(); 741 742 for (i = 1; i < argc; i++) { 743 /* First look at the keywords */ 744 for (pp = parsekwd; *pp; pp++) 745 if (**pp == *argv[i] && equal(*pp, argv[i])) 746 break; 747 748 if (*pp) { 749 if (cmd == TYPECMD_SMALLV) 750 out1fmt("%s\n", argv[i]); 751 else 752 out1fmt("%s is a shell keyword\n", argv[i]); 753 continue; 754 } 755 756 /* Then look at the aliases */ 757 if ((ap = lookupalias(argv[i], 1)) != NULL) { 758 if (cmd == TYPECMD_SMALLV) { 759 out1fmt("alias %s=", argv[i]); 760 out1qstr(ap->val); 761 outcslow('\n', out1); 762 } else 763 out1fmt("%s is an alias for %s\n", argv[i], 764 ap->val); 765 continue; 766 } 767 768 /* Then check if it is a tracked alias */ 769 if ((cmdp = cmdlookup(argv[i], 0)) != NULL) { 770 entry.cmdtype = cmdp->cmdtype; 771 entry.u = cmdp->param; 772 entry.special = cmdp->special; 773 } 774 else { 775 /* Finally use brute force */ 776 find_command(argv[i], &entry, 0, path); 777 } 778 779 switch (entry.cmdtype) { 780 case CMDNORMAL: { 781 if (strchr(argv[i], '/') == NULL) { 782 const char *path2 = path; 783 const char *opt2; 784 char *name; 785 int j = entry.u.index; 786 do { 787 name = padvance(&path2, &opt2, argv[i]); 788 stunalloc(name); 789 } while (--j >= 0); 790 if (cmd != TYPECMD_SMALLV) 791 out1fmt("%s is%s ", argv[i], 792 (cmdp && cmd == TYPECMD_TYPE) ? 793 " a tracked alias for" : ""); 794 print_absolute_path(name); 795 } else { 796 if (eaccess(argv[i], X_OK) == 0) { 797 if (cmd != TYPECMD_SMALLV) 798 out1fmt("%s is ", argv[i]); 799 print_absolute_path(argv[i]); 800 } else { 801 if (cmd != TYPECMD_SMALLV) 802 outfmt(out2, "%s: %s\n", 803 argv[i], strerror(errno)); 804 error1 |= 127; 805 } 806 } 807 break; 808 } 809 case CMDFUNCTION: 810 if (cmd == TYPECMD_SMALLV) 811 out1fmt("%s\n", argv[i]); 812 else 813 out1fmt("%s is a shell function\n", argv[i]); 814 break; 815 816 case CMDBUILTIN: 817 if (cmd == TYPECMD_SMALLV) 818 out1fmt("%s\n", argv[i]); 819 else if (entry.special) 820 out1fmt("%s is a special shell builtin\n", 821 argv[i]); 822 else 823 out1fmt("%s is a shell builtin\n", argv[i]); 824 break; 825 826 default: 827 if (cmd != TYPECMD_SMALLV) 828 outfmt(out2, "%s: not found\n", argv[i]); 829 error1 |= 127; 830 break; 831 } 832 } 833 834 if (path != pathval()) 835 clearcmdentry(); 836 837 return error1; 838 } 839 840 /* 841 * Locate and print what a word is... 842 */ 843 844 int 845 typecmd(int argc, char **argv) 846 { 847 if (argc > 2 && strcmp(argv[1], "--") == 0) 848 argc--, argv++; 849 return typecmd_impl(argc, argv, TYPECMD_TYPE, bltinlookup("PATH", 1)); 850 } 851