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