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