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