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