1 /* $NetBSD: man.c,v 1.23 2000/05/27 21:33:26 jdolecek Exp $ */ 2 3 /* 4 * Copyright (c) 1987, 1993, 1994, 1995 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by the University of 18 * California, Berkeley and its contributors. 19 * 4. Neither the name of the University nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36 #include <sys/cdefs.h> 37 38 #ifndef lint 39 __COPYRIGHT("@(#) Copyright (c) 1987, 1993, 1994, 1995\n\ 40 The Regents of the University of California. All rights reserved.\n"); 41 #endif /* not lint */ 42 43 #ifndef lint 44 #if 0 45 static char sccsid[] = "@(#)man.c 8.17 (Berkeley) 1/31/95"; 46 #else 47 __RCSID("$NetBSD: man.c,v 1.23 2000/05/27 21:33:26 jdolecek Exp $"); 48 #endif 49 #endif /* not lint */ 50 51 #include <sys/param.h> 52 #include <sys/queue.h> 53 #include <sys/utsname.h> 54 55 #include <ctype.h> 56 #include <err.h> 57 #include <errno.h> 58 #include <fcntl.h> 59 #include <fnmatch.h> 60 #include <glob.h> 61 #include <signal.h> 62 #include <stdio.h> 63 #include <stdlib.h> 64 #include <string.h> 65 #include <unistd.h> 66 67 #include "config.h" 68 #include "pathnames.h" 69 70 int f_all, f_where; 71 72 int main __P((int, char **)); 73 static void build_page __P((char *, char **)); 74 static void cat __P((char *)); 75 static const char *check_pager __P((const char *)); 76 static int cleanup __P((void)); 77 static void how __P((char *)); 78 static void jump __P((char **, char *, char *)); 79 static int manual __P((char *, TAG *, glob_t *, const char *)); 80 static void onsig __P((int)); 81 static void usage __P((void)); 82 83 int 84 main(argc, argv) 85 int argc; 86 char *argv[]; 87 { 88 TAG *defp, *section, *newpathp, *subp; 89 ENTRY *e_defp, *e_subp; 90 glob_t pg; 91 size_t len; 92 int ch, f_cat, f_how, found, abs_section; 93 char **ap, *cmd, *p, *p_add, *p_path; 94 const char *machine, *pager, *conffile, *pathsearch; 95 char buf[MAXPATHLEN * 2]; 96 97 #ifdef __GNUC__ 98 pager = NULL; /* XXX gcc -Wuninitialized */ 99 #endif 100 101 f_cat = f_how = 0; 102 conffile = p_add = p_path = NULL; 103 pathsearch = NULL; 104 while ((ch = getopt(argc, argv, "-aC:cfhkM:m:P:S:w")) != -1) 105 switch (ch) { 106 case 'a': 107 f_all = 1; 108 break; 109 case 'C': 110 conffile = optarg; 111 break; 112 case 'c': 113 case '-': /* Deprecated. */ 114 f_cat = 1; 115 break; 116 case 'h': 117 f_how = 1; 118 break; 119 case 'm': 120 p_add = optarg; 121 break; 122 case 'M': 123 case 'P': /* Backward compatibility. */ 124 p_path = strdup(optarg); 125 break; 126 /* 127 * The -f and -k options are backward compatible, 128 * undocumented ways of calling whatis(1) and apropos(1). 129 */ 130 case 'f': 131 jump(argv, "-f", "whatis"); 132 /* NOTREACHED */ 133 case 'k': 134 jump(argv, "-k", "apropos"); 135 /* NOTREACHED */ 136 case 'S': 137 pathsearch = optarg; 138 break; 139 case 'w': 140 f_all = f_where = 1; 141 break; 142 case '?': 143 default: 144 usage(); 145 } 146 argc -= optind; 147 argv += optind; 148 149 if (!argc) 150 usage(); 151 152 if (!f_cat && !f_how && !f_where) { 153 if (!isatty(STDOUT_FILENO)) { 154 f_cat = 1; 155 } else { 156 if ((pager = getenv("PAGER")) != NULL && 157 pager[0] != '\0') 158 pager = check_pager(pager); 159 else 160 pager = _PATH_PAGER; 161 } 162 } 163 164 /* Read the configuration file. */ 165 config(conffile); 166 167 /* Get the machine type. */ 168 if ((machine = getenv("MACHINE")) == NULL) { 169 struct utsname utsname; 170 171 if (uname(&utsname) == -1) { 172 perror("uname"); 173 exit(1); 174 } 175 machine = utsname.machine; 176 } 177 178 /* create an empty _default list if the config file didn't have one */ 179 if ((defp = getlist("_default")) == NULL) 180 defp = addlist("_default"); 181 182 /* if -M wasn't specified, check for MANPATH */ 183 if (p_path == NULL) 184 p_path = getenv("MANPATH"); 185 186 /* 187 * get section. abs_section will be non-zero iff the user 188 * specified a section and it had absolute (rather than 189 * relative) paths in the man.conf file. 190 */ 191 if (argc > 1 && (section = getlist(*argv)) != NULL) { 192 argv++; 193 argc--; 194 abs_section = (TAILQ_FIRST(§ion->list) != NULL && 195 *(TAILQ_FIRST(§ion->list)->s) == '/'); 196 } else { 197 section = NULL; 198 abs_section = 0; 199 } 200 201 /* get subdir list */ 202 subp = getlist("_subdir"); 203 if (!subp) 204 subp = addlist("_subdir"); 205 206 /* 207 * now that we have all the inputs we must generate a search path. 208 */ 209 210 /* 211 * 1: If user specified a section and it has absolute paths 212 * in the config file, then that overrides _default, MANPATH and 213 * path passed via -M. 214 */ 215 if (abs_section) { 216 p_path = NULL; /* zap -M/MANPATH */ 217 defp = section; /* zap _default */ 218 section = NULL; /* promoted to defp */ 219 } 220 221 222 /* 223 * 2: Section can be non-null only if a section was specified 224 * and the config file has relative paths - the section list 225 * overrides _subdir in this case. 226 */ 227 if (section) 228 subp = section; 229 230 231 /* 232 * 3: now we either have text string path (p_path) or a tag 233 * based path (defp). we need to append subp and machine 234 * to each element in the path. 235 * 236 * for backward compat, we do not append subp if abs_section 237 * and the path does not end in "/". 238 */ 239 newpathp = addlist("_new_path"); 240 if (p_path) { 241 /* use p_path */ 242 for (; (p = strtok(p_path, ":")) != NULL; p_path = NULL) { 243 for ( e_subp = TAILQ_FIRST(&subp->list) ; 244 e_subp != NULL ; 245 e_subp = TAILQ_NEXT(e_subp, q)) { 246 snprintf(buf, sizeof(buf), "%s/%s{/%s,}", 247 p, e_subp->s, machine); 248 addentry(newpathp, buf, 0); 249 } 250 } 251 } else { 252 /* use defp rather than p_path */ 253 for (e_defp = TAILQ_FIRST(&defp->list) ; 254 e_defp != NULL ; 255 e_defp = TAILQ_NEXT(e_defp, q)) { 256 257 /* handle trailing "/" magic here ... */ 258 if (abs_section && 259 e_defp->s[strlen(e_defp->s) - 1] != '/') { 260 261 (void)snprintf(buf, sizeof(buf), 262 "%s{/%s,}", e_defp->s, machine); 263 addentry(newpathp, buf, 0); 264 continue; 265 } 266 267 for ( e_subp = TAILQ_FIRST(&subp->list) ; 268 e_subp != NULL ; 269 e_subp = TAILQ_NEXT(e_subp, q)) { 270 snprintf(buf, sizeof(buf), "%s%s%s{/%s,}", 271 e_defp->s, (abs_section) ? "" : "/", 272 e_subp->s, machine); 273 addentry(newpathp, buf, 0); 274 } 275 } 276 } /* using defp ... */ 277 278 /* now replace the current path with the new one */ 279 defp = newpathp; 280 281 /* 282 * 4: prepend the "-m" path, if specified. we always add 283 * subp and machine to this part of the path. 284 */ 285 286 if (p_add) { 287 for (p = strtok(p_add, ":") ; p ; p = strtok(NULL, ":")) { 288 for ( e_subp = TAILQ_FIRST(&subp->list) ; 289 e_subp != NULL ; 290 e_subp = TAILQ_NEXT(e_subp, q)) { 291 snprintf(buf, sizeof(buf), "%s/%s{/%s,}", 292 p, e_subp->s, machine); 293 addentry(newpathp, buf, 1); 294 } 295 } 296 } 297 298 299 /* 300 * 5: Search for the files. Set up an interrupt handler, so the 301 * temporary files go away. 302 */ 303 (void)signal(SIGINT, onsig); 304 (void)signal(SIGHUP, onsig); 305 (void)signal(SIGPIPE, onsig); 306 307 memset(&pg, 0, sizeof(pg)); 308 for (found = 0; *argv; ++argv) 309 if (manual(*argv, defp, &pg, pathsearch)) 310 found = 1; 311 312 /* 6: If nothing found, we're done. */ 313 if (!found) { 314 (void)cleanup(); 315 exit (1); 316 } 317 318 /* 7: If it's simple, display it fast. */ 319 if (f_cat) { 320 for (ap = pg.gl_pathv; *ap != NULL; ++ap) { 321 if (**ap == '\0') 322 continue; 323 cat(*ap); 324 } 325 exit (cleanup()); 326 } 327 if (f_how) { 328 for (ap = pg.gl_pathv; *ap != NULL; ++ap) { 329 if (**ap == '\0') 330 continue; 331 how(*ap); 332 } 333 exit(cleanup()); 334 } 335 if (f_where) { 336 for (ap = pg.gl_pathv; *ap != NULL; ++ap) { 337 if (**ap == '\0') 338 continue; 339 (void)printf("%s\n", *ap); 340 } 341 exit(cleanup()); 342 } 343 344 /* 345 * 8: We display things in a single command; build a list of things 346 * to display. 347 */ 348 for (ap = pg.gl_pathv, len = strlen(pager) + 1; *ap != NULL; ++ap) { 349 if (**ap == '\0') 350 continue; 351 len += strlen(*ap) + 1; 352 } 353 if ((cmd = malloc(len)) == NULL) { 354 warn("malloc"); 355 (void)cleanup(); 356 exit(1); 357 } 358 p = cmd; 359 len = strlen(pager); 360 memmove(p, pager, len); 361 p += len; 362 *p++ = ' '; 363 for (ap = pg.gl_pathv; *ap != NULL; ++ap) { 364 if (**ap == '\0') 365 continue; 366 len = strlen(*ap); 367 memmove(p, *ap, len); 368 p += len; 369 *p++ = ' '; 370 } 371 *--p = '\0'; 372 373 /* Use system(3) in case someone's pager is "pager arg1 arg2". */ 374 (void)system(cmd); 375 376 exit(cleanup()); 377 } 378 379 /* 380 * manual -- 381 * Search the manuals for the pages. 382 */ 383 static int 384 manual(page, tag, pg, pathsearch) 385 char *page; 386 TAG *tag; 387 glob_t *pg; 388 const char *pathsearch; 389 { 390 ENTRY *ep, *e_sufp, *e_tag; 391 TAG *missp, *sufp; 392 int anyfound, cnt, error, found; 393 char *p, buf[MAXPATHLEN]; 394 395 anyfound = 0; 396 buf[0] = '*'; 397 398 /* For each element in the list... */ 399 e_tag = tag == NULL ? NULL : tag->list.tqh_first; 400 for (; e_tag != NULL; e_tag = e_tag->q.tqe_next) { 401 (void)snprintf(buf, sizeof(buf), "%s/%s.*", e_tag->s, page); 402 if ((error = glob(buf, 403 GLOB_APPEND | GLOB_BRACE | GLOB_NOSORT, NULL, pg)) != 0) { 404 if (error == GLOB_NOMATCH) 405 continue; 406 else { 407 warn("globbing"); 408 (void)cleanup(); 409 exit(1); 410 } 411 } 412 if (pg->gl_matchc == 0) 413 continue; 414 415 /* Find out if it's really a man page. */ 416 for (cnt = pg->gl_pathc - pg->gl_matchc; 417 cnt < pg->gl_pathc; ++cnt) { 418 419 if (pathsearch) { 420 p = strstr(pg->gl_pathv[cnt], pathsearch); 421 if (!p || strchr(p, '/') == NULL) { 422 pg->gl_pathv[cnt] = ""; 423 continue; 424 } 425 } 426 427 /* 428 * Try the _suffix key words first. 429 * 430 * XXX 431 * Older versions of man.conf didn't have the suffix 432 * key words, it was assumed that everything was a .0. 433 * We just test for .0 first, it's fast and probably 434 * going to hit. 435 */ 436 (void)snprintf(buf, sizeof(buf), "*/%s.0", page); 437 if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) 438 goto next; 439 440 e_sufp = (sufp = getlist("_suffix")) == NULL ? 441 NULL : sufp->list.tqh_first; 442 for (found = 0; 443 e_sufp != NULL; e_sufp = e_sufp->q.tqe_next) { 444 (void)snprintf(buf, 445 sizeof(buf), "*/%s%s", page, e_sufp->s); 446 if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) { 447 found = 1; 448 break; 449 } 450 } 451 if (found) 452 goto next; 453 454 /* Try the _build key words next. */ 455 e_sufp = (sufp = getlist("_build")) == NULL ? 456 NULL : sufp->list.tqh_first; 457 for (found = 0; 458 e_sufp != NULL; e_sufp = e_sufp->q.tqe_next) { 459 for (p = e_sufp->s; 460 *p != '\0' && !isspace((unsigned char)*p); ++p); 461 if (*p == '\0') 462 continue; 463 *p = '\0'; 464 (void)snprintf(buf, 465 sizeof(buf), "*/%s%s", page, e_sufp->s); 466 if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) { 467 if (!f_where) 468 build_page(p + 1, 469 &pg->gl_pathv[cnt]); 470 *p = ' '; 471 found = 1; 472 break; 473 } 474 *p = ' '; 475 } 476 if (found) { 477 next: anyfound = 1; 478 if (!f_all) { 479 /* Delete any other matches. */ 480 while (++cnt< pg->gl_pathc) 481 pg->gl_pathv[cnt] = ""; 482 break; 483 } 484 continue; 485 } 486 487 /* It's not a man page, forget about it. */ 488 pg->gl_pathv[cnt] = ""; 489 } 490 491 if (anyfound && !f_all) 492 break; 493 } 494 495 /* If not found, enter onto the missing list. */ 496 if (!anyfound) { 497 if ((missp = getlist("_missing")) == NULL) 498 missp = addlist("_missing"); 499 if ((ep = malloc(sizeof(ENTRY))) == NULL || 500 (ep->s = strdup(page)) == NULL) { 501 warn("malloc"); 502 (void)cleanup(); 503 exit(1); 504 } 505 TAILQ_INSERT_TAIL(&missp->list, ep, q); 506 } 507 return (anyfound); 508 } 509 510 /* 511 * build_page -- 512 * Build a man page for display. 513 */ 514 static void 515 build_page(fmt, pathp) 516 char *fmt, **pathp; 517 { 518 static int warned; 519 ENTRY *ep; 520 TAG *intmpp; 521 int fd, n; 522 char *p, *b; 523 char buf[MAXPATHLEN], cmd[MAXPATHLEN], tpath[MAXPATHLEN]; 524 const char *tmpdir; 525 526 /* Let the user know this may take awhile. */ 527 if (!warned) { 528 warned = 1; 529 warnx("Formatting manual page..."); 530 } 531 532 /* 533 * Historically man chdir'd to the root of the man tree. 534 * This was used in man pages that contained relative ".so" 535 * directives (including other man pages for command aliases etc.) 536 * It even went one step farther, by examining the first line 537 * of the man page and parsing the .so filename so it would 538 * make hard(?) links to the cat'ted man pages for space savings. 539 * (We don't do that here, but we could). 540 */ 541 542 /* copy and find the end */ 543 for (b = buf, p = *pathp; (*b++ = *p++) != '\0';) 544 continue; 545 546 /* skip the last two path components, page name and man[n] */ 547 for (--b, --p, n = 2; b != buf; b--, p--) 548 if (*b == '/') 549 if (--n == 0) { 550 *b = '\0'; 551 (void) chdir(buf); 552 p++; 553 break; 554 } 555 556 557 /* Add a remove-when-done list. */ 558 if ((intmpp = getlist("_intmp")) == NULL) 559 intmpp = addlist("_intmp"); 560 561 /* Move to the printf(3) format string. */ 562 for (; *fmt && isspace((unsigned char)*fmt); ++fmt) 563 continue; 564 565 /* 566 * Get a temporary file and build a version of the file 567 * to display. Replace the old file name with the new one. 568 */ 569 if ((tmpdir = getenv("TMPDIR")) == NULL) 570 tmpdir = _PATH_TMP; 571 (void)snprintf(tpath, sizeof (tpath), "%s/%s", tmpdir, TMPFILE); 572 if ((fd = mkstemp(tpath)) == -1) { 573 warn("%s", tpath); 574 (void)cleanup(); 575 exit(1); 576 } 577 (void)snprintf(buf, sizeof(buf), "%s > %s", fmt, tpath); 578 (void)snprintf(cmd, sizeof(cmd), buf, p); 579 (void)system(cmd); 580 (void)close(fd); 581 if ((*pathp = strdup(tpath)) == NULL) { 582 warn("malloc"); 583 (void)cleanup(); 584 exit(1); 585 } 586 587 /* Link the built file into the remove-when-done list. */ 588 if ((ep = malloc(sizeof(ENTRY))) == NULL) { 589 warn("malloc"); 590 (void)cleanup(); 591 exit(1); 592 } 593 ep->s = *pathp; 594 TAILQ_INSERT_TAIL(&intmpp->list, ep, q); 595 } 596 597 /* 598 * how -- 599 * display how information 600 */ 601 static void 602 how(fname) 603 char *fname; 604 { 605 FILE *fp; 606 607 int lcnt, print; 608 char *p, buf[256]; 609 610 if (!(fp = fopen(fname, "r"))) { 611 warn("%s", fname); 612 (void)cleanup(); 613 exit (1); 614 } 615 #define S1 "SYNOPSIS" 616 #define S2 "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS" 617 #define D1 "DESCRIPTION" 618 #define D2 "D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN" 619 for (lcnt = print = 0; fgets(buf, sizeof(buf), fp);) { 620 if (!strncmp(buf, S1, sizeof(S1) - 1) || 621 !strncmp(buf, S2, sizeof(S2) - 1)) { 622 print = 1; 623 continue; 624 } else if (!strncmp(buf, D1, sizeof(D1) - 1) || 625 !strncmp(buf, D2, sizeof(D2) - 1)) 626 return; 627 if (!print) 628 continue; 629 if (*buf == '\n') 630 ++lcnt; 631 else { 632 for(; lcnt; --lcnt) 633 (void)putchar('\n'); 634 for (p = buf; isspace((unsigned char)*p); ++p) 635 continue; 636 (void)fputs(p, stdout); 637 } 638 } 639 (void)fclose(fp); 640 } 641 642 /* 643 * cat -- 644 * cat out the file 645 */ 646 static void 647 cat(fname) 648 char *fname; 649 { 650 int fd, n; 651 char buf[2048]; 652 653 if ((fd = open(fname, O_RDONLY, 0)) < 0) { 654 warn("%s", fname); 655 (void)cleanup(); 656 exit(1); 657 } 658 while ((n = read(fd, buf, sizeof(buf))) > 0) 659 if (write(STDOUT_FILENO, buf, n) != n) { 660 warn("write"); 661 (void)cleanup(); 662 exit (1); 663 } 664 if (n == -1) { 665 warn("read"); 666 (void)cleanup(); 667 exit(1); 668 } 669 (void)close(fd); 670 } 671 672 /* 673 * check_pager -- 674 * check the user supplied page information 675 */ 676 static const char * 677 check_pager(name) 678 const char *name; 679 { 680 const char *p, *save; 681 682 /* 683 * if the user uses "more", we make it "more -s"; watch out for 684 * PAGER = "mypager /usr/ucb/more" 685 */ 686 for (p = name; *p && !isspace((unsigned char)*p); ++p) 687 continue; 688 for (; p > name && *p != '/'; --p); 689 if (p != name) 690 ++p; 691 692 /* make sure it's "more", not "morex" */ 693 if (!strncmp(p, "more", 4) && (!p[4] || isspace((unsigned char)p[4]))){ 694 char *newname; 695 (void)asprintf(&newname, "%s %s", save, "-s"); 696 name = newname; 697 } 698 699 return (name); 700 } 701 702 /* 703 * jump -- 704 * strip out flag argument and jump 705 */ 706 static void 707 jump(argv, flag, name) 708 char **argv, *flag, *name; 709 { 710 char **arg; 711 712 argv[0] = name; 713 for (arg = argv + 1; *arg; ++arg) 714 if (!strcmp(*arg, flag)) 715 break; 716 for (; *arg; ++arg) 717 arg[0] = arg[1]; 718 execvp(name, argv); 719 (void)fprintf(stderr, "%s: Command not found.\n", name); 720 exit(1); 721 } 722 723 /* 724 * onsig -- 725 * If signaled, delete the temporary files. 726 */ 727 static void 728 onsig(signo) 729 int signo; 730 { 731 sigset_t set; 732 733 (void)cleanup(); 734 735 (void)signal(signo, SIG_DFL); 736 737 /* unblock the signal */ 738 sigemptyset(&set); 739 sigaddset(&set, signo); 740 sigprocmask(SIG_UNBLOCK, &set, (sigset_t *) NULL); 741 742 (void)kill(getpid(), signo); 743 744 /* NOTREACHED */ 745 exit (1); 746 } 747 748 /* 749 * cleanup -- 750 * Clean up temporary files, show any error messages. 751 */ 752 static int 753 cleanup() 754 { 755 TAG *intmpp, *missp; 756 ENTRY *ep; 757 int rval; 758 759 rval = 0; 760 ep = (missp = getlist("_missing")) == NULL ? 761 NULL : missp->list.tqh_first; 762 if (ep != NULL) 763 for (; ep != NULL; ep = ep->q.tqe_next) { 764 warnx("no entry for %s in the manual.", ep->s); 765 rval = 1; 766 } 767 768 ep = (intmpp = getlist("_intmp")) == NULL ? 769 NULL : intmpp->list.tqh_first; 770 for (; ep != NULL; ep = ep->q.tqe_next) 771 (void)unlink(ep->s); 772 return (rval); 773 } 774 775 /* 776 * usage -- 777 * print usage message and die 778 */ 779 static void 780 usage() 781 { 782 extern char *__progname; 783 (void)fprintf(stderr, 784 "Usage: %s [-achw] [-C file] [-M path] [-m path] [-S srch] [section] title ...\n", 785 __progname); 786 exit(1); 787 } 788