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