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