1 /* $OpenBSD: main.c,v 1.205 2017/08/21 15:41:26 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 (ferror(stdout)) { 458 if (tag_files != NULL) { 459 warn("%s", tag_files->ofn); 460 tag_unlink(); 461 tag_files = NULL; 462 } else 463 warn("stdout"); 464 rc = MANDOCLEVEL_SYSERR; 465 break; 466 } 467 468 if (argc > 1 && curp.outtype <= OUTT_UTF8) { 469 if (curp.outdata == NULL) 470 outdata_alloc(&curp); 471 terminal_sepline(curp.outdata); 472 } 473 } else if (rc < MANDOCLEVEL_ERROR) 474 rc = MANDOCLEVEL_ERROR; 475 476 if (MANDOCLEVEL_OK != rc && curp.wstop) 477 break; 478 479 if (resp != NULL) 480 resp++; 481 else 482 argv++; 483 if (--argc) 484 mparse_reset(curp.mp); 485 } 486 487 if (curp.outdata != NULL) { 488 switch (curp.outtype) { 489 case OUTT_HTML: 490 html_free(curp.outdata); 491 break; 492 case OUTT_UTF8: 493 case OUTT_LOCALE: 494 case OUTT_ASCII: 495 ascii_free(curp.outdata); 496 break; 497 case OUTT_PDF: 498 case OUTT_PS: 499 pspdf_free(curp.outdata); 500 break; 501 default: 502 break; 503 } 504 } 505 mandoc_xr_free(); 506 mparse_free(curp.mp); 507 mchars_free(); 508 509 out: 510 if (search.argmode != ARG_FILE) { 511 manconf_free(&conf); 512 mansearch_free(res, sz); 513 } 514 515 free(curp.os_s); 516 517 /* 518 * When using a pager, finish writing both temporary files, 519 * fork it, wait for the user to close it, and clean up. 520 */ 521 522 if (tag_files != NULL) { 523 fclose(stdout); 524 tag_write(); 525 man_pgid = getpgid(0); 526 tag_files->tcpgid = man_pgid == getpid() ? 527 getpgid(getppid()) : man_pgid; 528 pager_pid = 0; 529 signum = SIGSTOP; 530 for (;;) { 531 532 /* Stop here until moved to the foreground. */ 533 534 tc_pgid = tcgetpgrp(tag_files->ofd); 535 if (tc_pgid != man_pgid) { 536 if (tc_pgid == pager_pid) { 537 (void)tcsetpgrp(tag_files->ofd, 538 man_pgid); 539 if (signum == SIGTTIN) 540 continue; 541 } else 542 tag_files->tcpgid = tc_pgid; 543 kill(0, signum); 544 continue; 545 } 546 547 /* Once in the foreground, activate the pager. */ 548 549 if (pager_pid) { 550 (void)tcsetpgrp(tag_files->ofd, pager_pid); 551 kill(pager_pid, SIGCONT); 552 } else 553 pager_pid = spawn_pager(tag_files); 554 555 /* Wait for the pager to stop or exit. */ 556 557 while ((pid = waitpid(pager_pid, &status, 558 WUNTRACED)) == -1 && errno == EINTR) 559 continue; 560 561 if (pid == -1) { 562 warn("wait"); 563 rc = MANDOCLEVEL_SYSERR; 564 break; 565 } 566 if (!WIFSTOPPED(status)) 567 break; 568 569 signum = WSTOPSIG(status); 570 } 571 tag_unlink(); 572 } 573 574 return (int)rc; 575 } 576 577 static void 578 usage(enum argmode argmode) 579 { 580 581 switch (argmode) { 582 case ARG_FILE: 583 fputs("usage: mandoc [-ac] [-I os=name] " 584 "[-K encoding] [-mdoc | -man] [-O options]\n" 585 "\t [-T output] [-W level] [file ...]\n", stderr); 586 break; 587 case ARG_NAME: 588 fputs("usage: man [-acfhklw] [-C file] [-M path] " 589 "[-m path] [-S subsection]\n" 590 "\t [[-s] section] name ...\n", stderr); 591 break; 592 case ARG_WORD: 593 fputs("usage: whatis [-afk] [-C file] " 594 "[-M path] [-m path] [-O outkey] [-S arch]\n" 595 "\t [-s section] name ...\n", stderr); 596 break; 597 case ARG_EXPR: 598 fputs("usage: apropos [-afk] [-C file] " 599 "[-M path] [-m path] [-O outkey] [-S arch]\n" 600 "\t [-s section] expression ...\n", stderr); 601 break; 602 } 603 exit((int)MANDOCLEVEL_BADARG); 604 } 605 606 static int 607 fs_lookup(const struct manpaths *paths, size_t ipath, 608 const char *sec, const char *arch, const char *name, 609 struct manpage **res, size_t *ressz) 610 { 611 glob_t globinfo; 612 struct manpage *page; 613 char *file; 614 int globres; 615 enum form form; 616 617 form = FORM_SRC; 618 mandoc_asprintf(&file, "%s/man%s/%s.%s", 619 paths->paths[ipath], sec, name, sec); 620 if (access(file, R_OK) != -1) 621 goto found; 622 free(file); 623 624 mandoc_asprintf(&file, "%s/cat%s/%s.0", 625 paths->paths[ipath], sec, name); 626 if (access(file, R_OK) != -1) { 627 form = FORM_CAT; 628 goto found; 629 } 630 free(file); 631 632 if (arch != NULL) { 633 mandoc_asprintf(&file, "%s/man%s/%s/%s.%s", 634 paths->paths[ipath], sec, arch, name, sec); 635 if (access(file, R_OK) != -1) 636 goto found; 637 free(file); 638 } 639 640 mandoc_asprintf(&file, "%s/man%s/%s.[01-9]*", 641 paths->paths[ipath], sec, name); 642 globres = glob(file, 0, NULL, &globinfo); 643 if (globres != 0 && globres != GLOB_NOMATCH) 644 warn("%s: glob", file); 645 free(file); 646 if (globres == 0) 647 file = mandoc_strdup(*globinfo.gl_pathv); 648 globfree(&globinfo); 649 if (globres == 0) 650 goto found; 651 if (res != NULL || ipath + 1 != paths->sz) 652 return 0; 653 654 mandoc_asprintf(&file, "%s.%s", name, sec); 655 globres = access(file, R_OK); 656 free(file); 657 return globres != -1; 658 659 found: 660 warnx("outdated mandoc.db lacks %s(%s) entry, run makewhatis %s", 661 name, sec, paths->paths[ipath]); 662 if (res == NULL) { 663 free(file); 664 return 1; 665 } 666 *res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage)); 667 page = *res + (*ressz - 1); 668 page->file = file; 669 page->names = NULL; 670 page->output = NULL; 671 page->ipath = ipath; 672 page->bits = NAME_FILE & NAME_MASK; 673 page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10; 674 page->form = form; 675 return 1; 676 } 677 678 static int 679 fs_search(const struct mansearch *cfg, const struct manpaths *paths, 680 int argc, char **argv, struct manpage **res, size_t *ressz) 681 { 682 const char *const sections[] = 683 {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"}; 684 const size_t nsec = sizeof(sections)/sizeof(sections[0]); 685 686 size_t ipath, isec, lastsz; 687 688 assert(cfg->argmode == ARG_NAME); 689 690 if (res != NULL) 691 *res = NULL; 692 *ressz = lastsz = 0; 693 while (argc) { 694 for (ipath = 0; ipath < paths->sz; ipath++) { 695 if (cfg->sec != NULL) { 696 if (fs_lookup(paths, ipath, cfg->sec, 697 cfg->arch, *argv, res, ressz) && 698 cfg->firstmatch) 699 return 1; 700 } else for (isec = 0; isec < nsec; isec++) 701 if (fs_lookup(paths, ipath, sections[isec], 702 cfg->arch, *argv, res, ressz) && 703 cfg->firstmatch) 704 return 1; 705 } 706 if (res != NULL && *ressz == lastsz) 707 warnx("No entry for %s in the manual.", *argv); 708 lastsz = *ressz; 709 argv++; 710 argc--; 711 } 712 return 0; 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 mandoc_xr_reset(); 750 if (man->macroset == MACROSET_MDOC) { 751 if (curp->outtype != OUTT_TREE || !curp->outopts->noval) 752 mdoc_validate(man); 753 switch (curp->outtype) { 754 case OUTT_HTML: 755 html_mdoc(curp->outdata, man); 756 break; 757 case OUTT_TREE: 758 tree_mdoc(curp->outdata, man); 759 break; 760 case OUTT_MAN: 761 man_mdoc(curp->outdata, man); 762 break; 763 case OUTT_PDF: 764 case OUTT_ASCII: 765 case OUTT_UTF8: 766 case OUTT_LOCALE: 767 case OUTT_PS: 768 terminal_mdoc(curp->outdata, man); 769 break; 770 case OUTT_MARKDOWN: 771 markdown_mdoc(curp->outdata, man); 772 break; 773 default: 774 break; 775 } 776 } 777 if (man->macroset == MACROSET_MAN) { 778 if (curp->outtype != OUTT_TREE || !curp->outopts->noval) 779 man_validate(man); 780 switch (curp->outtype) { 781 case OUTT_HTML: 782 html_man(curp->outdata, man); 783 break; 784 case OUTT_TREE: 785 tree_man(curp->outdata, man); 786 break; 787 case OUTT_MAN: 788 man_man(curp->outdata, man); 789 break; 790 case OUTT_PDF: 791 case OUTT_ASCII: 792 case OUTT_UTF8: 793 case OUTT_LOCALE: 794 case OUTT_PS: 795 terminal_man(curp->outdata, man); 796 break; 797 default: 798 break; 799 } 800 } 801 if (curp->mmin < MANDOCERR_STYLE) 802 check_xr(file); 803 mparse_updaterc(curp->mp, &rc); 804 } 805 806 static void 807 check_xr(const char *file) 808 { 809 static struct manpaths paths; 810 struct mansearch search; 811 struct mandoc_xr *xr; 812 char *cp; 813 size_t sz; 814 815 if (paths.sz == 0) 816 manpath_base(&paths); 817 818 for (xr = mandoc_xr_get(); xr != NULL; xr = xr->next) { 819 if (xr->line == -1) 820 continue; 821 search.arch = NULL; 822 search.sec = xr->sec; 823 search.outkey = NULL; 824 search.argmode = ARG_NAME; 825 search.firstmatch = 1; 826 if (mansearch(&search, &paths, 1, &xr->name, NULL, &sz)) 827 continue; 828 if (fs_search(&search, &paths, 1, &xr->name, NULL, &sz)) 829 continue; 830 if (xr->count == 1) 831 mandoc_asprintf(&cp, "Xr %s %s", xr->name, xr->sec); 832 else 833 mandoc_asprintf(&cp, "Xr %s %s (%d times)", 834 xr->name, xr->sec, xr->count); 835 mmsg(MANDOCERR_XR_BAD, MANDOCLEVEL_STYLE, 836 file, xr->line, xr->pos + 1, cp); 837 free(cp); 838 } 839 } 840 841 static void 842 outdata_alloc(struct curparse *curp) 843 { 844 switch (curp->outtype) { 845 case OUTT_HTML: 846 curp->outdata = html_alloc(curp->outopts); 847 break; 848 case OUTT_UTF8: 849 curp->outdata = utf8_alloc(curp->outopts); 850 break; 851 case OUTT_LOCALE: 852 curp->outdata = locale_alloc(curp->outopts); 853 break; 854 case OUTT_ASCII: 855 curp->outdata = ascii_alloc(curp->outopts); 856 break; 857 case OUTT_PDF: 858 curp->outdata = pdf_alloc(curp->outopts); 859 break; 860 case OUTT_PS: 861 curp->outdata = ps_alloc(curp->outopts); 862 break; 863 default: 864 break; 865 } 866 } 867 868 static void 869 passthrough(const char *file, int fd, int synopsis_only) 870 { 871 const char synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS"; 872 const char synr[] = "SYNOPSIS"; 873 874 FILE *stream; 875 const char *syscall; 876 char *line, *cp; 877 size_t linesz; 878 ssize_t len, written; 879 int print; 880 881 line = NULL; 882 linesz = 0; 883 884 if (fflush(stdout) == EOF) { 885 syscall = "fflush"; 886 goto fail; 887 } 888 889 if ((stream = fdopen(fd, "r")) == NULL) { 890 close(fd); 891 syscall = "fdopen"; 892 goto fail; 893 } 894 895 print = 0; 896 while ((len = getline(&line, &linesz, stream)) != -1) { 897 cp = line; 898 if (synopsis_only) { 899 if (print) { 900 if ( ! isspace((unsigned char)*cp)) 901 goto done; 902 while (isspace((unsigned char)*cp)) { 903 cp++; 904 len--; 905 } 906 } else { 907 if (strcmp(cp, synb) == 0 || 908 strcmp(cp, synr) == 0) 909 print = 1; 910 continue; 911 } 912 } 913 for (; len > 0; len -= written) { 914 if ((written = write(STDOUT_FILENO, cp, len)) != -1) 915 continue; 916 fclose(stream); 917 syscall = "write"; 918 goto fail; 919 } 920 } 921 922 if (ferror(stream)) { 923 fclose(stream); 924 syscall = "getline"; 925 goto fail; 926 } 927 928 done: 929 free(line); 930 fclose(stream); 931 return; 932 933 fail: 934 free(line); 935 warn("%s: SYSERR: %s", file, syscall); 936 if (rc < MANDOCLEVEL_SYSERR) 937 rc = MANDOCLEVEL_SYSERR; 938 } 939 940 static int 941 koptions(int *options, char *arg) 942 { 943 944 if ( ! strcmp(arg, "utf-8")) { 945 *options |= MPARSE_UTF8; 946 *options &= ~MPARSE_LATIN1; 947 } else if ( ! strcmp(arg, "iso-8859-1")) { 948 *options |= MPARSE_LATIN1; 949 *options &= ~MPARSE_UTF8; 950 } else if ( ! strcmp(arg, "us-ascii")) { 951 *options &= ~(MPARSE_UTF8 | MPARSE_LATIN1); 952 } else { 953 warnx("-K %s: Bad argument", arg); 954 return 0; 955 } 956 return 1; 957 } 958 959 static void 960 moptions(int *options, char *arg) 961 { 962 963 if (arg == NULL) 964 return; 965 if (strcmp(arg, "doc") == 0) 966 *options |= MPARSE_MDOC; 967 else if (strcmp(arg, "an") == 0) 968 *options |= MPARSE_MAN; 969 } 970 971 static int 972 toptions(struct curparse *curp, char *arg) 973 { 974 975 if (0 == strcmp(arg, "ascii")) 976 curp->outtype = OUTT_ASCII; 977 else if (0 == strcmp(arg, "lint")) { 978 curp->outtype = OUTT_LINT; 979 curp->mmin = MANDOCERR_BASE; 980 mmsg_stream = stdout; 981 } else if (0 == strcmp(arg, "tree")) 982 curp->outtype = OUTT_TREE; 983 else if (0 == strcmp(arg, "man")) 984 curp->outtype = OUTT_MAN; 985 else if (0 == strcmp(arg, "html")) 986 curp->outtype = OUTT_HTML; 987 else if (0 == strcmp(arg, "markdown")) 988 curp->outtype = OUTT_MARKDOWN; 989 else if (0 == strcmp(arg, "utf8")) 990 curp->outtype = OUTT_UTF8; 991 else if (0 == strcmp(arg, "locale")) 992 curp->outtype = OUTT_LOCALE; 993 else if (0 == strcmp(arg, "ps")) 994 curp->outtype = OUTT_PS; 995 else if (0 == strcmp(arg, "pdf")) 996 curp->outtype = OUTT_PDF; 997 else { 998 warnx("-T %s: Bad argument", arg); 999 return 0; 1000 } 1001 1002 return 1; 1003 } 1004 1005 static int 1006 woptions(struct curparse *curp, char *arg) 1007 { 1008 char *v, *o; 1009 const char *toks[11]; 1010 1011 toks[0] = "stop"; 1012 toks[1] = "all"; 1013 toks[2] = "base"; 1014 toks[3] = "style"; 1015 toks[4] = "warning"; 1016 toks[5] = "error"; 1017 toks[6] = "unsupp"; 1018 toks[7] = "fatal"; 1019 toks[8] = "openbsd"; 1020 toks[9] = "netbsd"; 1021 toks[10] = NULL; 1022 1023 while (*arg) { 1024 o = arg; 1025 switch (getsubopt(&arg, (char * const *)toks, &v)) { 1026 case 0: 1027 curp->wstop = 1; 1028 break; 1029 case 1: 1030 case 2: 1031 curp->mmin = MANDOCERR_BASE; 1032 break; 1033 case 3: 1034 curp->mmin = MANDOCERR_STYLE; 1035 break; 1036 case 4: 1037 curp->mmin = MANDOCERR_WARNING; 1038 break; 1039 case 5: 1040 curp->mmin = MANDOCERR_ERROR; 1041 break; 1042 case 6: 1043 curp->mmin = MANDOCERR_UNSUPP; 1044 break; 1045 case 7: 1046 curp->mmin = MANDOCERR_MAX; 1047 break; 1048 case 8: 1049 curp->mmin = MANDOCERR_BASE; 1050 curp->os_e = MANDOC_OS_OPENBSD; 1051 break; 1052 case 9: 1053 curp->mmin = MANDOCERR_BASE; 1054 curp->os_e = MANDOC_OS_NETBSD; 1055 break; 1056 default: 1057 warnx("-W %s: Bad argument", o); 1058 return 0; 1059 } 1060 } 1061 return 1; 1062 } 1063 1064 static void 1065 mmsg(enum mandocerr t, enum mandoclevel lvl, 1066 const char *file, int line, int col, const char *msg) 1067 { 1068 const char *mparse_msg; 1069 1070 fprintf(mmsg_stream, "%s: %s:", getprogname(), 1071 file == NULL ? "<stdin>" : file); 1072 1073 if (line) 1074 fprintf(mmsg_stream, "%d:%d:", line, col + 1); 1075 1076 fprintf(mmsg_stream, " %s", mparse_strlevel(lvl)); 1077 1078 if ((mparse_msg = mparse_strerror(t)) != NULL) 1079 fprintf(mmsg_stream, ": %s", mparse_msg); 1080 1081 if (msg) 1082 fprintf(mmsg_stream, ": %s", msg); 1083 1084 fputc('\n', mmsg_stream); 1085 } 1086 1087 static pid_t 1088 spawn_pager(struct tag_files *tag_files) 1089 { 1090 const struct timespec timeout = { 0, 100000000 }; /* 0.1s */ 1091 #define MAX_PAGER_ARGS 16 1092 char *argv[MAX_PAGER_ARGS]; 1093 const char *pager; 1094 char *cp; 1095 size_t cmdlen; 1096 int argc; 1097 pid_t pager_pid; 1098 1099 pager = getenv("MANPAGER"); 1100 if (pager == NULL || *pager == '\0') 1101 pager = getenv("PAGER"); 1102 if (pager == NULL || *pager == '\0') 1103 pager = "more -s"; 1104 cp = mandoc_strdup(pager); 1105 1106 /* 1107 * Parse the pager command into words. 1108 * Intentionally do not do anything fancy here. 1109 */ 1110 1111 argc = 0; 1112 while (argc + 4 < MAX_PAGER_ARGS) { 1113 argv[argc++] = cp; 1114 cp = strchr(cp, ' '); 1115 if (cp == NULL) 1116 break; 1117 *cp++ = '\0'; 1118 while (*cp == ' ') 1119 cp++; 1120 if (*cp == '\0') 1121 break; 1122 } 1123 1124 /* For more(1) and less(1), use the tag file. */ 1125 1126 if ((cmdlen = strlen(argv[0])) >= 4) { 1127 cp = argv[0] + cmdlen - 4; 1128 if (strcmp(cp, "less") == 0 || strcmp(cp, "more") == 0) { 1129 argv[argc++] = mandoc_strdup("-T"); 1130 argv[argc++] = tag_files->tfn; 1131 } 1132 } 1133 argv[argc++] = tag_files->ofn; 1134 argv[argc] = NULL; 1135 1136 switch (pager_pid = fork()) { 1137 case -1: 1138 err((int)MANDOCLEVEL_SYSERR, "fork"); 1139 case 0: 1140 break; 1141 default: 1142 (void)setpgid(pager_pid, 0); 1143 (void)tcsetpgrp(tag_files->ofd, pager_pid); 1144 if (pledge("stdio rpath tmppath tty proc", NULL) == -1) 1145 err((int)MANDOCLEVEL_SYSERR, "pledge"); 1146 tag_files->pager_pid = pager_pid; 1147 return pager_pid; 1148 } 1149 1150 /* The child process becomes the pager. */ 1151 1152 if (dup2(tag_files->ofd, STDOUT_FILENO) == -1) 1153 err((int)MANDOCLEVEL_SYSERR, "pager stdout"); 1154 close(tag_files->ofd); 1155 close(tag_files->tfd); 1156 1157 /* Do not start the pager before controlling the terminal. */ 1158 1159 while (tcgetpgrp(STDOUT_FILENO) != getpid()) 1160 nanosleep(&timeout, NULL); 1161 1162 execvp(argv[0], argv); 1163 err((int)MANDOCLEVEL_SYSERR, "exec %s", argv[0]); 1164 } 1165