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