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