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