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