1 /* $OpenBSD: main.c,v 1.108 2014/11/26 21:40:11 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2010, 2011, 2012, 2014 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 AUTHOR DISCLAIMS ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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 20 #include <sys/types.h> 21 22 #include <assert.h> 23 #include <ctype.h> 24 #include <errno.h> 25 #include <fcntl.h> 26 #include <stdio.h> 27 #include <stdint.h> 28 #include <stdlib.h> 29 #include <string.h> 30 #include <unistd.h> 31 32 #include "mandoc.h" 33 #include "mandoc_aux.h" 34 #include "main.h" 35 #include "mdoc.h" 36 #include "man.h" 37 #include "manpath.h" 38 #include "mansearch.h" 39 40 enum outmode { 41 OUTMODE_DEF = 0, 42 OUTMODE_FLN, 43 OUTMODE_LST, 44 OUTMODE_ALL, 45 OUTMODE_INT, 46 OUTMODE_ONE 47 }; 48 49 typedef void (*out_mdoc)(void *, const struct mdoc *); 50 typedef void (*out_man)(void *, const struct man *); 51 typedef void (*out_free)(void *); 52 53 enum outt { 54 OUTT_ASCII = 0, /* -Tascii */ 55 OUTT_LOCALE, /* -Tlocale */ 56 OUTT_UTF8, /* -Tutf8 */ 57 OUTT_TREE, /* -Ttree */ 58 OUTT_MAN, /* -Tman */ 59 OUTT_HTML, /* -Thtml */ 60 OUTT_LINT, /* -Tlint */ 61 OUTT_PS, /* -Tps */ 62 OUTT_PDF /* -Tpdf */ 63 }; 64 65 struct curparse { 66 struct mparse *mp; 67 struct mchars *mchars; /* character table */ 68 enum mandoclevel wlevel; /* ignore messages below this */ 69 int wstop; /* stop after a file with a warning */ 70 enum outt outtype; /* which output to use */ 71 out_mdoc outmdoc; /* mdoc output ptr */ 72 out_man outman; /* man output ptr */ 73 out_free outfree; /* free output ptr */ 74 void *outdata; /* data for output */ 75 char outopts[BUFSIZ]; /* buf of output opts */ 76 }; 77 78 static int koptions(int *, char *); 79 int mandocdb(int, char**); 80 static int moptions(int *, char *); 81 static void mmsg(enum mandocerr, enum mandoclevel, 82 const char *, int, int, const char *); 83 static void parse(struct curparse *, int, 84 const char *, enum mandoclevel *); 85 static enum mandoclevel passthrough(const char *, int, int); 86 static void spawn_pager(void); 87 static int toptions(struct curparse *, char *); 88 static void usage(enum argmode) __attribute__((noreturn)); 89 static void version(void) __attribute__((noreturn)); 90 static int woptions(struct curparse *, char *); 91 92 static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9}; 93 static const char *progname; 94 95 96 int 97 main(int argc, char *argv[]) 98 { 99 struct curparse curp; 100 struct mansearch search; 101 struct manpaths paths; 102 char *conf_file, *defpaths, *auxpaths; 103 char *defos; 104 struct manpage *res, *resp; 105 size_t isec, i, sz; 106 int prio, best_prio; 107 char sec; 108 enum mandoclevel rc; 109 enum outmode outmode; 110 int fd; 111 int show_usage; 112 int use_pager; 113 int synopsis_only; 114 int options; 115 int c; 116 117 progname = strrchr(argv[0], '/'); 118 if (progname == NULL) 119 progname = argv[0]; 120 else 121 ++progname; 122 123 if (0 == strncmp(progname, "mandocdb", 8) || 124 0 == strncmp(progname, "makewhatis", 10)) 125 return(mandocdb(argc, argv)); 126 127 /* Search options. */ 128 129 memset(&paths, 0, sizeof(struct manpaths)); 130 conf_file = defpaths = auxpaths = NULL; 131 132 memset(&search, 0, sizeof(struct mansearch)); 133 search.outkey = "Nd"; 134 135 if (strcmp(progname, "man") == 0) 136 search.argmode = ARG_NAME; 137 else if (strncmp(progname, "apropos", 7) == 0) 138 search.argmode = ARG_EXPR; 139 else if (strncmp(progname, "whatis", 6) == 0) 140 search.argmode = ARG_WORD; 141 else 142 search.argmode = ARG_FILE; 143 144 /* Parser and formatter options. */ 145 146 memset(&curp, 0, sizeof(struct curparse)); 147 curp.outtype = OUTT_ASCII; 148 curp.wlevel = MANDOCLEVEL_FATAL; 149 options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1; 150 defos = NULL; 151 152 use_pager = 1; 153 show_usage = 0; 154 synopsis_only = 0; 155 outmode = OUTMODE_DEF; 156 157 while (-1 != (c = getopt(argc, argv, 158 "aC:cfhI:iK:klM:m:O:S:s:T:VW:w"))) { 159 switch (c) { 160 case 'a': 161 outmode = OUTMODE_ALL; 162 break; 163 case 'C': 164 conf_file = optarg; 165 break; 166 case 'c': 167 use_pager = 0; 168 break; 169 case 'f': 170 search.argmode = ARG_WORD; 171 break; 172 case 'h': 173 (void)strlcat(curp.outopts, "synopsis,", BUFSIZ); 174 synopsis_only = 1; 175 use_pager = 0; 176 outmode = OUTMODE_ALL; 177 break; 178 case 'I': 179 if (strncmp(optarg, "os=", 3)) { 180 fprintf(stderr, 181 "%s: -I%s: Bad argument\n", 182 progname, optarg); 183 return((int)MANDOCLEVEL_BADARG); 184 } 185 if (defos) { 186 fprintf(stderr, 187 "%s: -I%s: Duplicate argument\n", 188 progname, optarg); 189 return((int)MANDOCLEVEL_BADARG); 190 } 191 defos = mandoc_strdup(optarg + 3); 192 break; 193 case 'i': 194 outmode = OUTMODE_INT; 195 break; 196 case 'K': 197 if ( ! koptions(&options, optarg)) 198 return((int)MANDOCLEVEL_BADARG); 199 break; 200 case 'k': 201 search.argmode = ARG_EXPR; 202 break; 203 case 'l': 204 search.argmode = ARG_FILE; 205 outmode = OUTMODE_ALL; 206 break; 207 case 'M': 208 defpaths = optarg; 209 break; 210 case 'm': 211 auxpaths = optarg; 212 break; 213 case 'O': 214 search.outkey = optarg; 215 (void)strlcat(curp.outopts, optarg, BUFSIZ); 216 (void)strlcat(curp.outopts, ",", BUFSIZ); 217 break; 218 case 'S': 219 search.arch = optarg; 220 break; 221 case 's': 222 search.sec = optarg; 223 break; 224 case 'T': 225 if ( ! toptions(&curp, optarg)) 226 return((int)MANDOCLEVEL_BADARG); 227 break; 228 case 'W': 229 if ( ! woptions(&curp, optarg)) 230 return((int)MANDOCLEVEL_BADARG); 231 break; 232 case 'w': 233 outmode = OUTMODE_FLN; 234 break; 235 case 'V': 236 version(); 237 /* NOTREACHED */ 238 default: 239 show_usage = 1; 240 break; 241 } 242 } 243 244 if (show_usage) 245 usage(search.argmode); 246 247 /* Postprocess options. */ 248 249 if (outmode == OUTMODE_DEF) { 250 switch (search.argmode) { 251 case ARG_FILE: 252 outmode = OUTMODE_ALL; 253 use_pager = 0; 254 break; 255 case ARG_NAME: 256 outmode = OUTMODE_ONE; 257 break; 258 default: 259 outmode = OUTMODE_LST; 260 break; 261 } 262 } 263 264 /* Parse arguments. */ 265 266 argc -= optind; 267 argv += optind; 268 resp = NULL; 269 270 /* Quirk for a man(1) section argument without -s. */ 271 272 if (search.argmode == ARG_NAME && 273 argv[0] != NULL && 274 isdigit((unsigned char)argv[0][0]) && 275 (argv[0][1] == '\0' || !strcmp(argv[0], "3p"))) { 276 search.sec = argv[0]; 277 argv++; 278 argc--; 279 } 280 281 rc = MANDOCLEVEL_OK; 282 283 /* man(1), whatis(1), apropos(1) */ 284 285 if (search.argmode != ARG_FILE) { 286 if (argc == 0) 287 usage(search.argmode); 288 289 if (search.argmode == ARG_NAME && 290 outmode == OUTMODE_ONE) 291 search.firstmatch = 1; 292 293 /* Access the mandoc database. */ 294 295 manpath_parse(&paths, conf_file, defpaths, auxpaths); 296 mansearch_setup(1); 297 if( ! mansearch(&search, &paths, argc, argv, &res, &sz)) 298 usage(search.argmode); 299 resp = res; 300 301 if (sz == 0) { 302 if (search.argmode == ARG_NAME) 303 fprintf(stderr, "%s: No entry for %s " 304 "in the manual.\n", progname, argv[0]); 305 rc = MANDOCLEVEL_BADARG; 306 goto out; 307 } 308 309 /* 310 * For standard man(1) and -a output mode, 311 * prepare for copying filename pointers 312 * into the program parameter array. 313 */ 314 315 if (outmode == OUTMODE_ONE) { 316 argc = 1; 317 best_prio = 10; 318 } else if (outmode == OUTMODE_ALL) 319 argc = (int)sz; 320 321 /* Iterate all matching manuals. */ 322 323 for (i = 0; i < sz; i++) { 324 if (outmode == OUTMODE_FLN) 325 puts(res[i].file); 326 else if (outmode == OUTMODE_LST) 327 printf("%s - %s\n", res[i].names, 328 res[i].output == NULL ? "" : 329 res[i].output); 330 else if (outmode == OUTMODE_ONE) { 331 /* Search for the best section. */ 332 isec = strcspn(res[i].file, "123456789"); 333 sec = res[i].file[isec]; 334 if ('\0' == sec) 335 continue; 336 prio = sec_prios[sec - '1']; 337 if (prio >= best_prio) 338 continue; 339 best_prio = prio; 340 resp = res + i; 341 } 342 } 343 344 /* 345 * For man(1), -a and -i output mode, fall through 346 * to the main mandoc(1) code iterating files 347 * and running the parsers on each of them. 348 */ 349 350 if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST) 351 goto out; 352 } 353 354 /* mandoc(1) */ 355 356 if ( ! moptions(&options, auxpaths)) 357 return((int)MANDOCLEVEL_BADARG); 358 359 if (use_pager && isatty(STDOUT_FILENO)) 360 spawn_pager(); 361 362 curp.mchars = mchars_alloc(); 363 curp.mp = mparse_alloc(options, curp.wlevel, mmsg, 364 curp.mchars, defos); 365 366 /* 367 * Conditionally start up the lookaside buffer before parsing. 368 */ 369 if (OUTT_MAN == curp.outtype) 370 mparse_keep(curp.mp); 371 372 if (argc == 0) 373 parse(&curp, STDIN_FILENO, "<stdin>", &rc); 374 375 while (argc) { 376 if (resp != NULL) { 377 rc = mparse_open(curp.mp, &fd, resp->file); 378 if (fd == -1) 379 /* nothing */; 380 else if (resp->form & FORM_SRC) { 381 /* For .so only; ignore failure. */ 382 chdir(paths.paths[resp->ipath]); 383 parse(&curp, fd, resp->file, &rc); 384 } else 385 rc = passthrough(resp->file, fd, 386 synopsis_only); 387 resp++; 388 } else { 389 rc = mparse_open(curp.mp, &fd, *argv++); 390 if (fd != -1) 391 parse(&curp, fd, argv[-1], &rc); 392 } 393 394 if (mparse_wait(curp.mp) != MANDOCLEVEL_OK) 395 rc = MANDOCLEVEL_SYSERR; 396 397 if (MANDOCLEVEL_OK != rc && curp.wstop) 398 break; 399 argc--; 400 } 401 402 if (curp.outfree) 403 (*curp.outfree)(curp.outdata); 404 mparse_free(curp.mp); 405 mchars_free(curp.mchars); 406 407 out: 408 if (search.argmode != ARG_FILE) { 409 manpath_free(&paths); 410 mansearch_free(res, sz); 411 mansearch_setup(0); 412 } 413 414 free(defos); 415 416 return((int)rc); 417 } 418 419 static void 420 version(void) 421 { 422 423 printf("mandoc %s\n", VERSION); 424 exit((int)MANDOCLEVEL_OK); 425 } 426 427 static void 428 usage(enum argmode argmode) 429 { 430 431 switch (argmode) { 432 case ARG_FILE: 433 fputs("usage: mandoc [-acfhklV] [-Ios=name] " 434 "[-Kencoding] [-mformat] [-Ooption]\n" 435 "\t [-Toutput] [-Wlevel] [file ...]\n", stderr); 436 break; 437 case ARG_NAME: 438 fputs("usage: man [-acfhklVw] [-C file] " 439 "[-M path] [-m path] [-S arch] [-s section]\n" 440 "\t [section] name ...\n", stderr); 441 break; 442 case ARG_WORD: 443 fputs("usage: whatis [-acfhklVw] [-C file] " 444 "[-M path] [-m path] [-O outkey] [-S arch]\n" 445 "\t [-s section] name ...\n", stderr); 446 break; 447 case ARG_EXPR: 448 fputs("usage: apropos [-acfhklVw] [-C file] " 449 "[-M path] [-m path] [-O outkey] [-S arch]\n" 450 "\t [-s section] expression ...\n", stderr); 451 break; 452 } 453 exit((int)MANDOCLEVEL_BADARG); 454 } 455 456 static void 457 parse(struct curparse *curp, int fd, const char *file, 458 enum mandoclevel *level) 459 { 460 enum mandoclevel rc; 461 struct mdoc *mdoc; 462 struct man *man; 463 464 /* Begin by parsing the file itself. */ 465 466 assert(file); 467 assert(fd >= -1); 468 469 rc = mparse_readfd(curp->mp, fd, file); 470 471 /* Stop immediately if the parse has failed. */ 472 473 if (MANDOCLEVEL_FATAL <= rc) 474 goto cleanup; 475 476 /* 477 * With -Wstop and warnings or errors of at least the requested 478 * level, do not produce output. 479 */ 480 481 if (MANDOCLEVEL_OK != rc && curp->wstop) 482 goto cleanup; 483 484 /* If unset, allocate output dev now (if applicable). */ 485 486 if ( ! (curp->outman && curp->outmdoc)) { 487 switch (curp->outtype) { 488 case OUTT_HTML: 489 curp->outdata = html_alloc(curp->mchars, 490 curp->outopts); 491 curp->outfree = html_free; 492 break; 493 case OUTT_UTF8: 494 curp->outdata = utf8_alloc(curp->mchars, 495 curp->outopts); 496 curp->outfree = ascii_free; 497 break; 498 case OUTT_LOCALE: 499 curp->outdata = locale_alloc(curp->mchars, 500 curp->outopts); 501 curp->outfree = ascii_free; 502 break; 503 case OUTT_ASCII: 504 curp->outdata = ascii_alloc(curp->mchars, 505 curp->outopts); 506 curp->outfree = ascii_free; 507 break; 508 case OUTT_PDF: 509 curp->outdata = pdf_alloc(curp->mchars, 510 curp->outopts); 511 curp->outfree = pspdf_free; 512 break; 513 case OUTT_PS: 514 curp->outdata = ps_alloc(curp->mchars, 515 curp->outopts); 516 curp->outfree = pspdf_free; 517 break; 518 default: 519 break; 520 } 521 522 switch (curp->outtype) { 523 case OUTT_HTML: 524 curp->outman = html_man; 525 curp->outmdoc = html_mdoc; 526 break; 527 case OUTT_TREE: 528 curp->outman = tree_man; 529 curp->outmdoc = tree_mdoc; 530 break; 531 case OUTT_MAN: 532 curp->outmdoc = man_mdoc; 533 curp->outman = man_man; 534 break; 535 case OUTT_PDF: 536 /* FALLTHROUGH */ 537 case OUTT_ASCII: 538 /* FALLTHROUGH */ 539 case OUTT_UTF8: 540 /* FALLTHROUGH */ 541 case OUTT_LOCALE: 542 /* FALLTHROUGH */ 543 case OUTT_PS: 544 curp->outman = terminal_man; 545 curp->outmdoc = terminal_mdoc; 546 break; 547 default: 548 break; 549 } 550 } 551 552 mparse_result(curp->mp, &mdoc, &man, NULL); 553 554 /* Execute the out device, if it exists. */ 555 556 if (man && curp->outman) 557 (*curp->outman)(curp->outdata, man); 558 if (mdoc && curp->outmdoc) 559 (*curp->outmdoc)(curp->outdata, mdoc); 560 561 cleanup: 562 563 mparse_reset(curp->mp); 564 565 if (*level < rc) 566 *level = rc; 567 } 568 569 static enum mandoclevel 570 passthrough(const char *file, int fd, int synopsis_only) 571 { 572 const char synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS"; 573 const char synr[] = "SYNOPSIS"; 574 575 FILE *stream; 576 const char *syscall; 577 char *line; 578 size_t len, off; 579 ssize_t nw; 580 int print; 581 582 if ((stream = fdopen(fd, "r")) == NULL) { 583 close(fd); 584 syscall = "fdopen"; 585 goto fail; 586 } 587 588 print = 0; 589 while ((line = fgetln(stream, &len)) != NULL) { 590 if (synopsis_only) { 591 if (print) { 592 if ( ! isspace((unsigned char)*line)) 593 goto done; 594 while (len && 595 isspace((unsigned char)*line)) { 596 line++; 597 len--; 598 } 599 } else { 600 if ((len == sizeof(synb) && 601 ! strncmp(line, synb, len - 1)) || 602 (len == sizeof(synr) && 603 ! strncmp(line, synr, len - 1))) 604 print = 1; 605 continue; 606 } 607 } 608 for (off = 0; off < len; off += nw) 609 if ((nw = write(STDOUT_FILENO, line + off, 610 len - off)) == -1 || nw == 0) { 611 fclose(stream); 612 syscall = "write"; 613 goto fail; 614 } 615 } 616 617 if (ferror(stream)) { 618 fclose(stream); 619 syscall = "fgetln"; 620 goto fail; 621 } 622 623 done: 624 fclose(stream); 625 return(MANDOCLEVEL_OK); 626 627 fail: 628 fprintf(stderr, "%s: %s: SYSERR: %s: %s", 629 progname, file, syscall, strerror(errno)); 630 return(MANDOCLEVEL_SYSERR); 631 } 632 633 static int 634 koptions(int *options, char *arg) 635 { 636 637 if ( ! strcmp(arg, "utf-8")) { 638 *options |= MPARSE_UTF8; 639 *options &= ~MPARSE_LATIN1; 640 } else if ( ! strcmp(arg, "iso-8859-1")) { 641 *options |= MPARSE_LATIN1; 642 *options &= ~MPARSE_UTF8; 643 } else if ( ! strcmp(arg, "us-ascii")) { 644 *options &= ~(MPARSE_UTF8 | MPARSE_LATIN1); 645 } else { 646 fprintf(stderr, "%s: -K%s: Bad argument\n", 647 progname, arg); 648 return(0); 649 } 650 return(1); 651 } 652 653 static int 654 moptions(int *options, char *arg) 655 { 656 657 if (arg == NULL) 658 /* nothing to do */; 659 else if (0 == strcmp(arg, "doc")) 660 *options |= MPARSE_MDOC; 661 else if (0 == strcmp(arg, "andoc")) 662 /* nothing to do */; 663 else if (0 == strcmp(arg, "an")) 664 *options |= MPARSE_MAN; 665 else { 666 fprintf(stderr, "%s: -m%s: Bad argument\n", 667 progname, arg); 668 return(0); 669 } 670 671 return(1); 672 } 673 674 static int 675 toptions(struct curparse *curp, char *arg) 676 { 677 678 if (0 == strcmp(arg, "ascii")) 679 curp->outtype = OUTT_ASCII; 680 else if (0 == strcmp(arg, "lint")) { 681 curp->outtype = OUTT_LINT; 682 curp->wlevel = MANDOCLEVEL_WARNING; 683 } else if (0 == strcmp(arg, "tree")) 684 curp->outtype = OUTT_TREE; 685 else if (0 == strcmp(arg, "man")) 686 curp->outtype = OUTT_MAN; 687 else if (0 == strcmp(arg, "html")) 688 curp->outtype = OUTT_HTML; 689 else if (0 == strcmp(arg, "utf8")) 690 curp->outtype = OUTT_UTF8; 691 else if (0 == strcmp(arg, "locale")) 692 curp->outtype = OUTT_LOCALE; 693 else if (0 == strcmp(arg, "xhtml")) 694 curp->outtype = OUTT_HTML; 695 else if (0 == strcmp(arg, "ps")) 696 curp->outtype = OUTT_PS; 697 else if (0 == strcmp(arg, "pdf")) 698 curp->outtype = OUTT_PDF; 699 else { 700 fprintf(stderr, "%s: -T%s: Bad argument\n", 701 progname, arg); 702 return(0); 703 } 704 705 return(1); 706 } 707 708 static int 709 woptions(struct curparse *curp, char *arg) 710 { 711 char *v, *o; 712 const char *toks[6]; 713 714 toks[0] = "stop"; 715 toks[1] = "all"; 716 toks[2] = "warning"; 717 toks[3] = "error"; 718 toks[4] = "fatal"; 719 toks[5] = NULL; 720 721 while (*arg) { 722 o = arg; 723 switch (getsubopt(&arg, UNCONST(toks), &v)) { 724 case 0: 725 curp->wstop = 1; 726 break; 727 case 1: 728 /* FALLTHROUGH */ 729 case 2: 730 curp->wlevel = MANDOCLEVEL_WARNING; 731 break; 732 case 3: 733 curp->wlevel = MANDOCLEVEL_ERROR; 734 break; 735 case 4: 736 curp->wlevel = MANDOCLEVEL_FATAL; 737 break; 738 default: 739 fprintf(stderr, "%s: -W%s: Bad argument\n", 740 progname, o); 741 return(0); 742 } 743 } 744 745 return(1); 746 } 747 748 static void 749 mmsg(enum mandocerr t, enum mandoclevel lvl, 750 const char *file, int line, int col, const char *msg) 751 { 752 const char *mparse_msg; 753 754 fprintf(stderr, "%s: %s:", progname, file); 755 756 if (line) 757 fprintf(stderr, "%d:%d:", line, col + 1); 758 759 fprintf(stderr, " %s", mparse_strlevel(lvl)); 760 761 if (NULL != (mparse_msg = mparse_strerror(t))) 762 fprintf(stderr, ": %s", mparse_msg); 763 764 if (msg) 765 fprintf(stderr, ": %s", msg); 766 767 fputc('\n', stderr); 768 } 769 770 static void 771 spawn_pager(void) 772 { 773 #define MAX_PAGER_ARGS 16 774 char *argv[MAX_PAGER_ARGS]; 775 const char *pager; 776 char *cp; 777 int fildes[2]; 778 int argc; 779 780 if (pipe(fildes) == -1) { 781 fprintf(stderr, "%s: pipe: %s\n", 782 progname, strerror(errno)); 783 return; 784 } 785 786 switch (fork()) { 787 case -1: 788 fprintf(stderr, "%s: fork: %s\n", 789 progname, strerror(errno)); 790 exit((int)MANDOCLEVEL_SYSERR); 791 case 0: 792 close(fildes[0]); 793 if (dup2(fildes[1], STDOUT_FILENO) == -1) { 794 fprintf(stderr, "%s: dup output: %s\n", 795 progname, strerror(errno)); 796 exit((int)MANDOCLEVEL_SYSERR); 797 } 798 return; 799 default: 800 break; 801 } 802 803 /* The original process becomes the pager. */ 804 805 close(fildes[1]); 806 if (dup2(fildes[0], STDIN_FILENO) == -1) { 807 fprintf(stderr, "%s: dup input: %s\n", 808 progname, strerror(errno)); 809 exit((int)MANDOCLEVEL_SYSERR); 810 } 811 812 pager = getenv("MANPAGER"); 813 if (pager == NULL || *pager == '\0') 814 pager = getenv("PAGER"); 815 if (pager == NULL || *pager == '\0') 816 pager = "/usr/bin/more -s"; 817 cp = mandoc_strdup(pager); 818 819 /* 820 * Parse the pager command into words. 821 * Intentionally do not do anything fancy here. 822 */ 823 824 argc = 0; 825 while (argc + 1 < MAX_PAGER_ARGS) { 826 argv[argc++] = cp; 827 cp = strchr(cp, ' '); 828 if (cp == NULL) 829 break; 830 *cp++ = '\0'; 831 while (*cp == ' ') 832 cp++; 833 if (*cp == '\0') 834 break; 835 } 836 argv[argc] = NULL; 837 838 /* Hand over to the pager. */ 839 840 execvp(argv[0], argv); 841 fprintf(stderr, "%s: exec: %s\n", 842 progname, strerror(errno)); 843 exit((int)MANDOCLEVEL_SYSERR); 844 } 845