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