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