Magellan Linux

Annotation of /trunk/mkinitrd-magellan/klibc/usr/dash/exec.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 532 - (hide annotations) (download)
Sat Sep 1 22:45:15 2007 UTC (16 years, 8 months ago) by niro
File MIME type: text/plain
File size: 18121 byte(s)
-import if magellan mkinitrd; it is a fork of redhats mkinitrd-5.0.8 with all magellan patches and features; deprecates magellan-src/mkinitrd

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