1 /* $OpenBSD: main.c,v 1.176 2016/07/15 19:31:53 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2010-2012, 2014-2016 Ingo Schwarze <schwarze@openbsd.org> 5 * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org> 6 * 7 * Permission to use, copy, modify, and distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 */ 19 20 #include <sys/types.h> 21 #include <sys/param.h> /* MACHINE */ 22 #include <sys/wait.h> 23 24 #include <assert.h> 25 #include <ctype.h> 26 #include <err.h> 27 #include <errno.h> 28 #include <fcntl.h> 29 #include <glob.h> 30 #include <signal.h> 31 #include <stdio.h> 32 #include <stdint.h> 33 #include <stdlib.h> 34 #include <string.h> 35 #include <time.h> 36 #include <unistd.h> 37 38 #include "mandoc_aux.h" 39 #include "mandoc.h" 40 #include "roff.h" 41 #include "mdoc.h" 42 #include "man.h" 43 #include "tag.h" 44 #include "main.h" 45 #include "manconf.h" 46 #include "mansearch.h" 47 48 enum outmode { 49 OUTMODE_DEF = 0, 50 OUTMODE_FLN, 51 OUTMODE_LST, 52 OUTMODE_ALL, 53 OUTMODE_INT, 54 OUTMODE_ONE 55 }; 56 57 enum outt { 58 OUTT_ASCII = 0, /* -Tascii */ 59 OUTT_LOCALE, /* -Tlocale */ 60 OUTT_UTF8, /* -Tutf8 */ 61 OUTT_TREE, /* -Ttree */ 62 OUTT_MAN, /* -Tman */ 63 OUTT_HTML, /* -Thtml */ 64 OUTT_LINT, /* -Tlint */ 65 OUTT_PS, /* -Tps */ 66 OUTT_PDF /* -Tpdf */ 67 }; 68 69 struct curparse { 70 struct mparse *mp; 71 enum mandoclevel wlevel; /* ignore messages below this */ 72 int wstop; /* stop after a file with a warning */ 73 enum outt outtype; /* which output to use */ 74 void *outdata; /* data for output */ 75 struct manoutput *outopts; /* output options */ 76 }; 77 78 79 int mandocdb(int, char *[]); 80 81 static int fs_lookup(const struct manpaths *, 82 size_t ipath, const char *, 83 const char *, const char *, 84 struct manpage **, size_t *); 85 static void fs_search(const struct mansearch *, 86 const struct manpaths *, int, char**, 87 struct manpage **, size_t *); 88 static int koptions(int *, char *); 89 static int moptions(int *, char *); 90 static void mmsg(enum mandocerr, enum mandoclevel, 91 const char *, int, int, const char *); 92 static void parse(struct curparse *, int, const char *); 93 static void passthrough(const char *, int, int); 94 static pid_t spawn_pager(struct tag_files *); 95 static int toptions(struct curparse *, char *); 96 static void usage(enum argmode) __attribute__((noreturn)); 97 static int woptions(struct curparse *, char *); 98 99 static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9}; 100 static char help_arg[] = "help"; 101 static char *help_argv[] = {help_arg, NULL}; 102 static enum mandoclevel rc; 103 104 105 int 106 main(int argc, char *argv[]) 107 { 108 struct manconf conf; 109 struct curparse curp; 110 struct mansearch search; 111 struct tag_files *tag_files; 112 const char *progname; 113 char *auxpaths; 114 char *defos; 115 unsigned char *uc; 116 struct manpage *res, *resp; 117 char *conf_file, *defpaths; 118 const char *sec; 119 size_t i, sz; 120 int prio, best_prio; 121 enum outmode outmode; 122 int fd; 123 int show_usage; 124 int options; 125 int use_pager; 126 int status, signum; 127 int c; 128 pid_t pager_pid, tc_pgid, man_pgid, pid; 129 130 progname = getprogname(); 131 if (strncmp(progname, "mandocdb", 8) == 0 || 132 strncmp(progname, "makewhatis", 10) == 0) 133 return mandocdb(argc, argv); 134 135 if (pledge("stdio rpath tmppath tty proc exec flock", NULL) == -1) 136 err((int)MANDOCLEVEL_SYSERR, "pledge"); 137 138 /* Search options. */ 139 140 memset(&conf, 0, sizeof(conf)); 141 conf_file = defpaths = NULL; 142 auxpaths = NULL; 143 144 memset(&search, 0, sizeof(struct mansearch)); 145 search.outkey = "Nd"; 146 147 if (strcmp(progname, "man") == 0) 148 search.argmode = ARG_NAME; 149 else if (strncmp(progname, "apropos", 7) == 0) 150 search.argmode = ARG_EXPR; 151 else if (strncmp(progname, "whatis", 6) == 0) 152 search.argmode = ARG_WORD; 153 else if (strncmp(progname, "help", 4) == 0) 154 search.argmode = ARG_NAME; 155 else 156 search.argmode = ARG_FILE; 157 158 /* Parser and formatter options. */ 159 160 memset(&curp, 0, sizeof(struct curparse)); 161 curp.outtype = OUTT_LOCALE; 162 curp.wlevel = MANDOCLEVEL_BADARG; 163 curp.outopts = &conf.output; 164 options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1; 165 defos = NULL; 166 167 use_pager = 1; 168 tag_files = NULL; 169 show_usage = 0; 170 outmode = OUTMODE_DEF; 171 172 while (-1 != (c = getopt(argc, argv, 173 "aC:cfhI:iK:klM:m:O:S:s:T:VW:w"))) { 174 switch (c) { 175 case 'a': 176 outmode = OUTMODE_ALL; 177 break; 178 case 'C': 179 conf_file = optarg; 180 break; 181 case 'c': 182 use_pager = 0; 183 break; 184 case 'f': 185 search.argmode = ARG_WORD; 186 break; 187 case 'h': 188 conf.output.synopsisonly = 1; 189 use_pager = 0; 190 outmode = OUTMODE_ALL; 191 break; 192 case 'I': 193 if (strncmp(optarg, "os=", 3)) { 194 warnx("-I %s: Bad argument", optarg); 195 return (int)MANDOCLEVEL_BADARG; 196 } 197 if (defos) { 198 warnx("-I %s: Duplicate argument", optarg); 199 return (int)MANDOCLEVEL_BADARG; 200 } 201 defos = mandoc_strdup(optarg + 3); 202 break; 203 case 'i': 204 outmode = OUTMODE_INT; 205 break; 206 case 'K': 207 if ( ! koptions(&options, optarg)) 208 return (int)MANDOCLEVEL_BADARG; 209 break; 210 case 'k': 211 search.argmode = ARG_EXPR; 212 break; 213 case 'l': 214 search.argmode = ARG_FILE; 215 outmode = OUTMODE_ALL; 216 break; 217 case 'M': 218 defpaths = optarg; 219 break; 220 case 'm': 221 auxpaths = optarg; 222 break; 223 case 'O': 224 search.outkey = optarg; 225 while (optarg != NULL) 226 manconf_output(&conf.output, 227 strsep(&optarg, ",")); 228 break; 229 case 'S': 230 search.arch = optarg; 231 break; 232 case 's': 233 search.sec = optarg; 234 break; 235 case 'T': 236 if ( ! toptions(&curp, optarg)) 237 return (int)MANDOCLEVEL_BADARG; 238 break; 239 case 'W': 240 if ( ! woptions(&curp, optarg)) 241 return (int)MANDOCLEVEL_BADARG; 242 break; 243 case 'w': 244 outmode = OUTMODE_FLN; 245 break; 246 default: 247 show_usage = 1; 248 break; 249 } 250 } 251 252 if (show_usage) 253 usage(search.argmode); 254 255 /* Postprocess options. */ 256 257 if (outmode == OUTMODE_DEF) { 258 switch (search.argmode) { 259 case ARG_FILE: 260 outmode = OUTMODE_ALL; 261 use_pager = 0; 262 break; 263 case ARG_NAME: 264 outmode = OUTMODE_ONE; 265 break; 266 default: 267 outmode = OUTMODE_LST; 268 break; 269 } 270 } 271 272 if (outmode == OUTMODE_FLN || 273 outmode == OUTMODE_LST || 274 !isatty(STDOUT_FILENO)) 275 use_pager = 0; 276 277 if (!use_pager) 278 if (pledge("stdio rpath flock", NULL) == -1) 279 err((int)MANDOCLEVEL_SYSERR, "pledge"); 280 281 /* Parse arguments. */ 282 283 if (argc > 0) { 284 argc -= optind; 285 argv += optind; 286 } 287 resp = NULL; 288 289 /* 290 * Quirks for help(1) 291 * and for a man(1) section argument without -s. 292 */ 293 294 if (search.argmode == ARG_NAME) { 295 if (*progname == 'h') { 296 if (argc == 0) { 297 argv = help_argv; 298 argc = 1; 299 } 300 } else if (argc > 1 && 301 ((uc = (unsigned char *)argv[0]) != NULL) && 302 ((isdigit(uc[0]) && (uc[1] == '\0' || 303 (isalpha(uc[1]) && uc[2] == '\0'))) || 304 (uc[0] == 'n' && uc[1] == '\0'))) { 305 search.sec = (char *)uc; 306 argv++; 307 argc--; 308 } 309 if (search.arch == NULL) 310 search.arch = getenv("MACHINE"); 311 if (search.arch == NULL) 312 search.arch = MACHINE; 313 } 314 315 rc = MANDOCLEVEL_OK; 316 317 /* man(1), whatis(1), apropos(1) */ 318 319 if (search.argmode != ARG_FILE) { 320 if (argc == 0) 321 usage(search.argmode); 322 323 if (search.argmode == ARG_NAME && 324 outmode == OUTMODE_ONE) 325 search.firstmatch = 1; 326 327 /* Access the mandoc database. */ 328 329 manconf_parse(&conf, conf_file, defpaths, auxpaths); 330 mansearch_setup(1); 331 if ( ! mansearch(&search, &conf.manpath, 332 argc, argv, &res, &sz)) 333 usage(search.argmode); 334 335 if (sz == 0) { 336 if (search.argmode == ARG_NAME) 337 fs_search(&search, &conf.manpath, 338 argc, argv, &res, &sz); 339 else 340 warnx("nothing appropriate"); 341 } 342 343 if (sz == 0) { 344 rc = MANDOCLEVEL_BADARG; 345 goto out; 346 } 347 348 /* 349 * For standard man(1) and -a output mode, 350 * prepare for copying filename pointers 351 * into the program parameter array. 352 */ 353 354 if (outmode == OUTMODE_ONE) { 355 argc = 1; 356 best_prio = 20; 357 } else if (outmode == OUTMODE_ALL) 358 argc = (int)sz; 359 360 /* Iterate all matching manuals. */ 361 362 resp = res; 363 for (i = 0; i < sz; i++) { 364 if (outmode == OUTMODE_FLN) 365 puts(res[i].file); 366 else if (outmode == OUTMODE_LST) 367 printf("%s - %s\n", res[i].names, 368 res[i].output == NULL ? "" : 369 res[i].output); 370 else if (outmode == OUTMODE_ONE) { 371 /* Search for the best section. */ 372 sec = res[i].file; 373 sec += strcspn(sec, "123456789"); 374 if (sec[0] == '\0') 375 continue; 376 prio = sec_prios[sec[0] - '1']; 377 if (sec[1] != '/') 378 prio += 10; 379 if (prio >= best_prio) 380 continue; 381 best_prio = prio; 382 resp = res + i; 383 } 384 } 385 386 /* 387 * For man(1), -a and -i output mode, fall through 388 * to the main mandoc(1) code iterating files 389 * and running the parsers on each of them. 390 */ 391 392 if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST) 393 goto out; 394 } 395 396 /* mandoc(1) */ 397 398 if (use_pager) { 399 if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1) 400 err((int)MANDOCLEVEL_SYSERR, "pledge"); 401 } else { 402 if (pledge("stdio rpath", NULL) == -1) 403 err((int)MANDOCLEVEL_SYSERR, "pledge"); 404 } 405 406 if (search.argmode == ARG_FILE && ! moptions(&options, auxpaths)) 407 return (int)MANDOCLEVEL_BADARG; 408 409 mchars_alloc(); 410 curp.mp = mparse_alloc(options, curp.wlevel, mmsg, defos); 411 412 /* 413 * Conditionally start up the lookaside buffer before parsing. 414 */ 415 if (OUTT_MAN == curp.outtype) 416 mparse_keep(curp.mp); 417 418 if (argc < 1) { 419 if (use_pager) 420 tag_files = tag_init(); 421 parse(&curp, STDIN_FILENO, "<stdin>"); 422 } 423 424 while (argc > 0) { 425 fd = mparse_open(curp.mp, resp != NULL ? resp->file : *argv); 426 if (fd != -1) { 427 if (use_pager) { 428 tag_files = tag_init(); 429 use_pager = 0; 430 } 431 432 if (resp == NULL) 433 parse(&curp, fd, *argv); 434 else if (resp->form & FORM_SRC) { 435 /* For .so only; ignore failure. */ 436 chdir(conf.manpath.paths[resp->ipath]); 437 parse(&curp, fd, resp->file); 438 } else 439 passthrough(resp->file, fd, 440 conf.output.synopsisonly); 441 442 if (argc > 1 && curp.outtype <= OUTT_UTF8) 443 terminal_sepline(curp.outdata); 444 } else if (rc < MANDOCLEVEL_ERROR) 445 rc = MANDOCLEVEL_ERROR; 446 447 if (MANDOCLEVEL_OK != rc && curp.wstop) 448 break; 449 450 if (resp != NULL) 451 resp++; 452 else 453 argv++; 454 if (--argc) 455 mparse_reset(curp.mp); 456 } 457 458 if (curp.outdata != NULL) { 459 switch (curp.outtype) { 460 case OUTT_HTML: 461 html_free(curp.outdata); 462 break; 463 case OUTT_UTF8: 464 case OUTT_LOCALE: 465 case OUTT_ASCII: 466 ascii_free(curp.outdata); 467 break; 468 case OUTT_PDF: 469 case OUTT_PS: 470 pspdf_free(curp.outdata); 471 break; 472 default: 473 break; 474 } 475 } 476 mparse_free(curp.mp); 477 mchars_free(); 478 479 out: 480 if (search.argmode != ARG_FILE) { 481 manconf_free(&conf); 482 mansearch_free(res, sz); 483 mansearch_setup(0); 484 } 485 486 free(defos); 487 488 /* 489 * When using a pager, finish writing both temporary files, 490 * fork it, wait for the user to close it, and clean up. 491 */ 492 493 if (tag_files != NULL) { 494 fclose(stdout); 495 tag_write(); 496 man_pgid = getpgid(0); 497 tag_files->tcpgid = man_pgid == getpid() ? 498 getpgid(getppid()) : man_pgid; 499 pager_pid = 0; 500 signum = SIGSTOP; 501 for (;;) { 502 503 /* Stop here until moved to the foreground. */ 504 505 tc_pgid = tcgetpgrp(STDIN_FILENO); 506 if (tc_pgid != man_pgid) { 507 if (tc_pgid == pager_pid) { 508 (void)tcsetpgrp(STDIN_FILENO, 509 man_pgid); 510 if (signum == SIGTTIN) 511 continue; 512 } else 513 tag_files->tcpgid = tc_pgid; 514 kill(0, signum); 515 continue; 516 } 517 518 /* Once in the foreground, activate the pager. */ 519 520 if (pager_pid) { 521 (void)tcsetpgrp(STDIN_FILENO, pager_pid); 522 kill(pager_pid, SIGCONT); 523 } else 524 pager_pid = spawn_pager(tag_files); 525 526 /* Wait for the pager to stop or exit. */ 527 528 while ((pid = waitpid(pager_pid, &status, 529 WUNTRACED)) == -1 && errno == EINTR) 530 continue; 531 532 if (pid == -1) { 533 warn("wait"); 534 rc = MANDOCLEVEL_SYSERR; 535 break; 536 } 537 if (!WIFSTOPPED(status)) 538 break; 539 540 signum = WSTOPSIG(status); 541 } 542 tag_unlink(); 543 } 544 545 return (int)rc; 546 } 547 548 static void 549 usage(enum argmode argmode) 550 { 551 552 switch (argmode) { 553 case ARG_FILE: 554 fputs("usage: mandoc [-acfhkl] [-I os=name] " 555 "[-K encoding] [-mformat] [-O option]\n" 556 "\t [-T output] [-W level] [file ...]\n", stderr); 557 break; 558 case ARG_NAME: 559 fputs("usage: man [-acfhklw] [-C file] [-I os=name] " 560 "[-K encoding] [-M path] [-m path]\n" 561 "\t [-O option=value] [-S subsection] [-s section] " 562 "[-T output] [-W level]\n" 563 "\t [section] name ...\n", stderr); 564 break; 565 case ARG_WORD: 566 fputs("usage: whatis [-acfhklw] [-C file] " 567 "[-M path] [-m path] [-O outkey] [-S arch]\n" 568 "\t [-s section] name ...\n", stderr); 569 break; 570 case ARG_EXPR: 571 fputs("usage: apropos [-acfhklw] [-C file] " 572 "[-M path] [-m path] [-O outkey] [-S arch]\n" 573 "\t [-s section] expression ...\n", stderr); 574 break; 575 } 576 exit((int)MANDOCLEVEL_BADARG); 577 } 578 579 static int 580 fs_lookup(const struct manpaths *paths, size_t ipath, 581 const char *sec, const char *arch, const char *name, 582 struct manpage **res, size_t *ressz) 583 { 584 glob_t globinfo; 585 struct manpage *page; 586 char *file; 587 int form, globres; 588 589 form = FORM_SRC; 590 mandoc_asprintf(&file, "%s/man%s/%s.%s", 591 paths->paths[ipath], sec, name, sec); 592 if (access(file, R_OK) != -1) 593 goto found; 594 free(file); 595 596 mandoc_asprintf(&file, "%s/cat%s/%s.0", 597 paths->paths[ipath], sec, name); 598 if (access(file, R_OK) != -1) { 599 form = FORM_CAT; 600 goto found; 601 } 602 free(file); 603 604 if (arch != NULL) { 605 mandoc_asprintf(&file, "%s/man%s/%s/%s.%s", 606 paths->paths[ipath], sec, arch, name, sec); 607 if (access(file, R_OK) != -1) 608 goto found; 609 free(file); 610 } 611 612 mandoc_asprintf(&file, "%s/man%s/%s.[01-9]*", 613 paths->paths[ipath], sec, name); 614 globres = glob(file, 0, NULL, &globinfo); 615 if (globres != 0 && globres != GLOB_NOMATCH) 616 warn("%s: glob", file); 617 free(file); 618 if (globres == 0) 619 file = mandoc_strdup(*globinfo.gl_pathv); 620 globfree(&globinfo); 621 if (globres != 0) 622 return 0; 623 624 found: 625 warnx("outdated mandoc.db lacks %s(%s) entry, run makewhatis %s", 626 name, sec, paths->paths[ipath]); 627 *res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage)); 628 page = *res + (*ressz - 1); 629 page->file = file; 630 page->names = NULL; 631 page->output = NULL; 632 page->ipath = ipath; 633 page->bits = NAME_FILE & NAME_MASK; 634 page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10; 635 page->form = form; 636 return 1; 637 } 638 639 static void 640 fs_search(const struct mansearch *cfg, const struct manpaths *paths, 641 int argc, char **argv, struct manpage **res, size_t *ressz) 642 { 643 const char *const sections[] = 644 {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"}; 645 const size_t nsec = sizeof(sections)/sizeof(sections[0]); 646 647 size_t ipath, isec, lastsz; 648 649 assert(cfg->argmode == ARG_NAME); 650 651 *res = NULL; 652 *ressz = lastsz = 0; 653 while (argc) { 654 for (ipath = 0; ipath < paths->sz; ipath++) { 655 if (cfg->sec != NULL) { 656 if (fs_lookup(paths, ipath, cfg->sec, 657 cfg->arch, *argv, res, ressz) && 658 cfg->firstmatch) 659 return; 660 } else for (isec = 0; isec < nsec; isec++) 661 if (fs_lookup(paths, ipath, sections[isec], 662 cfg->arch, *argv, res, ressz) && 663 cfg->firstmatch) 664 return; 665 } 666 if (*ressz == lastsz) 667 warnx("No entry for %s in the manual.", *argv); 668 lastsz = *ressz; 669 argv++; 670 argc--; 671 } 672 } 673 674 static void 675 parse(struct curparse *curp, int fd, const char *file) 676 { 677 enum mandoclevel rctmp; 678 struct roff_man *man; 679 680 /* Begin by parsing the file itself. */ 681 682 assert(file); 683 assert(fd >= 0); 684 685 rctmp = mparse_readfd(curp->mp, fd, file); 686 if (fd != STDIN_FILENO) 687 close(fd); 688 if (rc < rctmp) 689 rc = rctmp; 690 691 /* 692 * With -Wstop and warnings or errors of at least the requested 693 * level, do not produce output. 694 */ 695 696 if (rctmp != MANDOCLEVEL_OK && curp->wstop) 697 return; 698 699 /* If unset, allocate output dev now (if applicable). */ 700 701 if (curp->outdata == NULL) { 702 switch (curp->outtype) { 703 case OUTT_HTML: 704 curp->outdata = html_alloc(curp->outopts); 705 break; 706 case OUTT_UTF8: 707 curp->outdata = utf8_alloc(curp->outopts); 708 break; 709 case OUTT_LOCALE: 710 curp->outdata = locale_alloc(curp->outopts); 711 break; 712 case OUTT_ASCII: 713 curp->outdata = ascii_alloc(curp->outopts); 714 break; 715 case OUTT_PDF: 716 curp->outdata = pdf_alloc(curp->outopts); 717 break; 718 case OUTT_PS: 719 curp->outdata = ps_alloc(curp->outopts); 720 break; 721 default: 722 break; 723 } 724 } 725 726 mparse_result(curp->mp, &man, NULL); 727 728 /* Execute the out device, if it exists. */ 729 730 if (man == NULL) 731 return; 732 if (man->macroset == MACROSET_MDOC) { 733 mdoc_validate(man); 734 switch (curp->outtype) { 735 case OUTT_HTML: 736 html_mdoc(curp->outdata, man); 737 break; 738 case OUTT_TREE: 739 tree_mdoc(curp->outdata, man); 740 break; 741 case OUTT_MAN: 742 man_mdoc(curp->outdata, man); 743 break; 744 case OUTT_PDF: 745 case OUTT_ASCII: 746 case OUTT_UTF8: 747 case OUTT_LOCALE: 748 case OUTT_PS: 749 terminal_mdoc(curp->outdata, man); 750 break; 751 default: 752 break; 753 } 754 } 755 if (man->macroset == MACROSET_MAN) { 756 man_validate(man); 757 switch (curp->outtype) { 758 case OUTT_HTML: 759 html_man(curp->outdata, man); 760 break; 761 case OUTT_TREE: 762 tree_man(curp->outdata, man); 763 break; 764 case OUTT_MAN: 765 man_man(curp->outdata, man); 766 break; 767 case OUTT_PDF: 768 case OUTT_ASCII: 769 case OUTT_UTF8: 770 case OUTT_LOCALE: 771 case OUTT_PS: 772 terminal_man(curp->outdata, man); 773 break; 774 default: 775 break; 776 } 777 } 778 } 779 780 static void 781 passthrough(const char *file, int fd, int synopsis_only) 782 { 783 const char synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS"; 784 const char synr[] = "SYNOPSIS"; 785 786 FILE *stream; 787 const char *syscall; 788 char *line, *cp; 789 size_t linesz; 790 int print; 791 792 line = NULL; 793 linesz = 0; 794 795 if ((stream = fdopen(fd, "r")) == NULL) { 796 close(fd); 797 syscall = "fdopen"; 798 goto fail; 799 } 800 801 print = 0; 802 while (getline(&line, &linesz, stream) != -1) { 803 cp = line; 804 if (synopsis_only) { 805 if (print) { 806 if ( ! isspace((unsigned char)*cp)) 807 goto done; 808 while (isspace((unsigned char)*cp)) 809 cp++; 810 } else { 811 if (strcmp(cp, synb) == 0 || 812 strcmp(cp, synr) == 0) 813 print = 1; 814 continue; 815 } 816 } 817 if (fputs(cp, stdout)) { 818 fclose(stream); 819 syscall = "fputs"; 820 goto fail; 821 } 822 } 823 824 if (ferror(stream)) { 825 fclose(stream); 826 syscall = "getline"; 827 goto fail; 828 } 829 830 done: 831 free(line); 832 fclose(stream); 833 return; 834 835 fail: 836 free(line); 837 warn("%s: SYSERR: %s", file, syscall); 838 if (rc < MANDOCLEVEL_SYSERR) 839 rc = MANDOCLEVEL_SYSERR; 840 } 841 842 static int 843 koptions(int *options, char *arg) 844 { 845 846 if ( ! strcmp(arg, "utf-8")) { 847 *options |= MPARSE_UTF8; 848 *options &= ~MPARSE_LATIN1; 849 } else if ( ! strcmp(arg, "iso-8859-1")) { 850 *options |= MPARSE_LATIN1; 851 *options &= ~MPARSE_UTF8; 852 } else if ( ! strcmp(arg, "us-ascii")) { 853 *options &= ~(MPARSE_UTF8 | MPARSE_LATIN1); 854 } else { 855 warnx("-K %s: Bad argument", arg); 856 return 0; 857 } 858 return 1; 859 } 860 861 static int 862 moptions(int *options, char *arg) 863 { 864 865 if (arg == NULL) 866 /* nothing to do */; 867 else if (0 == strcmp(arg, "doc")) 868 *options |= MPARSE_MDOC; 869 else if (0 == strcmp(arg, "andoc")) 870 /* nothing to do */; 871 else if (0 == strcmp(arg, "an")) 872 *options |= MPARSE_MAN; 873 else { 874 warnx("-m %s: Bad argument", arg); 875 return 0; 876 } 877 878 return 1; 879 } 880 881 static int 882 toptions(struct curparse *curp, char *arg) 883 { 884 885 if (0 == strcmp(arg, "ascii")) 886 curp->outtype = OUTT_ASCII; 887 else if (0 == strcmp(arg, "lint")) { 888 curp->outtype = OUTT_LINT; 889 curp->wlevel = MANDOCLEVEL_WARNING; 890 } else if (0 == strcmp(arg, "tree")) 891 curp->outtype = OUTT_TREE; 892 else if (0 == strcmp(arg, "man")) 893 curp->outtype = OUTT_MAN; 894 else if (0 == strcmp(arg, "html")) 895 curp->outtype = OUTT_HTML; 896 else if (0 == strcmp(arg, "utf8")) 897 curp->outtype = OUTT_UTF8; 898 else if (0 == strcmp(arg, "locale")) 899 curp->outtype = OUTT_LOCALE; 900 else if (0 == strcmp(arg, "xhtml")) 901 curp->outtype = OUTT_HTML; 902 else if (0 == strcmp(arg, "ps")) 903 curp->outtype = OUTT_PS; 904 else if (0 == strcmp(arg, "pdf")) 905 curp->outtype = OUTT_PDF; 906 else { 907 warnx("-T %s: Bad argument", arg); 908 return 0; 909 } 910 911 return 1; 912 } 913 914 static int 915 woptions(struct curparse *curp, char *arg) 916 { 917 char *v, *o; 918 const char *toks[7]; 919 920 toks[0] = "stop"; 921 toks[1] = "all"; 922 toks[2] = "warning"; 923 toks[3] = "error"; 924 toks[4] = "unsupp"; 925 toks[5] = "fatal"; 926 toks[6] = NULL; 927 928 while (*arg) { 929 o = arg; 930 switch (getsubopt(&arg, (char * const *)toks, &v)) { 931 case 0: 932 curp->wstop = 1; 933 break; 934 case 1: 935 case 2: 936 curp->wlevel = MANDOCLEVEL_WARNING; 937 break; 938 case 3: 939 curp->wlevel = MANDOCLEVEL_ERROR; 940 break; 941 case 4: 942 curp->wlevel = MANDOCLEVEL_UNSUPP; 943 break; 944 case 5: 945 curp->wlevel = MANDOCLEVEL_BADARG; 946 break; 947 default: 948 warnx("-W %s: Bad argument", o); 949 return 0; 950 } 951 } 952 953 return 1; 954 } 955 956 static void 957 mmsg(enum mandocerr t, enum mandoclevel lvl, 958 const char *file, int line, int col, const char *msg) 959 { 960 const char *mparse_msg; 961 962 fprintf(stderr, "%s: %s:", getprogname(), file); 963 964 if (line) 965 fprintf(stderr, "%d:%d:", line, col + 1); 966 967 fprintf(stderr, " %s", mparse_strlevel(lvl)); 968 969 if (NULL != (mparse_msg = mparse_strerror(t))) 970 fprintf(stderr, ": %s", mparse_msg); 971 972 if (msg) 973 fprintf(stderr, ": %s", msg); 974 975 fputc('\n', stderr); 976 } 977 978 static pid_t 979 spawn_pager(struct tag_files *tag_files) 980 { 981 const struct timespec timeout = { 0, 100000000 }; /* 0.1s */ 982 #define MAX_PAGER_ARGS 16 983 char *argv[MAX_PAGER_ARGS]; 984 const char *pager; 985 char *cp; 986 size_t cmdlen; 987 int argc; 988 pid_t pager_pid; 989 990 pager = getenv("MANPAGER"); 991 if (pager == NULL || *pager == '\0') 992 pager = getenv("PAGER"); 993 if (pager == NULL || *pager == '\0') 994 pager = "more -s"; 995 cp = mandoc_strdup(pager); 996 997 /* 998 * Parse the pager command into words. 999 * Intentionally do not do anything fancy here. 1000 */ 1001 1002 argc = 0; 1003 while (argc + 4 < MAX_PAGER_ARGS) { 1004 argv[argc++] = cp; 1005 cp = strchr(cp, ' '); 1006 if (cp == NULL) 1007 break; 1008 *cp++ = '\0'; 1009 while (*cp == ' ') 1010 cp++; 1011 if (*cp == '\0') 1012 break; 1013 } 1014 1015 /* For more(1) and less(1), use the tag file. */ 1016 1017 if ((cmdlen = strlen(argv[0])) >= 4) { 1018 cp = argv[0] + cmdlen - 4; 1019 if (strcmp(cp, "less") == 0 || strcmp(cp, "more") == 0) { 1020 argv[argc++] = mandoc_strdup("-T"); 1021 argv[argc++] = tag_files->tfn; 1022 } 1023 } 1024 argv[argc++] = tag_files->ofn; 1025 argv[argc] = NULL; 1026 1027 switch (pager_pid = fork()) { 1028 case -1: 1029 err((int)MANDOCLEVEL_SYSERR, "fork"); 1030 case 0: 1031 break; 1032 default: 1033 (void)setpgid(pager_pid, 0); 1034 (void)tcsetpgrp(STDIN_FILENO, pager_pid); 1035 if (pledge("stdio rpath tmppath tty proc", NULL) == -1) 1036 err((int)MANDOCLEVEL_SYSERR, "pledge"); 1037 tag_files->pager_pid = pager_pid; 1038 return pager_pid; 1039 } 1040 1041 /* The child process becomes the pager. */ 1042 1043 if (dup2(tag_files->ofd, STDOUT_FILENO) == -1) 1044 err((int)MANDOCLEVEL_SYSERR, "pager stdout"); 1045 close(tag_files->ofd); 1046 close(tag_files->tfd); 1047 1048 /* Do not start the pager before controlling the terminal. */ 1049 1050 while (tcgetpgrp(STDIN_FILENO) != getpid()) 1051 nanosleep(&timeout, NULL); 1052 1053 execvp(argv[0], argv); 1054 err((int)MANDOCLEVEL_SYSERR, "exec %s", argv[0]); 1055 } 1056