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