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