1 /* $OpenBSD: main.c,v 1.182 2017/01/09 17:49:55 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_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 outdata_alloc(struct curparse *); 93 static void parse(struct curparse *, int, const char *); 94 static void passthrough(const char *, int, int); 95 static pid_t spawn_pager(struct tag_files *); 96 static int toptions(struct curparse *, char *); 97 static void usage(enum argmode) __attribute__((noreturn)); 98 static int woptions(struct curparse *, char *); 99 100 static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9}; 101 static char help_arg[] = "help"; 102 static char *help_argv[] = {help_arg, NULL}; 103 static enum mandoclevel rc; 104 105 106 int 107 main(int argc, char *argv[]) 108 { 109 struct manconf conf; 110 struct curparse curp; 111 struct mansearch search; 112 struct tag_files *tag_files; 113 const char *progname; 114 char *auxpaths; 115 char *defos; 116 unsigned char *uc; 117 struct manpage *res, *resp; 118 char *conf_file, *defpaths; 119 const char *sec; 120 size_t i, sz; 121 int prio, best_prio; 122 enum outmode outmode; 123 int fd; 124 int show_usage; 125 int options; 126 int use_pager; 127 int status, signum; 128 int c; 129 pid_t pager_pid, tc_pgid, man_pgid, pid; 130 131 progname = getprogname(); 132 if (strncmp(progname, "mandocdb", 8) == 0 || 133 strncmp(progname, "makewhatis", 10) == 0) 134 return mandocdb(argc, argv); 135 136 if (pledge("stdio rpath tmppath tty proc exec flock", NULL) == -1) 137 err((int)MANDOCLEVEL_SYSERR, "pledge"); 138 139 /* Search options. */ 140 141 memset(&conf, 0, sizeof(conf)); 142 conf_file = defpaths = NULL; 143 auxpaths = NULL; 144 145 memset(&search, 0, sizeof(struct mansearch)); 146 search.outkey = "Nd"; 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 search.outkey = optarg; 226 while (optarg != NULL) 227 manconf_output(&conf.output, 228 strsep(&optarg, ",")); 229 break; 230 case 'S': 231 search.arch = optarg; 232 break; 233 case 's': 234 search.sec = optarg; 235 break; 236 case 'T': 237 if ( ! toptions(&curp, optarg)) 238 return (int)MANDOCLEVEL_BADARG; 239 break; 240 case 'W': 241 if ( ! woptions(&curp, optarg)) 242 return (int)MANDOCLEVEL_BADARG; 243 break; 244 case 'w': 245 outmode = OUTMODE_FLN; 246 break; 247 default: 248 show_usage = 1; 249 break; 250 } 251 } 252 253 if (show_usage) 254 usage(search.argmode); 255 256 /* Postprocess options. */ 257 258 if (outmode == OUTMODE_DEF) { 259 switch (search.argmode) { 260 case ARG_FILE: 261 outmode = OUTMODE_ALL; 262 use_pager = 0; 263 break; 264 case ARG_NAME: 265 outmode = OUTMODE_ONE; 266 break; 267 default: 268 outmode = OUTMODE_LST; 269 break; 270 } 271 } 272 273 if (outmode == OUTMODE_FLN || 274 outmode == OUTMODE_LST || 275 !isatty(STDOUT_FILENO)) 276 use_pager = 0; 277 278 if (!use_pager) 279 if (pledge("stdio rpath flock", NULL) == -1) 280 err((int)MANDOCLEVEL_SYSERR, "pledge"); 281 282 /* Parse arguments. */ 283 284 if (argc > 0) { 285 argc -= optind; 286 argv += optind; 287 } 288 resp = NULL; 289 290 /* 291 * Quirks for help(1) 292 * and for a man(1) section argument without -s. 293 */ 294 295 if (search.argmode == ARG_NAME) { 296 if (*progname == 'h') { 297 if (argc == 0) { 298 argv = help_argv; 299 argc = 1; 300 } 301 } else if (argc > 1 && 302 ((uc = (unsigned char *)argv[0]) != NULL) && 303 ((isdigit(uc[0]) && (uc[1] == '\0' || 304 (isalpha(uc[1]) && uc[2] == '\0'))) || 305 (uc[0] == 'n' && uc[1] == '\0'))) { 306 search.sec = (char *)uc; 307 argv++; 308 argc--; 309 } 310 if (search.arch == NULL) 311 search.arch = getenv("MACHINE"); 312 if (search.arch == NULL) 313 search.arch = MACHINE; 314 } 315 316 rc = MANDOCLEVEL_OK; 317 318 /* man(1), whatis(1), apropos(1) */ 319 320 if (search.argmode != ARG_FILE) { 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 if ( ! mansearch(&search, &conf.manpath, 329 argc, argv, &res, &sz)) 330 usage(search.argmode); 331 332 if (sz == 0) { 333 if (search.argmode == ARG_NAME) 334 fs_search(&search, &conf.manpath, 335 argc, argv, &res, &sz); 336 else 337 warnx("nothing appropriate"); 338 } 339 340 if (sz == 0) { 341 rc = MANDOCLEVEL_BADARG; 342 goto out; 343 } 344 345 /* 346 * For standard man(1) and -a output mode, 347 * prepare for copying filename pointers 348 * into the program parameter array. 349 */ 350 351 if (outmode == OUTMODE_ONE) { 352 argc = 1; 353 best_prio = 20; 354 } else if (outmode == OUTMODE_ALL) 355 argc = (int)sz; 356 357 /* Iterate all matching manuals. */ 358 359 resp = res; 360 for (i = 0; i < sz; i++) { 361 if (outmode == OUTMODE_FLN) 362 puts(res[i].file); 363 else if (outmode == OUTMODE_LST) 364 printf("%s - %s\n", res[i].names, 365 res[i].output == NULL ? "" : 366 res[i].output); 367 else if (outmode == OUTMODE_ONE) { 368 /* Search for the best section. */ 369 sec = res[i].file; 370 sec += strcspn(sec, "123456789"); 371 if (sec[0] == '\0') 372 continue; 373 prio = sec_prios[sec[0] - '1']; 374 if (sec[1] != '/') 375 prio += 10; 376 if (prio >= best_prio) 377 continue; 378 best_prio = prio; 379 resp = res + i; 380 } 381 } 382 383 /* 384 * For man(1), -a and -i output mode, fall through 385 * to the main mandoc(1) code iterating files 386 * and running the parsers on each of them. 387 */ 388 389 if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST) 390 goto out; 391 } 392 393 /* mandoc(1) */ 394 395 if (use_pager) { 396 if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1) 397 err((int)MANDOCLEVEL_SYSERR, "pledge"); 398 } else { 399 if (pledge("stdio rpath", NULL) == -1) 400 err((int)MANDOCLEVEL_SYSERR, "pledge"); 401 } 402 403 if (search.argmode == ARG_FILE && ! moptions(&options, auxpaths)) 404 return (int)MANDOCLEVEL_BADARG; 405 406 mchars_alloc(); 407 curp.mp = mparse_alloc(options, curp.wlevel, mmsg, defos); 408 409 /* 410 * Conditionally start up the lookaside buffer before parsing. 411 */ 412 if (OUTT_MAN == curp.outtype) 413 mparse_keep(curp.mp); 414 415 if (argc < 1) { 416 if (use_pager) 417 tag_files = tag_init(); 418 parse(&curp, STDIN_FILENO, "<stdin>"); 419 } 420 421 while (argc > 0) { 422 fd = mparse_open(curp.mp, resp != NULL ? resp->file : *argv); 423 if (fd != -1) { 424 if (use_pager) { 425 tag_files = tag_init(); 426 use_pager = 0; 427 } 428 429 if (resp == NULL) 430 parse(&curp, fd, *argv); 431 else if (resp->form == FORM_SRC) { 432 /* For .so only; ignore failure. */ 433 chdir(conf.manpath.paths[resp->ipath]); 434 parse(&curp, fd, resp->file); 435 } else 436 passthrough(resp->file, fd, 437 conf.output.synopsisonly); 438 439 if (argc > 1 && curp.outtype <= OUTT_UTF8) { 440 if (curp.outdata == NULL) 441 outdata_alloc(&curp); 442 terminal_sepline(curp.outdata); 443 } 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 } 484 485 free(defos); 486 487 /* 488 * When using a pager, finish writing both temporary files, 489 * fork it, wait for the user to close it, and clean up. 490 */ 491 492 if (tag_files != NULL) { 493 fclose(stdout); 494 tag_write(); 495 man_pgid = getpgid(0); 496 tag_files->tcpgid = man_pgid == getpid() ? 497 getpgid(getppid()) : man_pgid; 498 pager_pid = 0; 499 signum = SIGSTOP; 500 for (;;) { 501 502 /* Stop here until moved to the foreground. */ 503 504 tc_pgid = tcgetpgrp(tag_files->ofd); 505 if (tc_pgid != man_pgid) { 506 if (tc_pgid == pager_pid) { 507 (void)tcsetpgrp(tag_files->ofd, 508 man_pgid); 509 if (signum == SIGTTIN) 510 continue; 511 } else 512 tag_files->tcpgid = tc_pgid; 513 kill(0, signum); 514 continue; 515 } 516 517 /* Once in the foreground, activate the pager. */ 518 519 if (pager_pid) { 520 (void)tcsetpgrp(tag_files->ofd, pager_pid); 521 kill(pager_pid, SIGCONT); 522 } else 523 pager_pid = spawn_pager(tag_files); 524 525 /* Wait for the pager to stop or exit. */ 526 527 while ((pid = waitpid(pager_pid, &status, 528 WUNTRACED)) == -1 && errno == EINTR) 529 continue; 530 531 if (pid == -1) { 532 warn("wait"); 533 rc = MANDOCLEVEL_SYSERR; 534 break; 535 } 536 if (!WIFSTOPPED(status)) 537 break; 538 539 signum = WSTOPSIG(status); 540 } 541 tag_unlink(); 542 } 543 544 return (int)rc; 545 } 546 547 static void 548 usage(enum argmode argmode) 549 { 550 551 switch (argmode) { 552 case ARG_FILE: 553 fputs("usage: mandoc [-acfhkl] [-I os=name] " 554 "[-K encoding] [-mformat] [-O option]\n" 555 "\t [-T output] [-W level] [file ...]\n", stderr); 556 break; 557 case ARG_NAME: 558 fputs("usage: man [-acfhklw] [-C file] [-I os=name] " 559 "[-K encoding] [-M path] [-m path]\n" 560 "\t [-O option=value] [-S subsection] [-s section] " 561 "[-T output] [-W level]\n" 562 "\t [section] name ...\n", stderr); 563 break; 564 case ARG_WORD: 565 fputs("usage: whatis [-acfhklw] [-C file] " 566 "[-M path] [-m path] [-O outkey] [-S arch]\n" 567 "\t [-s section] name ...\n", stderr); 568 break; 569 case ARG_EXPR: 570 fputs("usage: apropos [-acfhklw] [-C file] " 571 "[-M path] [-m path] [-O outkey] [-S arch]\n" 572 "\t [-s section] expression ...\n", stderr); 573 break; 574 } 575 exit((int)MANDOCLEVEL_BADARG); 576 } 577 578 static int 579 fs_lookup(const struct manpaths *paths, size_t ipath, 580 const char *sec, const char *arch, const char *name, 581 struct manpage **res, size_t *ressz) 582 { 583 glob_t globinfo; 584 struct manpage *page; 585 char *file; 586 int globres; 587 enum form form; 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 (curp->outdata == NULL) 700 outdata_alloc(curp); 701 702 mparse_result(curp->mp, &man, NULL); 703 704 /* Execute the out device, if it exists. */ 705 706 if (man == NULL) 707 return; 708 if (man->macroset == MACROSET_MDOC) { 709 mdoc_validate(man); 710 switch (curp->outtype) { 711 case OUTT_HTML: 712 html_mdoc(curp->outdata, man); 713 break; 714 case OUTT_TREE: 715 tree_mdoc(curp->outdata, man); 716 break; 717 case OUTT_MAN: 718 man_mdoc(curp->outdata, man); 719 break; 720 case OUTT_PDF: 721 case OUTT_ASCII: 722 case OUTT_UTF8: 723 case OUTT_LOCALE: 724 case OUTT_PS: 725 terminal_mdoc(curp->outdata, man); 726 break; 727 default: 728 break; 729 } 730 } 731 if (man->macroset == MACROSET_MAN) { 732 man_validate(man); 733 switch (curp->outtype) { 734 case OUTT_HTML: 735 html_man(curp->outdata, man); 736 break; 737 case OUTT_TREE: 738 tree_man(curp->outdata, man); 739 break; 740 case OUTT_MAN: 741 man_man(curp->outdata, man); 742 break; 743 case OUTT_PDF: 744 case OUTT_ASCII: 745 case OUTT_UTF8: 746 case OUTT_LOCALE: 747 case OUTT_PS: 748 terminal_man(curp->outdata, man); 749 break; 750 default: 751 break; 752 } 753 } 754 mparse_updaterc(curp->mp, &rc); 755 } 756 757 static void 758 outdata_alloc(struct curparse *curp) 759 { 760 switch (curp->outtype) { 761 case OUTT_HTML: 762 curp->outdata = html_alloc(curp->outopts); 763 break; 764 case OUTT_UTF8: 765 curp->outdata = utf8_alloc(curp->outopts); 766 break; 767 case OUTT_LOCALE: 768 curp->outdata = locale_alloc(curp->outopts); 769 break; 770 case OUTT_ASCII: 771 curp->outdata = ascii_alloc(curp->outopts); 772 break; 773 case OUTT_PDF: 774 curp->outdata = pdf_alloc(curp->outopts); 775 break; 776 case OUTT_PS: 777 curp->outdata = ps_alloc(curp->outopts); 778 break; 779 default: 780 break; 781 } 782 } 783 784 static void 785 passthrough(const char *file, int fd, int synopsis_only) 786 { 787 const char synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS"; 788 const char synr[] = "SYNOPSIS"; 789 790 FILE *stream; 791 const char *syscall; 792 char *line, *cp; 793 size_t linesz; 794 ssize_t len, written; 795 int print; 796 797 line = NULL; 798 linesz = 0; 799 800 if (fflush(stdout) == EOF) { 801 syscall = "fflush"; 802 goto fail; 803 } 804 805 if ((stream = fdopen(fd, "r")) == NULL) { 806 close(fd); 807 syscall = "fdopen"; 808 goto fail; 809 } 810 811 print = 0; 812 while ((len = getline(&line, &linesz, stream)) != -1) { 813 cp = line; 814 if (synopsis_only) { 815 if (print) { 816 if ( ! isspace((unsigned char)*cp)) 817 goto done; 818 while (isspace((unsigned char)*cp)) { 819 cp++; 820 len--; 821 } 822 } else { 823 if (strcmp(cp, synb) == 0 || 824 strcmp(cp, synr) == 0) 825 print = 1; 826 continue; 827 } 828 } 829 for (; len > 0; len -= written) { 830 if ((written = write(STDOUT_FILENO, cp, len)) != -1) 831 continue; 832 fclose(stream); 833 syscall = "write"; 834 goto fail; 835 } 836 } 837 838 if (ferror(stream)) { 839 fclose(stream); 840 syscall = "getline"; 841 goto fail; 842 } 843 844 done: 845 free(line); 846 fclose(stream); 847 return; 848 849 fail: 850 free(line); 851 warn("%s: SYSERR: %s", file, syscall); 852 if (rc < MANDOCLEVEL_SYSERR) 853 rc = MANDOCLEVEL_SYSERR; 854 } 855 856 static int 857 koptions(int *options, char *arg) 858 { 859 860 if ( ! strcmp(arg, "utf-8")) { 861 *options |= MPARSE_UTF8; 862 *options &= ~MPARSE_LATIN1; 863 } else if ( ! strcmp(arg, "iso-8859-1")) { 864 *options |= MPARSE_LATIN1; 865 *options &= ~MPARSE_UTF8; 866 } else if ( ! strcmp(arg, "us-ascii")) { 867 *options &= ~(MPARSE_UTF8 | MPARSE_LATIN1); 868 } else { 869 warnx("-K %s: Bad argument", arg); 870 return 0; 871 } 872 return 1; 873 } 874 875 static int 876 moptions(int *options, char *arg) 877 { 878 879 if (arg == NULL) 880 /* nothing to do */; 881 else if (0 == strcmp(arg, "doc")) 882 *options |= MPARSE_MDOC; 883 else if (0 == strcmp(arg, "andoc")) 884 /* nothing to do */; 885 else if (0 == strcmp(arg, "an")) 886 *options |= MPARSE_MAN; 887 else { 888 warnx("-m %s: Bad argument", arg); 889 return 0; 890 } 891 892 return 1; 893 } 894 895 static int 896 toptions(struct curparse *curp, char *arg) 897 { 898 899 if (0 == strcmp(arg, "ascii")) 900 curp->outtype = OUTT_ASCII; 901 else if (0 == strcmp(arg, "lint")) { 902 curp->outtype = OUTT_LINT; 903 curp->wlevel = MANDOCLEVEL_WARNING; 904 } else if (0 == strcmp(arg, "tree")) 905 curp->outtype = OUTT_TREE; 906 else if (0 == strcmp(arg, "man")) 907 curp->outtype = OUTT_MAN; 908 else if (0 == strcmp(arg, "html")) 909 curp->outtype = OUTT_HTML; 910 else if (0 == strcmp(arg, "utf8")) 911 curp->outtype = OUTT_UTF8; 912 else if (0 == strcmp(arg, "locale")) 913 curp->outtype = OUTT_LOCALE; 914 else if (0 == strcmp(arg, "xhtml")) 915 curp->outtype = OUTT_HTML; 916 else if (0 == strcmp(arg, "ps")) 917 curp->outtype = OUTT_PS; 918 else if (0 == strcmp(arg, "pdf")) 919 curp->outtype = OUTT_PDF; 920 else { 921 warnx("-T %s: Bad argument", arg); 922 return 0; 923 } 924 925 return 1; 926 } 927 928 static int 929 woptions(struct curparse *curp, char *arg) 930 { 931 char *v, *o; 932 const char *toks[7]; 933 934 toks[0] = "stop"; 935 toks[1] = "all"; 936 toks[2] = "warning"; 937 toks[3] = "error"; 938 toks[4] = "unsupp"; 939 toks[5] = "fatal"; 940 toks[6] = NULL; 941 942 while (*arg) { 943 o = arg; 944 switch (getsubopt(&arg, (char * const *)toks, &v)) { 945 case 0: 946 curp->wstop = 1; 947 break; 948 case 1: 949 case 2: 950 curp->wlevel = MANDOCLEVEL_WARNING; 951 break; 952 case 3: 953 curp->wlevel = MANDOCLEVEL_ERROR; 954 break; 955 case 4: 956 curp->wlevel = MANDOCLEVEL_UNSUPP; 957 break; 958 case 5: 959 curp->wlevel = MANDOCLEVEL_BADARG; 960 break; 961 default: 962 warnx("-W %s: Bad argument", o); 963 return 0; 964 } 965 } 966 967 return 1; 968 } 969 970 static void 971 mmsg(enum mandocerr t, enum mandoclevel lvl, 972 const char *file, int line, int col, const char *msg) 973 { 974 const char *mparse_msg; 975 976 fprintf(stderr, "%s: %s:", getprogname(), 977 file == NULL ? "<stdin>" : file); 978 979 if (line) 980 fprintf(stderr, "%d:%d:", line, col + 1); 981 982 fprintf(stderr, " %s", mparse_strlevel(lvl)); 983 984 if (NULL != (mparse_msg = mparse_strerror(t))) 985 fprintf(stderr, ": %s", mparse_msg); 986 987 if (msg) 988 fprintf(stderr, ": %s", msg); 989 990 fputc('\n', stderr); 991 } 992 993 static pid_t 994 spawn_pager(struct tag_files *tag_files) 995 { 996 const struct timespec timeout = { 0, 100000000 }; /* 0.1s */ 997 #define MAX_PAGER_ARGS 16 998 char *argv[MAX_PAGER_ARGS]; 999 const char *pager; 1000 char *cp; 1001 size_t cmdlen; 1002 int argc; 1003 pid_t pager_pid; 1004 1005 pager = getenv("MANPAGER"); 1006 if (pager == NULL || *pager == '\0') 1007 pager = getenv("PAGER"); 1008 if (pager == NULL || *pager == '\0') 1009 pager = "more -s"; 1010 cp = mandoc_strdup(pager); 1011 1012 /* 1013 * Parse the pager command into words. 1014 * Intentionally do not do anything fancy here. 1015 */ 1016 1017 argc = 0; 1018 while (argc + 4 < MAX_PAGER_ARGS) { 1019 argv[argc++] = cp; 1020 cp = strchr(cp, ' '); 1021 if (cp == NULL) 1022 break; 1023 *cp++ = '\0'; 1024 while (*cp == ' ') 1025 cp++; 1026 if (*cp == '\0') 1027 break; 1028 } 1029 1030 /* For more(1) and less(1), use the tag file. */ 1031 1032 if ((cmdlen = strlen(argv[0])) >= 4) { 1033 cp = argv[0] + cmdlen - 4; 1034 if (strcmp(cp, "less") == 0 || strcmp(cp, "more") == 0) { 1035 argv[argc++] = mandoc_strdup("-T"); 1036 argv[argc++] = tag_files->tfn; 1037 } 1038 } 1039 argv[argc++] = tag_files->ofn; 1040 argv[argc] = NULL; 1041 1042 switch (pager_pid = fork()) { 1043 case -1: 1044 err((int)MANDOCLEVEL_SYSERR, "fork"); 1045 case 0: 1046 break; 1047 default: 1048 (void)setpgid(pager_pid, 0); 1049 (void)tcsetpgrp(tag_files->ofd, pager_pid); 1050 if (pledge("stdio rpath tmppath tty proc", NULL) == -1) 1051 err((int)MANDOCLEVEL_SYSERR, "pledge"); 1052 tag_files->pager_pid = pager_pid; 1053 return pager_pid; 1054 } 1055 1056 /* The child process becomes the pager. */ 1057 1058 if (dup2(tag_files->ofd, STDOUT_FILENO) == -1) 1059 err((int)MANDOCLEVEL_SYSERR, "pager stdout"); 1060 close(tag_files->ofd); 1061 close(tag_files->tfd); 1062 1063 /* Do not start the pager before controlling the terminal. */ 1064 1065 while (tcgetpgrp(STDOUT_FILENO) != getpid()) 1066 nanosleep(&timeout, NULL); 1067 1068 execvp(argv[0], argv); 1069 err((int)MANDOCLEVEL_SYSERR, "exec %s", argv[0]); 1070 } 1071