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