1 /* $Vendor-Id: main.c,v 1.57 2009/11/02 08:29:25 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 #include <sys/stat.h> 18 19 #include <assert.h> 20 #include <fcntl.h> 21 #include <stdio.h> 22 #include <stdint.h> 23 #include <stdlib.h> 24 #include <string.h> 25 #include <unistd.h> 26 27 #include "mdoc.h" 28 #include "man.h" 29 #include "main.h" 30 31 #define UNCONST(a) ((void *)(uintptr_t)(const void *)(a)) 32 33 /* FIXME: Intel's compiler? LLVM? pcc? */ 34 35 #if !defined(__GNUC__) || (__GNUC__ < 2) 36 # if !defined(lint) 37 # define __attribute__(x) 38 # endif 39 #endif /* !defined(__GNUC__) || (__GNUC__ < 2) */ 40 41 #ifdef __linux__ 42 extern int getsubopt(char **, char * const *, char **); 43 extern size_t strlcat(char *, const char *, size_t); 44 #endif 45 46 typedef void (*out_mdoc)(void *, const struct mdoc *); 47 typedef void (*out_man)(void *, const struct man *); 48 typedef void (*out_free)(void *); 49 50 struct buf { 51 char *buf; 52 size_t sz; 53 }; 54 55 enum intt { 56 INTT_AUTO, 57 INTT_MDOC, 58 INTT_MAN 59 }; 60 61 enum outt { 62 OUTT_ASCII = 0, 63 OUTT_TREE, 64 OUTT_HTML, 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_HTML): 433 curp->outdata = html_alloc(curp->outopts); 434 curp->outman = html_man; 435 curp->outmdoc = html_mdoc; 436 curp->outfree = html_free; 437 break; 438 case (OUTT_TREE): 439 curp->outman = tree_man; 440 curp->outmdoc = tree_mdoc; 441 break; 442 case (OUTT_LINT): 443 break; 444 default: 445 curp->outdata = ascii_alloc(); 446 curp->outman = terminal_man; 447 curp->outmdoc = terminal_mdoc; 448 curp->outfree = terminal_free; 449 break; 450 } 451 } 452 453 /* Execute the out device, if it exists. */ 454 455 if (man && curp->outman) 456 (*curp->outman)(curp->outdata, man); 457 if (mdoc && curp->outmdoc) 458 (*curp->outmdoc)(curp->outdata, mdoc); 459 460 return(1); 461 } 462 463 464 static int 465 pset(const char *buf, int pos, struct curparse *curp, 466 struct man **man, struct mdoc **mdoc) 467 { 468 int i; 469 470 /* 471 * Try to intuit which kind of manual parser should be used. If 472 * passed in by command-line (-man, -mdoc), then use that 473 * explicitly. If passed as -mandoc, then try to guess from the 474 * line: either skip dot-lines, use -mdoc when finding `.Dt', or 475 * default to -man, which is more lenient. 476 */ 477 478 if (buf[0] == '.') { 479 for (i = 1; buf[i]; i++) 480 if (' ' != buf[i] && '\t' != buf[i]) 481 break; 482 if (0 == buf[i]) 483 return(1); 484 } 485 486 switch (curp->inttype) { 487 case (INTT_MDOC): 488 if (NULL == curp->mdoc) 489 curp->mdoc = mdoc_init(curp); 490 if (NULL == (*mdoc = curp->mdoc)) 491 return(0); 492 curp->lastmdoc = *mdoc; 493 return(1); 494 case (INTT_MAN): 495 if (NULL == curp->man) 496 curp->man = man_init(curp); 497 if (NULL == (*man = curp->man)) 498 return(0); 499 curp->lastman = *man; 500 return(1); 501 default: 502 break; 503 } 504 505 if (pos >= 3 && 0 == memcmp(buf, ".Dd", 3)) { 506 if (NULL == curp->mdoc) 507 curp->mdoc = mdoc_init(curp); 508 if (NULL == (*mdoc = curp->mdoc)) 509 return(0); 510 curp->lastmdoc = *mdoc; 511 return(1); 512 } 513 514 if (NULL == curp->man) 515 curp->man = man_init(curp); 516 if (NULL == (*man = curp->man)) 517 return(0); 518 curp->lastman = *man; 519 return(1); 520 } 521 522 523 static int 524 moptions(enum intt *tflags, char *arg) 525 { 526 527 if (0 == strcmp(arg, "doc")) 528 *tflags = INTT_MDOC; 529 else if (0 == strcmp(arg, "andoc")) 530 *tflags = INTT_AUTO; 531 else if (0 == strcmp(arg, "an")) 532 *tflags = INTT_MAN; 533 else { 534 fprintf(stderr, "%s: Bad argument\n", arg); 535 return(0); 536 } 537 538 return(1); 539 } 540 541 542 static int 543 toptions(enum outt *tflags, char *arg) 544 { 545 546 if (0 == strcmp(arg, "ascii")) 547 *tflags = OUTT_ASCII; 548 else if (0 == strcmp(arg, "lint")) 549 *tflags = OUTT_LINT; 550 else if (0 == strcmp(arg, "tree")) 551 *tflags = OUTT_TREE; 552 else if (0 == strcmp(arg, "html")) 553 *tflags = OUTT_HTML; 554 else { 555 fprintf(stderr, "%s: Bad argument\n", arg); 556 return(0); 557 } 558 559 return(1); 560 } 561 562 563 static int 564 foptions(int *fflags, char *arg) 565 { 566 char *v, *o; 567 const char *toks[8]; 568 569 toks[0] = "ign-scope"; 570 toks[1] = "no-ign-escape"; 571 toks[2] = "no-ign-macro"; 572 toks[3] = "no-ign-chars"; 573 toks[4] = "ign-errors"; 574 toks[5] = "strict"; 575 toks[6] = "ign-escape"; 576 toks[7] = NULL; 577 578 while (*arg) { 579 o = arg; 580 switch (getsubopt(&arg, UNCONST(toks), &v)) { 581 case (0): 582 *fflags |= IGN_SCOPE; 583 break; 584 case (1): 585 *fflags |= NO_IGN_ESCAPE; 586 break; 587 case (2): 588 *fflags |= NO_IGN_MACRO; 589 break; 590 case (3): 591 *fflags |= NO_IGN_CHARS; 592 break; 593 case (4): 594 *fflags |= IGN_ERRORS; 595 break; 596 case (5): 597 *fflags |= NO_IGN_ESCAPE | 598 NO_IGN_MACRO | NO_IGN_CHARS; 599 break; 600 case (6): 601 *fflags &= ~NO_IGN_ESCAPE; 602 break; 603 default: 604 fprintf(stderr, "%s: Bad argument\n", o); 605 return(0); 606 } 607 } 608 609 return(1); 610 } 611 612 613 static int 614 woptions(int *wflags, char *arg) 615 { 616 char *v, *o; 617 const char *toks[3]; 618 619 toks[0] = "all"; 620 toks[1] = "error"; 621 toks[2] = NULL; 622 623 while (*arg) { 624 o = arg; 625 switch (getsubopt(&arg, UNCONST(toks), &v)) { 626 case (0): 627 *wflags |= WARN_WALL; 628 break; 629 case (1): 630 *wflags |= WARN_WERR; 631 break; 632 default: 633 fprintf(stderr, "%s: Bad argument\n", o); 634 return(0); 635 } 636 } 637 638 return(1); 639 } 640 641 642 /* ARGSUSED */ 643 static int 644 merr(void *arg, int line, int col, const char *msg) 645 { 646 struct curparse *curp; 647 648 curp = (struct curparse *)arg; 649 650 (void)fprintf(stderr, "%s:%d:%d: error: %s\n", 651 curp->file, line, col + 1, msg); 652 653 return(0); 654 } 655 656 657 static int 658 mwarn(void *arg, int line, int col, const char *msg) 659 { 660 struct curparse *curp; 661 662 curp = (struct curparse *)arg; 663 664 if ( ! (curp->wflags & WARN_WALL)) 665 return(1); 666 667 (void)fprintf(stderr, "%s:%d:%d: warning: %s\n", 668 curp->file, line, col + 1, msg); 669 670 if ( ! (curp->wflags & WARN_WERR)) 671 return(1); 672 673 return(0); 674 } 675 676