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