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