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