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