1 /* $OpenBSD: main.c,v 1.178 2016/08/09 15:08:15 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 (search.argmode == ARG_NAME && 321 outmode == OUTMODE_ONE) 322 search.firstmatch = 1; 323 324 /* Access the mandoc database. */ 325 326 manconf_parse(&conf, conf_file, defpaths, auxpaths); 327 if ( ! mansearch(&search, &conf.manpath, 328 argc, argv, &res, &sz)) 329 usage(search.argmode); 330 331 if (sz == 0) { 332 if (search.argmode == ARG_NAME) 333 fs_search(&search, &conf.manpath, 334 argc, argv, &res, &sz); 335 else 336 warnx("nothing appropriate"); 337 } 338 339 if (sz == 0) { 340 rc = MANDOCLEVEL_BADARG; 341 goto out; 342 } 343 344 /* 345 * For standard man(1) and -a output mode, 346 * prepare for copying filename pointers 347 * into the program parameter array. 348 */ 349 350 if (outmode == OUTMODE_ONE) { 351 argc = 1; 352 best_prio = 20; 353 } else if (outmode == OUTMODE_ALL) 354 argc = (int)sz; 355 356 /* Iterate all matching manuals. */ 357 358 resp = res; 359 for (i = 0; i < sz; i++) { 360 if (outmode == OUTMODE_FLN) 361 puts(res[i].file); 362 else if (outmode == OUTMODE_LST) 363 printf("%s - %s\n", res[i].names, 364 res[i].output == NULL ? "" : 365 res[i].output); 366 else if (outmode == OUTMODE_ONE) { 367 /* Search for the best section. */ 368 sec = res[i].file; 369 sec += strcspn(sec, "123456789"); 370 if (sec[0] == '\0') 371 continue; 372 prio = sec_prios[sec[0] - '1']; 373 if (sec[1] != '/') 374 prio += 10; 375 if (prio >= best_prio) 376 continue; 377 best_prio = prio; 378 resp = res + i; 379 } 380 } 381 382 /* 383 * For man(1), -a and -i output mode, fall through 384 * to the main mandoc(1) code iterating files 385 * and running the parsers on each of them. 386 */ 387 388 if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST) 389 goto out; 390 } 391 392 /* mandoc(1) */ 393 394 if (use_pager) { 395 if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1) 396 err((int)MANDOCLEVEL_SYSERR, "pledge"); 397 } else { 398 if (pledge("stdio rpath", NULL) == -1) 399 err((int)MANDOCLEVEL_SYSERR, "pledge"); 400 } 401 402 if (search.argmode == ARG_FILE && ! moptions(&options, auxpaths)) 403 return (int)MANDOCLEVEL_BADARG; 404 405 mchars_alloc(); 406 curp.mp = mparse_alloc(options, curp.wlevel, mmsg, defos); 407 408 /* 409 * Conditionally start up the lookaside buffer before parsing. 410 */ 411 if (OUTT_MAN == curp.outtype) 412 mparse_keep(curp.mp); 413 414 if (argc < 1) { 415 if (use_pager) 416 tag_files = tag_init(); 417 parse(&curp, STDIN_FILENO, "<stdin>"); 418 } 419 420 while (argc > 0) { 421 fd = mparse_open(curp.mp, resp != NULL ? resp->file : *argv); 422 if (fd != -1) { 423 if (use_pager) { 424 tag_files = tag_init(); 425 use_pager = 0; 426 } 427 428 if (resp == NULL) 429 parse(&curp, fd, *argv); 430 else if (resp->form == FORM_SRC) { 431 /* For .so only; ignore failure. */ 432 chdir(conf.manpath.paths[resp->ipath]); 433 parse(&curp, fd, resp->file); 434 } else 435 passthrough(resp->file, fd, 436 conf.output.synopsisonly); 437 438 if (argc > 1 && curp.outtype <= OUTT_UTF8) 439 terminal_sepline(curp.outdata); 440 } else if (rc < MANDOCLEVEL_ERROR) 441 rc = MANDOCLEVEL_ERROR; 442 443 if (MANDOCLEVEL_OK != rc && curp.wstop) 444 break; 445 446 if (resp != NULL) 447 resp++; 448 else 449 argv++; 450 if (--argc) 451 mparse_reset(curp.mp); 452 } 453 454 if (curp.outdata != NULL) { 455 switch (curp.outtype) { 456 case OUTT_HTML: 457 html_free(curp.outdata); 458 break; 459 case OUTT_UTF8: 460 case OUTT_LOCALE: 461 case OUTT_ASCII: 462 ascii_free(curp.outdata); 463 break; 464 case OUTT_PDF: 465 case OUTT_PS: 466 pspdf_free(curp.outdata); 467 break; 468 default: 469 break; 470 } 471 } 472 mparse_free(curp.mp); 473 mchars_free(); 474 475 out: 476 if (search.argmode != ARG_FILE) { 477 manconf_free(&conf); 478 mansearch_free(res, sz); 479 } 480 481 free(defos); 482 483 /* 484 * When using a pager, finish writing both temporary files, 485 * fork it, wait for the user to close it, and clean up. 486 */ 487 488 if (tag_files != NULL) { 489 fclose(stdout); 490 tag_write(); 491 man_pgid = getpgid(0); 492 tag_files->tcpgid = man_pgid == getpid() ? 493 getpgid(getppid()) : man_pgid; 494 pager_pid = 0; 495 signum = SIGSTOP; 496 for (;;) { 497 498 /* Stop here until moved to the foreground. */ 499 500 tc_pgid = tcgetpgrp(STDIN_FILENO); 501 if (tc_pgid != man_pgid) { 502 if (tc_pgid == pager_pid) { 503 (void)tcsetpgrp(STDIN_FILENO, 504 man_pgid); 505 if (signum == SIGTTIN) 506 continue; 507 } else 508 tag_files->tcpgid = tc_pgid; 509 kill(0, signum); 510 continue; 511 } 512 513 /* Once in the foreground, activate the pager. */ 514 515 if (pager_pid) { 516 (void)tcsetpgrp(STDIN_FILENO, pager_pid); 517 kill(pager_pid, SIGCONT); 518 } else 519 pager_pid = spawn_pager(tag_files); 520 521 /* Wait for the pager to stop or exit. */ 522 523 while ((pid = waitpid(pager_pid, &status, 524 WUNTRACED)) == -1 && errno == EINTR) 525 continue; 526 527 if (pid == -1) { 528 warn("wait"); 529 rc = MANDOCLEVEL_SYSERR; 530 break; 531 } 532 if (!WIFSTOPPED(status)) 533 break; 534 535 signum = WSTOPSIG(status); 536 } 537 tag_unlink(); 538 } 539 540 return (int)rc; 541 } 542 543 static void 544 usage(enum argmode argmode) 545 { 546 547 switch (argmode) { 548 case ARG_FILE: 549 fputs("usage: mandoc [-acfhkl] [-I os=name] " 550 "[-K encoding] [-mformat] [-O option]\n" 551 "\t [-T output] [-W level] [file ...]\n", stderr); 552 break; 553 case ARG_NAME: 554 fputs("usage: man [-acfhklw] [-C file] [-I os=name] " 555 "[-K encoding] [-M path] [-m path]\n" 556 "\t [-O option=value] [-S subsection] [-s section] " 557 "[-T output] [-W level]\n" 558 "\t [section] name ...\n", stderr); 559 break; 560 case ARG_WORD: 561 fputs("usage: whatis [-acfhklw] [-C file] " 562 "[-M path] [-m path] [-O outkey] [-S arch]\n" 563 "\t [-s section] name ...\n", stderr); 564 break; 565 case ARG_EXPR: 566 fputs("usage: apropos [-acfhklw] [-C file] " 567 "[-M path] [-m path] [-O outkey] [-S arch]\n" 568 "\t [-s section] expression ...\n", stderr); 569 break; 570 } 571 exit((int)MANDOCLEVEL_BADARG); 572 } 573 574 static int 575 fs_lookup(const struct manpaths *paths, size_t ipath, 576 const char *sec, const char *arch, const char *name, 577 struct manpage **res, size_t *ressz) 578 { 579 glob_t globinfo; 580 struct manpage *page; 581 char *file; 582 int globres; 583 enum form form; 584 585 form = FORM_SRC; 586 mandoc_asprintf(&file, "%s/man%s/%s.%s", 587 paths->paths[ipath], sec, name, sec); 588 if (access(file, R_OK) != -1) 589 goto found; 590 free(file); 591 592 mandoc_asprintf(&file, "%s/cat%s/%s.0", 593 paths->paths[ipath], sec, name); 594 if (access(file, R_OK) != -1) { 595 form = FORM_CAT; 596 goto found; 597 } 598 free(file); 599 600 if (arch != NULL) { 601 mandoc_asprintf(&file, "%s/man%s/%s/%s.%s", 602 paths->paths[ipath], sec, arch, name, sec); 603 if (access(file, R_OK) != -1) 604 goto found; 605 free(file); 606 } 607 608 mandoc_asprintf(&file, "%s/man%s/%s.[01-9]*", 609 paths->paths[ipath], sec, name); 610 globres = glob(file, 0, NULL, &globinfo); 611 if (globres != 0 && globres != GLOB_NOMATCH) 612 warn("%s: glob", file); 613 free(file); 614 if (globres == 0) 615 file = mandoc_strdup(*globinfo.gl_pathv); 616 globfree(&globinfo); 617 if (globres != 0) 618 return 0; 619 620 found: 621 warnx("outdated mandoc.db lacks %s(%s) entry, run makewhatis %s", 622 name, sec, paths->paths[ipath]); 623 *res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage)); 624 page = *res + (*ressz - 1); 625 page->file = file; 626 page->names = NULL; 627 page->output = NULL; 628 page->ipath = ipath; 629 page->bits = NAME_FILE & NAME_MASK; 630 page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10; 631 page->form = form; 632 return 1; 633 } 634 635 static void 636 fs_search(const struct mansearch *cfg, const struct manpaths *paths, 637 int argc, char **argv, struct manpage **res, size_t *ressz) 638 { 639 const char *const sections[] = 640 {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"}; 641 const size_t nsec = sizeof(sections)/sizeof(sections[0]); 642 643 size_t ipath, isec, lastsz; 644 645 assert(cfg->argmode == ARG_NAME); 646 647 *res = NULL; 648 *ressz = lastsz = 0; 649 while (argc) { 650 for (ipath = 0; ipath < paths->sz; ipath++) { 651 if (cfg->sec != NULL) { 652 if (fs_lookup(paths, ipath, cfg->sec, 653 cfg->arch, *argv, res, ressz) && 654 cfg->firstmatch) 655 return; 656 } else for (isec = 0; isec < nsec; isec++) 657 if (fs_lookup(paths, ipath, sections[isec], 658 cfg->arch, *argv, res, ressz) && 659 cfg->firstmatch) 660 return; 661 } 662 if (*ressz == lastsz) 663 warnx("No entry for %s in the manual.", *argv); 664 lastsz = *ressz; 665 argv++; 666 argc--; 667 } 668 } 669 670 static void 671 parse(struct curparse *curp, int fd, const char *file) 672 { 673 enum mandoclevel rctmp; 674 struct roff_man *man; 675 676 /* Begin by parsing the file itself. */ 677 678 assert(file); 679 assert(fd >= 0); 680 681 rctmp = mparse_readfd(curp->mp, fd, file); 682 if (fd != STDIN_FILENO) 683 close(fd); 684 if (rc < rctmp) 685 rc = rctmp; 686 687 /* 688 * With -Wstop and warnings or errors of at least the requested 689 * level, do not produce output. 690 */ 691 692 if (rctmp != MANDOCLEVEL_OK && curp->wstop) 693 return; 694 695 /* If unset, allocate output dev now (if applicable). */ 696 697 if (curp->outdata == NULL) { 698 switch (curp->outtype) { 699 case OUTT_HTML: 700 curp->outdata = html_alloc(curp->outopts); 701 break; 702 case OUTT_UTF8: 703 curp->outdata = utf8_alloc(curp->outopts); 704 break; 705 case OUTT_LOCALE: 706 curp->outdata = locale_alloc(curp->outopts); 707 break; 708 case OUTT_ASCII: 709 curp->outdata = ascii_alloc(curp->outopts); 710 break; 711 case OUTT_PDF: 712 curp->outdata = pdf_alloc(curp->outopts); 713 break; 714 case OUTT_PS: 715 curp->outdata = ps_alloc(curp->outopts); 716 break; 717 default: 718 break; 719 } 720 } 721 722 mparse_result(curp->mp, &man, NULL); 723 724 /* Execute the out device, if it exists. */ 725 726 if (man == NULL) 727 return; 728 if (man->macroset == MACROSET_MDOC) { 729 mdoc_validate(man); 730 switch (curp->outtype) { 731 case OUTT_HTML: 732 html_mdoc(curp->outdata, man); 733 break; 734 case OUTT_TREE: 735 tree_mdoc(curp->outdata, man); 736 break; 737 case OUTT_MAN: 738 man_mdoc(curp->outdata, man); 739 break; 740 case OUTT_PDF: 741 case OUTT_ASCII: 742 case OUTT_UTF8: 743 case OUTT_LOCALE: 744 case OUTT_PS: 745 terminal_mdoc(curp->outdata, man); 746 break; 747 default: 748 break; 749 } 750 } 751 if (man->macroset == MACROSET_MAN) { 752 man_validate(man); 753 switch (curp->outtype) { 754 case OUTT_HTML: 755 html_man(curp->outdata, man); 756 break; 757 case OUTT_TREE: 758 tree_man(curp->outdata, man); 759 break; 760 case OUTT_MAN: 761 man_man(curp->outdata, man); 762 break; 763 case OUTT_PDF: 764 case OUTT_ASCII: 765 case OUTT_UTF8: 766 case OUTT_LOCALE: 767 case OUTT_PS: 768 terminal_man(curp->outdata, man); 769 break; 770 default: 771 break; 772 } 773 } 774 } 775 776 static void 777 passthrough(const char *file, int fd, int synopsis_only) 778 { 779 const char synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS"; 780 const char synr[] = "SYNOPSIS"; 781 782 FILE *stream; 783 const char *syscall; 784 char *line, *cp; 785 size_t linesz; 786 int print; 787 788 line = NULL; 789 linesz = 0; 790 791 if ((stream = fdopen(fd, "r")) == NULL) { 792 close(fd); 793 syscall = "fdopen"; 794 goto fail; 795 } 796 797 print = 0; 798 while (getline(&line, &linesz, stream) != -1) { 799 cp = line; 800 if (synopsis_only) { 801 if (print) { 802 if ( ! isspace((unsigned char)*cp)) 803 goto done; 804 while (isspace((unsigned char)*cp)) 805 cp++; 806 } else { 807 if (strcmp(cp, synb) == 0 || 808 strcmp(cp, synr) == 0) 809 print = 1; 810 continue; 811 } 812 } 813 if (fputs(cp, stdout)) { 814 fclose(stream); 815 syscall = "fputs"; 816 goto fail; 817 } 818 } 819 820 if (ferror(stream)) { 821 fclose(stream); 822 syscall = "getline"; 823 goto fail; 824 } 825 826 done: 827 free(line); 828 fclose(stream); 829 return; 830 831 fail: 832 free(line); 833 warn("%s: SYSERR: %s", file, syscall); 834 if (rc < MANDOCLEVEL_SYSERR) 835 rc = MANDOCLEVEL_SYSERR; 836 } 837 838 static int 839 koptions(int *options, char *arg) 840 { 841 842 if ( ! strcmp(arg, "utf-8")) { 843 *options |= MPARSE_UTF8; 844 *options &= ~MPARSE_LATIN1; 845 } else if ( ! strcmp(arg, "iso-8859-1")) { 846 *options |= MPARSE_LATIN1; 847 *options &= ~MPARSE_UTF8; 848 } else if ( ! strcmp(arg, "us-ascii")) { 849 *options &= ~(MPARSE_UTF8 | MPARSE_LATIN1); 850 } else { 851 warnx("-K %s: Bad argument", arg); 852 return 0; 853 } 854 return 1; 855 } 856 857 static int 858 moptions(int *options, char *arg) 859 { 860 861 if (arg == NULL) 862 /* nothing to do */; 863 else if (0 == strcmp(arg, "doc")) 864 *options |= MPARSE_MDOC; 865 else if (0 == strcmp(arg, "andoc")) 866 /* nothing to do */; 867 else if (0 == strcmp(arg, "an")) 868 *options |= MPARSE_MAN; 869 else { 870 warnx("-m %s: Bad argument", arg); 871 return 0; 872 } 873 874 return 1; 875 } 876 877 static int 878 toptions(struct curparse *curp, char *arg) 879 { 880 881 if (0 == strcmp(arg, "ascii")) 882 curp->outtype = OUTT_ASCII; 883 else if (0 == strcmp(arg, "lint")) { 884 curp->outtype = OUTT_LINT; 885 curp->wlevel = MANDOCLEVEL_WARNING; 886 } else if (0 == strcmp(arg, "tree")) 887 curp->outtype = OUTT_TREE; 888 else if (0 == strcmp(arg, "man")) 889 curp->outtype = OUTT_MAN; 890 else if (0 == strcmp(arg, "html")) 891 curp->outtype = OUTT_HTML; 892 else if (0 == strcmp(arg, "utf8")) 893 curp->outtype = OUTT_UTF8; 894 else if (0 == strcmp(arg, "locale")) 895 curp->outtype = OUTT_LOCALE; 896 else if (0 == strcmp(arg, "xhtml")) 897 curp->outtype = OUTT_HTML; 898 else if (0 == strcmp(arg, "ps")) 899 curp->outtype = OUTT_PS; 900 else if (0 == strcmp(arg, "pdf")) 901 curp->outtype = OUTT_PDF; 902 else { 903 warnx("-T %s: Bad argument", arg); 904 return 0; 905 } 906 907 return 1; 908 } 909 910 static int 911 woptions(struct curparse *curp, char *arg) 912 { 913 char *v, *o; 914 const char *toks[7]; 915 916 toks[0] = "stop"; 917 toks[1] = "all"; 918 toks[2] = "warning"; 919 toks[3] = "error"; 920 toks[4] = "unsupp"; 921 toks[5] = "fatal"; 922 toks[6] = NULL; 923 924 while (*arg) { 925 o = arg; 926 switch (getsubopt(&arg, (char * const *)toks, &v)) { 927 case 0: 928 curp->wstop = 1; 929 break; 930 case 1: 931 case 2: 932 curp->wlevel = MANDOCLEVEL_WARNING; 933 break; 934 case 3: 935 curp->wlevel = MANDOCLEVEL_ERROR; 936 break; 937 case 4: 938 curp->wlevel = MANDOCLEVEL_UNSUPP; 939 break; 940 case 5: 941 curp->wlevel = MANDOCLEVEL_BADARG; 942 break; 943 default: 944 warnx("-W %s: Bad argument", o); 945 return 0; 946 } 947 } 948 949 return 1; 950 } 951 952 static void 953 mmsg(enum mandocerr t, enum mandoclevel lvl, 954 const char *file, int line, int col, const char *msg) 955 { 956 const char *mparse_msg; 957 958 fprintf(stderr, "%s: %s:", getprogname(), 959 file == NULL ? "<stdin>" : file); 960 961 if (line) 962 fprintf(stderr, "%d:%d:", line, col + 1); 963 964 fprintf(stderr, " %s", mparse_strlevel(lvl)); 965 966 if (NULL != (mparse_msg = mparse_strerror(t))) 967 fprintf(stderr, ": %s", mparse_msg); 968 969 if (msg) 970 fprintf(stderr, ": %s", msg); 971 972 fputc('\n', stderr); 973 } 974 975 static pid_t 976 spawn_pager(struct tag_files *tag_files) 977 { 978 const struct timespec timeout = { 0, 100000000 }; /* 0.1s */ 979 #define MAX_PAGER_ARGS 16 980 char *argv[MAX_PAGER_ARGS]; 981 const char *pager; 982 char *cp; 983 size_t cmdlen; 984 int argc; 985 pid_t pager_pid; 986 987 pager = getenv("MANPAGER"); 988 if (pager == NULL || *pager == '\0') 989 pager = getenv("PAGER"); 990 if (pager == NULL || *pager == '\0') 991 pager = "more -s"; 992 cp = mandoc_strdup(pager); 993 994 /* 995 * Parse the pager command into words. 996 * Intentionally do not do anything fancy here. 997 */ 998 999 argc = 0; 1000 while (argc + 4 < MAX_PAGER_ARGS) { 1001 argv[argc++] = cp; 1002 cp = strchr(cp, ' '); 1003 if (cp == NULL) 1004 break; 1005 *cp++ = '\0'; 1006 while (*cp == ' ') 1007 cp++; 1008 if (*cp == '\0') 1009 break; 1010 } 1011 1012 /* For more(1) and less(1), use the tag file. */ 1013 1014 if ((cmdlen = strlen(argv[0])) >= 4) { 1015 cp = argv[0] + cmdlen - 4; 1016 if (strcmp(cp, "less") == 0 || strcmp(cp, "more") == 0) { 1017 argv[argc++] = mandoc_strdup("-T"); 1018 argv[argc++] = tag_files->tfn; 1019 } 1020 } 1021 argv[argc++] = tag_files->ofn; 1022 argv[argc] = NULL; 1023 1024 switch (pager_pid = fork()) { 1025 case -1: 1026 err((int)MANDOCLEVEL_SYSERR, "fork"); 1027 case 0: 1028 break; 1029 default: 1030 (void)setpgid(pager_pid, 0); 1031 (void)tcsetpgrp(STDIN_FILENO, pager_pid); 1032 if (pledge("stdio rpath tmppath tty proc", NULL) == -1) 1033 err((int)MANDOCLEVEL_SYSERR, "pledge"); 1034 tag_files->pager_pid = pager_pid; 1035 return pager_pid; 1036 } 1037 1038 /* The child process becomes the pager. */ 1039 1040 if (dup2(tag_files->ofd, STDOUT_FILENO) == -1) 1041 err((int)MANDOCLEVEL_SYSERR, "pager stdout"); 1042 close(tag_files->ofd); 1043 close(tag_files->tfd); 1044 1045 /* Do not start the pager before controlling the terminal. */ 1046 1047 while (tcgetpgrp(STDIN_FILENO) != getpid()) 1048 nanosleep(&timeout, NULL); 1049 1050 execvp(argv[0], argv); 1051 err((int)MANDOCLEVEL_SYSERR, "exec %s", argv[0]); 1052 } 1053