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