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