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