1 /* $OpenBSD: main.c,v 1.143 2015/04/29 11:03:48 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2010-2012, 2014, 2015 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/param.h> /* MACHINE */ 22 #include <sys/wait.h> 23 24 #include <assert.h> 25 #include <ctype.h> 26 #include <errno.h> 27 #include <fcntl.h> 28 #include <glob.h> 29 #include <signal.h> 30 #include <stdio.h> 31 #include <stdint.h> 32 #include <stdlib.h> 33 #include <string.h> 34 #include <unistd.h> 35 36 #include "mandoc_aux.h" 37 #include "mandoc.h" 38 #include "roff.h" 39 #include "mdoc.h" 40 #include "man.h" 41 #include "main.h" 42 #include "manconf.h" 43 #include "mansearch.h" 44 45 enum outmode { 46 OUTMODE_DEF = 0, 47 OUTMODE_FLN, 48 OUTMODE_LST, 49 OUTMODE_ALL, 50 OUTMODE_INT, 51 OUTMODE_ONE 52 }; 53 54 typedef void (*out_mdoc)(void *, const struct roff_man *); 55 typedef void (*out_man)(void *, const struct roff_man *); 56 typedef void (*out_free)(void *); 57 58 enum outt { 59 OUTT_ASCII = 0, /* -Tascii */ 60 OUTT_LOCALE, /* -Tlocale */ 61 OUTT_UTF8, /* -Tutf8 */ 62 OUTT_TREE, /* -Ttree */ 63 OUTT_MAN, /* -Tman */ 64 OUTT_HTML, /* -Thtml */ 65 OUTT_LINT, /* -Tlint */ 66 OUTT_PS, /* -Tps */ 67 OUTT_PDF /* -Tpdf */ 68 }; 69 70 struct curparse { 71 struct mparse *mp; 72 struct mchars *mchars; /* character table */ 73 enum mandoclevel wlevel; /* ignore messages below this */ 74 int wstop; /* stop after a file with a warning */ 75 enum outt outtype; /* which output to use */ 76 out_mdoc outmdoc; /* mdoc output ptr */ 77 out_man outman; /* man output ptr */ 78 out_free outfree; /* free output ptr */ 79 void *outdata; /* data for output */ 80 struct manoutput *outopts; /* output options */ 81 }; 82 83 static int fs_lookup(const struct manpaths *, 84 size_t ipath, const char *, 85 const char *, const char *, 86 struct manpage **, size_t *); 87 static void fs_search(const struct mansearch *, 88 const struct manpaths *, int, char**, 89 struct manpage **, size_t *); 90 static void handle_sigpipe(int); 91 static int koptions(int *, char *); 92 int mandocdb(int, char**); 93 static int moptions(int *, char *); 94 static void mmsg(enum mandocerr, enum mandoclevel, 95 const char *, int, int, const char *); 96 static void parse(struct curparse *, int, const char *); 97 static void passthrough(const char *, int, int); 98 static pid_t spawn_pager(void); 99 static int toptions(struct curparse *, char *); 100 static void usage(enum argmode) __attribute__((noreturn)); 101 static int woptions(struct curparse *, char *); 102 103 static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9}; 104 static char help_arg[] = "help"; 105 static char *help_argv[] = {help_arg, NULL}; 106 static const char *progname; 107 static enum mandoclevel rc; 108 109 110 int 111 main(int argc, char *argv[]) 112 { 113 struct manconf conf; 114 struct curparse curp; 115 struct mansearch search; 116 char *auxpaths; 117 char *defos; 118 unsigned char *uc; 119 struct manpage *res, *resp; 120 char *conf_file, *defpaths; 121 size_t isec, i, sz; 122 int prio, best_prio; 123 char sec; 124 enum mandoclevel rctmp; 125 enum outmode outmode; 126 int fd; 127 int show_usage; 128 int options; 129 int c; 130 pid_t pager_pid; /* 0: don't use; 1: not yet spawned. */ 131 132 if (argc < 1) 133 progname = "mandoc"; 134 else if ((progname = strrchr(argv[0], '/')) == NULL) 135 progname = argv[0]; 136 else 137 ++progname; 138 139 if (0 == strncmp(progname, "mandocdb", 8) || 140 0 == strncmp(progname, "makewhatis", 10)) 141 return(mandocdb(argc, argv)); 142 143 /* Search options. */ 144 145 memset(&conf, 0, sizeof(conf)); 146 conf_file = defpaths = NULL; 147 auxpaths = NULL; 148 149 memset(&search, 0, sizeof(struct mansearch)); 150 search.outkey = "Nd"; 151 152 if (strcmp(progname, "man") == 0) 153 search.argmode = ARG_NAME; 154 else if (strncmp(progname, "apropos", 7) == 0) 155 search.argmode = ARG_EXPR; 156 else if (strncmp(progname, "whatis", 6) == 0) 157 search.argmode = ARG_WORD; 158 else if (strncmp(progname, "help", 4) == 0) 159 search.argmode = ARG_NAME; 160 else 161 search.argmode = ARG_FILE; 162 163 /* Parser and formatter options. */ 164 165 memset(&curp, 0, sizeof(struct curparse)); 166 curp.outtype = OUTT_LOCALE; 167 curp.wlevel = MANDOCLEVEL_BADARG; 168 curp.outopts = &conf.output; 169 options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1; 170 defos = NULL; 171 172 pager_pid = 1; 173 show_usage = 0; 174 outmode = OUTMODE_DEF; 175 176 while (-1 != (c = getopt(argc, argv, 177 "aC:cfhI:iK:klM:m:O:S:s:T:VW:w"))) { 178 switch (c) { 179 case 'a': 180 outmode = OUTMODE_ALL; 181 break; 182 case 'C': 183 conf_file = optarg; 184 break; 185 case 'c': 186 pager_pid = 0; 187 break; 188 case 'f': 189 search.argmode = ARG_WORD; 190 break; 191 case 'h': 192 conf.output.synopsisonly = 1; 193 pager_pid = 0; 194 outmode = OUTMODE_ALL; 195 break; 196 case 'I': 197 if (strncmp(optarg, "os=", 3)) { 198 fprintf(stderr, 199 "%s: -I %s: Bad argument\n", 200 progname, optarg); 201 return((int)MANDOCLEVEL_BADARG); 202 } 203 if (defos) { 204 fprintf(stderr, 205 "%s: -I %s: Duplicate argument\n", 206 progname, optarg); 207 return((int)MANDOCLEVEL_BADARG); 208 } 209 defos = mandoc_strdup(optarg + 3); 210 break; 211 case 'i': 212 outmode = OUTMODE_INT; 213 break; 214 case 'K': 215 if ( ! koptions(&options, optarg)) 216 return((int)MANDOCLEVEL_BADARG); 217 break; 218 case 'k': 219 search.argmode = ARG_EXPR; 220 break; 221 case 'l': 222 search.argmode = ARG_FILE; 223 outmode = OUTMODE_ALL; 224 break; 225 case 'M': 226 defpaths = optarg; 227 break; 228 case 'm': 229 auxpaths = optarg; 230 break; 231 case 'O': 232 search.outkey = optarg; 233 while (optarg != NULL) 234 manconf_output(&conf.output, 235 strsep(&optarg, ",")); 236 break; 237 case 'S': 238 search.arch = optarg; 239 break; 240 case 's': 241 search.sec = optarg; 242 break; 243 case 'T': 244 if ( ! toptions(&curp, optarg)) 245 return((int)MANDOCLEVEL_BADARG); 246 break; 247 case 'W': 248 if ( ! woptions(&curp, optarg)) 249 return((int)MANDOCLEVEL_BADARG); 250 break; 251 case 'w': 252 outmode = OUTMODE_FLN; 253 break; 254 default: 255 show_usage = 1; 256 break; 257 } 258 } 259 260 if (show_usage) 261 usage(search.argmode); 262 263 /* Postprocess options. */ 264 265 if (outmode == OUTMODE_DEF) { 266 switch (search.argmode) { 267 case ARG_FILE: 268 outmode = OUTMODE_ALL; 269 pager_pid = 0; 270 break; 271 case ARG_NAME: 272 outmode = OUTMODE_ONE; 273 break; 274 default: 275 outmode = OUTMODE_LST; 276 break; 277 } 278 } 279 280 /* Parse arguments. */ 281 282 if (argc > 0) { 283 argc -= optind; 284 argv += optind; 285 } 286 resp = NULL; 287 288 /* 289 * Quirks for help(1) 290 * and for a man(1) section argument without -s. 291 */ 292 293 if (search.argmode == ARG_NAME) { 294 if (*progname == 'h') { 295 if (argc == 0) { 296 argv = help_argv; 297 argc = 1; 298 } 299 } else if (argc > 1 && 300 ((uc = (unsigned char *)argv[0]) != NULL) && 301 ((isdigit(uc[0]) && (uc[1] == '\0' || 302 (isalpha(uc[1]) && uc[2] == '\0'))) || 303 (uc[0] == 'n' && uc[1] == '\0'))) { 304 search.sec = (char *)uc; 305 argv++; 306 argc--; 307 } 308 if (search.arch == NULL) 309 search.arch = getenv("MACHINE"); 310 if (search.arch == NULL) 311 search.arch = MACHINE; 312 } 313 314 rc = MANDOCLEVEL_OK; 315 316 /* man(1), whatis(1), apropos(1) */ 317 318 if (search.argmode != ARG_FILE) { 319 if (argc == 0) 320 usage(search.argmode); 321 322 if (search.argmode == ARG_NAME && 323 outmode == OUTMODE_ONE) 324 search.firstmatch = 1; 325 326 /* Access the mandoc database. */ 327 328 manconf_parse(&conf, conf_file, defpaths, auxpaths); 329 mansearch_setup(1); 330 if ( ! mansearch(&search, &conf.manpath, 331 argc, argv, &res, &sz)) 332 usage(search.argmode); 333 334 if (sz == 0) { 335 if (search.argmode == ARG_NAME) 336 fs_search(&search, &conf.manpath, 337 argc, argv, &res, &sz); 338 else 339 fprintf(stderr, 340 "%s: nothing appropriate\n", 341 progname); 342 } 343 344 if (sz == 0) { 345 rc = MANDOCLEVEL_BADARG; 346 goto out; 347 } 348 349 /* 350 * For standard man(1) and -a output mode, 351 * prepare for copying filename pointers 352 * into the program parameter array. 353 */ 354 355 if (outmode == OUTMODE_ONE) { 356 argc = 1; 357 best_prio = 10; 358 } else if (outmode == OUTMODE_ALL) 359 argc = (int)sz; 360 361 /* Iterate all matching manuals. */ 362 363 resp = res; 364 for (i = 0; i < sz; i++) { 365 if (outmode == OUTMODE_FLN) 366 puts(res[i].file); 367 else if (outmode == OUTMODE_LST) 368 printf("%s - %s\n", res[i].names, 369 res[i].output == NULL ? "" : 370 res[i].output); 371 else if (outmode == OUTMODE_ONE) { 372 /* Search for the best section. */ 373 isec = strcspn(res[i].file, "123456789"); 374 sec = res[i].file[isec]; 375 if ('\0' == sec) 376 continue; 377 prio = sec_prios[sec - '1']; 378 if (prio >= best_prio) 379 continue; 380 best_prio = prio; 381 resp = res + i; 382 } 383 } 384 385 /* 386 * For man(1), -a and -i output mode, fall through 387 * to the main mandoc(1) code iterating files 388 * and running the parsers on each of them. 389 */ 390 391 if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST) 392 goto out; 393 } 394 395 /* mandoc(1) */ 396 397 if (search.argmode == ARG_FILE && ! moptions(&options, auxpaths)) 398 return((int)MANDOCLEVEL_BADARG); 399 400 curp.mchars = mchars_alloc(); 401 curp.mp = mparse_alloc(options, curp.wlevel, mmsg, 402 curp.mchars, defos); 403 404 /* 405 * Conditionally start up the lookaside buffer before parsing. 406 */ 407 if (OUTT_MAN == curp.outtype) 408 mparse_keep(curp.mp); 409 410 if (argc < 1) { 411 if (pager_pid == 1 && isatty(STDOUT_FILENO)) 412 pager_pid = spawn_pager(); 413 parse(&curp, STDIN_FILENO, "<stdin>"); 414 } 415 416 while (argc > 0) { 417 rctmp = mparse_open(curp.mp, &fd, 418 resp != NULL ? resp->file : *argv); 419 if (rc < rctmp) 420 rc = rctmp; 421 422 if (fd != -1) { 423 if (pager_pid == 1 && isatty(STDOUT_FILENO)) 424 pager_pid = spawn_pager(); 425 426 if (resp == NULL) 427 parse(&curp, fd, *argv); 428 else if (resp->form & FORM_SRC) { 429 /* For .so only; ignore failure. */ 430 chdir(conf.manpath.paths[resp->ipath]); 431 parse(&curp, fd, resp->file); 432 } else 433 passthrough(resp->file, fd, 434 conf.output.synopsisonly); 435 436 rctmp = mparse_wait(curp.mp); 437 if (rc < rctmp) 438 rc = rctmp; 439 440 if (argc > 1 && curp.outtype <= OUTT_UTF8) 441 ascii_sepline(curp.outdata); 442 } 443 444 if (MANDOCLEVEL_OK != rc && curp.wstop) 445 break; 446 447 if (resp != NULL) 448 resp++; 449 else 450 argv++; 451 if (--argc) 452 mparse_reset(curp.mp); 453 } 454 455 if (curp.outfree) 456 (*curp.outfree)(curp.outdata); 457 mparse_free(curp.mp); 458 mchars_free(curp.mchars); 459 460 out: 461 if (search.argmode != ARG_FILE) { 462 manconf_free(&conf); 463 mansearch_free(res, sz); 464 mansearch_setup(0); 465 } 466 467 free(defos); 468 469 /* 470 * If a pager is attached, flush the pipe leading to it 471 * and signal end of file such that the user can browse 472 * to the end. Then wait for the user to close the pager. 473 */ 474 475 if (pager_pid != 0 && pager_pid != 1) { 476 fclose(stdout); 477 waitpid(pager_pid, NULL, 0); 478 } 479 480 return((int)rc); 481 } 482 483 static void 484 usage(enum argmode argmode) 485 { 486 487 switch (argmode) { 488 case ARG_FILE: 489 fputs("usage: mandoc [-acfhkl] [-I os=name] " 490 "[-K encoding] [-mformat] [-O option]\n" 491 "\t [-T output] [-W level] [file ...]\n", stderr); 492 break; 493 case ARG_NAME: 494 fputs("usage: man [-acfhklw] [-C file] [-I os=name] " 495 "[-K encoding] [-M path] [-m path]\n" 496 "\t [-O option=value] [-S subsection] [-s section] " 497 "[-T output] [-W level]\n" 498 "\t [section] name ...\n", stderr); 499 break; 500 case ARG_WORD: 501 fputs("usage: whatis [-acfhklw] [-C file] " 502 "[-M path] [-m path] [-O outkey] [-S arch]\n" 503 "\t [-s section] name ...\n", stderr); 504 break; 505 case ARG_EXPR: 506 fputs("usage: apropos [-acfhklw] [-C file] " 507 "[-M path] [-m path] [-O outkey] [-S arch]\n" 508 "\t [-s section] expression ...\n", stderr); 509 break; 510 } 511 exit((int)MANDOCLEVEL_BADARG); 512 } 513 514 static int 515 fs_lookup(const struct manpaths *paths, size_t ipath, 516 const char *sec, const char *arch, const char *name, 517 struct manpage **res, size_t *ressz) 518 { 519 glob_t globinfo; 520 struct manpage *page; 521 char *file; 522 int form, globres; 523 524 form = FORM_SRC; 525 mandoc_asprintf(&file, "%s/man%s/%s.%s", 526 paths->paths[ipath], sec, name, sec); 527 if (access(file, R_OK) != -1) 528 goto found; 529 free(file); 530 531 mandoc_asprintf(&file, "%s/cat%s/%s.0", 532 paths->paths[ipath], sec, name); 533 if (access(file, R_OK) != -1) { 534 form = FORM_CAT; 535 goto found; 536 } 537 free(file); 538 539 if (arch != NULL) { 540 mandoc_asprintf(&file, "%s/man%s/%s/%s.%s", 541 paths->paths[ipath], sec, arch, name, sec); 542 if (access(file, R_OK) != -1) 543 goto found; 544 free(file); 545 } 546 547 mandoc_asprintf(&file, "%s/man%s/%s.*", 548 paths->paths[ipath], sec, name); 549 globres = glob(file, 0, NULL, &globinfo); 550 if (globres != 0 && globres != GLOB_NOMATCH) 551 fprintf(stderr, "%s: %s: glob: %s\n", 552 progname, file, strerror(errno)); 553 free(file); 554 if (globres == 0) 555 file = mandoc_strdup(*globinfo.gl_pathv); 556 globfree(&globinfo); 557 if (globres != 0) 558 return(0); 559 560 found: 561 fprintf(stderr, "%s: outdated mandoc.db lacks %s(%s) entry, run " 562 "makewhatis %s\n", progname, name, sec, paths->paths[ipath]); 563 *res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage)); 564 page = *res + (*ressz - 1); 565 page->file = file; 566 page->names = NULL; 567 page->output = NULL; 568 page->ipath = ipath; 569 page->bits = NAME_FILE & NAME_MASK; 570 page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10; 571 page->form = form; 572 return(1); 573 } 574 575 static void 576 fs_search(const struct mansearch *cfg, const struct manpaths *paths, 577 int argc, char **argv, struct manpage **res, size_t *ressz) 578 { 579 const char *const sections[] = 580 {"1", "8", "6", "2", "3", "3p", "5", "7", "4", "9"}; 581 const size_t nsec = sizeof(sections)/sizeof(sections[0]); 582 583 size_t ipath, isec, lastsz; 584 585 assert(cfg->argmode == ARG_NAME); 586 587 *res = NULL; 588 *ressz = lastsz = 0; 589 while (argc) { 590 for (ipath = 0; ipath < paths->sz; ipath++) { 591 if (cfg->sec != NULL) { 592 if (fs_lookup(paths, ipath, cfg->sec, 593 cfg->arch, *argv, res, ressz) && 594 cfg->firstmatch) 595 return; 596 } else for (isec = 0; isec < nsec; isec++) 597 if (fs_lookup(paths, ipath, sections[isec], 598 cfg->arch, *argv, res, ressz) && 599 cfg->firstmatch) 600 return; 601 } 602 if (*ressz == lastsz) 603 fprintf(stderr, 604 "%s: No entry for %s in the manual.\n", 605 progname, *argv); 606 lastsz = *ressz; 607 argv++; 608 argc--; 609 } 610 } 611 612 static void 613 parse(struct curparse *curp, int fd, const char *file) 614 { 615 enum mandoclevel rctmp; 616 struct roff_man *man; 617 618 /* Begin by parsing the file itself. */ 619 620 assert(file); 621 assert(fd >= -1); 622 623 rctmp = mparse_readfd(curp->mp, fd, file); 624 if (rc < rctmp) 625 rc = rctmp; 626 627 /* 628 * With -Wstop and warnings or errors of at least the requested 629 * level, do not produce output. 630 */ 631 632 if (rctmp != MANDOCLEVEL_OK && curp->wstop) 633 return; 634 635 /* If unset, allocate output dev now (if applicable). */ 636 637 if ( ! (curp->outman && curp->outmdoc)) { 638 switch (curp->outtype) { 639 case OUTT_HTML: 640 curp->outdata = html_alloc(curp->mchars, 641 curp->outopts); 642 curp->outfree = html_free; 643 break; 644 case OUTT_UTF8: 645 curp->outdata = utf8_alloc(curp->mchars, 646 curp->outopts); 647 curp->outfree = ascii_free; 648 break; 649 case OUTT_LOCALE: 650 curp->outdata = locale_alloc(curp->mchars, 651 curp->outopts); 652 curp->outfree = ascii_free; 653 break; 654 case OUTT_ASCII: 655 curp->outdata = ascii_alloc(curp->mchars, 656 curp->outopts); 657 curp->outfree = ascii_free; 658 break; 659 case OUTT_PDF: 660 curp->outdata = pdf_alloc(curp->mchars, 661 curp->outopts); 662 curp->outfree = pspdf_free; 663 break; 664 case OUTT_PS: 665 curp->outdata = ps_alloc(curp->mchars, 666 curp->outopts); 667 curp->outfree = pspdf_free; 668 break; 669 default: 670 break; 671 } 672 673 switch (curp->outtype) { 674 case OUTT_HTML: 675 curp->outman = html_man; 676 curp->outmdoc = html_mdoc; 677 break; 678 case OUTT_TREE: 679 curp->outman = tree_man; 680 curp->outmdoc = tree_mdoc; 681 break; 682 case OUTT_MAN: 683 curp->outmdoc = man_mdoc; 684 curp->outman = man_man; 685 break; 686 case OUTT_PDF: 687 /* FALLTHROUGH */ 688 case OUTT_ASCII: 689 /* FALLTHROUGH */ 690 case OUTT_UTF8: 691 /* FALLTHROUGH */ 692 case OUTT_LOCALE: 693 /* FALLTHROUGH */ 694 case OUTT_PS: 695 curp->outman = terminal_man; 696 curp->outmdoc = terminal_mdoc; 697 break; 698 default: 699 break; 700 } 701 } 702 703 mparse_result(curp->mp, &man, NULL); 704 705 /* Execute the out device, if it exists. */ 706 707 if (man == NULL) 708 return; 709 if (curp->outmdoc != NULL && man->macroset == MACROSET_MDOC) 710 (*curp->outmdoc)(curp->outdata, man); 711 if (curp->outman != NULL && man->macroset == MACROSET_MAN) 712 (*curp->outman)(curp->outdata, man); 713 } 714 715 static void 716 passthrough(const char *file, int fd, int synopsis_only) 717 { 718 const char synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS"; 719 const char synr[] = "SYNOPSIS"; 720 721 FILE *stream; 722 const char *syscall; 723 char *line; 724 size_t len, off; 725 ssize_t nw; 726 int print; 727 728 fflush(stdout); 729 730 if ((stream = fdopen(fd, "r")) == NULL) { 731 close(fd); 732 syscall = "fdopen"; 733 goto fail; 734 } 735 736 print = 0; 737 while ((line = fgetln(stream, &len)) != NULL) { 738 if (synopsis_only) { 739 if (print) { 740 if ( ! isspace((unsigned char)*line)) 741 goto done; 742 while (len && 743 isspace((unsigned char)*line)) { 744 line++; 745 len--; 746 } 747 } else { 748 if ((len == sizeof(synb) && 749 ! strncmp(line, synb, len - 1)) || 750 (len == sizeof(synr) && 751 ! strncmp(line, synr, len - 1))) 752 print = 1; 753 continue; 754 } 755 } 756 for (off = 0; off < len; off += nw) 757 if ((nw = write(STDOUT_FILENO, line + off, 758 len - off)) == -1 || nw == 0) { 759 fclose(stream); 760 syscall = "write"; 761 goto fail; 762 } 763 } 764 765 if (ferror(stream)) { 766 fclose(stream); 767 syscall = "fgetln"; 768 goto fail; 769 } 770 771 done: 772 fclose(stream); 773 return; 774 775 fail: 776 fprintf(stderr, "%s: %s: SYSERR: %s: %s", 777 progname, file, syscall, strerror(errno)); 778 if (rc < MANDOCLEVEL_SYSERR) 779 rc = MANDOCLEVEL_SYSERR; 780 } 781 782 static int 783 koptions(int *options, char *arg) 784 { 785 786 if ( ! strcmp(arg, "utf-8")) { 787 *options |= MPARSE_UTF8; 788 *options &= ~MPARSE_LATIN1; 789 } else if ( ! strcmp(arg, "iso-8859-1")) { 790 *options |= MPARSE_LATIN1; 791 *options &= ~MPARSE_UTF8; 792 } else if ( ! strcmp(arg, "us-ascii")) { 793 *options &= ~(MPARSE_UTF8 | MPARSE_LATIN1); 794 } else { 795 fprintf(stderr, "%s: -K %s: Bad argument\n", 796 progname, arg); 797 return(0); 798 } 799 return(1); 800 } 801 802 static int 803 moptions(int *options, char *arg) 804 { 805 806 if (arg == NULL) 807 /* nothing to do */; 808 else if (0 == strcmp(arg, "doc")) 809 *options |= MPARSE_MDOC; 810 else if (0 == strcmp(arg, "andoc")) 811 /* nothing to do */; 812 else if (0 == strcmp(arg, "an")) 813 *options |= MPARSE_MAN; 814 else { 815 fprintf(stderr, "%s: -m %s: Bad argument\n", 816 progname, arg); 817 return(0); 818 } 819 820 return(1); 821 } 822 823 static int 824 toptions(struct curparse *curp, char *arg) 825 { 826 827 if (0 == strcmp(arg, "ascii")) 828 curp->outtype = OUTT_ASCII; 829 else if (0 == strcmp(arg, "lint")) { 830 curp->outtype = OUTT_LINT; 831 curp->wlevel = MANDOCLEVEL_WARNING; 832 } else if (0 == strcmp(arg, "tree")) 833 curp->outtype = OUTT_TREE; 834 else if (0 == strcmp(arg, "man")) 835 curp->outtype = OUTT_MAN; 836 else if (0 == strcmp(arg, "html")) 837 curp->outtype = OUTT_HTML; 838 else if (0 == strcmp(arg, "utf8")) 839 curp->outtype = OUTT_UTF8; 840 else if (0 == strcmp(arg, "locale")) 841 curp->outtype = OUTT_LOCALE; 842 else if (0 == strcmp(arg, "xhtml")) 843 curp->outtype = OUTT_HTML; 844 else if (0 == strcmp(arg, "ps")) 845 curp->outtype = OUTT_PS; 846 else if (0 == strcmp(arg, "pdf")) 847 curp->outtype = OUTT_PDF; 848 else { 849 fprintf(stderr, "%s: -T %s: Bad argument\n", 850 progname, arg); 851 return(0); 852 } 853 854 return(1); 855 } 856 857 static int 858 woptions(struct curparse *curp, char *arg) 859 { 860 char *v, *o; 861 const char *toks[7]; 862 863 toks[0] = "stop"; 864 toks[1] = "all"; 865 toks[2] = "warning"; 866 toks[3] = "error"; 867 toks[4] = "unsupp"; 868 toks[5] = "fatal"; 869 toks[6] = NULL; 870 871 while (*arg) { 872 o = arg; 873 switch (getsubopt(&arg, UNCONST(toks), &v)) { 874 case 0: 875 curp->wstop = 1; 876 break; 877 case 1: 878 /* FALLTHROUGH */ 879 case 2: 880 curp->wlevel = MANDOCLEVEL_WARNING; 881 break; 882 case 3: 883 curp->wlevel = MANDOCLEVEL_ERROR; 884 break; 885 case 4: 886 curp->wlevel = MANDOCLEVEL_UNSUPP; 887 break; 888 case 5: 889 curp->wlevel = MANDOCLEVEL_BADARG; 890 break; 891 default: 892 fprintf(stderr, "%s: -W %s: Bad argument\n", 893 progname, o); 894 return(0); 895 } 896 } 897 898 return(1); 899 } 900 901 static void 902 mmsg(enum mandocerr t, enum mandoclevel lvl, 903 const char *file, int line, int col, const char *msg) 904 { 905 const char *mparse_msg; 906 907 fprintf(stderr, "%s: %s:", progname, file); 908 909 if (line) 910 fprintf(stderr, "%d:%d:", line, col + 1); 911 912 fprintf(stderr, " %s", mparse_strlevel(lvl)); 913 914 if (NULL != (mparse_msg = mparse_strerror(t))) 915 fprintf(stderr, ": %s", mparse_msg); 916 917 if (msg) 918 fprintf(stderr, ": %s", msg); 919 920 fputc('\n', stderr); 921 } 922 923 static void 924 handle_sigpipe(int signum) 925 { 926 927 exit((int)rc); 928 } 929 930 static pid_t 931 spawn_pager(void) 932 { 933 #define MAX_PAGER_ARGS 16 934 char *argv[MAX_PAGER_ARGS]; 935 const char *pager; 936 char *cp; 937 int fildes[2]; 938 int argc; 939 pid_t pager_pid; 940 941 if (pipe(fildes) == -1) { 942 fprintf(stderr, "%s: pipe: %s\n", 943 progname, strerror(errno)); 944 return(0); 945 } 946 947 switch (pager_pid = fork()) { 948 case -1: 949 fprintf(stderr, "%s: fork: %s\n", 950 progname, strerror(errno)); 951 exit((int)MANDOCLEVEL_SYSERR); 952 case 0: 953 break; 954 default: 955 close(fildes[0]); 956 if (dup2(fildes[1], STDOUT_FILENO) == -1) { 957 fprintf(stderr, "%s: dup output: %s\n", 958 progname, strerror(errno)); 959 exit((int)MANDOCLEVEL_SYSERR); 960 } 961 close(fildes[1]); 962 signal(SIGPIPE, handle_sigpipe); 963 return(pager_pid); 964 } 965 966 /* The child process becomes the pager. */ 967 968 close(fildes[1]); 969 if (dup2(fildes[0], STDIN_FILENO) == -1) { 970 fprintf(stderr, "%s: dup input: %s\n", 971 progname, strerror(errno)); 972 exit((int)MANDOCLEVEL_SYSERR); 973 } 974 close(fildes[0]); 975 976 pager = getenv("MANPAGER"); 977 if (pager == NULL || *pager == '\0') 978 pager = getenv("PAGER"); 979 if (pager == NULL || *pager == '\0') 980 pager = "more -s"; 981 cp = mandoc_strdup(pager); 982 983 /* 984 * Parse the pager command into words. 985 * Intentionally do not do anything fancy here. 986 */ 987 988 argc = 0; 989 while (argc + 1 < MAX_PAGER_ARGS) { 990 argv[argc++] = cp; 991 cp = strchr(cp, ' '); 992 if (cp == NULL) 993 break; 994 *cp++ = '\0'; 995 while (*cp == ' ') 996 cp++; 997 if (*cp == '\0') 998 break; 999 } 1000 argv[argc] = NULL; 1001 1002 /* Hand over to the pager. */ 1003 1004 execvp(argv[0], argv); 1005 fprintf(stderr, "%s: exec %s: %s\n", 1006 progname, argv[0], strerror(errno)); 1007 exit((int)MANDOCLEVEL_SYSERR); 1008 } 1009