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