1 /* $NetBSD: man.c,v 1.68 2020/04/06 19:53:22 maya 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\ 36 The Regents of the University of California. All rights reserved."); 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.68 2020/04/06 19:53:22 maya Exp $"); 44 #endif 45 #endif /* not lint */ 46 47 #include <sys/param.h> 48 #include <sys/queue.h> 49 #include <sys/stat.h> 50 #include <sys/utsname.h> 51 52 #include <ctype.h> 53 #include <err.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 #include <util.h> 63 #include <locale.h> 64 65 #include "manconf.h" 66 #include "pathnames.h" 67 68 #ifndef MAN_DEBUG 69 #define MAN_DEBUG 0 /* debug path output */ 70 #endif 71 72 /* 73 * manstate: structure collecting the current global state so we can 74 * easily identify it and pass it to helper functions in one arg. 75 */ 76 struct manstate { 77 /* command line flags */ 78 int all; /* -a: show all matches rather than first */ 79 int cat; /* -c: do not use a pager */ 80 char *conffile; /* -C: use alternate config file */ 81 int how; /* -h: show SYNOPSIS only */ 82 char *manpath; /* -M: alternate MANPATH */ 83 char *addpath; /* -m: add these dirs to front of manpath */ 84 char *pathsearch; /* -S: path of man must contain this string */ 85 char *sectionname; /* -s: limit search to a given man section */ 86 int where; /* -w: just show paths of all matching files */ 87 int getpath; /* -p: print the path of directories containing man pages */ 88 89 /* important tags from the config file */ 90 TAG *defaultpath; /* _default: default MANPATH */ 91 TAG *subdirs; /* _subdir: default subdir search list */ 92 TAG *suffixlist; /* _suffix: for files that can be cat()'d */ 93 TAG *buildlist; /* _build: for files that must be built */ 94 95 /* tags for internal use */ 96 TAG *intmp; /* _intmp: tmp files we must cleanup */ 97 TAG *missinglist; /* _missing: pages we couldn't find */ 98 TAG *mymanpath; /* _new_path: final version of MANPATH */ 99 TAG *section; /* <sec>: tag for m.sectionname */ 100 101 /* other misc stuff */ 102 const char *pager; /* pager to use */ 103 size_t pagerlen; /* length of the above */ 104 const char *machine; /* machine */ 105 const char *machclass; /* machine class */ 106 }; 107 108 /* 109 * prototypes 110 */ 111 static void build_page(const char *, char **, struct manstate *); 112 static void cat(const char *); 113 static const char *check_pager(const char *); 114 static int cleanup(void); 115 static void how(const char *); 116 static void jump(char **, const char *, const char *) __dead; 117 static int manual(char *, struct manstate *, glob_t *); 118 static void onsig(int) __dead; 119 static void usage(void) __dead; 120 static void addpath(struct manstate *, const char *, size_t, const char *); 121 static const char *getclass(const char *); 122 static void printmanpath(struct manstate *); 123 124 /* 125 * main function 126 */ 127 int 128 main(int argc, char **argv) 129 { 130 static struct manstate m; 131 struct utsname utsname; 132 int ch, abs_section, found; 133 ENTRY *esubd, *epath; 134 char *p, **ap, *cmd; 135 size_t len; 136 glob_t pg; 137 138 setprogname(argv[0]); 139 setlocale(LC_ALL, ""); 140 /* 141 * parse command line... 142 */ 143 while ((ch = getopt(argc, argv, "-aC:cfhkM:m:P:ps:S:w")) != -1) 144 switch (ch) { 145 case 'a': 146 m.all = 1; 147 break; 148 case 'C': 149 m.conffile = optarg; 150 break; 151 case 'c': 152 case '-': /* XXX: '-' is a deprecated version of '-c' */ 153 m.cat = 1; 154 break; 155 case 'h': 156 m.how = 1; 157 break; 158 case 'm': 159 m.addpath = optarg; 160 break; 161 case 'M': 162 case 'P': /* -P for backward compatibility */ 163 if ((m.manpath = strdup(optarg)) == NULL) 164 err(EXIT_FAILURE, "malloc failed"); 165 break; 166 case 'p': 167 m.getpath = 1; 168 break; 169 /* 170 * The -f and -k options are backward compatible, 171 * undocumented ways of calling whatis(1) and apropos(1). 172 */ 173 case 'f': 174 jump(argv, "-f", "whatis"); 175 /* NOTREACHED */ 176 case 'k': 177 jump(argv, "-k", "apropos"); 178 /* NOTREACHED */ 179 case 's': 180 if (m.sectionname != NULL) 181 usage(); 182 m.sectionname = optarg; 183 break; 184 case 'S': 185 m.pathsearch = optarg; 186 break; 187 case 'w': 188 m.all = m.where = 1; 189 break; 190 case '?': 191 default: 192 usage(); 193 } 194 argc -= optind; 195 argv += optind; 196 197 if (!m.getpath && !argc) 198 usage(); 199 200 /* 201 * read the configuration file and collect any other information 202 * we will need (machine type, pager, section [if specified 203 * without '-s'], and MANPATH through the environment). 204 */ 205 config(m.conffile); /* exits on error ... */ 206 207 if ((m.machine = getenv("MACHINE")) == NULL) { 208 if (uname(&utsname) == -1) 209 err(EXIT_FAILURE, "uname"); 210 m.machine = utsname.machine; 211 } 212 213 m.machclass = getclass(m.machine); 214 215 if (!m.cat && !m.how && !m.where) { /* if we need a pager ... */ 216 if (!isatty(STDOUT_FILENO)) { 217 m.cat = 1; 218 } else { 219 if ((m.pager = getenv("PAGER")) != NULL && 220 m.pager[0] != '\0') 221 m.pager = check_pager(m.pager); 222 else 223 m.pager = _PATH_PAGER; 224 m.pagerlen = strlen(m.pager); 225 } 226 } 227 228 /* do we need to set m.section to a non-null value? */ 229 if (m.sectionname) { 230 231 m.section = gettag(m.sectionname, 0); /* -s must be a section */ 232 if (m.section == NULL) 233 errx(EXIT_FAILURE, "unknown section: %s", m.sectionname); 234 235 } else if (argc > 1) { 236 237 m.section = gettag(*argv, 0); /* might be a section? */ 238 if (m.section) { 239 argv++; 240 argc--; 241 } 242 243 } 244 245 if (m.manpath == NULL) 246 m.manpath = getenv("MANPATH"); /* note: -M overrides getenv */ 247 248 249 /* 250 * get default values from config file, plus create the tags we 251 * use for keeping internal state. make sure all our mallocs 252 * go through. 253 */ 254 /* from cfg file */ 255 m.defaultpath = gettag("_default", 1); 256 m.subdirs = gettag("_subdir", 1); 257 m.suffixlist = gettag("_suffix", 1); 258 m.buildlist = gettag("_build", 1); 259 /* internal use */ 260 m.mymanpath = gettag("_new_path", 1); 261 m.missinglist = gettag("_missing", 1); 262 m.intmp = gettag("_intmp", 1); 263 if (!m.defaultpath || !m.subdirs || !m.suffixlist || !m.buildlist || 264 !m.mymanpath || !m.missinglist || !m.intmp) 265 errx(EXIT_FAILURE, "malloc failed"); 266 267 /* 268 * are we using a section whose elements are all absolute paths? 269 * (we only need to look at the first entry on the section list, 270 * as config() will ensure that any additional entries will match 271 * the first one.) 272 */ 273 abs_section = (m.section != NULL && 274 !TAILQ_EMPTY(&m.section->entrylist) && 275 *(TAILQ_FIRST(&m.section->entrylist)->s) == '/'); 276 277 /* 278 * now that we have all the data we need, we must determine the 279 * manpath we are going to use to find the requested entries using 280 * the following steps... 281 * 282 * [1] if the user specified a section and that section's elements 283 * from the config file are all absolute paths, then we override 284 * defaultpath and -M/MANPATH with the section's absolute paths. 285 */ 286 if (abs_section) { 287 m.manpath = NULL; /* ignore -M/MANPATH */ 288 m.defaultpath = m.section; /* overwrite _default path */ 289 m.section = NULL; /* promoted to defaultpath */ 290 } 291 292 /* 293 * [2] section can now only be non-null if the user asked for 294 * a section and that section's elements did not have 295 * absolute paths. in this case we use the section's 296 * elements to override _subdir from the config file. 297 * 298 * after this step, we are done processing "m.section"... 299 */ 300 if (m.section) 301 m.subdirs = m.section; 302 303 /* 304 * [3] we need to setup the path we want to use (m.mymanpath). 305 * if the user gave us a path (m.manpath) use it, otherwise 306 * go with the default. in either case we need to append 307 * the subdir and machine spec to each element of the path. 308 * 309 * for absolute section paths that come from the config file, 310 * we only append the subdir spec if the path ends in 311 * a '/' --- elements that do not end in '/' are assumed to 312 * not have subdirectories. this is mainly for backward compat, 313 * but it allows non-subdir configs like: 314 * sect3 /usr/share/man/{old/,}cat3 315 * doc /usr/{pkg,share}/doc/{sendmail/op,sendmail/intro} 316 * 317 * note that we try and be careful to not put double slashes 318 * in the path (e.g. we want /usr/share/man/man1, not 319 * /usr/share/man//man1) because "more" will put the filename 320 * we generate in its prompt and the double slashes look ugly. 321 */ 322 if (m.manpath) { 323 324 /* note: strtok is going to destroy m.manpath */ 325 for (p = strtok(m.manpath, ":") ; p ; p = strtok(NULL, ":")) { 326 len = strlen(p); 327 if (len < 1) 328 continue; 329 TAILQ_FOREACH(esubd, &m.subdirs->entrylist, q) 330 addpath(&m, p, len, esubd->s); 331 } 332 333 } else { 334 335 TAILQ_FOREACH(epath, &m.defaultpath->entrylist, q) { 336 /* handle trailing "/" magic here ... */ 337 if (abs_section && epath->s[epath->len - 1] != '/') { 338 addpath(&m, "", 1, epath->s); 339 continue; 340 } 341 342 TAILQ_FOREACH(esubd, &m.subdirs->entrylist, q) 343 addpath(&m, epath->s, epath->len, esubd->s); 344 } 345 346 } 347 348 /* 349 * [4] finally, prepend the "-m" m.addpath to mymanpath if it 350 * was specified. subdirs and machine are always applied to 351 * m.addpath. 352 */ 353 if (m.addpath) { 354 355 /* note: strtok is going to destroy m.addpath */ 356 for (p = strtok(m.addpath, ":") ; p ; p = strtok(NULL, ":")) { 357 len = strlen(p); 358 if (len < 1) 359 continue; 360 TAILQ_FOREACH(esubd, &m.subdirs->entrylist, q) 361 addpath(&m, p, len, esubd->s); 362 } 363 364 } 365 366 if (m.getpath) { 367 printmanpath(&m); 368 exit(cleanup()); 369 } 370 371 /* 372 * now m.mymanpath is complete! 373 */ 374 #if MAN_DEBUG 375 printf("mymanpath:\n"); 376 TAILQ_FOREACH(epath, &m.mymanpath->entrylist, q) { 377 printf("\t%s\n", epath->s); 378 } 379 #endif 380 381 /* 382 * start searching for matching files and format them if necessary. 383 * setup an interrupt handler so that we can ensure that temporary 384 * files go away. 385 */ 386 (void)signal(SIGINT, onsig); 387 (void)signal(SIGHUP, onsig); 388 (void)signal(SIGPIPE, onsig); 389 390 memset(&pg, 0, sizeof(pg)); 391 for (found = 0; *argv; ++argv) 392 if (manual(*argv, &m, &pg)) { 393 found = 1; 394 } 395 396 /* if nothing found, we're done. */ 397 if (!found) { 398 (void)cleanup(); 399 exit(EXIT_FAILURE); 400 } 401 402 /* 403 * handle the simple display cases first (m.cat, m.how, m.where) 404 */ 405 if (m.cat) { 406 for (ap = pg.gl_pathv; *ap != NULL; ++ap) { 407 if (**ap == '\0') 408 continue; 409 cat(*ap); 410 } 411 exit(cleanup()); 412 } 413 if (m.how) { 414 for (ap = pg.gl_pathv; *ap != NULL; ++ap) { 415 if (**ap == '\0') 416 continue; 417 how(*ap); 418 } 419 exit(cleanup()); 420 } 421 if (m.where) { 422 for (ap = pg.gl_pathv; *ap != NULL; ++ap) { 423 if (**ap == '\0') 424 continue; 425 (void)printf("%s\n", *ap); 426 } 427 exit(cleanup()); 428 } 429 430 /* 431 * normal case - we display things in a single command, so 432 * build a list of things to display. first compute total 433 * length of buffer we will need so we can malloc it. 434 */ 435 for (ap = pg.gl_pathv, len = m.pagerlen + 1; *ap != NULL; ++ap) { 436 if (**ap == '\0') 437 continue; 438 len += strlen(*ap) + 1; 439 } 440 if ((cmd = malloc(len)) == NULL) { 441 warn("malloc"); 442 (void)cleanup(); 443 exit(EXIT_FAILURE); 444 } 445 446 /* now build the command string... */ 447 p = cmd; 448 len = m.pagerlen; 449 memcpy(p, m.pager, len); 450 p += len; 451 *p++ = ' '; 452 for (ap = pg.gl_pathv; *ap != NULL; ++ap) { 453 if (**ap == '\0') 454 continue; 455 len = strlen(*ap); 456 memcpy(p, *ap, len); 457 p += len; 458 *p++ = ' '; 459 } 460 *--p = '\0'; 461 462 /* Use system(3) in case someone's pager is "pager arg1 arg2". */ 463 (void)system(cmd); 464 465 exit(cleanup()); 466 } 467 468 static int 469 manual_find_literalfile(struct manstate *mp, char **pv) 470 { 471 ENTRY *suffix; 472 int found; 473 char buf[MAXPATHLEN]; 474 const char *p; 475 int suflen; 476 477 found = 0; 478 479 /* 480 * Expand both '*' and suffix to force an actual 481 * match via fnmatch(3). Since the only match in pg 482 * is the literal file, the match is genuine. 483 */ 484 485 TAILQ_FOREACH(suffix, &mp->buildlist->entrylist, q) { 486 for (p = suffix->s, suflen = 0; 487 *p != '\0' && !isspace((unsigned char)*p); 488 ++p) 489 ++suflen; 490 if (*p == '\0') 491 continue; 492 493 (void)snprintf(buf, sizeof(buf), "*%.*s", suflen, suffix->s); 494 495 if (!fnmatch(buf, *pv, 0)) { 496 if (!mp->where) 497 build_page(p + 1, pv, mp); 498 found = 1; 499 break; 500 } 501 } 502 503 return found; 504 } 505 506 static int 507 manual_find_buildkeyword(const char *prefix, const char *escpage, 508 struct manstate *mp, char **pv) 509 { 510 ENTRY *suffix; 511 int found; 512 char buf[MAXPATHLEN]; 513 const char *p; 514 int suflen; 515 516 found = 0; 517 /* Try the _build keywords next. */ 518 TAILQ_FOREACH(suffix, &mp->buildlist->entrylist, q) { 519 for (p = suffix->s, suflen = 0; 520 *p != '\0' && !isspace((unsigned char)*p); 521 ++p) 522 ++suflen; 523 if (*p == '\0') 524 continue; 525 526 (void)snprintf(buf, sizeof(buf), "%s%s%.*s", 527 prefix, escpage, suflen, suffix->s); 528 if (!fnmatch(buf, *pv, 0)) { 529 if (!mp->where) 530 build_page(p + 1, pv, mp); 531 found = 1; 532 break; 533 } 534 } 535 536 return found; 537 } 538 539 /* 540 * manual -- 541 * Search the manuals for the pages. 542 */ 543 static int 544 manual(char *page, struct manstate *mp, glob_t *pg) 545 { 546 ENTRY *suffix, *mdir; 547 int anyfound, error, found; 548 size_t cnt; 549 char *p, buf[MAXPATHLEN], *escpage, *eptr; 550 static const char escglob[] = "\\~?*{}[]"; 551 552 anyfound = 0; 553 554 /* 555 * Fixup page which may contain glob(3) special characters, e.g. 556 * the famous "No man page for [" FAQ. 557 */ 558 if ((escpage = malloc((2 * strlen(page)) + 1)) == NULL) { 559 warn("malloc"); 560 (void)cleanup(); 561 exit(EXIT_FAILURE); 562 } 563 564 p = page; 565 eptr = escpage; 566 567 while (*p) { 568 if (strchr(escglob, *p) != NULL) { 569 *eptr++ = '\\'; 570 *eptr++ = *p++; 571 } else 572 *eptr++ = *p++; 573 } 574 575 *eptr = '\0'; 576 577 /* 578 * If 'page' is given with an absolute path, 579 * or a relative path explicitly beginning with "./" 580 * or "../", then interpret it as a file specification. 581 */ 582 if ((page[0] == '/') 583 || (page[0] == '.' && page[1] == '/') 584 || (page[0] == '.' && page[1] == '.' && page[2] == '/') 585 ) { 586 /* check if file actually exists */ 587 (void)strlcpy(buf, escpage, sizeof(buf)); 588 error = glob(buf, GLOB_APPEND | GLOB_BRACE | GLOB_NOSORT, NULL, pg); 589 if (error != 0) { 590 if (error == GLOB_NOMATCH) { 591 goto notfound; 592 } else { 593 errx(EXIT_FAILURE, "glob failed"); 594 } 595 } 596 597 if (pg->gl_matchc == 0) 598 goto notfound; 599 600 /* literal file only yields one match */ 601 cnt = pg->gl_pathc - pg->gl_matchc; 602 603 if (manual_find_literalfile(mp, &pg->gl_pathv[cnt])) { 604 anyfound = 1; 605 } else { 606 /* It's not a man page, forget about it. */ 607 *pg->gl_pathv[cnt] = '\0'; 608 } 609 610 notfound: 611 if (!anyfound) { 612 if (addentry(mp->missinglist, page, 0) < 0) { 613 warn("malloc"); 614 (void)cleanup(); 615 exit(EXIT_FAILURE); 616 } 617 } 618 free(escpage); 619 return anyfound; 620 } 621 622 /* For each man directory in mymanpath ... */ 623 TAILQ_FOREACH(mdir, &mp->mymanpath->entrylist, q) { 624 625 /* 626 * use glob(3) to look in the filesystem for matching files. 627 * match any suffix here, as we will check that later. 628 */ 629 (void)snprintf(buf, sizeof(buf), "%s/%s.*", mdir->s, escpage); 630 if ((error = glob(buf, 631 GLOB_APPEND | GLOB_BRACE | GLOB_NOSORT, NULL, pg)) != 0) { 632 if (error == GLOB_NOMATCH) 633 continue; 634 else { 635 warn("globbing"); 636 (void)cleanup(); 637 exit(EXIT_FAILURE); 638 } 639 } 640 if (pg->gl_matchc == 0) 641 continue; 642 643 /* 644 * start going through the matches glob(3) just found and 645 * use m.pathsearch (if present) to filter out pages we 646 * don't want. then verify the suffix is valid, and build 647 * the page if we have a _build suffix. 648 */ 649 for (cnt = pg->gl_pathc - pg->gl_matchc; 650 cnt < pg->gl_pathc; ++cnt) { 651 652 /* filter on directory path name */ 653 if (mp->pathsearch) { 654 p = strstr(pg->gl_pathv[cnt], mp->pathsearch); 655 if (!p || strchr(p, '/') == NULL) { 656 *pg->gl_pathv[cnt] = '\0'; /* zap! */ 657 continue; 658 } 659 } 660 661 /* 662 * Try the _suffix keywords first. 663 * 664 * XXX 665 * Older versions of man.conf didn't have the _suffix 666 * keywords, it was assumed that everything was a .0. 667 * We just test for .0 first, it's fast and probably 668 * going to hit. 669 */ 670 (void)snprintf(buf, sizeof(buf), "*/%s.0", escpage); 671 if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) 672 goto next; 673 674 found = 0; 675 TAILQ_FOREACH(suffix, &mp->suffixlist->entrylist, q) { 676 (void)snprintf(buf, 677 sizeof(buf), "*/%s%s", escpage, 678 suffix->s); 679 if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) { 680 found = 1; 681 break; 682 } 683 } 684 if (found) 685 goto next; 686 687 /* Try the _build keywords next. */ 688 found = manual_find_buildkeyword("*/", escpage, 689 mp, &pg->gl_pathv[cnt]); 690 if (found) { 691 next: anyfound = 1; 692 if (!mp->all) { 693 /* Delete any other matches. */ 694 while (++cnt< pg->gl_pathc) 695 *pg->gl_pathv[cnt] = '\0'; 696 break; 697 } 698 continue; 699 } 700 701 /* It's not a man page, forget about it. */ 702 *pg->gl_pathv[cnt] = '\0'; 703 } 704 705 if (anyfound && !mp->all) 706 break; 707 } 708 709 /* If not found, enter onto the missing list. */ 710 if (!anyfound) { 711 if (addentry(mp->missinglist, page, 0) < 0) { 712 warn("malloc"); 713 (void)cleanup(); 714 exit(EXIT_FAILURE); 715 } 716 } 717 718 free(escpage); 719 return anyfound; 720 } 721 722 /* 723 * A do-nothing counterpart to fmtcheck(3) that only supplies the 724 * __format_arg marker. Actual fmtcheck(3) call is done once in 725 * config(). 726 */ 727 __always_inline __format_arg(2) 728 static inline const char * 729 fmtcheck_ok(const char *userfmt, const char *template) 730 { 731 return userfmt; 732 } 733 734 /* 735 * build_page -- 736 * Build a man page for display. 737 */ 738 static void 739 build_page(const char *fmt, char **pathp, struct manstate *mp) 740 { 741 static int warned; 742 int olddir, fd, n; 743 size_t tmpdirlen; 744 char *p, *b; 745 char buf[MAXPATHLEN], cmd[MAXPATHLEN], tpath[MAXPATHLEN]; 746 const char *tmpdir; 747 748 /* Let the user know this may take awhile. */ 749 if (!warned) { 750 warned = 1; 751 warnx("Formatting manual page..."); 752 } 753 754 /* 755 * Historically man chdir'd to the root of the man tree. 756 * This was used in man pages that contained relative ".so" 757 * directives (including other man pages for command aliases etc.) 758 * It even went one step farther, by examining the first line 759 * of the man page and parsing the .so filename so it would 760 * make hard(?) links to the cat'ted man pages for space savings. 761 * (We don't do that here, but we could). 762 */ 763 764 /* copy and find the end */ 765 for (b = buf, p = *pathp; (*b++ = *p++) != '\0';) 766 continue; 767 768 /* 769 * skip the last two path components, page name and man[n] ... 770 * (e.g. buf will be "/usr/share/man" and p will be "man1/man.1") 771 * we also save a pointer to our current directory so that we 772 * can fchdir() back to it. this allows relative MANDIR paths 773 * to work with multiple man pages... e.g. consider: 774 * cd /usr/share && man -M ./man cat ls 775 * when no "cat1" subdir files are present. 776 */ 777 olddir = -1; 778 for (--b, --p, n = 2; b != buf; b--, p--) 779 if (*b == '/') 780 if (--n == 0) { 781 *b = '\0'; 782 olddir = open(".", O_RDONLY); 783 (void) chdir(buf); 784 p++; 785 break; 786 } 787 788 789 /* advance fmt past the suffix spec to the printf format string */ 790 for (; *fmt && isspace((unsigned char)*fmt); ++fmt) 791 continue; 792 793 /* 794 * Get a temporary file and build a version of the file 795 * to display. Replace the old file name with the new one. 796 */ 797 if ((tmpdir = getenv("TMPDIR")) == NULL) 798 tmpdir = _PATH_TMP; 799 tmpdirlen = strlen(tmpdir); 800 (void)snprintf(tpath, sizeof (tpath), "%s%s%s", tmpdir, 801 (tmpdirlen > 0 && tmpdir[tmpdirlen-1] == '/') ? "" : "/", TMPFILE); 802 if ((fd = mkstemp(tpath)) == -1) { 803 warn("%s", tpath); 804 (void)cleanup(); 805 exit(EXIT_FAILURE); 806 } 807 (void)snprintf(buf, sizeof(buf), "%s > %s", fmt, tpath); 808 (void)snprintf(cmd, sizeof(cmd), fmtcheck_ok(buf, "%s"), p); 809 (void)system(cmd); 810 (void)close(fd); 811 if ((*pathp = strdup(tpath)) == NULL) { 812 warn("malloc"); 813 (void)cleanup(); 814 exit(EXIT_FAILURE); 815 } 816 817 /* Link the built file into the remove-when-done list. */ 818 if (addentry(mp->intmp, *pathp, 0) < 0) { 819 warn("malloc"); 820 (void)cleanup(); 821 exit(EXIT_FAILURE); 822 } 823 824 /* restore old directory so relative manpaths still work */ 825 if (olddir != -1) { 826 fchdir(olddir); 827 close(olddir); 828 } 829 } 830 831 /* 832 * how -- 833 * display how information 834 */ 835 static void 836 how(const char *fname) 837 { 838 FILE *fp; 839 840 int lcnt, print; 841 char buf[256]; 842 const char *p; 843 844 if (!(fp = fopen(fname, "r"))) { 845 warn("%s", fname); 846 (void)cleanup(); 847 exit(EXIT_FAILURE); 848 } 849 #define S1 "SYNOPSIS" 850 #define S2 "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS" 851 #define D1 "DESCRIPTION" 852 #define D2 "D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN" 853 for (lcnt = print = 0; fgets(buf, sizeof(buf), fp);) { 854 if (!strncmp(buf, S1, sizeof(S1) - 1) || 855 !strncmp(buf, S2, sizeof(S2) - 1)) { 856 print = 1; 857 continue; 858 } else if (!strncmp(buf, D1, sizeof(D1) - 1) || 859 !strncmp(buf, D2, sizeof(D2) - 1)) { 860 if (fp) 861 (void)fclose(fp); 862 return; 863 } 864 if (!print) 865 continue; 866 if (*buf == '\n') 867 ++lcnt; 868 else { 869 for(; lcnt; --lcnt) 870 (void)putchar('\n'); 871 for (p = buf; isspace((unsigned char)*p); ++p) 872 continue; 873 (void)fputs(p, stdout); 874 } 875 } 876 (void)fclose(fp); 877 } 878 879 /* 880 * cat -- 881 * cat out the file 882 */ 883 static void 884 cat(const char *fname) 885 { 886 int fd; 887 ssize_t n; 888 char buf[2048]; 889 890 if ((fd = open(fname, O_RDONLY, 0)) < 0) { 891 warn("%s", fname); 892 (void)cleanup(); 893 exit(EXIT_FAILURE); 894 } 895 while ((n = read(fd, buf, sizeof(buf))) > 0) 896 if (write(STDOUT_FILENO, buf, (size_t)n) != n) { 897 warn("write"); 898 (void)cleanup(); 899 exit(EXIT_FAILURE); 900 } 901 if (n == -1) { 902 warn("read"); 903 (void)cleanup(); 904 exit(EXIT_FAILURE); 905 } 906 (void)close(fd); 907 } 908 909 /* 910 * check_pager -- 911 * check the user supplied page information 912 */ 913 static const char * 914 check_pager(const char *name) 915 { 916 const char *p; 917 918 /* 919 * if the user uses "more", we make it "more -s"; watch out for 920 * PAGER = "mypager /usr/ucb/more" 921 */ 922 for (p = name; *p && !isspace((unsigned char)*p); ++p) 923 continue; 924 for (; p > name && *p != '/'; --p); 925 if (p != name) 926 ++p; 927 928 /* make sure it's "more", not "morex" */ 929 if (!strncmp(p, "more", 4) && (!p[4] || isspace((unsigned char)p[4]))){ 930 char *newname; 931 (void)asprintf(&newname, "%s %s", p, "-s"); 932 name = newname; 933 } 934 935 return name; 936 } 937 938 /* 939 * jump -- 940 * strip out flag argument and jump 941 */ 942 static void 943 jump(char **argv, const char *flag, const char *name) 944 { 945 char **arg; 946 947 argv[0] = __UNCONST(name); 948 for (arg = argv + 1; *arg; ++arg) 949 if (!strcmp(*arg, flag)) 950 break; 951 for (; *arg; ++arg) 952 arg[0] = arg[1]; 953 execvp(name, argv); 954 err(EXIT_FAILURE, "Cannot execute `%s'", name); 955 } 956 957 /* 958 * onsig -- 959 * If signaled, delete the temporary files. 960 */ 961 static void 962 onsig(int signo) 963 { 964 965 (void)cleanup(); 966 967 (void)raise_default_signal(signo); 968 969 /* NOTREACHED */ 970 exit(EXIT_FAILURE); 971 } 972 973 /* 974 * cleanup -- 975 * Clean up temporary files, show any error messages. 976 */ 977 static int 978 cleanup(void) 979 { 980 TAG *intmpp, *missp; 981 ENTRY *ep; 982 int rval; 983 984 rval = EXIT_SUCCESS; 985 /* 986 * note that _missing and _intmp were created by main(), so 987 * gettag() cannot return NULL here. 988 */ 989 missp = gettag("_missing", 0); /* missing man pages */ 990 intmpp = gettag("_intmp", 0); /* tmp files we need to unlink */ 991 992 TAILQ_FOREACH(ep, &missp->entrylist, q) { 993 warnx("no entry for %s in the manual.", ep->s); 994 rval = EXIT_FAILURE; 995 } 996 997 TAILQ_FOREACH(ep, &intmpp->entrylist, q) 998 (void)unlink(ep->s); 999 1000 return rval; 1001 } 1002 1003 static const char * 1004 getclass(const char *machine) 1005 { 1006 char buf[BUFSIZ]; 1007 TAG *t; 1008 snprintf(buf, sizeof(buf), "_%s", machine); 1009 t = gettag(buf, 0); 1010 return t != NULL && !TAILQ_EMPTY(&t->entrylist) ? 1011 TAILQ_FIRST(&t->entrylist)->s : NULL; 1012 } 1013 1014 static void 1015 addpath(struct manstate *m, const char *dir, size_t len, const char *sub) 1016 { 1017 char buf[2 * MAXPATHLEN + 1]; 1018 (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,%s%s%s}", 1019 dir, (dir[len - 1] == '/') ? "" : "/", sub, m->machine, 1020 m->machclass ? "/" : "", m->machclass ? m->machclass : "", 1021 m->machclass ? "," : ""); 1022 if (addentry(m->mymanpath, buf, 0) < 0) 1023 errx(EXIT_FAILURE, "malloc failed"); 1024 } 1025 1026 /* 1027 * usage -- 1028 * print usage message and die 1029 */ 1030 static void 1031 usage(void) 1032 { 1033 (void)fprintf(stderr, "Usage: %s [-acw|-h] [-C cfg] [-M path] " 1034 "[-m path] [-S srch] [[-s] sect] name ...\n", getprogname()); 1035 (void)fprintf(stderr, "Usage: %s [-C file] -f command ...\n", getprogname()); 1036 (void)fprintf(stderr, 1037 "Usage: %s [-C file] -k keyword ...\n", 1038 getprogname()); 1039 (void)fprintf(stderr, "Usage: %s -p\n", getprogname()); 1040 exit(EXIT_FAILURE); 1041 } 1042 1043 /* 1044 * printmanpath -- 1045 * Prints a list of directories containing man pages. 1046 */ 1047 static void 1048 printmanpath(struct manstate *m) 1049 { 1050 ENTRY *epath; 1051 char **ap; 1052 glob_t pg; 1053 struct stat sb; 1054 TAG *path = m->mymanpath; 1055 1056 /* the tail queue is empty if no _default tag is defined in * man.conf */ 1057 if (TAILQ_EMPTY(&path->entrylist)) 1058 errx(EXIT_FAILURE, "Empty manpath"); 1059 1060 TAILQ_FOREACH(epath, &path->entrylist, q) { 1061 if (glob(epath->s, GLOB_BRACE | GLOB_NOSORT, NULL, &pg) != 0) 1062 err(EXIT_FAILURE, "glob failed"); 1063 1064 if (pg.gl_matchc == 0) { 1065 globfree(&pg); 1066 continue; 1067 } 1068 1069 for (ap = pg.gl_pathv; *ap != NULL; ++ap) { 1070 /* Skip cat page directories */ 1071 if (strstr(*ap, "/cat") != NULL) 1072 continue; 1073 /* Skip non-directories. */ 1074 if (stat(*ap, &sb) == 0 && S_ISDIR(sb.st_mode)) 1075 printf("%s\n", *ap); 1076 } 1077 globfree(&pg); 1078 } 1079 } 1080