1 /* $Id: main.c,v 1.322 2019/03/06 10:18:58 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2010-2012, 2014-2019 Ingo Schwarze <schwarze@openbsd.org> 5 * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org> 6 * 7 * Permission to use, copy, modify, and distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 */ 19 #include "config.h" 20 21 #include <sys/types.h> 22 #include <sys/ioctl.h> 23 #include <sys/param.h> /* MACHINE */ 24 #include <sys/wait.h> 25 26 #include <assert.h> 27 #include <ctype.h> 28 #if HAVE_ERR 29 #include <err.h> 30 #endif 31 #include <errno.h> 32 #include <fcntl.h> 33 #include <glob.h> 34 #if HAVE_SANDBOX_INIT 35 #include <sandbox.h> 36 #endif 37 #include <signal.h> 38 #include <stdio.h> 39 #include <stdint.h> 40 #include <stdlib.h> 41 #include <string.h> 42 #include <termios.h> 43 #include <time.h> 44 #include <unistd.h> 45 46 #include "mandoc_aux.h" 47 #include "mandoc.h" 48 #include "mandoc_xr.h" 49 #include "roff.h" 50 #include "mdoc.h" 51 #include "man.h" 52 #include "mandoc_parse.h" 53 #include "tag.h" 54 #include "main.h" 55 #include "manconf.h" 56 #include "mansearch.h" 57 58 enum outmode { 59 OUTMODE_DEF = 0, 60 OUTMODE_FLN, 61 OUTMODE_LST, 62 OUTMODE_ALL, 63 OUTMODE_ONE 64 }; 65 66 enum outt { 67 OUTT_ASCII = 0, /* -Tascii */ 68 OUTT_LOCALE, /* -Tlocale */ 69 OUTT_UTF8, /* -Tutf8 */ 70 OUTT_TREE, /* -Ttree */ 71 OUTT_MAN, /* -Tman */ 72 OUTT_HTML, /* -Thtml */ 73 OUTT_MARKDOWN, /* -Tmarkdown */ 74 OUTT_LINT, /* -Tlint */ 75 OUTT_PS, /* -Tps */ 76 OUTT_PDF /* -Tpdf */ 77 }; 78 79 struct curparse { 80 struct mparse *mp; 81 struct manoutput *outopts; /* output options */ 82 void *outdata; /* data for output */ 83 char *os_s; /* operating system for display */ 84 int wstop; /* stop after a file with a warning */ 85 enum mandoc_os os_e; /* check base system conventions */ 86 enum outt outtype; /* which output to use */ 87 }; 88 89 90 int mandocdb(int, char *[]); 91 92 static void check_xr(void); 93 static int fs_lookup(const struct manpaths *, 94 size_t ipath, const char *, 95 const char *, const char *, 96 struct manpage **, size_t *); 97 static int fs_search(const struct mansearch *, 98 const struct manpaths *, int, char**, 99 struct manpage **, size_t *); 100 static int koptions(int *, char *); 101 static void moptions(int *, char *); 102 static void outdata_alloc(struct curparse *); 103 static void parse(struct curparse *, int, const char *); 104 static void passthrough(const char *, int, int); 105 static pid_t spawn_pager(struct tag_files *); 106 static int toptions(struct curparse *, char *); 107 static void usage(enum argmode) __attribute__((__noreturn__)); 108 static int woptions(struct curparse *, char *); 109 110 static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9}; 111 static char help_arg[] = "help"; 112 static char *help_argv[] = {help_arg, NULL}; 113 114 115 int 116 main(int argc, char *argv[]) 117 { 118 struct manconf conf; 119 struct mansearch search; 120 struct curparse curp; 121 struct winsize ws; 122 struct tag_files *tag_files; 123 struct manpage *res, *resp; 124 const char *progname, *sec, *thisarg; 125 char *conf_file, *defpaths, *auxpaths; 126 char *oarg, *tagarg; 127 unsigned char *uc; 128 size_t i, sz; 129 int prio, best_prio; 130 enum outmode outmode; 131 int fd, startdir; 132 int show_usage; 133 int options; 134 int use_pager; 135 int status, signum; 136 int c; 137 pid_t pager_pid, tc_pgid, man_pgid, pid; 138 139 #if HAVE_PROGNAME 140 progname = getprogname(); 141 #else 142 if (argc < 1) 143 progname = mandoc_strdup("mandoc"); 144 else if ((progname = strrchr(argv[0], '/')) == NULL) 145 progname = argv[0]; 146 else 147 ++progname; 148 setprogname(progname); 149 #endif 150 151 mandoc_msg_setoutfile(stderr); 152 if (strncmp(progname, "mandocdb", 8) == 0 || 153 strcmp(progname, BINM_MAKEWHATIS) == 0) 154 return mandocdb(argc, argv); 155 156 #if HAVE_PLEDGE 157 if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1) 158 err((int)MANDOCLEVEL_SYSERR, "pledge"); 159 #endif 160 161 #if HAVE_SANDBOX_INIT 162 if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1) 163 errx((int)MANDOCLEVEL_SYSERR, "sandbox_init"); 164 #endif 165 166 /* Search options. */ 167 168 memset(&conf, 0, sizeof(conf)); 169 conf_file = defpaths = NULL; 170 auxpaths = NULL; 171 172 memset(&search, 0, sizeof(struct mansearch)); 173 search.outkey = "Nd"; 174 oarg = NULL; 175 176 if (strcmp(progname, BINM_MAN) == 0) 177 search.argmode = ARG_NAME; 178 else if (strcmp(progname, BINM_APROPOS) == 0) 179 search.argmode = ARG_EXPR; 180 else if (strcmp(progname, BINM_WHATIS) == 0) 181 search.argmode = ARG_WORD; 182 else if (strncmp(progname, "help", 4) == 0) 183 search.argmode = ARG_NAME; 184 else 185 search.argmode = ARG_FILE; 186 187 /* Parser and formatter options. */ 188 189 memset(&curp, 0, sizeof(struct curparse)); 190 curp.outtype = OUTT_LOCALE; 191 curp.outopts = &conf.output; 192 options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1; 193 194 use_pager = 1; 195 tag_files = NULL; 196 show_usage = 0; 197 outmode = OUTMODE_DEF; 198 199 while ((c = getopt(argc, argv, 200 "aC:cfhI:iK:klM:m:O:S:s:T:VW:w")) != -1) { 201 if (c == 'i' && search.argmode == ARG_EXPR) { 202 optind--; 203 break; 204 } 205 switch (c) { 206 case 'a': 207 outmode = OUTMODE_ALL; 208 break; 209 case 'C': 210 conf_file = optarg; 211 break; 212 case 'c': 213 use_pager = 0; 214 break; 215 case 'f': 216 search.argmode = ARG_WORD; 217 break; 218 case 'h': 219 conf.output.synopsisonly = 1; 220 use_pager = 0; 221 outmode = OUTMODE_ALL; 222 break; 223 case 'I': 224 if (strncmp(optarg, "os=", 3)) { 225 warnx("-I %s: Bad argument", optarg); 226 return (int)MANDOCLEVEL_BADARG; 227 } 228 if (curp.os_s != NULL) { 229 warnx("-I %s: Duplicate argument", optarg); 230 return (int)MANDOCLEVEL_BADARG; 231 } 232 curp.os_s = mandoc_strdup(optarg + 3); 233 break; 234 case 'K': 235 if ( ! koptions(&options, optarg)) 236 return (int)MANDOCLEVEL_BADARG; 237 break; 238 case 'k': 239 search.argmode = ARG_EXPR; 240 break; 241 case 'l': 242 search.argmode = ARG_FILE; 243 outmode = OUTMODE_ALL; 244 break; 245 case 'M': 246 #ifdef __FreeBSD__ 247 defpaths = strdup(optarg); 248 if (defpaths == NULL) 249 err(1, "strdup"); 250 #else 251 defpaths = optarg; 252 #endif 253 break; 254 case 'm': 255 auxpaths = optarg; 256 break; 257 case 'O': 258 oarg = optarg; 259 break; 260 case 'S': 261 search.arch = optarg; 262 break; 263 case 's': 264 search.sec = optarg; 265 break; 266 case 'T': 267 if ( ! toptions(&curp, optarg)) 268 return (int)MANDOCLEVEL_BADARG; 269 break; 270 case 'W': 271 if ( ! woptions(&curp, optarg)) 272 return (int)MANDOCLEVEL_BADARG; 273 break; 274 case 'w': 275 outmode = OUTMODE_FLN; 276 break; 277 default: 278 show_usage = 1; 279 break; 280 } 281 } 282 283 if (show_usage) 284 usage(search.argmode); 285 286 /* Postprocess options. */ 287 288 if (outmode == OUTMODE_DEF) { 289 switch (search.argmode) { 290 case ARG_FILE: 291 outmode = OUTMODE_ALL; 292 use_pager = 0; 293 break; 294 case ARG_NAME: 295 outmode = OUTMODE_ONE; 296 break; 297 default: 298 outmode = OUTMODE_LST; 299 break; 300 } 301 } 302 303 if (oarg != NULL) { 304 if (outmode == OUTMODE_LST) 305 search.outkey = oarg; 306 else { 307 while (oarg != NULL) { 308 thisarg = oarg; 309 if (manconf_output(&conf.output, 310 strsep(&oarg, ","), 0) == 0) 311 continue; 312 warnx("-O %s: Bad argument", thisarg); 313 return (int)MANDOCLEVEL_BADARG; 314 } 315 } 316 } 317 318 if (curp.outtype != OUTT_TREE || !curp.outopts->noval) 319 options |= MPARSE_VALIDATE; 320 321 if (outmode == OUTMODE_FLN || 322 outmode == OUTMODE_LST || 323 !isatty(STDOUT_FILENO)) 324 use_pager = 0; 325 326 if (use_pager && 327 (conf.output.width == 0 || conf.output.indent == 0) && 328 ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 && 329 ws.ws_col > 1) { 330 if (conf.output.width == 0 && ws.ws_col < 79) 331 conf.output.width = ws.ws_col - 1; 332 if (conf.output.indent == 0 && ws.ws_col < 66) 333 conf.output.indent = 3; 334 } 335 336 #if HAVE_PLEDGE 337 if (!use_pager) 338 if (pledge("stdio rpath", NULL) == -1) 339 err((int)MANDOCLEVEL_SYSERR, "pledge"); 340 #endif 341 342 /* Parse arguments. */ 343 344 if (argc > 0) { 345 argc -= optind; 346 argv += optind; 347 } 348 resp = NULL; 349 350 /* 351 * Quirks for help(1) 352 * and for a man(1) section argument without -s. 353 */ 354 355 if (search.argmode == ARG_NAME) { 356 if (*progname == 'h') { 357 if (argc == 0) { 358 argv = help_argv; 359 argc = 1; 360 } 361 } else if (argc > 1 && 362 ((uc = (unsigned char *)argv[0]) != NULL) && 363 ((isdigit(uc[0]) && (uc[1] == '\0' || 364 (isalpha(uc[1]) && uc[2] == '\0'))) || 365 (uc[0] == 'n' && uc[1] == '\0'))) { 366 search.sec = (char *)uc; 367 argv++; 368 argc--; 369 } 370 if (search.arch == NULL) 371 search.arch = getenv("MACHINE"); 372 #ifdef MACHINE 373 if (search.arch == NULL) 374 search.arch = MACHINE; 375 #endif 376 } 377 378 /* 379 * Use the first argument for -O tag in addition to 380 * using it as a search term for man(1) or apropos(1). 381 */ 382 383 if (conf.output.tag != NULL && *conf.output.tag == '\0') { 384 tagarg = argc > 0 && search.argmode == ARG_EXPR ? 385 strchr(*argv, '=') : NULL; 386 conf.output.tag = tagarg == NULL ? *argv : tagarg + 1; 387 } 388 389 /* man(1), whatis(1), apropos(1) */ 390 391 if (search.argmode != ARG_FILE) { 392 if (search.argmode == ARG_NAME && 393 outmode == OUTMODE_ONE) 394 search.firstmatch = 1; 395 396 #ifdef __FreeBSD__ 397 /* 398 * Use manpath(1) to populate defpaths if -M is not specified. 399 * Don't treat any failures as fatal. 400 */ 401 if (defpaths == NULL) { 402 FILE *fp; 403 size_t linecap = 0; 404 ssize_t linelen; 405 406 if ((fp = popen("/usr/bin/manpath -q", "r")) != NULL) { 407 if ((linelen = getline(&defpaths, 408 &linecap, fp)) > 0) { 409 /* Strip trailing newline */ 410 defpaths[linelen - 1] = '\0'; 411 } 412 pclose(fp); 413 } 414 } 415 #endif 416 417 /* Access the mandoc database. */ 418 419 manconf_parse(&conf, conf_file, defpaths, auxpaths); 420 #ifdef __FreeBSD__ 421 free(defpaths); 422 #endif 423 424 if ( ! mansearch(&search, &conf.manpath, 425 argc, argv, &res, &sz)) 426 usage(search.argmode); 427 428 if (sz == 0 && search.argmode == ARG_NAME) 429 fs_search(&search, &conf.manpath, 430 argc, argv, &res, &sz); 431 432 if (search.argmode == ARG_NAME) { 433 for (c = 0; c < argc; c++) { 434 if (strchr(argv[c], '/') == NULL) 435 continue; 436 if (access(argv[c], R_OK) == -1) { 437 warn("%s", argv[c]); 438 continue; 439 } 440 res = mandoc_reallocarray(res, 441 sz + 1, sizeof(*res)); 442 res[sz].file = mandoc_strdup(argv[c]); 443 res[sz].names = NULL; 444 res[sz].output = NULL; 445 res[sz].ipath = SIZE_MAX; 446 res[sz].sec = 10; 447 res[sz].form = FORM_SRC; 448 sz++; 449 } 450 } 451 452 if (sz == 0) { 453 if (search.argmode != ARG_NAME) 454 warnx("nothing appropriate"); 455 mandoc_msg_setrc(MANDOCLEVEL_BADARG); 456 goto out; 457 } 458 459 /* 460 * For standard man(1) and -a output mode, 461 * prepare for copying filename pointers 462 * into the program parameter array. 463 */ 464 465 if (outmode == OUTMODE_ONE) { 466 argc = 1; 467 best_prio = 20; 468 } else if (outmode == OUTMODE_ALL) 469 argc = (int)sz; 470 471 /* Iterate all matching manuals. */ 472 473 resp = res; 474 for (i = 0; i < sz; i++) { 475 if (outmode == OUTMODE_FLN) 476 puts(res[i].file); 477 else if (outmode == OUTMODE_LST) 478 printf("%s - %s\n", res[i].names, 479 res[i].output == NULL ? "" : 480 res[i].output); 481 else if (outmode == OUTMODE_ONE) { 482 /* Search for the best section. */ 483 sec = res[i].file; 484 sec += strcspn(sec, "123456789"); 485 if (sec[0] == '\0') 486 continue; 487 prio = sec_prios[sec[0] - '1']; 488 if (sec[1] != '/') 489 prio += 10; 490 if (prio >= best_prio) 491 continue; 492 best_prio = prio; 493 resp = res + i; 494 } 495 } 496 497 /* 498 * For man(1), -a and -i output mode, fall through 499 * to the main mandoc(1) code iterating files 500 * and running the parsers on each of them. 501 */ 502 503 if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST) 504 goto out; 505 } 506 507 /* mandoc(1) */ 508 509 #if HAVE_PLEDGE 510 if (use_pager) { 511 if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1) 512 err((int)MANDOCLEVEL_SYSERR, "pledge"); 513 } else { 514 if (pledge("stdio rpath", NULL) == -1) 515 err((int)MANDOCLEVEL_SYSERR, "pledge"); 516 } 517 #endif 518 519 if (search.argmode == ARG_FILE) 520 moptions(&options, auxpaths); 521 522 mchars_alloc(); 523 curp.mp = mparse_alloc(options, curp.os_e, curp.os_s); 524 525 if (argc < 1) { 526 if (use_pager) { 527 tag_files = tag_init(); 528 tag_files->tagname = conf.output.tag; 529 } 530 thisarg = "<stdin>"; 531 mandoc_msg_setinfilename(thisarg); 532 parse(&curp, STDIN_FILENO, thisarg); 533 mandoc_msg_setinfilename(NULL); 534 } 535 536 /* 537 * Remember the original working directory, if possible. 538 * This will be needed if some names on the command line 539 * are page names and some are relative file names. 540 * Do not error out if the current directory is not 541 * readable: Maybe it won't be needed after all. 542 */ 543 startdir = open(".", O_RDONLY | O_DIRECTORY); 544 545 while (argc > 0) { 546 547 /* 548 * Changing directories is not needed in ARG_FILE mode. 549 * Do it on a best-effort basis. Even in case of 550 * failure, some functionality may still work. 551 */ 552 if (resp != NULL) { 553 if (resp->ipath != SIZE_MAX) 554 (void)chdir(conf.manpath.paths[resp->ipath]); 555 else if (startdir != -1) 556 (void)fchdir(startdir); 557 thisarg = resp->file; 558 } else 559 thisarg = *argv; 560 561 fd = mparse_open(curp.mp, thisarg); 562 if (fd != -1) { 563 if (use_pager) { 564 use_pager = 0; 565 tag_files = tag_init(); 566 tag_files->tagname = conf.output.tag; 567 } 568 569 mandoc_msg_setinfilename(thisarg); 570 if (resp == NULL || resp->form == FORM_SRC) 571 parse(&curp, fd, thisarg); 572 else 573 passthrough(resp->file, fd, 574 conf.output.synopsisonly); 575 mandoc_msg_setinfilename(NULL); 576 577 if (ferror(stdout)) { 578 if (tag_files != NULL) { 579 warn("%s", tag_files->ofn); 580 tag_unlink(); 581 tag_files = NULL; 582 } else 583 warn("stdout"); 584 mandoc_msg_setrc(MANDOCLEVEL_SYSERR); 585 break; 586 } 587 588 if (argc > 1 && curp.outtype <= OUTT_UTF8) { 589 if (curp.outdata == NULL) 590 outdata_alloc(&curp); 591 terminal_sepline(curp.outdata); 592 } 593 } else 594 mandoc_msg(MANDOCERR_FILE, 0, 0, 595 "%s: %s", thisarg, strerror(errno)); 596 597 if (curp.wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK) 598 break; 599 600 if (resp != NULL) 601 resp++; 602 else 603 argv++; 604 if (--argc) 605 mparse_reset(curp.mp); 606 } 607 if (startdir != -1) { 608 (void)fchdir(startdir); 609 close(startdir); 610 } 611 612 if (curp.outdata != NULL) { 613 switch (curp.outtype) { 614 case OUTT_HTML: 615 html_free(curp.outdata); 616 break; 617 case OUTT_UTF8: 618 case OUTT_LOCALE: 619 case OUTT_ASCII: 620 ascii_free(curp.outdata); 621 break; 622 case OUTT_PDF: 623 case OUTT_PS: 624 pspdf_free(curp.outdata); 625 break; 626 default: 627 break; 628 } 629 } 630 mandoc_xr_free(); 631 mparse_free(curp.mp); 632 mchars_free(); 633 634 out: 635 if (search.argmode != ARG_FILE) { 636 manconf_free(&conf); 637 mansearch_free(res, sz); 638 } 639 640 free(curp.os_s); 641 642 /* 643 * When using a pager, finish writing both temporary files, 644 * fork it, wait for the user to close it, and clean up. 645 */ 646 647 if (tag_files != NULL) { 648 fclose(stdout); 649 tag_write(); 650 man_pgid = getpgid(0); 651 tag_files->tcpgid = man_pgid == getpid() ? 652 getpgid(getppid()) : man_pgid; 653 pager_pid = 0; 654 signum = SIGSTOP; 655 for (;;) { 656 657 /* Stop here until moved to the foreground. */ 658 659 tc_pgid = tcgetpgrp(tag_files->ofd); 660 if (tc_pgid != man_pgid) { 661 if (tc_pgid == pager_pid) { 662 (void)tcsetpgrp(tag_files->ofd, 663 man_pgid); 664 if (signum == SIGTTIN) 665 continue; 666 } else 667 tag_files->tcpgid = tc_pgid; 668 kill(0, signum); 669 continue; 670 } 671 672 /* Once in the foreground, activate the pager. */ 673 674 if (pager_pid) { 675 (void)tcsetpgrp(tag_files->ofd, pager_pid); 676 kill(pager_pid, SIGCONT); 677 } else 678 pager_pid = spawn_pager(tag_files); 679 680 /* Wait for the pager to stop or exit. */ 681 682 while ((pid = waitpid(pager_pid, &status, 683 WUNTRACED)) == -1 && errno == EINTR) 684 continue; 685 686 if (pid == -1) { 687 warn("wait"); 688 mandoc_msg_setrc(MANDOCLEVEL_SYSERR); 689 break; 690 } 691 if (!WIFSTOPPED(status)) 692 break; 693 694 signum = WSTOPSIG(status); 695 } 696 tag_unlink(); 697 } 698 return (int)mandoc_msg_getrc(); 699 } 700 701 static void 702 usage(enum argmode argmode) 703 { 704 705 switch (argmode) { 706 case ARG_FILE: 707 fputs("usage: mandoc [-ac] [-I os=name] " 708 "[-K encoding] [-mdoc | -man] [-O options]\n" 709 "\t [-T output] [-W level] [file ...]\n", stderr); 710 break; 711 case ARG_NAME: 712 fputs("usage: man [-acfhklw] [-C file] [-M path] " 713 "[-m path] [-S subsection]\n" 714 "\t [[-s] section] name ...\n", stderr); 715 break; 716 case ARG_WORD: 717 fputs("usage: whatis [-afk] [-C file] " 718 "[-M path] [-m path] [-O outkey] [-S arch]\n" 719 "\t [-s section] name ...\n", stderr); 720 break; 721 case ARG_EXPR: 722 fputs("usage: apropos [-afk] [-C file] " 723 "[-M path] [-m path] [-O outkey] [-S arch]\n" 724 "\t [-s section] expression ...\n", stderr); 725 break; 726 } 727 exit((int)MANDOCLEVEL_BADARG); 728 } 729 730 static int 731 fs_lookup(const struct manpaths *paths, size_t ipath, 732 const char *sec, const char *arch, const char *name, 733 struct manpage **res, size_t *ressz) 734 { 735 glob_t globinfo; 736 struct manpage *page; 737 char *file; 738 int globres; 739 enum form form; 740 741 form = FORM_SRC; 742 mandoc_asprintf(&file, "%s/man%s/%s.%s", 743 paths->paths[ipath], sec, name, sec); 744 if (access(file, R_OK) != -1) 745 goto found; 746 free(file); 747 748 mandoc_asprintf(&file, "%s/cat%s/%s.0", 749 paths->paths[ipath], sec, name); 750 if (access(file, R_OK) != -1) { 751 form = FORM_CAT; 752 goto found; 753 } 754 free(file); 755 756 if (arch != NULL) { 757 mandoc_asprintf(&file, "%s/man%s/%s/%s.%s", 758 paths->paths[ipath], sec, arch, name, sec); 759 if (access(file, R_OK) != -1) 760 goto found; 761 free(file); 762 } 763 764 mandoc_asprintf(&file, "%s/man%s/%s.[01-9]*", 765 paths->paths[ipath], sec, name); 766 globres = glob(file, 0, NULL, &globinfo); 767 if (globres != 0 && globres != GLOB_NOMATCH) 768 warn("%s: glob", file); 769 free(file); 770 if (globres == 0) 771 file = mandoc_strdup(*globinfo.gl_pathv); 772 globfree(&globinfo); 773 if (globres == 0) 774 goto found; 775 if (res != NULL || ipath + 1 != paths->sz) 776 return 0; 777 778 mandoc_asprintf(&file, "%s.%s", name, sec); 779 globres = access(file, R_OK); 780 free(file); 781 return globres != -1; 782 783 found: 784 warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s", 785 name, sec, BINM_MAKEWHATIS, paths->paths[ipath]); 786 if (res == NULL) { 787 free(file); 788 return 1; 789 } 790 *res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage)); 791 page = *res + (*ressz - 1); 792 page->file = file; 793 page->names = NULL; 794 page->output = NULL; 795 page->ipath = ipath; 796 page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10; 797 page->form = form; 798 return 1; 799 } 800 801 static int 802 fs_search(const struct mansearch *cfg, const struct manpaths *paths, 803 int argc, char **argv, struct manpage **res, size_t *ressz) 804 { 805 const char *const sections[] = 806 {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"}; 807 const size_t nsec = sizeof(sections)/sizeof(sections[0]); 808 809 size_t ipath, isec, lastsz; 810 811 assert(cfg->argmode == ARG_NAME); 812 813 if (res != NULL) 814 *res = NULL; 815 *ressz = lastsz = 0; 816 while (argc) { 817 for (ipath = 0; ipath < paths->sz; ipath++) { 818 if (cfg->sec != NULL) { 819 if (fs_lookup(paths, ipath, cfg->sec, 820 cfg->arch, *argv, res, ressz) && 821 cfg->firstmatch) 822 return 1; 823 } else for (isec = 0; isec < nsec; isec++) 824 if (fs_lookup(paths, ipath, sections[isec], 825 cfg->arch, *argv, res, ressz) && 826 cfg->firstmatch) 827 return 1; 828 } 829 if (res != NULL && *ressz == lastsz && 830 strchr(*argv, '/') == NULL) { 831 if (cfg->arch != NULL && 832 arch_valid(cfg->arch, OSENUM) == 0) 833 warnx("Unknown architecture \"%s\".", 834 cfg->arch); 835 else if (cfg->sec == NULL) 836 warnx("No entry for %s in the manual.", 837 *argv); 838 else 839 warnx("No entry for %s in section %s " 840 "of the manual.", *argv, cfg->sec); 841 } 842 lastsz = *ressz; 843 argv++; 844 argc--; 845 } 846 return 0; 847 } 848 849 static void 850 parse(struct curparse *curp, int fd, const char *file) 851 { 852 struct roff_meta *meta; 853 854 /* Begin by parsing the file itself. */ 855 856 assert(file); 857 assert(fd >= 0); 858 859 mparse_readfd(curp->mp, fd, file); 860 if (fd != STDIN_FILENO) 861 close(fd); 862 863 /* 864 * With -Wstop and warnings or errors of at least the requested 865 * level, do not produce output. 866 */ 867 868 if (curp->wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK) 869 return; 870 871 if (curp->outdata == NULL) 872 outdata_alloc(curp); 873 else if (curp->outtype == OUTT_HTML) 874 html_reset(curp); 875 876 mandoc_xr_reset(); 877 meta = mparse_result(curp->mp); 878 879 /* Execute the out device, if it exists. */ 880 881 if (meta->macroset == MACROSET_MDOC) { 882 switch (curp->outtype) { 883 case OUTT_HTML: 884 html_mdoc(curp->outdata, meta); 885 break; 886 case OUTT_TREE: 887 tree_mdoc(curp->outdata, meta); 888 break; 889 case OUTT_MAN: 890 man_mdoc(curp->outdata, meta); 891 break; 892 case OUTT_PDF: 893 case OUTT_ASCII: 894 case OUTT_UTF8: 895 case OUTT_LOCALE: 896 case OUTT_PS: 897 terminal_mdoc(curp->outdata, meta); 898 break; 899 case OUTT_MARKDOWN: 900 markdown_mdoc(curp->outdata, meta); 901 break; 902 default: 903 break; 904 } 905 } 906 if (meta->macroset == MACROSET_MAN) { 907 switch (curp->outtype) { 908 case OUTT_HTML: 909 html_man(curp->outdata, meta); 910 break; 911 case OUTT_TREE: 912 tree_man(curp->outdata, meta); 913 break; 914 case OUTT_MAN: 915 mparse_copy(curp->mp); 916 break; 917 case OUTT_PDF: 918 case OUTT_ASCII: 919 case OUTT_UTF8: 920 case OUTT_LOCALE: 921 case OUTT_PS: 922 terminal_man(curp->outdata, meta); 923 break; 924 default: 925 break; 926 } 927 } 928 if (mandoc_msg_getmin() < MANDOCERR_STYLE) 929 check_xr(); 930 } 931 932 static void 933 check_xr(void) 934 { 935 static struct manpaths paths; 936 struct mansearch search; 937 struct mandoc_xr *xr; 938 size_t sz; 939 940 if (paths.sz == 0) 941 manpath_base(&paths); 942 943 for (xr = mandoc_xr_get(); xr != NULL; xr = xr->next) { 944 if (xr->line == -1) 945 continue; 946 search.arch = NULL; 947 search.sec = xr->sec; 948 search.outkey = NULL; 949 search.argmode = ARG_NAME; 950 search.firstmatch = 1; 951 if (mansearch(&search, &paths, 1, &xr->name, NULL, &sz)) 952 continue; 953 if (fs_search(&search, &paths, 1, &xr->name, NULL, &sz)) 954 continue; 955 if (xr->count == 1) 956 mandoc_msg(MANDOCERR_XR_BAD, xr->line, 957 xr->pos + 1, "Xr %s %s", xr->name, xr->sec); 958 else 959 mandoc_msg(MANDOCERR_XR_BAD, xr->line, 960 xr->pos + 1, "Xr %s %s (%d times)", 961 xr->name, xr->sec, xr->count); 962 } 963 } 964 965 static void 966 outdata_alloc(struct curparse *curp) 967 { 968 switch (curp->outtype) { 969 case OUTT_HTML: 970 curp->outdata = html_alloc(curp->outopts); 971 break; 972 case OUTT_UTF8: 973 curp->outdata = utf8_alloc(curp->outopts); 974 break; 975 case OUTT_LOCALE: 976 curp->outdata = locale_alloc(curp->outopts); 977 break; 978 case OUTT_ASCII: 979 curp->outdata = ascii_alloc(curp->outopts); 980 break; 981 case OUTT_PDF: 982 curp->outdata = pdf_alloc(curp->outopts); 983 break; 984 case OUTT_PS: 985 curp->outdata = ps_alloc(curp->outopts); 986 break; 987 default: 988 break; 989 } 990 } 991 992 static void 993 passthrough(const char *file, int fd, int synopsis_only) 994 { 995 const char synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS"; 996 const char synr[] = "SYNOPSIS"; 997 998 FILE *stream; 999 const char *syscall; 1000 char *line, *cp; 1001 size_t linesz; 1002 ssize_t len, written; 1003 int print; 1004 1005 line = NULL; 1006 linesz = 0; 1007 1008 if (fflush(stdout) == EOF) { 1009 syscall = "fflush"; 1010 goto fail; 1011 } 1012 1013 if ((stream = fdopen(fd, "r")) == NULL) { 1014 close(fd); 1015 syscall = "fdopen"; 1016 goto fail; 1017 } 1018 1019 print = 0; 1020 while ((len = getline(&line, &linesz, stream)) != -1) { 1021 cp = line; 1022 if (synopsis_only) { 1023 if (print) { 1024 if ( ! isspace((unsigned char)*cp)) 1025 goto done; 1026 while (isspace((unsigned char)*cp)) { 1027 cp++; 1028 len--; 1029 } 1030 } else { 1031 if (strcmp(cp, synb) == 0 || 1032 strcmp(cp, synr) == 0) 1033 print = 1; 1034 continue; 1035 } 1036 } 1037 for (; len > 0; len -= written) { 1038 if ((written = write(STDOUT_FILENO, cp, len)) != -1) 1039 continue; 1040 fclose(stream); 1041 syscall = "write"; 1042 goto fail; 1043 } 1044 } 1045 1046 if (ferror(stream)) { 1047 fclose(stream); 1048 syscall = "getline"; 1049 goto fail; 1050 } 1051 1052 done: 1053 free(line); 1054 fclose(stream); 1055 return; 1056 1057 fail: 1058 free(line); 1059 warn("%s: SYSERR: %s", file, syscall); 1060 mandoc_msg_setrc(MANDOCLEVEL_SYSERR); 1061 } 1062 1063 static int 1064 koptions(int *options, char *arg) 1065 { 1066 1067 if ( ! strcmp(arg, "utf-8")) { 1068 *options |= MPARSE_UTF8; 1069 *options &= ~MPARSE_LATIN1; 1070 } else if ( ! strcmp(arg, "iso-8859-1")) { 1071 *options |= MPARSE_LATIN1; 1072 *options &= ~MPARSE_UTF8; 1073 } else if ( ! strcmp(arg, "us-ascii")) { 1074 *options &= ~(MPARSE_UTF8 | MPARSE_LATIN1); 1075 } else { 1076 warnx("-K %s: Bad argument", arg); 1077 return 0; 1078 } 1079 return 1; 1080 } 1081 1082 static void 1083 moptions(int *options, char *arg) 1084 { 1085 1086 if (arg == NULL) 1087 return; 1088 if (strcmp(arg, "doc") == 0) 1089 *options |= MPARSE_MDOC; 1090 else if (strcmp(arg, "an") == 0) 1091 *options |= MPARSE_MAN; 1092 } 1093 1094 static int 1095 toptions(struct curparse *curp, char *arg) 1096 { 1097 1098 if (0 == strcmp(arg, "ascii")) 1099 curp->outtype = OUTT_ASCII; 1100 else if (0 == strcmp(arg, "lint")) { 1101 curp->outtype = OUTT_LINT; 1102 mandoc_msg_setoutfile(stdout); 1103 mandoc_msg_setmin(MANDOCERR_BASE); 1104 } else if (0 == strcmp(arg, "tree")) 1105 curp->outtype = OUTT_TREE; 1106 else if (0 == strcmp(arg, "man")) 1107 curp->outtype = OUTT_MAN; 1108 else if (0 == strcmp(arg, "html")) 1109 curp->outtype = OUTT_HTML; 1110 else if (0 == strcmp(arg, "markdown")) 1111 curp->outtype = OUTT_MARKDOWN; 1112 else if (0 == strcmp(arg, "utf8")) 1113 curp->outtype = OUTT_UTF8; 1114 else if (0 == strcmp(arg, "locale")) 1115 curp->outtype = OUTT_LOCALE; 1116 else if (0 == strcmp(arg, "ps")) 1117 curp->outtype = OUTT_PS; 1118 else if (0 == strcmp(arg, "pdf")) 1119 curp->outtype = OUTT_PDF; 1120 else { 1121 warnx("-T %s: Bad argument", arg); 1122 return 0; 1123 } 1124 1125 return 1; 1126 } 1127 1128 static int 1129 woptions(struct curparse *curp, char *arg) 1130 { 1131 char *v, *o; 1132 const char *toks[11]; 1133 1134 toks[0] = "stop"; 1135 toks[1] = "all"; 1136 toks[2] = "base"; 1137 toks[3] = "style"; 1138 toks[4] = "warning"; 1139 toks[5] = "error"; 1140 toks[6] = "unsupp"; 1141 toks[7] = "fatal"; 1142 toks[8] = "openbsd"; 1143 toks[9] = "netbsd"; 1144 toks[10] = NULL; 1145 1146 while (*arg) { 1147 o = arg; 1148 switch (getsubopt(&arg, (char * const *)toks, &v)) { 1149 case 0: 1150 curp->wstop = 1; 1151 break; 1152 case 1: 1153 case 2: 1154 mandoc_msg_setmin(MANDOCERR_BASE); 1155 break; 1156 case 3: 1157 mandoc_msg_setmin(MANDOCERR_STYLE); 1158 break; 1159 case 4: 1160 mandoc_msg_setmin(MANDOCERR_WARNING); 1161 break; 1162 case 5: 1163 mandoc_msg_setmin(MANDOCERR_ERROR); 1164 break; 1165 case 6: 1166 mandoc_msg_setmin(MANDOCERR_UNSUPP); 1167 break; 1168 case 7: 1169 mandoc_msg_setmin(MANDOCERR_MAX); 1170 break; 1171 case 8: 1172 mandoc_msg_setmin(MANDOCERR_BASE); 1173 curp->os_e = MANDOC_OS_OPENBSD; 1174 break; 1175 case 9: 1176 mandoc_msg_setmin(MANDOCERR_BASE); 1177 curp->os_e = MANDOC_OS_NETBSD; 1178 break; 1179 default: 1180 warnx("-W %s: Bad argument", o); 1181 return 0; 1182 } 1183 } 1184 return 1; 1185 } 1186 1187 static pid_t 1188 spawn_pager(struct tag_files *tag_files) 1189 { 1190 const struct timespec timeout = { 0, 100000000 }; /* 0.1s */ 1191 #define MAX_PAGER_ARGS 16 1192 char *argv[MAX_PAGER_ARGS]; 1193 const char *pager; 1194 char *cp; 1195 #if HAVE_LESS_T 1196 size_t cmdlen; 1197 #endif 1198 int argc, use_ofn; 1199 pid_t pager_pid; 1200 1201 pager = getenv("MANPAGER"); 1202 if (pager == NULL || *pager == '\0') 1203 pager = getenv("PAGER"); 1204 if (pager == NULL || *pager == '\0') 1205 pager = "less -s"; 1206 cp = mandoc_strdup(pager); 1207 1208 /* 1209 * Parse the pager command into words. 1210 * Intentionally do not do anything fancy here. 1211 */ 1212 1213 argc = 0; 1214 while (argc + 5 < MAX_PAGER_ARGS) { 1215 argv[argc++] = cp; 1216 cp = strchr(cp, ' '); 1217 if (cp == NULL) 1218 break; 1219 *cp++ = '\0'; 1220 while (*cp == ' ') 1221 cp++; 1222 if (*cp == '\0') 1223 break; 1224 } 1225 1226 /* For less(1), use the tag file. */ 1227 1228 use_ofn = 1; 1229 #if HAVE_LESS_T 1230 if ((cmdlen = strlen(argv[0])) >= 4) { 1231 cp = argv[0] + cmdlen - 4; 1232 if (strcmp(cp, "less") == 0) { 1233 argv[argc++] = mandoc_strdup("-T"); 1234 argv[argc++] = tag_files->tfn; 1235 if (tag_files->tagname != NULL) { 1236 argv[argc++] = mandoc_strdup("-t"); 1237 argv[argc++] = tag_files->tagname; 1238 use_ofn = 0; 1239 } 1240 } 1241 } 1242 #endif 1243 if (use_ofn) 1244 argv[argc++] = tag_files->ofn; 1245 argv[argc] = NULL; 1246 1247 switch (pager_pid = fork()) { 1248 case -1: 1249 err((int)MANDOCLEVEL_SYSERR, "fork"); 1250 case 0: 1251 break; 1252 default: 1253 (void)setpgid(pager_pid, 0); 1254 (void)tcsetpgrp(tag_files->ofd, pager_pid); 1255 #if HAVE_PLEDGE 1256 if (pledge("stdio rpath tmppath tty proc", NULL) == -1) 1257 err((int)MANDOCLEVEL_SYSERR, "pledge"); 1258 #endif 1259 tag_files->pager_pid = pager_pid; 1260 return pager_pid; 1261 } 1262 1263 /* The child process becomes the pager. */ 1264 1265 if (dup2(tag_files->ofd, STDOUT_FILENO) == -1) 1266 err((int)MANDOCLEVEL_SYSERR, "pager stdout"); 1267 close(tag_files->ofd); 1268 assert(tag_files->tfd == -1); 1269 1270 /* Do not start the pager before controlling the terminal. */ 1271 1272 while (tcgetpgrp(STDOUT_FILENO) != getpid()) 1273 nanosleep(&timeout, NULL); 1274 1275 execvp(argv[0], argv); 1276 err((int)MANDOCLEVEL_SYSERR, "exec %s", argv[0]); 1277 } 1278