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