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