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