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