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