Magellan Linux

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1122 - (show annotations) (download)
Wed Aug 18 21:11:40 2010 UTC (13 years, 8 months ago) by niro
File MIME type: text/plain
File size: 17934 byte(s)
-updated to klibc-1.5.19
1 /*-
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 char *const path_bshell = _PATH_BSHELL;
153
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 if (cmd != path_bshell && errno == ENOEXEC) {
163 *argv-- = cmd;
164 *argv = cmd = path_bshell;
165 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 char *cmd;
836 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 cmd = *argptr;
853 if (verify && cmd)
854 return describe_command(out1, cmd, verify - VERIFY_BRIEF);
855
856 return 0;
857 }