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