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