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