1 /* $Vendor-Id: main.c,v 1.59 2010/01/29 14:39:38 kristaps Exp $ */ 2 /* 3 * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 #ifdef HAVE_CONFIG_H 18 #include "config.h" 19 #endif 20 21 #include <sys/stat.h> 22 23 #include <assert.h> 24 #include <fcntl.h> 25 #include <stdio.h> 26 #include <stdint.h> 27 #include <stdlib.h> 28 #include <string.h> 29 #include <unistd.h> 30 31 #include "mdoc.h" 32 #include "man.h" 33 #include "main.h" 34 35 #define UNCONST(a) ((void *)(uintptr_t)(const void *)(a)) 36 37 /* FIXME: Intel's compiler? LLVM? pcc? */ 38 39 #if !defined(__GNUC__) || (__GNUC__ < 2) 40 # if !defined(lint) 41 # define __attribute__(x) 42 # endif 43 #endif /* !defined(__GNUC__) || (__GNUC__ < 2) */ 44 45 typedef void (*out_mdoc)(void *, const struct mdoc *); 46 typedef void (*out_man)(void *, const struct man *); 47 typedef void (*out_free)(void *); 48 49 struct buf { 50 char *buf; 51 size_t sz; 52 }; 53 54 enum intt { 55 INTT_AUTO, 56 INTT_MDOC, 57 INTT_MAN 58 }; 59 60 enum outt { 61 OUTT_ASCII = 0, 62 OUTT_TREE, 63 OUTT_HTML, 64 OUTT_XHTML, 65 OUTT_LINT 66 }; 67 68 struct curparse { 69 const char *file; /* Current parse. */ 70 int fd; /* Current parse. */ 71 int wflags; 72 #define WARN_WALL (1 << 0) /* All-warnings mask. */ 73 #define WARN_WERR (1 << 2) /* Warnings->errors. */ 74 int fflags; 75 #define IGN_SCOPE (1 << 0) /* Ignore scope errors. */ 76 #define NO_IGN_ESCAPE (1 << 1) /* Don't ignore bad escapes. */ 77 #define NO_IGN_MACRO (1 << 2) /* Don't ignore bad macros. */ 78 #define NO_IGN_CHARS (1 << 3) /* Don't ignore bad chars. */ 79 #define IGN_ERRORS (1 << 4) /* Ignore failed parse. */ 80 enum intt inttype; /* Input parsers... */ 81 struct man *man; 82 struct man *lastman; 83 struct mdoc *mdoc; 84 struct mdoc *lastmdoc; 85 enum outt outtype; /* Output devices... */ 86 out_mdoc outmdoc; 87 out_man outman; 88 out_free outfree; 89 void *outdata; 90 char outopts[BUFSIZ]; 91 }; 92 93 static int foptions(int *, char *); 94 static int toptions(enum outt *, char *); 95 static int moptions(enum intt *, char *); 96 static int woptions(int *, char *); 97 static int merr(void *, int, int, const char *); 98 static int mwarn(void *, int, int, const char *); 99 static int ffile(struct buf *, struct buf *, 100 const char *, struct curparse *); 101 static int fdesc(struct buf *, struct buf *, 102 struct curparse *); 103 static int pset(const char *, int, struct curparse *, 104 struct man **, struct mdoc **); 105 static struct man *man_init(struct curparse *); 106 static struct mdoc *mdoc_init(struct curparse *); 107 static void version(void) __attribute__((noreturn)); 108 static void usage(void) __attribute__((noreturn)); 109 110 static const char *progname; 111 112 113 int 114 main(int argc, char *argv[]) 115 { 116 int c, rc; 117 struct buf ln, blk; 118 struct curparse curp; 119 120 progname = strrchr(argv[0], '/'); 121 if (progname == NULL) 122 progname = argv[0]; 123 else 124 ++progname; 125 126 memset(&curp, 0, sizeof(struct curparse)); 127 128 curp.inttype = INTT_AUTO; 129 curp.outtype = OUTT_ASCII; 130 131 /* LINTED */ 132 while (-1 != (c = getopt(argc, argv, "f:m:O:T:VW:"))) 133 switch (c) { 134 case ('f'): 135 if ( ! foptions(&curp.fflags, optarg)) 136 return(EXIT_FAILURE); 137 break; 138 case ('m'): 139 if ( ! moptions(&curp.inttype, optarg)) 140 return(EXIT_FAILURE); 141 break; 142 case ('O'): 143 (void)strlcat(curp.outopts, optarg, BUFSIZ); 144 (void)strlcat(curp.outopts, ",", BUFSIZ); 145 break; 146 case ('T'): 147 if ( ! toptions(&curp.outtype, optarg)) 148 return(EXIT_FAILURE); 149 break; 150 case ('W'): 151 if ( ! woptions(&curp.wflags, optarg)) 152 return(EXIT_FAILURE); 153 break; 154 case ('V'): 155 version(); 156 /* NOTREACHED */ 157 default: 158 usage(); 159 /* NOTREACHED */ 160 } 161 162 argc -= optind; 163 argv += optind; 164 165 memset(&ln, 0, sizeof(struct buf)); 166 memset(&blk, 0, sizeof(struct buf)); 167 168 rc = 1; 169 170 if (NULL == *argv) { 171 curp.file = "<stdin>"; 172 curp.fd = STDIN_FILENO; 173 174 c = fdesc(&blk, &ln, &curp); 175 if ( ! (IGN_ERRORS & curp.fflags)) 176 rc = 1 == c ? 1 : 0; 177 else 178 rc = -1 == c ? 0 : 1; 179 } 180 181 while (rc && *argv) { 182 c = ffile(&blk, &ln, *argv, &curp); 183 if ( ! (IGN_ERRORS & curp.fflags)) 184 rc = 1 == c ? 1 : 0; 185 else 186 rc = -1 == c ? 0 : 1; 187 188 argv++; 189 if (*argv && rc) { 190 if (curp.lastman) 191 man_reset(curp.lastman); 192 if (curp.lastmdoc) 193 mdoc_reset(curp.lastmdoc); 194 curp.lastman = NULL; 195 curp.lastmdoc = NULL; 196 } 197 } 198 199 if (blk.buf) 200 free(blk.buf); 201 if (ln.buf) 202 free(ln.buf); 203 if (curp.outfree) 204 (*curp.outfree)(curp.outdata); 205 if (curp.mdoc) 206 mdoc_free(curp.mdoc); 207 if (curp.man) 208 man_free(curp.man); 209 210 return(rc ? EXIT_SUCCESS : EXIT_FAILURE); 211 } 212 213 214 static void 215 version(void) 216 { 217 218 (void)printf("%s %s\n", progname, VERSION); 219 exit(EXIT_SUCCESS); 220 } 221 222 223 static void 224 usage(void) 225 { 226 227 (void)fprintf(stderr, "usage: %s [-V] [-foption...] " 228 "[-mformat] [-Ooption] [-Toutput] " 229 "[-Werr...]\n", progname); 230 exit(EXIT_FAILURE); 231 } 232 233 234 static struct man * 235 man_init(struct curparse *curp) 236 { 237 int pflags; 238 struct man_cb mancb; 239 240 mancb.man_err = merr; 241 mancb.man_warn = mwarn; 242 243 /* Defaults from mandoc.1. */ 244 245 pflags = MAN_IGN_MACRO | MAN_IGN_ESCAPE | MAN_IGN_CHARS; 246 247 if (curp->fflags & NO_IGN_MACRO) 248 pflags &= ~MAN_IGN_MACRO; 249 if (curp->fflags & NO_IGN_CHARS) 250 pflags &= ~MAN_IGN_CHARS; 251 if (curp->fflags & NO_IGN_ESCAPE) 252 pflags &= ~MAN_IGN_ESCAPE; 253 254 return(man_alloc(curp, pflags, &mancb)); 255 } 256 257 258 static struct mdoc * 259 mdoc_init(struct curparse *curp) 260 { 261 int pflags; 262 struct mdoc_cb mdoccb; 263 264 mdoccb.mdoc_err = merr; 265 mdoccb.mdoc_warn = mwarn; 266 267 /* Defaults from mandoc.1. */ 268 269 pflags = MDOC_IGN_MACRO | MDOC_IGN_ESCAPE | MDOC_IGN_CHARS; 270 271 if (curp->fflags & IGN_SCOPE) 272 pflags |= MDOC_IGN_SCOPE; 273 if (curp->fflags & NO_IGN_ESCAPE) 274 pflags &= ~MDOC_IGN_ESCAPE; 275 if (curp->fflags & NO_IGN_MACRO) 276 pflags &= ~MDOC_IGN_MACRO; 277 if (curp->fflags & NO_IGN_CHARS) 278 pflags &= ~MDOC_IGN_CHARS; 279 280 return(mdoc_alloc(curp, pflags, &mdoccb)); 281 } 282 283 284 static int 285 ffile(struct buf *blk, struct buf *ln, 286 const char *file, struct curparse *curp) 287 { 288 int c; 289 290 curp->file = file; 291 if (-1 == (curp->fd = open(curp->file, O_RDONLY, 0))) { 292 perror(curp->file); 293 return(-1); 294 } 295 296 c = fdesc(blk, ln, curp); 297 298 if (-1 == close(curp->fd)) 299 perror(curp->file); 300 301 return(c); 302 } 303 304 305 static int 306 fdesc(struct buf *blk, struct buf *ln, struct curparse *curp) 307 { 308 size_t sz; 309 ssize_t ssz; 310 struct stat st; 311 int j, i, pos, lnn, comment; 312 struct man *man; 313 struct mdoc *mdoc; 314 315 sz = BUFSIZ; 316 man = NULL; 317 mdoc = NULL; 318 319 /* 320 * Two buffers: ln and buf. buf is the input buffer optimised 321 * here for each file's block size. ln is a line buffer. Both 322 * growable, hence passed in by ptr-ptr. 323 */ 324 325 if (-1 == fstat(curp->fd, &st)) 326 perror(curp->file); 327 else if ((size_t)st.st_blksize > sz) 328 sz = st.st_blksize; 329 330 if (sz > blk->sz) { 331 blk->buf = realloc(blk->buf, sz); 332 if (NULL == blk->buf) { 333 perror(NULL); 334 exit(EXIT_FAILURE); 335 } 336 blk->sz = sz; 337 } 338 339 /* Fill buf with file blocksize. */ 340 341 for (lnn = pos = comment = 0; ; ) { 342 if (-1 == (ssz = read(curp->fd, blk->buf, sz))) { 343 perror(curp->file); 344 return(-1); 345 } else if (0 == ssz) 346 break; 347 348 /* Parse the read block into partial or full lines. */ 349 350 for (i = 0; i < (int)ssz; i++) { 351 if (pos >= (int)ln->sz) { 352 ln->sz += 256; /* Step-size. */ 353 ln->buf = realloc(ln->buf, ln->sz); 354 if (NULL == ln->buf) { 355 perror(NULL); 356 return(EXIT_FAILURE); 357 } 358 } 359 360 if ('\n' != blk->buf[i]) { 361 if (comment) 362 continue; 363 ln->buf[pos++] = blk->buf[i]; 364 365 /* Handle in-line `\"' comments. */ 366 367 if (1 == pos || '\"' != ln->buf[pos - 1]) 368 continue; 369 370 for (j = pos - 2; j >= 0; j--) 371 if ('\\' != ln->buf[j]) 372 break; 373 374 if ( ! ((pos - 2 - j) % 2)) 375 continue; 376 377 comment = 1; 378 pos -= 2; 379 continue; 380 } 381 382 /* Handle escaped `\\n' newlines. */ 383 384 if (pos > 0 && 0 == comment && 385 '\\' == ln->buf[pos - 1]) { 386 for (j = pos - 1; j >= 0; j--) 387 if ('\\' != ln->buf[j]) 388 break; 389 if ( ! ((pos - j) % 2)) { 390 pos--; 391 lnn++; 392 continue; 393 } 394 } 395 396 ln->buf[pos] = 0; 397 lnn++; 398 399 /* If unset, assign parser in pset(). */ 400 401 if ( ! (man || mdoc) && ! pset(ln->buf, 402 pos, curp, &man, &mdoc)) 403 return(-1); 404 405 pos = comment = 0; 406 407 /* Pass down into parsers. */ 408 409 if (man && ! man_parseln(man, lnn, ln->buf)) 410 return(0); 411 if (mdoc && ! mdoc_parseln(mdoc, lnn, ln->buf)) 412 return(0); 413 } 414 } 415 416 /* NOTE a parser may not have been assigned, yet. */ 417 418 if ( ! (man || mdoc)) { 419 fprintf(stderr, "%s: Not a manual\n", curp->file); 420 return(0); 421 } 422 423 if (mdoc && ! mdoc_endparse(mdoc)) 424 return(0); 425 if (man && ! man_endparse(man)) 426 return(0); 427 428 /* If unset, allocate output dev now (if applicable). */ 429 430 if ( ! (curp->outman && curp->outmdoc)) { 431 switch (curp->outtype) { 432 case (OUTT_XHTML): 433 curp->outdata = xhtml_alloc(curp->outopts); 434 curp->outman = html_man; 435 curp->outmdoc = html_mdoc; 436 curp->outfree = html_free; 437 break; 438 case (OUTT_HTML): 439 curp->outdata = html_alloc(curp->outopts); 440 curp->outman = html_man; 441 curp->outmdoc = html_mdoc; 442 curp->outfree = html_free; 443 break; 444 case (OUTT_TREE): 445 curp->outman = tree_man; 446 curp->outmdoc = tree_mdoc; 447 break; 448 case (OUTT_LINT): 449 break; 450 default: 451 curp->outdata = ascii_alloc(); 452 curp->outman = terminal_man; 453 curp->outmdoc = terminal_mdoc; 454 curp->outfree = terminal_free; 455 break; 456 } 457 } 458 459 /* Execute the out device, if it exists. */ 460 461 if (man && curp->outman) 462 (*curp->outman)(curp->outdata, man); 463 if (mdoc && curp->outmdoc) 464 (*curp->outmdoc)(curp->outdata, mdoc); 465 466 return(1); 467 } 468 469 470 static int 471 pset(const char *buf, int pos, struct curparse *curp, 472 struct man **man, struct mdoc **mdoc) 473 { 474 int i; 475 476 /* 477 * Try to intuit which kind of manual parser should be used. If 478 * passed in by command-line (-man, -mdoc), then use that 479 * explicitly. If passed as -mandoc, then try to guess from the 480 * line: either skip dot-lines, use -mdoc when finding `.Dt', or 481 * default to -man, which is more lenient. 482 */ 483 484 if (buf[0] == '.') { 485 for (i = 1; buf[i]; i++) 486 if (' ' != buf[i] && '\t' != buf[i]) 487 break; 488 if (0 == buf[i]) 489 return(1); 490 } 491 492 switch (curp->inttype) { 493 case (INTT_MDOC): 494 if (NULL == curp->mdoc) 495 curp->mdoc = mdoc_init(curp); 496 if (NULL == (*mdoc = curp->mdoc)) 497 return(0); 498 curp->lastmdoc = *mdoc; 499 return(1); 500 case (INTT_MAN): 501 if (NULL == curp->man) 502 curp->man = man_init(curp); 503 if (NULL == (*man = curp->man)) 504 return(0); 505 curp->lastman = *man; 506 return(1); 507 default: 508 break; 509 } 510 511 if (pos >= 3 && 0 == memcmp(buf, ".Dd", 3)) { 512 if (NULL == curp->mdoc) 513 curp->mdoc = mdoc_init(curp); 514 if (NULL == (*mdoc = curp->mdoc)) 515 return(0); 516 curp->lastmdoc = *mdoc; 517 return(1); 518 } 519 520 if (NULL == curp->man) 521 curp->man = man_init(curp); 522 if (NULL == (*man = curp->man)) 523 return(0); 524 curp->lastman = *man; 525 return(1); 526 } 527 528 529 static int 530 moptions(enum intt *tflags, char *arg) 531 { 532 533 if (0 == strcmp(arg, "doc")) 534 *tflags = INTT_MDOC; 535 else if (0 == strcmp(arg, "andoc")) 536 *tflags = INTT_AUTO; 537 else if (0 == strcmp(arg, "an")) 538 *tflags = INTT_MAN; 539 else { 540 fprintf(stderr, "%s: Bad argument\n", arg); 541 return(0); 542 } 543 544 return(1); 545 } 546 547 548 static int 549 toptions(enum outt *tflags, char *arg) 550 { 551 552 if (0 == strcmp(arg, "ascii")) 553 *tflags = OUTT_ASCII; 554 else if (0 == strcmp(arg, "lint")) 555 *tflags = OUTT_LINT; 556 else if (0 == strcmp(arg, "tree")) 557 *tflags = OUTT_TREE; 558 else if (0 == strcmp(arg, "html")) 559 *tflags = OUTT_HTML; 560 else if (0 == strcmp(arg, "xhtml")) 561 *tflags = OUTT_XHTML; 562 else { 563 fprintf(stderr, "%s: Bad argument\n", arg); 564 return(0); 565 } 566 567 return(1); 568 } 569 570 571 static int 572 foptions(int *fflags, char *arg) 573 { 574 char *v, *o; 575 const char *toks[8]; 576 577 toks[0] = "ign-scope"; 578 toks[1] = "no-ign-escape"; 579 toks[2] = "no-ign-macro"; 580 toks[3] = "no-ign-chars"; 581 toks[4] = "ign-errors"; 582 toks[5] = "strict"; 583 toks[6] = "ign-escape"; 584 toks[7] = NULL; 585 586 while (*arg) { 587 o = arg; 588 switch (getsubopt(&arg, UNCONST(toks), &v)) { 589 case (0): 590 *fflags |= IGN_SCOPE; 591 break; 592 case (1): 593 *fflags |= NO_IGN_ESCAPE; 594 break; 595 case (2): 596 *fflags |= NO_IGN_MACRO; 597 break; 598 case (3): 599 *fflags |= NO_IGN_CHARS; 600 break; 601 case (4): 602 *fflags |= IGN_ERRORS; 603 break; 604 case (5): 605 *fflags |= NO_IGN_ESCAPE | 606 NO_IGN_MACRO | NO_IGN_CHARS; 607 break; 608 case (6): 609 *fflags &= ~NO_IGN_ESCAPE; 610 break; 611 default: 612 fprintf(stderr, "%s: Bad argument\n", o); 613 return(0); 614 } 615 } 616 617 return(1); 618 } 619 620 621 static int 622 woptions(int *wflags, char *arg) 623 { 624 char *v, *o; 625 const char *toks[3]; 626 627 toks[0] = "all"; 628 toks[1] = "error"; 629 toks[2] = NULL; 630 631 while (*arg) { 632 o = arg; 633 switch (getsubopt(&arg, UNCONST(toks), &v)) { 634 case (0): 635 *wflags |= WARN_WALL; 636 break; 637 case (1): 638 *wflags |= WARN_WERR; 639 break; 640 default: 641 fprintf(stderr, "%s: Bad argument\n", o); 642 return(0); 643 } 644 } 645 646 return(1); 647 } 648 649 650 /* ARGSUSED */ 651 static int 652 merr(void *arg, int line, int col, const char *msg) 653 { 654 struct curparse *curp; 655 656 curp = (struct curparse *)arg; 657 658 (void)fprintf(stderr, "%s:%d:%d: error: %s\n", 659 curp->file, line, col + 1, msg); 660 661 return(0); 662 } 663 664 665 static int 666 mwarn(void *arg, int line, int col, const char *msg) 667 { 668 struct curparse *curp; 669 670 curp = (struct curparse *)arg; 671 672 if ( ! (curp->wflags & WARN_WALL)) 673 return(1); 674 675 (void)fprintf(stderr, "%s:%d:%d: warning: %s\n", 676 curp->file, line, col + 1, msg); 677 678 if ( ! (curp->wflags & WARN_WERR)) 679 return(1); 680 681 return(0); 682 } 683 684