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