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