1 /* $NetBSD: man.c,v 1.67 2018/06/15 20:16:35 mrg 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.67 2018/06/15 20:16:35 mrg 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 369 /* 370 * now m.mymanpath is complete! 371 */ 372 #if MAN_DEBUG 373 printf("mymanpath:\n"); 374 TAILQ_FOREACH(epath, &m.mymanpath->entrylist, q) { 375 printf("\t%s\n", epath->s); 376 } 377 #endif 378 379 /* 380 * start searching for matching files and format them if necessary. 381 * setup an interrupt handler so that we can ensure that temporary 382 * files go away. 383 */ 384 (void)signal(SIGINT, onsig); 385 (void)signal(SIGHUP, onsig); 386 (void)signal(SIGPIPE, onsig); 387 388 memset(&pg, 0, sizeof(pg)); 389 for (found = 0; *argv; ++argv) 390 if (manual(*argv, &m, &pg)) { 391 found = 1; 392 } 393 394 /* if nothing found, we're done. */ 395 if (!found) { 396 (void)cleanup(); 397 exit(EXIT_FAILURE); 398 } 399 400 /* 401 * handle the simple display cases first (m.cat, m.how, m.where) 402 */ 403 if (m.cat) { 404 for (ap = pg.gl_pathv; *ap != NULL; ++ap) { 405 if (**ap == '\0') 406 continue; 407 cat(*ap); 408 } 409 exit(cleanup()); 410 } 411 if (m.how) { 412 for (ap = pg.gl_pathv; *ap != NULL; ++ap) { 413 if (**ap == '\0') 414 continue; 415 how(*ap); 416 } 417 exit(cleanup()); 418 } 419 if (m.where) { 420 for (ap = pg.gl_pathv; *ap != NULL; ++ap) { 421 if (**ap == '\0') 422 continue; 423 (void)printf("%s\n", *ap); 424 } 425 exit(cleanup()); 426 } 427 428 /* 429 * normal case - we display things in a single command, so 430 * build a list of things to display. first compute total 431 * length of buffer we will need so we can malloc it. 432 */ 433 for (ap = pg.gl_pathv, len = m.pagerlen + 1; *ap != NULL; ++ap) { 434 if (**ap == '\0') 435 continue; 436 len += strlen(*ap) + 1; 437 } 438 if ((cmd = malloc(len)) == NULL) { 439 warn("malloc"); 440 (void)cleanup(); 441 exit(EXIT_FAILURE); 442 } 443 444 /* now build the command string... */ 445 p = cmd; 446 len = m.pagerlen; 447 memcpy(p, m.pager, len); 448 p += len; 449 *p++ = ' '; 450 for (ap = pg.gl_pathv; *ap != NULL; ++ap) { 451 if (**ap == '\0') 452 continue; 453 len = strlen(*ap); 454 memcpy(p, *ap, len); 455 p += len; 456 *p++ = ' '; 457 } 458 *--p = '\0'; 459 460 /* Use system(3) in case someone's pager is "pager arg1 arg2". */ 461 (void)system(cmd); 462 463 exit(cleanup()); 464 } 465 466 static int 467 manual_find_literalfile(struct manstate *mp, char **pv) 468 { 469 ENTRY *suffix; 470 int found; 471 char buf[MAXPATHLEN]; 472 const char *p; 473 int suflen; 474 475 found = 0; 476 477 /* 478 * Expand both '*' and suffix to force an actual 479 * match via fnmatch(3). Since the only match in pg 480 * is the literal file, the match is genuine. 481 */ 482 483 TAILQ_FOREACH(suffix, &mp->buildlist->entrylist, q) { 484 for (p = suffix->s, suflen = 0; 485 *p != '\0' && !isspace((unsigned char)*p); 486 ++p) 487 ++suflen; 488 if (*p == '\0') 489 continue; 490 491 (void)snprintf(buf, sizeof(buf), "*%.*s", suflen, suffix->s); 492 493 if (!fnmatch(buf, *pv, 0)) { 494 if (!mp->where) 495 build_page(p + 1, pv, mp); 496 found = 1; 497 break; 498 } 499 } 500 501 return found; 502 } 503 504 static int 505 manual_find_buildkeyword(const char *prefix, const char *escpage, 506 struct manstate *mp, char **pv) 507 { 508 ENTRY *suffix; 509 int found; 510 char buf[MAXPATHLEN]; 511 const char *p; 512 int suflen; 513 514 found = 0; 515 /* Try the _build keywords next. */ 516 TAILQ_FOREACH(suffix, &mp->buildlist->entrylist, q) { 517 for (p = suffix->s, suflen = 0; 518 *p != '\0' && !isspace((unsigned char)*p); 519 ++p) 520 ++suflen; 521 if (*p == '\0') 522 continue; 523 524 (void)snprintf(buf, sizeof(buf), "%s%s%.*s", 525 prefix, escpage, suflen, suffix->s); 526 if (!fnmatch(buf, *pv, 0)) { 527 if (!mp->where) 528 build_page(p + 1, pv, mp); 529 found = 1; 530 break; 531 } 532 } 533 534 return found; 535 } 536 537 /* 538 * manual -- 539 * Search the manuals for the pages. 540 */ 541 static int 542 manual(char *page, struct manstate *mp, glob_t *pg) 543 { 544 ENTRY *suffix, *mdir; 545 int anyfound, error, found; 546 size_t cnt; 547 char *p, buf[MAXPATHLEN], *escpage, *eptr; 548 static const char escglob[] = "\\~?*{}[]"; 549 550 anyfound = 0; 551 552 /* 553 * Fixup page which may contain glob(3) special characters, e.g. 554 * the famous "No man page for [" FAQ. 555 */ 556 if ((escpage = malloc((2 * strlen(page)) + 1)) == NULL) { 557 warn("malloc"); 558 (void)cleanup(); 559 exit(EXIT_FAILURE); 560 } 561 562 p = page; 563 eptr = escpage; 564 565 while (*p) { 566 if (strchr(escglob, *p) != NULL) { 567 *eptr++ = '\\'; 568 *eptr++ = *p++; 569 } else 570 *eptr++ = *p++; 571 } 572 573 *eptr = '\0'; 574 575 /* 576 * If 'page' is given with an absolute path, 577 * or a relative path explicitly beginning with "./" 578 * or "../", then interpret it as a file specification. 579 */ 580 if ((page[0] == '/') 581 || (page[0] == '.' && page[1] == '/') 582 || (page[0] == '.' && page[1] == '.' && page[2] == '/') 583 ) { 584 /* check if file actually exists */ 585 (void)strlcpy(buf, escpage, sizeof(buf)); 586 error = glob(buf, GLOB_APPEND | GLOB_BRACE | GLOB_NOSORT, NULL, pg); 587 if (error != 0) { 588 if (error == GLOB_NOMATCH) { 589 goto notfound; 590 } else { 591 errx(EXIT_FAILURE, "glob failed"); 592 } 593 } 594 595 if (pg->gl_matchc == 0) 596 goto notfound; 597 598 /* literal file only yields one match */ 599 cnt = pg->gl_pathc - pg->gl_matchc; 600 601 if (manual_find_literalfile(mp, &pg->gl_pathv[cnt])) { 602 anyfound = 1; 603 } else { 604 /* It's not a man page, forget about it. */ 605 *pg->gl_pathv[cnt] = '\0'; 606 } 607 608 notfound: 609 if (!anyfound) { 610 if (addentry(mp->missinglist, page, 0) < 0) { 611 warn("malloc"); 612 (void)cleanup(); 613 exit(EXIT_FAILURE); 614 } 615 } 616 free(escpage); 617 return anyfound; 618 } 619 620 /* For each man directory in mymanpath ... */ 621 TAILQ_FOREACH(mdir, &mp->mymanpath->entrylist, q) { 622 623 /* 624 * use glob(3) to look in the filesystem for matching files. 625 * match any suffix here, as we will check that later. 626 */ 627 (void)snprintf(buf, sizeof(buf), "%s/%s.*", mdir->s, escpage); 628 if ((error = glob(buf, 629 GLOB_APPEND | GLOB_BRACE | GLOB_NOSORT, NULL, pg)) != 0) { 630 if (error == GLOB_NOMATCH) 631 continue; 632 else { 633 warn("globbing"); 634 (void)cleanup(); 635 exit(EXIT_FAILURE); 636 } 637 } 638 if (pg->gl_matchc == 0) 639 continue; 640 641 /* 642 * start going through the matches glob(3) just found and 643 * use m.pathsearch (if present) to filter out pages we 644 * don't want. then verify the suffix is valid, and build 645 * the page if we have a _build suffix. 646 */ 647 for (cnt = pg->gl_pathc - pg->gl_matchc; 648 cnt < pg->gl_pathc; ++cnt) { 649 650 /* filter on directory path name */ 651 if (mp->pathsearch) { 652 p = strstr(pg->gl_pathv[cnt], mp->pathsearch); 653 if (!p || strchr(p, '/') == NULL) { 654 *pg->gl_pathv[cnt] = '\0'; /* zap! */ 655 continue; 656 } 657 } 658 659 /* 660 * Try the _suffix keywords first. 661 * 662 * XXX 663 * Older versions of man.conf didn't have the _suffix 664 * keywords, it was assumed that everything was a .0. 665 * We just test for .0 first, it's fast and probably 666 * going to hit. 667 */ 668 (void)snprintf(buf, sizeof(buf), "*/%s.0", escpage); 669 if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) 670 goto next; 671 672 found = 0; 673 TAILQ_FOREACH(suffix, &mp->suffixlist->entrylist, q) { 674 (void)snprintf(buf, 675 sizeof(buf), "*/%s%s", escpage, 676 suffix->s); 677 if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) { 678 found = 1; 679 break; 680 } 681 } 682 if (found) 683 goto next; 684 685 /* Try the _build keywords next. */ 686 found = manual_find_buildkeyword("*/", escpage, 687 mp, &pg->gl_pathv[cnt]); 688 if (found) { 689 next: anyfound = 1; 690 if (!mp->all) { 691 /* Delete any other matches. */ 692 while (++cnt< pg->gl_pathc) 693 *pg->gl_pathv[cnt] = '\0'; 694 break; 695 } 696 continue; 697 } 698 699 /* It's not a man page, forget about it. */ 700 *pg->gl_pathv[cnt] = '\0'; 701 } 702 703 if (anyfound && !mp->all) 704 break; 705 } 706 707 /* If not found, enter onto the missing list. */ 708 if (!anyfound) { 709 if (addentry(mp->missinglist, page, 0) < 0) { 710 warn("malloc"); 711 (void)cleanup(); 712 exit(EXIT_FAILURE); 713 } 714 } 715 716 free(escpage); 717 return anyfound; 718 } 719 720 /* 721 * A do-nothing counterpart to fmtcheck(3) that only supplies the 722 * __format_arg marker. Actual fmtcheck(3) call is done once in 723 * config(). 724 */ 725 __always_inline __format_arg(2) 726 static inline const char * 727 fmtcheck_ok(const char *userfmt, const char *template) 728 { 729 return userfmt; 730 } 731 732 /* 733 * build_page -- 734 * Build a man page for display. 735 */ 736 static void 737 build_page(const char *fmt, char **pathp, struct manstate *mp) 738 { 739 static int warned; 740 int olddir, fd, n; 741 size_t tmpdirlen; 742 char *p, *b; 743 char buf[MAXPATHLEN], cmd[MAXPATHLEN], tpath[MAXPATHLEN]; 744 const char *tmpdir; 745 746 /* Let the user know this may take awhile. */ 747 if (!warned) { 748 warned = 1; 749 warnx("Formatting manual page..."); 750 } 751 752 /* 753 * Historically man chdir'd to the root of the man tree. 754 * This was used in man pages that contained relative ".so" 755 * directives (including other man pages for command aliases etc.) 756 * It even went one step farther, by examining the first line 757 * of the man page and parsing the .so filename so it would 758 * make hard(?) links to the cat'ted man pages for space savings. 759 * (We don't do that here, but we could). 760 */ 761 762 /* copy and find the end */ 763 for (b = buf, p = *pathp; (*b++ = *p++) != '\0';) 764 continue; 765 766 /* 767 * skip the last two path components, page name and man[n] ... 768 * (e.g. buf will be "/usr/share/man" and p will be "man1/man.1") 769 * we also save a pointer to our current directory so that we 770 * can fchdir() back to it. this allows relative MANDIR paths 771 * to work with multiple man pages... e.g. consider: 772 * cd /usr/share && man -M ./man cat ls 773 * when no "cat1" subdir files are present. 774 */ 775 olddir = -1; 776 for (--b, --p, n = 2; b != buf; b--, p--) 777 if (*b == '/') 778 if (--n == 0) { 779 *b = '\0'; 780 olddir = open(".", O_RDONLY); 781 (void) chdir(buf); 782 p++; 783 break; 784 } 785 786 787 /* advance fmt past the suffix spec to the printf format string */ 788 for (; *fmt && isspace((unsigned char)*fmt); ++fmt) 789 continue; 790 791 /* 792 * Get a temporary file and build a version of the file 793 * to display. Replace the old file name with the new one. 794 */ 795 if ((tmpdir = getenv("TMPDIR")) == NULL) 796 tmpdir = _PATH_TMP; 797 tmpdirlen = strlen(tmpdir); 798 (void)snprintf(tpath, sizeof (tpath), "%s%s%s", tmpdir, 799 (tmpdirlen > 0 && tmpdir[tmpdirlen-1] == '/') ? "" : "/", TMPFILE); 800 if ((fd = mkstemp(tpath)) == -1) { 801 warn("%s", tpath); 802 (void)cleanup(); 803 exit(EXIT_FAILURE); 804 } 805 (void)snprintf(buf, sizeof(buf), "%s > %s", fmt, tpath); 806 (void)snprintf(cmd, sizeof(cmd), fmtcheck_ok(buf, "%s"), p); 807 (void)system(cmd); 808 (void)close(fd); 809 if ((*pathp = strdup(tpath)) == NULL) { 810 warn("malloc"); 811 (void)cleanup(); 812 exit(EXIT_FAILURE); 813 } 814 815 /* Link the built file into the remove-when-done list. */ 816 if (addentry(mp->intmp, *pathp, 0) < 0) { 817 warn("malloc"); 818 (void)cleanup(); 819 exit(EXIT_FAILURE); 820 } 821 822 /* restore old directory so relative manpaths still work */ 823 if (olddir != -1) { 824 fchdir(olddir); 825 close(olddir); 826 } 827 } 828 829 /* 830 * how -- 831 * display how information 832 */ 833 static void 834 how(const char *fname) 835 { 836 FILE *fp; 837 838 int lcnt, print; 839 char buf[256]; 840 const char *p; 841 842 if (!(fp = fopen(fname, "r"))) { 843 warn("%s", fname); 844 (void)cleanup(); 845 exit(EXIT_FAILURE); 846 } 847 #define S1 "SYNOPSIS" 848 #define S2 "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS" 849 #define D1 "DESCRIPTION" 850 #define D2 "D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN" 851 for (lcnt = print = 0; fgets(buf, sizeof(buf), fp);) { 852 if (!strncmp(buf, S1, sizeof(S1) - 1) || 853 !strncmp(buf, S2, sizeof(S2) - 1)) { 854 print = 1; 855 continue; 856 } else if (!strncmp(buf, D1, sizeof(D1) - 1) || 857 !strncmp(buf, D2, sizeof(D2) - 1)) { 858 if (fp) 859 (void)fclose(fp); 860 return; 861 } 862 if (!print) 863 continue; 864 if (*buf == '\n') 865 ++lcnt; 866 else { 867 for(; lcnt; --lcnt) 868 (void)putchar('\n'); 869 for (p = buf; isspace((unsigned char)*p); ++p) 870 continue; 871 (void)fputs(p, stdout); 872 } 873 } 874 (void)fclose(fp); 875 } 876 877 /* 878 * cat -- 879 * cat out the file 880 */ 881 static void 882 cat(const char *fname) 883 { 884 int fd; 885 ssize_t n; 886 char buf[2048]; 887 888 if ((fd = open(fname, O_RDONLY, 0)) < 0) { 889 warn("%s", fname); 890 (void)cleanup(); 891 exit(EXIT_FAILURE); 892 } 893 while ((n = read(fd, buf, sizeof(buf))) > 0) 894 if (write(STDOUT_FILENO, buf, (size_t)n) != n) { 895 warn("write"); 896 (void)cleanup(); 897 exit(EXIT_FAILURE); 898 } 899 if (n == -1) { 900 warn("read"); 901 (void)cleanup(); 902 exit(EXIT_FAILURE); 903 } 904 (void)close(fd); 905 } 906 907 /* 908 * check_pager -- 909 * check the user supplied page information 910 */ 911 static const char * 912 check_pager(const char *name) 913 { 914 const char *p; 915 916 /* 917 * if the user uses "more", we make it "more -s"; watch out for 918 * PAGER = "mypager /usr/ucb/more" 919 */ 920 for (p = name; *p && !isspace((unsigned char)*p); ++p) 921 continue; 922 for (; p > name && *p != '/'; --p); 923 if (p != name) 924 ++p; 925 926 /* make sure it's "more", not "morex" */ 927 if (!strncmp(p, "more", 4) && (!p[4] || isspace((unsigned char)p[4]))){ 928 char *newname; 929 (void)asprintf(&newname, "%s %s", p, "-s"); 930 name = newname; 931 } 932 933 return name; 934 } 935 936 /* 937 * jump -- 938 * strip out flag argument and jump 939 */ 940 static void 941 jump(char **argv, const char *flag, const char *name) 942 { 943 char **arg; 944 945 argv[0] = __UNCONST(name); 946 for (arg = argv + 1; *arg; ++arg) 947 if (!strcmp(*arg, flag)) 948 break; 949 for (; *arg; ++arg) 950 arg[0] = arg[1]; 951 execvp(name, argv); 952 err(EXIT_FAILURE, "Cannot execute `%s'", name); 953 } 954 955 /* 956 * onsig -- 957 * If signaled, delete the temporary files. 958 */ 959 static void 960 onsig(int signo) 961 { 962 963 (void)cleanup(); 964 965 (void)raise_default_signal(signo); 966 967 /* NOTREACHED */ 968 exit(EXIT_FAILURE); 969 } 970 971 /* 972 * cleanup -- 973 * Clean up temporary files, show any error messages. 974 */ 975 static int 976 cleanup(void) 977 { 978 TAG *intmpp, *missp; 979 ENTRY *ep; 980 int rval; 981 982 rval = EXIT_SUCCESS; 983 /* 984 * note that _missing and _intmp were created by main(), so 985 * gettag() cannot return NULL here. 986 */ 987 missp = gettag("_missing", 0); /* missing man pages */ 988 intmpp = gettag("_intmp", 0); /* tmp files we need to unlink */ 989 990 TAILQ_FOREACH(ep, &missp->entrylist, q) { 991 warnx("no entry for %s in the manual.", ep->s); 992 rval = EXIT_FAILURE; 993 } 994 995 TAILQ_FOREACH(ep, &intmpp->entrylist, q) 996 (void)unlink(ep->s); 997 998 return rval; 999 } 1000 1001 static const char * 1002 getclass(const char *machine) 1003 { 1004 char buf[BUFSIZ]; 1005 TAG *t; 1006 snprintf(buf, sizeof(buf), "_%s", machine); 1007 t = gettag(buf, 0); 1008 return t != NULL && !TAILQ_EMPTY(&t->entrylist) ? 1009 TAILQ_FIRST(&t->entrylist)->s : NULL; 1010 } 1011 1012 static void 1013 addpath(struct manstate *m, const char *dir, size_t len, const char *sub) 1014 { 1015 char buf[2 * MAXPATHLEN + 1]; 1016 (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,%s%s%s}", 1017 dir, (dir[len - 1] == '/') ? "" : "/", sub, m->machine, 1018 m->machclass ? "/" : "", m->machclass ? m->machclass : "", 1019 m->machclass ? "," : ""); 1020 if (addentry(m->mymanpath, buf, 0) < 0) 1021 errx(EXIT_FAILURE, "malloc failed"); 1022 } 1023 1024 /* 1025 * usage -- 1026 * print usage message and die 1027 */ 1028 static void 1029 usage(void) 1030 { 1031 (void)fprintf(stderr, "Usage: %s [-acw|-h] [-C cfg] [-M path] " 1032 "[-m path] [-S srch] [[-s] sect] name ...\n", getprogname()); 1033 (void)fprintf(stderr, "Usage: %s [-C file] -f command ...\n", getprogname()); 1034 (void)fprintf(stderr, 1035 "Usage: %s [-C file] -k keyword ...\n", 1036 getprogname()); 1037 (void)fprintf(stderr, "Usage: %s -p\n", getprogname()); 1038 exit(EXIT_FAILURE); 1039 } 1040 1041 /* 1042 * printmanpath -- 1043 * Prints a list of directories containing man pages. 1044 */ 1045 static void 1046 printmanpath(struct manstate *m) 1047 { 1048 ENTRY *epath; 1049 char **ap; 1050 glob_t pg; 1051 struct stat sb; 1052 TAG *path = m->mymanpath; 1053 1054 /* the tail queue is empty if no _default tag is defined in * man.conf */ 1055 if (TAILQ_EMPTY(&path->entrylist)) 1056 errx(EXIT_FAILURE, "Empty manpath"); 1057 1058 TAILQ_FOREACH(epath, &path->entrylist, q) { 1059 if (glob(epath->s, GLOB_BRACE | GLOB_NOSORT, NULL, &pg) != 0) 1060 err(EXIT_FAILURE, "glob failed"); 1061 1062 if (pg.gl_matchc == 0) { 1063 globfree(&pg); 1064 continue; 1065 } 1066 1067 for (ap = pg.gl_pathv; *ap != NULL; ++ap) { 1068 /* Skip cat page directories */ 1069 if (strstr(*ap, "/cat") != NULL) 1070 continue; 1071 /* Skip non-directories. */ 1072 if (stat(*ap, &sb) == 0 && S_ISDIR(sb.st_mode)) 1073 printf("%s\n", *ap); 1074 } 1075 globfree(&pg); 1076 } 1077 } 1078