1 /* $OpenBSD: main.c,v 1.195 2017/06/03 12:16:19 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_ONE 54 }; 55 56 enum outt { 57 OUTT_ASCII = 0, /* -Tascii */ 58 OUTT_LOCALE, /* -Tlocale */ 59 OUTT_UTF8, /* -Tutf8 */ 60 OUTT_TREE, /* -Ttree */ 61 OUTT_MAN, /* -Tman */ 62 OUTT_HTML, /* -Thtml */ 63 OUTT_MARKDOWN, /* -Tmarkdown */ 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 void 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 mansearch search; 111 struct curparse curp; 112 struct tag_files *tag_files; 113 struct manpage *res, *resp; 114 const char *progname, *sec, *thisarg; 115 char *conf_file, *defpaths, *auxpaths; 116 char *defos, *oarg; 117 unsigned char *uc; 118 size_t i, sz; 119 int prio, best_prio; 120 enum outmode outmode; 121 int fd; 122 int show_usage; 123 int options; 124 int use_pager; 125 int status, signum; 126 int c; 127 pid_t pager_pid, tc_pgid, man_pgid, pid; 128 129 progname = getprogname(); 130 if (strncmp(progname, "mandocdb", 8) == 0 || 131 strncmp(progname, "makewhatis", 10) == 0) 132 return mandocdb(argc, argv); 133 134 if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1) 135 err((int)MANDOCLEVEL_SYSERR, "pledge"); 136 137 /* Search options. */ 138 139 memset(&conf, 0, sizeof(conf)); 140 conf_file = defpaths = NULL; 141 auxpaths = NULL; 142 143 memset(&search, 0, sizeof(struct mansearch)); 144 search.outkey = "Nd"; 145 oarg = NULL; 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 ((c = getopt(argc, argv, 173 "aC:cfhI:iK:klM:m:O:S:s:T:VW:w")) != -1) { 174 if (c == 'i' && search.argmode == ARG_EXPR) { 175 optind--; 176 break; 177 } 178 switch (c) { 179 case 'a': 180 outmode = OUTMODE_ALL; 181 break; 182 case 'C': 183 conf_file = optarg; 184 break; 185 case 'c': 186 use_pager = 0; 187 break; 188 case 'f': 189 search.argmode = ARG_WORD; 190 break; 191 case 'h': 192 conf.output.synopsisonly = 1; 193 use_pager = 0; 194 outmode = OUTMODE_ALL; 195 break; 196 case 'I': 197 if (strncmp(optarg, "os=", 3)) { 198 warnx("-I %s: Bad argument", optarg); 199 return (int)MANDOCLEVEL_BADARG; 200 } 201 if (defos) { 202 warnx("-I %s: Duplicate argument", optarg); 203 return (int)MANDOCLEVEL_BADARG; 204 } 205 defos = mandoc_strdup(optarg + 3); 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) 416 moptions(&options, auxpaths); 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 [-ac] [-I os=name] " 566 "[-K encoding] [-mdoc | -man] [-O options]\n" 567 "\t [-T output] [-W level] [file ...]\n", stderr); 568 break; 569 case ARG_NAME: 570 fputs("usage: man [-acfhklw] [-C file] [-M path] " 571 "[-m path] [-S subsection]\n" 572 "\t [[-s] section] name ...\n", stderr); 573 break; 574 case ARG_WORD: 575 fputs("usage: whatis [-afk] [-C file] " 576 "[-M path] [-m path] [-O outkey] [-S arch]\n" 577 "\t [-s section] name ...\n", stderr); 578 break; 579 case ARG_EXPR: 580 fputs("usage: apropos [-afk] [-C file] " 581 "[-M path] [-m path] [-O outkey] [-S arch]\n" 582 "\t [-s section] expression ...\n", stderr); 583 break; 584 } 585 exit((int)MANDOCLEVEL_BADARG); 586 } 587 588 static int 589 fs_lookup(const struct manpaths *paths, size_t ipath, 590 const char *sec, const char *arch, const char *name, 591 struct manpage **res, size_t *ressz) 592 { 593 glob_t globinfo; 594 struct manpage *page; 595 char *file; 596 int globres; 597 enum form form; 598 599 form = FORM_SRC; 600 mandoc_asprintf(&file, "%s/man%s/%s.%s", 601 paths->paths[ipath], sec, name, sec); 602 if (access(file, R_OK) != -1) 603 goto found; 604 free(file); 605 606 mandoc_asprintf(&file, "%s/cat%s/%s.0", 607 paths->paths[ipath], sec, name); 608 if (access(file, R_OK) != -1) { 609 form = FORM_CAT; 610 goto found; 611 } 612 free(file); 613 614 if (arch != NULL) { 615 mandoc_asprintf(&file, "%s/man%s/%s/%s.%s", 616 paths->paths[ipath], sec, arch, name, sec); 617 if (access(file, R_OK) != -1) 618 goto found; 619 free(file); 620 } 621 622 mandoc_asprintf(&file, "%s/man%s/%s.[01-9]*", 623 paths->paths[ipath], sec, name); 624 globres = glob(file, 0, NULL, &globinfo); 625 if (globres != 0 && globres != GLOB_NOMATCH) 626 warn("%s: glob", file); 627 free(file); 628 if (globres == 0) 629 file = mandoc_strdup(*globinfo.gl_pathv); 630 globfree(&globinfo); 631 if (globres != 0) 632 return 0; 633 634 found: 635 warnx("outdated mandoc.db lacks %s(%s) entry, run makewhatis %s", 636 name, sec, paths->paths[ipath]); 637 *res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage)); 638 page = *res + (*ressz - 1); 639 page->file = file; 640 page->names = NULL; 641 page->output = NULL; 642 page->ipath = ipath; 643 page->bits = NAME_FILE & NAME_MASK; 644 page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10; 645 page->form = form; 646 return 1; 647 } 648 649 static void 650 fs_search(const struct mansearch *cfg, const struct manpaths *paths, 651 int argc, char **argv, struct manpage **res, size_t *ressz) 652 { 653 const char *const sections[] = 654 {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"}; 655 const size_t nsec = sizeof(sections)/sizeof(sections[0]); 656 657 size_t ipath, isec, lastsz; 658 659 assert(cfg->argmode == ARG_NAME); 660 661 *res = NULL; 662 *ressz = lastsz = 0; 663 while (argc) { 664 for (ipath = 0; ipath < paths->sz; ipath++) { 665 if (cfg->sec != NULL) { 666 if (fs_lookup(paths, ipath, cfg->sec, 667 cfg->arch, *argv, res, ressz) && 668 cfg->firstmatch) 669 return; 670 } else for (isec = 0; isec < nsec; isec++) 671 if (fs_lookup(paths, ipath, sections[isec], 672 cfg->arch, *argv, res, ressz) && 673 cfg->firstmatch) 674 return; 675 } 676 if (*ressz == lastsz) 677 warnx("No entry for %s in the manual.", *argv); 678 lastsz = *ressz; 679 argv++; 680 argc--; 681 } 682 } 683 684 static void 685 parse(struct curparse *curp, int fd, const char *file) 686 { 687 enum mandoclevel rctmp; 688 struct roff_man *man; 689 690 /* Begin by parsing the file itself. */ 691 692 assert(file); 693 assert(fd >= 0); 694 695 rctmp = mparse_readfd(curp->mp, fd, file); 696 if (fd != STDIN_FILENO) 697 close(fd); 698 if (rc < rctmp) 699 rc = rctmp; 700 701 /* 702 * With -Wstop and warnings or errors of at least the requested 703 * level, do not produce output. 704 */ 705 706 if (rctmp != MANDOCLEVEL_OK && curp->wstop) 707 return; 708 709 if (curp->outdata == NULL) 710 outdata_alloc(curp); 711 712 mparse_result(curp->mp, &man, NULL); 713 714 /* Execute the out device, if it exists. */ 715 716 if (man == NULL) 717 return; 718 if (man->macroset == MACROSET_MDOC) { 719 if (curp->outtype != OUTT_TREE || !curp->outopts->noval) 720 mdoc_validate(man); 721 switch (curp->outtype) { 722 case OUTT_HTML: 723 html_mdoc(curp->outdata, man); 724 break; 725 case OUTT_TREE: 726 tree_mdoc(curp->outdata, man); 727 break; 728 case OUTT_MAN: 729 man_mdoc(curp->outdata, man); 730 break; 731 case OUTT_PDF: 732 case OUTT_ASCII: 733 case OUTT_UTF8: 734 case OUTT_LOCALE: 735 case OUTT_PS: 736 terminal_mdoc(curp->outdata, man); 737 break; 738 case OUTT_MARKDOWN: 739 markdown_mdoc(curp->outdata, man); 740 break; 741 default: 742 break; 743 } 744 } 745 if (man->macroset == MACROSET_MAN) { 746 if (curp->outtype != OUTT_TREE || !curp->outopts->noval) 747 man_validate(man); 748 switch (curp->outtype) { 749 case OUTT_HTML: 750 html_man(curp->outdata, man); 751 break; 752 case OUTT_TREE: 753 tree_man(curp->outdata, man); 754 break; 755 case OUTT_MAN: 756 man_man(curp->outdata, man); 757 break; 758 case OUTT_PDF: 759 case OUTT_ASCII: 760 case OUTT_UTF8: 761 case OUTT_LOCALE: 762 case OUTT_PS: 763 terminal_man(curp->outdata, man); 764 break; 765 default: 766 break; 767 } 768 } 769 mparse_updaterc(curp->mp, &rc); 770 } 771 772 static void 773 outdata_alloc(struct curparse *curp) 774 { 775 switch (curp->outtype) { 776 case OUTT_HTML: 777 curp->outdata = html_alloc(curp->outopts); 778 break; 779 case OUTT_UTF8: 780 curp->outdata = utf8_alloc(curp->outopts); 781 break; 782 case OUTT_LOCALE: 783 curp->outdata = locale_alloc(curp->outopts); 784 break; 785 case OUTT_ASCII: 786 curp->outdata = ascii_alloc(curp->outopts); 787 break; 788 case OUTT_PDF: 789 curp->outdata = pdf_alloc(curp->outopts); 790 break; 791 case OUTT_PS: 792 curp->outdata = ps_alloc(curp->outopts); 793 break; 794 default: 795 break; 796 } 797 } 798 799 static void 800 passthrough(const char *file, int fd, int synopsis_only) 801 { 802 const char synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS"; 803 const char synr[] = "SYNOPSIS"; 804 805 FILE *stream; 806 const char *syscall; 807 char *line, *cp; 808 size_t linesz; 809 ssize_t len, written; 810 int print; 811 812 line = NULL; 813 linesz = 0; 814 815 if (fflush(stdout) == EOF) { 816 syscall = "fflush"; 817 goto fail; 818 } 819 820 if ((stream = fdopen(fd, "r")) == NULL) { 821 close(fd); 822 syscall = "fdopen"; 823 goto fail; 824 } 825 826 print = 0; 827 while ((len = getline(&line, &linesz, stream)) != -1) { 828 cp = line; 829 if (synopsis_only) { 830 if (print) { 831 if ( ! isspace((unsigned char)*cp)) 832 goto done; 833 while (isspace((unsigned char)*cp)) { 834 cp++; 835 len--; 836 } 837 } else { 838 if (strcmp(cp, synb) == 0 || 839 strcmp(cp, synr) == 0) 840 print = 1; 841 continue; 842 } 843 } 844 for (; len > 0; len -= written) { 845 if ((written = write(STDOUT_FILENO, cp, len)) != -1) 846 continue; 847 fclose(stream); 848 syscall = "write"; 849 goto fail; 850 } 851 } 852 853 if (ferror(stream)) { 854 fclose(stream); 855 syscall = "getline"; 856 goto fail; 857 } 858 859 done: 860 free(line); 861 fclose(stream); 862 return; 863 864 fail: 865 free(line); 866 warn("%s: SYSERR: %s", file, syscall); 867 if (rc < MANDOCLEVEL_SYSERR) 868 rc = MANDOCLEVEL_SYSERR; 869 } 870 871 static int 872 koptions(int *options, char *arg) 873 { 874 875 if ( ! strcmp(arg, "utf-8")) { 876 *options |= MPARSE_UTF8; 877 *options &= ~MPARSE_LATIN1; 878 } else if ( ! strcmp(arg, "iso-8859-1")) { 879 *options |= MPARSE_LATIN1; 880 *options &= ~MPARSE_UTF8; 881 } else if ( ! strcmp(arg, "us-ascii")) { 882 *options &= ~(MPARSE_UTF8 | MPARSE_LATIN1); 883 } else { 884 warnx("-K %s: Bad argument", arg); 885 return 0; 886 } 887 return 1; 888 } 889 890 static void 891 moptions(int *options, char *arg) 892 { 893 894 if (arg == NULL) 895 return; 896 if (strcmp(arg, "doc") == 0) 897 *options |= MPARSE_MDOC; 898 else if (strcmp(arg, "an") == 0) 899 *options |= MPARSE_MAN; 900 } 901 902 static int 903 toptions(struct curparse *curp, char *arg) 904 { 905 906 if (0 == strcmp(arg, "ascii")) 907 curp->outtype = OUTT_ASCII; 908 else if (0 == strcmp(arg, "lint")) { 909 curp->outtype = OUTT_LINT; 910 curp->wlevel = MANDOCLEVEL_STYLE; 911 } else if (0 == strcmp(arg, "tree")) 912 curp->outtype = OUTT_TREE; 913 else if (0 == strcmp(arg, "man")) 914 curp->outtype = OUTT_MAN; 915 else if (0 == strcmp(arg, "html")) 916 curp->outtype = OUTT_HTML; 917 else if (0 == strcmp(arg, "markdown")) 918 curp->outtype = OUTT_MARKDOWN; 919 else if (0 == strcmp(arg, "utf8")) 920 curp->outtype = OUTT_UTF8; 921 else if (0 == strcmp(arg, "locale")) 922 curp->outtype = OUTT_LOCALE; 923 else if (0 == strcmp(arg, "ps")) 924 curp->outtype = OUTT_PS; 925 else if (0 == strcmp(arg, "pdf")) 926 curp->outtype = OUTT_PDF; 927 else { 928 warnx("-T %s: Bad argument", arg); 929 return 0; 930 } 931 932 return 1; 933 } 934 935 static int 936 woptions(struct curparse *curp, char *arg) 937 { 938 char *v, *o; 939 const char *toks[8]; 940 941 toks[0] = "stop"; 942 toks[1] = "all"; 943 toks[2] = "style"; 944 toks[3] = "warning"; 945 toks[4] = "error"; 946 toks[5] = "unsupp"; 947 toks[6] = "fatal"; 948 toks[7] = NULL; 949 950 while (*arg) { 951 o = arg; 952 switch (getsubopt(&arg, (char * const *)toks, &v)) { 953 case 0: 954 curp->wstop = 1; 955 break; 956 case 1: 957 case 2: 958 curp->wlevel = MANDOCLEVEL_STYLE; 959 break; 960 case 3: 961 curp->wlevel = MANDOCLEVEL_WARNING; 962 break; 963 case 4: 964 curp->wlevel = MANDOCLEVEL_ERROR; 965 break; 966 case 5: 967 curp->wlevel = MANDOCLEVEL_UNSUPP; 968 break; 969 case 6: 970 curp->wlevel = MANDOCLEVEL_BADARG; 971 break; 972 default: 973 warnx("-W %s: Bad argument", o); 974 return 0; 975 } 976 } 977 return 1; 978 } 979 980 static void 981 mmsg(enum mandocerr t, enum mandoclevel lvl, 982 const char *file, int line, int col, const char *msg) 983 { 984 const char *mparse_msg; 985 986 fprintf(stderr, "%s: %s:", getprogname(), 987 file == NULL ? "<stdin>" : file); 988 989 if (line) 990 fprintf(stderr, "%d:%d:", line, col + 1); 991 992 fprintf(stderr, " %s", mparse_strlevel(lvl)); 993 994 if (NULL != (mparse_msg = mparse_strerror(t))) 995 fprintf(stderr, ": %s", mparse_msg); 996 997 if (msg) 998 fprintf(stderr, ": %s", msg); 999 1000 fputc('\n', stderr); 1001 } 1002 1003 static pid_t 1004 spawn_pager(struct tag_files *tag_files) 1005 { 1006 const struct timespec timeout = { 0, 100000000 }; /* 0.1s */ 1007 #define MAX_PAGER_ARGS 16 1008 char *argv[MAX_PAGER_ARGS]; 1009 const char *pager; 1010 char *cp; 1011 size_t cmdlen; 1012 int argc; 1013 pid_t pager_pid; 1014 1015 pager = getenv("MANPAGER"); 1016 if (pager == NULL || *pager == '\0') 1017 pager = getenv("PAGER"); 1018 if (pager == NULL || *pager == '\0') 1019 pager = "more -s"; 1020 cp = mandoc_strdup(pager); 1021 1022 /* 1023 * Parse the pager command into words. 1024 * Intentionally do not do anything fancy here. 1025 */ 1026 1027 argc = 0; 1028 while (argc + 4 < MAX_PAGER_ARGS) { 1029 argv[argc++] = cp; 1030 cp = strchr(cp, ' '); 1031 if (cp == NULL) 1032 break; 1033 *cp++ = '\0'; 1034 while (*cp == ' ') 1035 cp++; 1036 if (*cp == '\0') 1037 break; 1038 } 1039 1040 /* For more(1) and less(1), use the tag file. */ 1041 1042 if ((cmdlen = strlen(argv[0])) >= 4) { 1043 cp = argv[0] + cmdlen - 4; 1044 if (strcmp(cp, "less") == 0 || strcmp(cp, "more") == 0) { 1045 argv[argc++] = mandoc_strdup("-T"); 1046 argv[argc++] = tag_files->tfn; 1047 } 1048 } 1049 argv[argc++] = tag_files->ofn; 1050 argv[argc] = NULL; 1051 1052 switch (pager_pid = fork()) { 1053 case -1: 1054 err((int)MANDOCLEVEL_SYSERR, "fork"); 1055 case 0: 1056 break; 1057 default: 1058 (void)setpgid(pager_pid, 0); 1059 (void)tcsetpgrp(tag_files->ofd, pager_pid); 1060 if (pledge("stdio rpath tmppath tty proc", NULL) == -1) 1061 err((int)MANDOCLEVEL_SYSERR, "pledge"); 1062 tag_files->pager_pid = pager_pid; 1063 return pager_pid; 1064 } 1065 1066 /* The child process becomes the pager. */ 1067 1068 if (dup2(tag_files->ofd, STDOUT_FILENO) == -1) 1069 err((int)MANDOCLEVEL_SYSERR, "pager stdout"); 1070 close(tag_files->ofd); 1071 close(tag_files->tfd); 1072 1073 /* Do not start the pager before controlling the terminal. */ 1074 1075 while (tcgetpgrp(STDOUT_FILENO) != getpid()) 1076 nanosleep(&timeout, NULL); 1077 1078 execvp(argv[0], argv); 1079 err((int)MANDOCLEVEL_SYSERR, "exec %s", argv[0]); 1080 } 1081