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