Magellan Linux

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

Parent Directory Parent Directory | Revision Log Revision Log


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