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