1 /* $OpenBSD: main.c,v 1.260 2021/09/04 22:37:26 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 continue; 498 } 499 500 /* Search for the best section. */ 501 502 best_prio = 40; 503 for (ib = i = 0; i < resnsz; i++) { 504 sec = resn[i].file; 505 sec += strcspn(sec, "123456789"); 506 if (sec[0] == '\0') 507 continue; /* No section at all. */ 508 prio = sec_prios[sec[0] - '1']; 509 if (search.sec != NULL) { 510 ssz = strlen(search.sec); 511 if (strncmp(sec, search.sec, ssz) == 0) 512 sec += ssz; 513 } else 514 sec++; /* Prefer without suffix. */ 515 if (*sec != '/') 516 prio += 10; /* Wrong dir name. */ 517 if (search.sec != NULL) { 518 ep = strchr(sec, '\0'); 519 if (ep - sec > 3 && 520 strncmp(ep - 3, ".gz", 3) == 0) 521 ep -= 3; 522 if ((size_t)(ep - sec) < ssz + 3 || 523 strncmp(ep - ssz, search.sec, 524 ssz) != 0) /* Wrong file */ 525 prio += 20; /* extension. */ 526 } 527 if (prio >= best_prio) 528 continue; 529 best_prio = prio; 530 ib = i; 531 } 532 res = mandoc_reallocarray(res, ressz + 1, 533 sizeof(*res)); 534 memcpy(res + ressz++, resn + ib, sizeof(*resn)); 535 } 536 537 /* apropos(1), whatis(1): Process the full search expression. */ 538 539 } else if (search.argmode != ARG_FILE) { 540 if (mansearch(&search, &conf.manpath, 541 argc, argv, &res, &ressz) == 0) 542 usage(search.argmode); 543 544 if (ressz == 0) { 545 warnx("nothing appropriate"); 546 mandoc_msg_setrc(MANDOCLEVEL_BADARG); 547 goto out; 548 } 549 550 /* mandoc(1): Take command line arguments as file names. */ 551 552 } else { 553 ressz = argc > 0 ? argc : 1; 554 res = mandoc_calloc(ressz, sizeof(*res)); 555 for (i = 0; i < ressz; i++) { 556 if (argc > 0) 557 res[i].file = mandoc_strdup(argv[i]); 558 res[i].ipath = SIZE_MAX; 559 res[i].form = FORM_SRC; 560 } 561 } 562 563 switch (outmode) { 564 case OUTMODE_FLN: 565 for (i = 0; i < ressz; i++) 566 puts(res[i].file); 567 goto out; 568 case OUTMODE_LST: 569 for (i = 0; i < ressz; i++) 570 printf("%s - %s\n", res[i].names, 571 res[i].output == NULL ? "" : 572 res[i].output); 573 goto out; 574 default: 575 break; 576 } 577 578 if (search.argmode == ARG_FILE && auxpaths != NULL) { 579 if (strcmp(auxpaths, "doc") == 0) 580 options |= MPARSE_MDOC; 581 else if (strcmp(auxpaths, "an") == 0) 582 options |= MPARSE_MAN; 583 } 584 585 mchars_alloc(); 586 mp = mparse_alloc(options, os_e, os_s); 587 588 /* 589 * Remember the original working directory, if possible. 590 * This will be needed if some names on the command line 591 * are page names and some are relative file names. 592 * Do not error out if the current directory is not 593 * readable: Maybe it won't be needed after all. 594 */ 595 startdir = open(".", O_RDONLY | O_DIRECTORY); 596 for (i = 0; i < ressz; i++) { 597 process_onefile(mp, res + i, startdir, &outst, &conf); 598 if (outst.wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK) 599 break; 600 } 601 if (startdir != -1) { 602 (void)fchdir(startdir); 603 close(startdir); 604 } 605 if (conf.output.tag != NULL && conf.output.tag_found == 0) { 606 mandoc_msg(MANDOCERR_TAG, 0, 0, "%s", conf.output.tag); 607 conf.output.tag = NULL; 608 } 609 if (outst.outdata != NULL) { 610 switch (outst.outtype) { 611 case OUTT_HTML: 612 html_free(outst.outdata); 613 break; 614 case OUTT_UTF8: 615 case OUTT_LOCALE: 616 case OUTT_ASCII: 617 ascii_free(outst.outdata); 618 break; 619 case OUTT_PDF: 620 case OUTT_PS: 621 pspdf_free(outst.outdata); 622 break; 623 default: 624 break; 625 } 626 } 627 mandoc_xr_free(); 628 mparse_free(mp); 629 mchars_free(); 630 631 out: 632 mansearch_free(res, ressz); 633 if (search.argmode != ARG_FILE) 634 manconf_free(&conf); 635 636 if (outst.tag_files != NULL) { 637 if (term_tag_close() != -1 && 638 conf.output.outfilename == NULL && 639 conf.output.tagfilename == NULL) 640 run_pager(&outst, conf.output.tag); 641 term_tag_unlink(); 642 } else if (outst.had_output && outst.outtype != OUTT_LINT) 643 mandoc_msg_summary(); 644 645 return (int)mandoc_msg_getrc(); 646 } 647 648 static void 649 usage(enum argmode argmode) 650 { 651 switch (argmode) { 652 case ARG_FILE: 653 fputs("usage: mandoc [-ac] [-I os=name] " 654 "[-K encoding] [-mdoc | -man] [-O options]\n" 655 "\t [-T output] [-W level] [file ...]\n", stderr); 656 break; 657 case ARG_NAME: 658 fputs("usage: man [-acfhklw] [-C file] [-M path] " 659 "[-m path] [-S subsection]\n" 660 "\t [[-s] section] name ...\n", stderr); 661 break; 662 case ARG_WORD: 663 fputs("usage: whatis [-afk] [-C file] " 664 "[-M path] [-m path] [-O outkey] [-S arch]\n" 665 "\t [-s section] name ...\n", stderr); 666 break; 667 case ARG_EXPR: 668 fputs("usage: apropos [-afk] [-C file] " 669 "[-M path] [-m path] [-O outkey] [-S arch]\n" 670 "\t [-s section] expression ...\n", stderr); 671 break; 672 } 673 exit((int)MANDOCLEVEL_BADARG); 674 } 675 676 static void 677 glob_esc(char **dst, const char *src, const char *suffix) 678 { 679 while (*src != '\0') { 680 if (strchr("*?[", *src) != NULL) 681 *(*dst)++ = '\\'; 682 *(*dst)++ = *src++; 683 } 684 while (*suffix != '\0') 685 *(*dst)++ = *suffix++; 686 } 687 688 static void 689 fs_append(char **file, size_t filesz, int copy, size_t ipath, 690 const char *sec, enum form form, struct manpage **res, size_t *ressz) 691 { 692 struct manpage *page; 693 694 *res = mandoc_reallocarray(*res, *ressz + filesz, sizeof(**res)); 695 page = *res + *ressz; 696 *ressz += filesz; 697 for (;;) { 698 page->file = copy ? mandoc_strdup(*file) : *file; 699 page->names = NULL; 700 page->output = NULL; 701 page->bits = NAME_FILE & NAME_MASK; 702 page->ipath = ipath; 703 page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10; 704 page->form = form; 705 if (--filesz == 0) 706 break; 707 file++; 708 page++; 709 } 710 } 711 712 static int 713 fs_lookup(const struct manpaths *paths, size_t ipath, 714 const char *sec, const char *arch, const char *name, 715 struct manpage **res, size_t *ressz) 716 { 717 struct stat sb; 718 glob_t globinfo; 719 char *file, *cp, secnum[2]; 720 int globres; 721 enum form form; 722 723 const char *const slman = "/man"; 724 const char *const slash = "/"; 725 const char *const sglob = ".[01-9]*"; 726 const char *const dot = "."; 727 const char *const aster = "*"; 728 729 memset(&globinfo, 0, sizeof(globinfo)); 730 form = FORM_SRC; 731 732 mandoc_asprintf(&file, "%s/man%s/%s.%s", 733 paths->paths[ipath], sec, name, sec); 734 if (stat(file, &sb) != -1) 735 goto found; 736 free(file); 737 738 mandoc_asprintf(&file, "%s/cat%s/%s.0", 739 paths->paths[ipath], sec, name); 740 if (stat(file, &sb) != -1) { 741 form = FORM_CAT; 742 goto found; 743 } 744 free(file); 745 746 if (arch != NULL) { 747 mandoc_asprintf(&file, "%s/man%s/%s/%s.%s", 748 paths->paths[ipath], sec, arch, name, sec); 749 if (stat(file, &sb) != -1) 750 goto found; 751 free(file); 752 } 753 754 cp = file = mandoc_malloc(strlen(paths->paths[ipath]) * 2 + 755 strlen(slman) + strlen(sec) * 2 + strlen(slash) + 756 strlen(name) * 2 + strlen(sglob) + 1); 757 glob_esc(&cp, paths->paths[ipath], slman); 758 glob_esc(&cp, sec, slash); 759 glob_esc(&cp, name, sglob); 760 *cp = '\0'; 761 globres = glob(file, 0, NULL, &globinfo); 762 if (globres != 0 && globres != GLOB_NOMATCH) 763 mandoc_msg(MANDOCERR_GLOB, 0, 0, 764 "%s: %s", file, strerror(errno)); 765 free(file); 766 file = NULL; 767 if (globres == 0) 768 goto found; 769 globfree(&globinfo); 770 771 if (sec[1] != '\0' && *ressz == 0) { 772 secnum[0] = sec[0]; 773 secnum[1] = '\0'; 774 cp = file = mandoc_malloc(strlen(paths->paths[ipath]) * 2 + 775 strlen(slman) + strlen(secnum) * 2 + strlen(slash) + 776 strlen(name) * 2 + strlen(dot) + 777 strlen(sec) * 2 + strlen(aster) + 1); 778 glob_esc(&cp, paths->paths[ipath], slman); 779 glob_esc(&cp, secnum, slash); 780 glob_esc(&cp, name, dot); 781 glob_esc(&cp, sec, aster); 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 794 if (res != NULL || ipath + 1 != paths->sz) 795 return -1; 796 797 mandoc_asprintf(&file, "%s.%s", name, sec); 798 globres = stat(file, &sb); 799 free(file); 800 return globres; 801 802 found: 803 warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s", 804 name, sec, BINM_MAKEWHATIS, paths->paths[ipath]); 805 if (res == NULL) 806 free(file); 807 else if (file == NULL) 808 fs_append(globinfo.gl_pathv, globinfo.gl_pathc, 1, 809 ipath, sec, form, res, ressz); 810 else 811 fs_append(&file, 1, 0, ipath, sec, form, res, ressz); 812 globfree(&globinfo); 813 return 0; 814 } 815 816 static int 817 fs_search(const struct mansearch *cfg, const struct manpaths *paths, 818 const char *name, struct manpage **res, size_t *ressz) 819 { 820 const char *const sections[] = 821 {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"}; 822 const size_t nsec = sizeof(sections)/sizeof(sections[0]); 823 824 size_t ipath, isec; 825 826 assert(cfg->argmode == ARG_NAME); 827 if (res != NULL) 828 *res = NULL; 829 *ressz = 0; 830 for (ipath = 0; ipath < paths->sz; ipath++) { 831 if (cfg->sec != NULL) { 832 if (fs_lookup(paths, ipath, cfg->sec, cfg->arch, 833 name, res, ressz) != -1 && cfg->firstmatch) 834 return 0; 835 } else { 836 for (isec = 0; isec < nsec; isec++) 837 if (fs_lookup(paths, ipath, sections[isec], 838 cfg->arch, name, res, ressz) != -1 && 839 cfg->firstmatch) 840 return 0; 841 } 842 } 843 return -1; 844 } 845 846 static void 847 process_onefile(struct mparse *mp, struct manpage *resp, int startdir, 848 struct outstate *outst, struct manconf *conf) 849 { 850 int fd; 851 852 /* 853 * Changing directories is not needed in ARG_FILE mode. 854 * Do it on a best-effort basis. Even in case of 855 * failure, some functionality may still work. 856 */ 857 if (resp->ipath != SIZE_MAX) 858 (void)chdir(conf->manpath.paths[resp->ipath]); 859 else if (startdir != -1) 860 (void)fchdir(startdir); 861 862 mandoc_msg_setinfilename(resp->file); 863 if (resp->file != NULL) { 864 if ((fd = mparse_open(mp, resp->file)) == -1) { 865 mandoc_msg(resp->ipath == SIZE_MAX ? 866 MANDOCERR_BADARG_BAD : MANDOCERR_OPEN, 867 0, 0, "%s", strerror(errno)); 868 mandoc_msg_setinfilename(NULL); 869 return; 870 } 871 } else 872 fd = STDIN_FILENO; 873 874 if (outst->use_pager) { 875 outst->use_pager = 0; 876 outst->tag_files = term_tag_init(conf->output.outfilename, 877 outst->outtype == OUTT_HTML ? ".html" : "", 878 conf->output.tagfilename); 879 if ((conf->output.outfilename != NULL || 880 conf->output.tagfilename != NULL) && 881 pledge("stdio rpath cpath", NULL) == -1) { 882 mandoc_msg(MANDOCERR_PLEDGE, 0, 0, 883 "%s", strerror(errno)); 884 exit(mandoc_msg_getrc()); 885 } 886 } 887 if (outst->had_output && outst->outtype <= OUTT_UTF8) { 888 if (outst->outdata == NULL) 889 outdata_alloc(outst, &conf->output); 890 terminal_sepline(outst->outdata); 891 } 892 893 if (resp->form == FORM_SRC) 894 parse(mp, fd, resp->file, outst, conf); 895 else { 896 passthrough(fd, conf->output.synopsisonly); 897 outst->had_output = 1; 898 } 899 900 if (ferror(stdout)) { 901 if (outst->tag_files != NULL) { 902 mandoc_msg(MANDOCERR_WRITE, 0, 0, "%s: %s", 903 outst->tag_files->ofn, strerror(errno)); 904 term_tag_unlink(); 905 outst->tag_files = NULL; 906 } else 907 mandoc_msg(MANDOCERR_WRITE, 0, 0, "%s", 908 strerror(errno)); 909 } 910 mandoc_msg_setinfilename(NULL); 911 } 912 913 static void 914 parse(struct mparse *mp, int fd, const char *file, 915 struct outstate *outst, struct manconf *conf) 916 { 917 static struct manpaths basepaths; 918 static int previous; 919 struct roff_meta *meta; 920 921 assert(fd >= 0); 922 if (file == NULL) 923 file = "<stdin>"; 924 925 if (previous) 926 mparse_reset(mp); 927 else 928 previous = 1; 929 930 mparse_readfd(mp, fd, file); 931 if (fd != STDIN_FILENO) 932 close(fd); 933 934 /* 935 * With -Wstop and warnings or errors of at least the requested 936 * level, do not produce output. 937 */ 938 939 if (outst->wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK) 940 return; 941 942 if (outst->outdata == NULL) 943 outdata_alloc(outst, &conf->output); 944 else if (outst->outtype == OUTT_HTML) 945 html_reset(outst->outdata); 946 947 mandoc_xr_reset(); 948 meta = mparse_result(mp); 949 950 /* Execute the out device, if it exists. */ 951 952 outst->had_output = 1; 953 if (meta->macroset == MACROSET_MDOC) { 954 switch (outst->outtype) { 955 case OUTT_HTML: 956 html_mdoc(outst->outdata, meta); 957 break; 958 case OUTT_TREE: 959 tree_mdoc(outst->outdata, meta); 960 break; 961 case OUTT_MAN: 962 man_mdoc(outst->outdata, meta); 963 break; 964 case OUTT_PDF: 965 case OUTT_ASCII: 966 case OUTT_UTF8: 967 case OUTT_LOCALE: 968 case OUTT_PS: 969 terminal_mdoc(outst->outdata, meta); 970 break; 971 case OUTT_MARKDOWN: 972 markdown_mdoc(outst->outdata, meta); 973 break; 974 default: 975 break; 976 } 977 } 978 if (meta->macroset == MACROSET_MAN) { 979 switch (outst->outtype) { 980 case OUTT_HTML: 981 html_man(outst->outdata, meta); 982 break; 983 case OUTT_TREE: 984 tree_man(outst->outdata, meta); 985 break; 986 case OUTT_MAN: 987 mparse_copy(mp); 988 break; 989 case OUTT_PDF: 990 case OUTT_ASCII: 991 case OUTT_UTF8: 992 case OUTT_LOCALE: 993 case OUTT_PS: 994 terminal_man(outst->outdata, meta); 995 break; 996 case OUTT_MARKDOWN: 997 mandoc_msg(MANDOCERR_MAN_TMARKDOWN, 0, 0, NULL); 998 break; 999 default: 1000 break; 1001 } 1002 } 1003 if (conf->output.tag != NULL && conf->output.tag_found == 0 && 1004 tag_exists(conf->output.tag)) 1005 conf->output.tag_found = 1; 1006 1007 if (mandoc_msg_getmin() < MANDOCERR_STYLE) { 1008 if (basepaths.sz == 0) 1009 manpath_base(&basepaths); 1010 check_xr(&basepaths); 1011 } else if (mandoc_msg_getmin() < MANDOCERR_WARNING) 1012 check_xr(&conf->manpath); 1013 } 1014 1015 static void 1016 check_xr(struct manpaths *paths) 1017 { 1018 struct mansearch search; 1019 struct mandoc_xr *xr; 1020 size_t sz; 1021 1022 for (xr = mandoc_xr_get(); xr != NULL; xr = xr->next) { 1023 if (xr->line == -1) 1024 continue; 1025 search.arch = NULL; 1026 search.sec = xr->sec; 1027 search.outkey = NULL; 1028 search.argmode = ARG_NAME; 1029 search.firstmatch = 1; 1030 if (mansearch(&search, paths, 1, &xr->name, NULL, &sz)) 1031 continue; 1032 if (fs_search(&search, paths, xr->name, NULL, &sz) != -1) 1033 continue; 1034 if (xr->count == 1) 1035 mandoc_msg(MANDOCERR_XR_BAD, xr->line, 1036 xr->pos + 1, "Xr %s %s", xr->name, xr->sec); 1037 else 1038 mandoc_msg(MANDOCERR_XR_BAD, xr->line, 1039 xr->pos + 1, "Xr %s %s (%d times)", 1040 xr->name, xr->sec, xr->count); 1041 } 1042 } 1043 1044 static void 1045 outdata_alloc(struct outstate *outst, struct manoutput *outconf) 1046 { 1047 switch (outst->outtype) { 1048 case OUTT_HTML: 1049 outst->outdata = html_alloc(outconf); 1050 break; 1051 case OUTT_UTF8: 1052 outst->outdata = utf8_alloc(outconf); 1053 break; 1054 case OUTT_LOCALE: 1055 outst->outdata = locale_alloc(outconf); 1056 break; 1057 case OUTT_ASCII: 1058 outst->outdata = ascii_alloc(outconf); 1059 break; 1060 case OUTT_PDF: 1061 outst->outdata = pdf_alloc(outconf); 1062 break; 1063 case OUTT_PS: 1064 outst->outdata = ps_alloc(outconf); 1065 break; 1066 default: 1067 break; 1068 } 1069 } 1070 1071 static void 1072 passthrough(int fd, int synopsis_only) 1073 { 1074 const char synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS"; 1075 const char synr[] = "SYNOPSIS"; 1076 1077 FILE *stream; 1078 char *line, *cp; 1079 size_t linesz; 1080 ssize_t len, written; 1081 int lno, print; 1082 1083 stream = NULL; 1084 line = NULL; 1085 linesz = 0; 1086 1087 if (fflush(stdout) == EOF) { 1088 mandoc_msg(MANDOCERR_FFLUSH, 0, 0, "%s", strerror(errno)); 1089 goto done; 1090 } 1091 if ((stream = fdopen(fd, "r")) == NULL) { 1092 close(fd); 1093 mandoc_msg(MANDOCERR_FDOPEN, 0, 0, "%s", strerror(errno)); 1094 goto done; 1095 } 1096 1097 lno = print = 0; 1098 while ((len = getline(&line, &linesz, stream)) != -1) { 1099 lno++; 1100 cp = line; 1101 if (synopsis_only) { 1102 if (print) { 1103 if ( ! isspace((unsigned char)*cp)) 1104 goto done; 1105 while (isspace((unsigned char)*cp)) { 1106 cp++; 1107 len--; 1108 } 1109 } else { 1110 if (strcmp(cp, synb) == 0 || 1111 strcmp(cp, synr) == 0) 1112 print = 1; 1113 continue; 1114 } 1115 } 1116 for (; len > 0; len -= written) { 1117 if ((written = write(STDOUT_FILENO, cp, len)) == -1) { 1118 mandoc_msg(MANDOCERR_WRITE, 0, 0, 1119 "%s", strerror(errno)); 1120 goto done; 1121 } 1122 } 1123 } 1124 if (ferror(stream)) 1125 mandoc_msg(MANDOCERR_GETLINE, lno, 0, "%s", strerror(errno)); 1126 1127 done: 1128 free(line); 1129 if (stream != NULL) 1130 fclose(stream); 1131 } 1132 1133 static int 1134 woptions(char *arg, enum mandoc_os *os_e, int *wstop) 1135 { 1136 char *v, *o; 1137 const char *toks[11]; 1138 1139 toks[0] = "stop"; 1140 toks[1] = "all"; 1141 toks[2] = "base"; 1142 toks[3] = "style"; 1143 toks[4] = "warning"; 1144 toks[5] = "error"; 1145 toks[6] = "unsupp"; 1146 toks[7] = "fatal"; 1147 toks[8] = "openbsd"; 1148 toks[9] = "netbsd"; 1149 toks[10] = NULL; 1150 1151 while (*arg) { 1152 o = arg; 1153 switch (getsubopt(&arg, (char * const *)toks, &v)) { 1154 case 0: 1155 *wstop = 1; 1156 break; 1157 case 1: 1158 case 2: 1159 mandoc_msg_setmin(MANDOCERR_BASE); 1160 break; 1161 case 3: 1162 mandoc_msg_setmin(MANDOCERR_STYLE); 1163 break; 1164 case 4: 1165 mandoc_msg_setmin(MANDOCERR_WARNING); 1166 break; 1167 case 5: 1168 mandoc_msg_setmin(MANDOCERR_ERROR); 1169 break; 1170 case 6: 1171 mandoc_msg_setmin(MANDOCERR_UNSUPP); 1172 break; 1173 case 7: 1174 mandoc_msg_setmin(MANDOCERR_BADARG); 1175 break; 1176 case 8: 1177 mandoc_msg_setmin(MANDOCERR_BASE); 1178 *os_e = MANDOC_OS_OPENBSD; 1179 break; 1180 case 9: 1181 mandoc_msg_setmin(MANDOCERR_BASE); 1182 *os_e = MANDOC_OS_NETBSD; 1183 break; 1184 default: 1185 mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0, "-W %s", o); 1186 return -1; 1187 } 1188 } 1189 return 0; 1190 } 1191 1192 /* 1193 * Wait until moved to the foreground, 1194 * then fork the pager and wait for the user to close it. 1195 */ 1196 static void 1197 run_pager(struct outstate *outst, char *tag_target) 1198 { 1199 int signum, status; 1200 pid_t man_pgid, tc_pgid; 1201 pid_t pager_pid, wait_pid; 1202 1203 man_pgid = getpgid(0); 1204 outst->tag_files->tcpgid = 1205 man_pgid == getpid() ? getpgid(getppid()) : man_pgid; 1206 pager_pid = 0; 1207 signum = SIGSTOP; 1208 1209 for (;;) { 1210 /* Stop here until moved to the foreground. */ 1211 1212 tc_pgid = tcgetpgrp(STDOUT_FILENO); 1213 if (tc_pgid != man_pgid) { 1214 if (tc_pgid == pager_pid) { 1215 (void)tcsetpgrp(STDOUT_FILENO, man_pgid); 1216 if (signum == SIGTTIN) 1217 continue; 1218 } else 1219 outst->tag_files->tcpgid = tc_pgid; 1220 kill(0, signum); 1221 continue; 1222 } 1223 1224 /* Once in the foreground, activate the pager. */ 1225 1226 if (pager_pid) { 1227 (void)tcsetpgrp(STDOUT_FILENO, pager_pid); 1228 kill(pager_pid, SIGCONT); 1229 } else 1230 pager_pid = spawn_pager(outst, tag_target); 1231 1232 /* Wait for the pager to stop or exit. */ 1233 1234 while ((wait_pid = waitpid(pager_pid, &status, 1235 WUNTRACED)) == -1 && errno == EINTR) 1236 continue; 1237 1238 if (wait_pid == -1) { 1239 mandoc_msg(MANDOCERR_WAIT, 0, 0, 1240 "%s", strerror(errno)); 1241 break; 1242 } 1243 if (!WIFSTOPPED(status)) 1244 break; 1245 1246 signum = WSTOPSIG(status); 1247 } 1248 } 1249 1250 static pid_t 1251 spawn_pager(struct outstate *outst, char *tag_target) 1252 { 1253 const struct timespec timeout = { 0, 100000000 }; /* 0.1s */ 1254 #define MAX_PAGER_ARGS 16 1255 char *argv[MAX_PAGER_ARGS]; 1256 const char *pager; 1257 char *cp; 1258 size_t cmdlen; 1259 int argc, use_ofn; 1260 pid_t pager_pid; 1261 1262 assert(outst->tag_files->ofd == -1); 1263 assert(outst->tag_files->tfs == NULL); 1264 1265 pager = getenv("MANPAGER"); 1266 if (pager == NULL || *pager == '\0') 1267 pager = getenv("PAGER"); 1268 if (pager == NULL || *pager == '\0') 1269 pager = "less"; 1270 cp = mandoc_strdup(pager); 1271 1272 /* 1273 * Parse the pager command into words. 1274 * Intentionally do not do anything fancy here. 1275 */ 1276 1277 argc = 0; 1278 while (argc + 5 < MAX_PAGER_ARGS) { 1279 argv[argc++] = cp; 1280 cp = strchr(cp, ' '); 1281 if (cp == NULL) 1282 break; 1283 *cp++ = '\0'; 1284 while (*cp == ' ') 1285 cp++; 1286 if (*cp == '\0') 1287 break; 1288 } 1289 1290 /* For more(1) and less(1), use the tag file. */ 1291 1292 use_ofn = 1; 1293 if (*outst->tag_files->tfn != '\0' && 1294 (cmdlen = strlen(argv[0])) >= 4) { 1295 cp = argv[0] + cmdlen - 4; 1296 if (strcmp(cp, "less") == 0 || strcmp(cp, "more") == 0) { 1297 argv[argc++] = mandoc_strdup("-T"); 1298 argv[argc++] = outst->tag_files->tfn; 1299 if (tag_target != NULL) { 1300 argv[argc++] = mandoc_strdup("-t"); 1301 argv[argc++] = tag_target; 1302 use_ofn = 0; 1303 } 1304 } 1305 } 1306 if (use_ofn) { 1307 if (outst->outtype == OUTT_HTML && tag_target != NULL) 1308 mandoc_asprintf(&argv[argc], "file://%s#%s", 1309 outst->tag_files->ofn, tag_target); 1310 else 1311 argv[argc] = outst->tag_files->ofn; 1312 argc++; 1313 } 1314 argv[argc] = NULL; 1315 1316 switch (pager_pid = fork()) { 1317 case -1: 1318 mandoc_msg(MANDOCERR_FORK, 0, 0, "%s", strerror(errno)); 1319 exit(mandoc_msg_getrc()); 1320 case 0: 1321 break; 1322 default: 1323 (void)setpgid(pager_pid, 0); 1324 (void)tcsetpgrp(STDOUT_FILENO, pager_pid); 1325 if (pledge("stdio rpath tmppath tty proc", NULL) == -1) { 1326 mandoc_msg(MANDOCERR_PLEDGE, 0, 0, 1327 "%s", strerror(errno)); 1328 exit(mandoc_msg_getrc()); 1329 } 1330 outst->tag_files->pager_pid = pager_pid; 1331 return pager_pid; 1332 } 1333 1334 /* 1335 * The child process becomes the pager. 1336 * Do not start it before controlling the terminal. 1337 */ 1338 1339 while (tcgetpgrp(STDOUT_FILENO) != getpid()) 1340 nanosleep(&timeout, NULL); 1341 1342 execvp(argv[0], argv); 1343 mandoc_msg(MANDOCERR_EXEC, 0, 0, "%s: %s", argv[0], strerror(errno)); 1344 _exit(mandoc_msg_getrc()); 1345 } 1346