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