1 /* $Id: read.c,v 1.9 2012/07/12 15:09:50 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 #include <sys/stat.h> 19 #include <sys/mman.h> 20 21 #include <assert.h> 22 #include <ctype.h> 23 #include <fcntl.h> 24 #include <stdarg.h> 25 #include <stdio.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <unistd.h> 29 30 #include "mandoc.h" 31 #include "libmandoc.h" 32 #include "mdoc.h" 33 #include "man.h" 34 35 #define REPARSE_LIMIT 1000 36 37 struct buf { 38 char *buf; /* binary input buffer */ 39 size_t sz; /* size of binary buffer */ 40 }; 41 42 struct mparse { 43 enum mandoclevel file_status; /* status of current parse */ 44 enum mandoclevel wlevel; /* ignore messages below this */ 45 int line; /* line number in the file */ 46 enum mparset inttype; /* which parser to use */ 47 struct man *pman; /* persistent man parser */ 48 struct mdoc *pmdoc; /* persistent mdoc parser */ 49 struct man *man; /* man parser */ 50 struct mdoc *mdoc; /* mdoc parser */ 51 struct roff *roff; /* roff parser (!NULL) */ 52 int reparse_count; /* finite interp. stack */ 53 mandocmsg mmsg; /* warning/error message handler */ 54 void *arg; /* argument to mmsg */ 55 const char *file; 56 struct buf *secondary; 57 char *defos; /* default operating system */ 58 }; 59 60 static void resize_buf(struct buf *, size_t); 61 static void mparse_buf_r(struct mparse *, struct buf, int); 62 static void mparse_readfd_r(struct mparse *, int, const char *, int); 63 static void pset(const char *, int, struct mparse *); 64 static void pdesc(struct mparse *, const char *, int); 65 static int read_whole_file(const char *, int, struct buf *, int *); 66 static void mparse_end(struct mparse *); 67 68 static const enum mandocerr mandoclimits[MANDOCLEVEL_MAX] = { 69 MANDOCERR_OK, 70 MANDOCERR_WARNING, 71 MANDOCERR_WARNING, 72 MANDOCERR_ERROR, 73 MANDOCERR_FATAL, 74 MANDOCERR_MAX, 75 MANDOCERR_MAX 76 }; 77 78 static const char * const mandocerrs[MANDOCERR_MAX] = { 79 "ok", 80 81 "generic warning", 82 83 /* related to the prologue */ 84 "no title in document", 85 "document title should be all caps", 86 "unknown manual section", 87 "date missing, using today's date", 88 "cannot parse date, using it verbatim", 89 "prologue macros out of order", 90 "duplicate prologue macro", 91 "macro not allowed in prologue", 92 "macro not allowed in body", 93 94 /* related to document structure */ 95 ".so is fragile, better use ln(1)", 96 "NAME section must come first", 97 "bad NAME section contents", 98 "sections out of conventional order", 99 "duplicate section name", 100 "section not in conventional manual section", 101 102 /* related to macros and nesting */ 103 "skipping obsolete macro", 104 "skipping paragraph macro", 105 "skipping no-space macro", 106 "blocks badly nested", 107 "child violates parent syntax", 108 "nested displays are not portable", 109 "already in literal mode", 110 "line scope broken", 111 112 /* related to missing macro arguments */ 113 "skipping empty macro", 114 "argument count wrong", 115 "missing display type", 116 "list type must come first", 117 "tag lists require a width argument", 118 "missing font type", 119 "skipping end of block that is not open", 120 121 /* related to bad macro arguments */ 122 "skipping argument", 123 "duplicate argument", 124 "duplicate display type", 125 "duplicate list type", 126 "unknown AT&T UNIX version", 127 "bad Boolean value", 128 "unknown font", 129 "unknown standard specifier", 130 "bad width argument", 131 132 /* related to plain text */ 133 "blank line in non-literal context", 134 "tab in non-literal context", 135 "end of line whitespace", 136 "bad comment style", 137 "bad escape sequence", 138 "unterminated quoted string", 139 140 /* related to equations */ 141 "unexpected literal in equation", 142 143 "generic error", 144 145 /* related to equations */ 146 "unexpected equation scope closure", 147 "equation scope open on exit", 148 "overlapping equation scopes", 149 "unexpected end of equation", 150 "equation syntax error", 151 152 /* related to tables */ 153 "bad table syntax", 154 "bad table option", 155 "bad table layout", 156 "no table layout cells specified", 157 "no table data cells specified", 158 "ignore data in cell", 159 "data block still open", 160 "ignoring extra data cells", 161 162 "input stack limit exceeded, infinite loop?", 163 "skipping bad character", 164 "escaped character not allowed in a name", 165 "manual name not yet set", 166 "skipping text before the first section header", 167 "skipping unknown macro", 168 "NOT IMPLEMENTED, please use groff: skipping request", 169 "argument count wrong", 170 "skipping end of block that is not open", 171 "missing end of block", 172 "scope open on exit", 173 "uname(3) system call failed", 174 "macro requires line argument(s)", 175 "macro requires body argument(s)", 176 "macro requires argument(s)", 177 "missing list type", 178 "line argument(s) will be lost", 179 "body argument(s) will be lost", 180 181 "generic fatal error", 182 183 "not a manual", 184 "column syntax is inconsistent", 185 "NOT IMPLEMENTED: .Bd -file", 186 "argument count wrong, violates syntax", 187 "child violates parent syntax", 188 "argument count wrong, violates syntax", 189 "NOT IMPLEMENTED: .so with absolute path or \"..\"", 190 "no document body", 191 "no document prologue", 192 "static buffer exhausted", 193 }; 194 195 static const char * const mandoclevels[MANDOCLEVEL_MAX] = { 196 "SUCCESS", 197 "RESERVED", 198 "WARNING", 199 "ERROR", 200 "FATAL", 201 "BADARG", 202 "SYSERR" 203 }; 204 205 static void 206 resize_buf(struct buf *buf, size_t initial) 207 { 208 209 buf->sz = buf->sz > initial/2 ? 2 * buf->sz : initial; 210 buf->buf = mandoc_realloc(buf->buf, buf->sz); 211 } 212 213 static void 214 pset(const char *buf, int pos, struct mparse *curp) 215 { 216 int i; 217 218 /* 219 * Try to intuit which kind of manual parser should be used. If 220 * passed in by command-line (-man, -mdoc), then use that 221 * explicitly. If passed as -mandoc, then try to guess from the 222 * line: either skip dot-lines, use -mdoc when finding `.Dt', or 223 * default to -man, which is more lenient. 224 * 225 * Separate out pmdoc/pman from mdoc/man: the first persists 226 * through all parsers, while the latter is used per-parse. 227 */ 228 229 if ('.' == buf[0] || '\'' == buf[0]) { 230 for (i = 1; buf[i]; i++) 231 if (' ' != buf[i] && '\t' != buf[i]) 232 break; 233 if ('\0' == buf[i]) 234 return; 235 } 236 237 switch (curp->inttype) { 238 case (MPARSE_MDOC): 239 if (NULL == curp->pmdoc) 240 curp->pmdoc = mdoc_alloc(curp->roff, curp, 241 curp->defos); 242 assert(curp->pmdoc); 243 curp->mdoc = curp->pmdoc; 244 return; 245 case (MPARSE_MAN): 246 if (NULL == curp->pman) 247 curp->pman = man_alloc(curp->roff, curp); 248 assert(curp->pman); 249 curp->man = curp->pman; 250 return; 251 default: 252 break; 253 } 254 255 if (pos >= 3 && 0 == memcmp(buf, ".Dd", 3)) { 256 if (NULL == curp->pmdoc) 257 curp->pmdoc = mdoc_alloc(curp->roff, curp, 258 curp->defos); 259 assert(curp->pmdoc); 260 curp->mdoc = curp->pmdoc; 261 return; 262 } 263 264 if (NULL == curp->pman) 265 curp->pman = man_alloc(curp->roff, curp); 266 assert(curp->pman); 267 curp->man = curp->pman; 268 } 269 270 /* 271 * Main parse routine for an opened file. This is called for each 272 * opened file and simply loops around the full input file, possibly 273 * nesting (i.e., with `so'). 274 */ 275 static void 276 mparse_buf_r(struct mparse *curp, struct buf blk, int start) 277 { 278 const struct tbl_span *span; 279 struct buf ln; 280 enum rofferr rr; 281 int i, of, rc; 282 int pos; /* byte number in the ln buffer */ 283 int lnn; /* line number in the real file */ 284 unsigned char c; 285 286 memset(&ln, 0, sizeof(struct buf)); 287 288 lnn = curp->line; 289 pos = 0; 290 291 for (i = 0; i < (int)blk.sz; ) { 292 if (0 == pos && '\0' == blk.buf[i]) 293 break; 294 295 if (start) { 296 curp->line = lnn; 297 curp->reparse_count = 0; 298 } 299 300 while (i < (int)blk.sz && (start || '\0' != blk.buf[i])) { 301 302 /* 303 * When finding an unescaped newline character, 304 * leave the character loop to process the line. 305 * Skip a preceding carriage return, if any. 306 */ 307 308 if ('\r' == blk.buf[i] && i + 1 < (int)blk.sz && 309 '\n' == blk.buf[i + 1]) 310 ++i; 311 if ('\n' == blk.buf[i]) { 312 ++i; 313 ++lnn; 314 break; 315 } 316 317 /* 318 * Warn about bogus characters. If you're using 319 * non-ASCII encoding, you're screwing your 320 * readers. Since I'd rather this not happen, 321 * I'll be helpful and replace these characters 322 * with "?", so we don't display gibberish. 323 * Note to manual writers: use special characters. 324 */ 325 326 c = (unsigned char) blk.buf[i]; 327 328 if ( ! (isascii(c) && 329 (isgraph(c) || isblank(c)))) { 330 mandoc_msg(MANDOCERR_BADCHAR, curp, 331 curp->line, pos, NULL); 332 i++; 333 if (pos >= (int)ln.sz) 334 resize_buf(&ln, 256); 335 ln.buf[pos++] = '?'; 336 continue; 337 } 338 339 /* Trailing backslash = a plain char. */ 340 341 if ('\\' != blk.buf[i] || i + 1 == (int)blk.sz) { 342 if (pos >= (int)ln.sz) 343 resize_buf(&ln, 256); 344 ln.buf[pos++] = blk.buf[i++]; 345 continue; 346 } 347 348 /* 349 * Found escape and at least one other character. 350 * When it's a newline character, skip it. 351 * When there is a carriage return in between, 352 * skip that one as well. 353 */ 354 355 if ('\r' == blk.buf[i + 1] && i + 2 < (int)blk.sz && 356 '\n' == blk.buf[i + 2]) 357 ++i; 358 if ('\n' == blk.buf[i + 1]) { 359 i += 2; 360 ++lnn; 361 continue; 362 } 363 364 if ('"' == blk.buf[i + 1] || '#' == blk.buf[i + 1]) { 365 i += 2; 366 /* Comment, skip to end of line */ 367 for (; i < (int)blk.sz; ++i) { 368 if ('\n' == blk.buf[i]) { 369 ++i; 370 ++lnn; 371 break; 372 } 373 } 374 375 /* Backout trailing whitespaces */ 376 for (; pos > 0; --pos) { 377 if (ln.buf[pos - 1] != ' ') 378 break; 379 if (pos > 2 && ln.buf[pos - 2] == '\\') 380 break; 381 } 382 break; 383 } 384 385 /* Some other escape sequence, copy & cont. */ 386 387 if (pos + 1 >= (int)ln.sz) 388 resize_buf(&ln, 256); 389 390 ln.buf[pos++] = blk.buf[i++]; 391 ln.buf[pos++] = blk.buf[i++]; 392 } 393 394 if (pos >= (int)ln.sz) 395 resize_buf(&ln, 256); 396 397 ln.buf[pos] = '\0'; 398 399 /* 400 * A significant amount of complexity is contained by 401 * the roff preprocessor. It's line-oriented but can be 402 * expressed on one line, so we need at times to 403 * readjust our starting point and re-run it. The roff 404 * preprocessor can also readjust the buffers with new 405 * data, so we pass them in wholesale. 406 */ 407 408 of = 0; 409 410 /* 411 * Maintain a lookaside buffer of all parsed lines. We 412 * only do this if mparse_keep() has been invoked (the 413 * buffer may be accessed with mparse_getkeep()). 414 */ 415 416 if (curp->secondary) { 417 curp->secondary->buf = 418 mandoc_realloc 419 (curp->secondary->buf, 420 curp->secondary->sz + pos + 2); 421 memcpy(curp->secondary->buf + 422 curp->secondary->sz, 423 ln.buf, pos); 424 curp->secondary->sz += pos; 425 curp->secondary->buf 426 [curp->secondary->sz] = '\n'; 427 curp->secondary->sz++; 428 curp->secondary->buf 429 [curp->secondary->sz] = '\0'; 430 } 431 rerun: 432 rr = roff_parseln 433 (curp->roff, curp->line, 434 &ln.buf, &ln.sz, of, &of); 435 436 switch (rr) { 437 case (ROFF_REPARSE): 438 if (REPARSE_LIMIT >= ++curp->reparse_count) 439 mparse_buf_r(curp, ln, 0); 440 else 441 mandoc_msg(MANDOCERR_ROFFLOOP, curp, 442 curp->line, pos, NULL); 443 pos = 0; 444 continue; 445 case (ROFF_APPEND): 446 pos = (int)strlen(ln.buf); 447 continue; 448 case (ROFF_RERUN): 449 goto rerun; 450 case (ROFF_IGN): 451 pos = 0; 452 continue; 453 case (ROFF_ERR): 454 assert(MANDOCLEVEL_FATAL <= curp->file_status); 455 break; 456 case (ROFF_SO): 457 /* 458 * We remove `so' clauses from our lookaside 459 * buffer because we're going to descend into 460 * the file recursively. 461 */ 462 if (curp->secondary) 463 curp->secondary->sz -= pos + 1; 464 mparse_readfd_r(curp, -1, ln.buf + of, 1); 465 if (MANDOCLEVEL_FATAL <= curp->file_status) 466 break; 467 pos = 0; 468 continue; 469 default: 470 break; 471 } 472 473 /* 474 * If we encounter errors in the recursive parse, make 475 * sure we don't continue parsing. 476 */ 477 478 if (MANDOCLEVEL_FATAL <= curp->file_status) 479 break; 480 481 /* 482 * If input parsers have not been allocated, do so now. 483 * We keep these instanced between parsers, but set them 484 * locally per parse routine since we can use different 485 * parsers with each one. 486 */ 487 488 if ( ! (curp->man || curp->mdoc)) 489 pset(ln.buf + of, pos - of, curp); 490 491 /* 492 * Lastly, push down into the parsers themselves. One 493 * of these will have already been set in the pset() 494 * routine. 495 * If libroff returns ROFF_TBL, then add it to the 496 * currently open parse. Since we only get here if 497 * there does exist data (see tbl_data.c), we're 498 * guaranteed that something's been allocated. 499 * Do the same for ROFF_EQN. 500 */ 501 502 rc = -1; 503 504 if (ROFF_TBL == rr) 505 while (NULL != (span = roff_span(curp->roff))) { 506 rc = curp->man ? 507 man_addspan(curp->man, span) : 508 mdoc_addspan(curp->mdoc, span); 509 if (0 == rc) 510 break; 511 } 512 else if (ROFF_EQN == rr) 513 rc = curp->mdoc ? 514 mdoc_addeqn(curp->mdoc, 515 roff_eqn(curp->roff)) : 516 man_addeqn(curp->man, 517 roff_eqn(curp->roff)); 518 else if (curp->man || curp->mdoc) 519 rc = curp->man ? 520 man_parseln(curp->man, 521 curp->line, ln.buf, of) : 522 mdoc_parseln(curp->mdoc, 523 curp->line, ln.buf, of); 524 525 if (0 == rc) { 526 assert(MANDOCLEVEL_FATAL <= curp->file_status); 527 break; 528 } 529 530 /* Temporary buffers typically are not full. */ 531 532 if (0 == start && '\0' == blk.buf[i]) 533 break; 534 535 /* Start the next input line. */ 536 537 pos = 0; 538 } 539 540 free(ln.buf); 541 } 542 543 static void 544 pdesc(struct mparse *curp, const char *file, int fd) 545 { 546 struct buf blk; 547 int with_mmap; 548 549 /* 550 * Run for each opened file; may be called more than once for 551 * each full parse sequence if the opened file is nested (i.e., 552 * from `so'). Simply sucks in the whole file and moves into 553 * the parse phase for the file. 554 */ 555 556 if ( ! read_whole_file(file, fd, &blk, &with_mmap)) { 557 curp->file_status = MANDOCLEVEL_SYSERR; 558 return; 559 } 560 561 /* Line number is per-file. */ 562 563 curp->line = 1; 564 565 mparse_buf_r(curp, blk, 1); 566 567 if (with_mmap) 568 munmap(blk.buf, blk.sz); 569 else 570 free(blk.buf); 571 } 572 573 static int 574 read_whole_file(const char *file, int fd, struct buf *fb, int *with_mmap) 575 { 576 struct stat st; 577 size_t off; 578 ssize_t ssz; 579 580 if (-1 == fstat(fd, &st)) { 581 perror(file); 582 return(0); 583 } 584 585 /* 586 * If we're a regular file, try just reading in the whole entry 587 * via mmap(). This is faster than reading it into blocks, and 588 * since each file is only a few bytes to begin with, I'm not 589 * concerned that this is going to tank any machines. 590 */ 591 592 if (S_ISREG(st.st_mode)) { 593 if (st.st_size >= (1U << 31)) { 594 fprintf(stderr, "%s: input too large\n", file); 595 return(0); 596 } 597 *with_mmap = 1; 598 fb->sz = (size_t)st.st_size; 599 fb->buf = mmap(NULL, fb->sz, PROT_READ, 600 MAP_FILE|MAP_SHARED, fd, 0); 601 if (fb->buf != MAP_FAILED) 602 return(1); 603 } 604 605 /* 606 * If this isn't a regular file (like, say, stdin), then we must 607 * go the old way and just read things in bit by bit. 608 */ 609 610 *with_mmap = 0; 611 off = 0; 612 fb->sz = 0; 613 fb->buf = NULL; 614 for (;;) { 615 if (off == fb->sz) { 616 if (fb->sz == (1U << 31)) { 617 fprintf(stderr, "%s: input too large\n", file); 618 break; 619 } 620 resize_buf(fb, 65536); 621 } 622 ssz = read(fd, fb->buf + (int)off, fb->sz - off); 623 if (ssz == 0) { 624 fb->sz = off; 625 return(1); 626 } 627 if (ssz == -1) { 628 perror(file); 629 break; 630 } 631 off += (size_t)ssz; 632 } 633 634 free(fb->buf); 635 fb->buf = NULL; 636 return(0); 637 } 638 639 static void 640 mparse_end(struct mparse *curp) 641 { 642 643 if (MANDOCLEVEL_FATAL <= curp->file_status) 644 return; 645 646 if (curp->mdoc && ! mdoc_endparse(curp->mdoc)) { 647 assert(MANDOCLEVEL_FATAL <= curp->file_status); 648 return; 649 } 650 651 if (curp->man && ! man_endparse(curp->man)) { 652 assert(MANDOCLEVEL_FATAL <= curp->file_status); 653 return; 654 } 655 656 if ( ! (curp->man || curp->mdoc)) { 657 mandoc_msg(MANDOCERR_NOTMANUAL, curp, 1, 0, NULL); 658 curp->file_status = MANDOCLEVEL_FATAL; 659 return; 660 } 661 662 roff_endparse(curp->roff); 663 } 664 665 static void 666 mparse_readfd_r(struct mparse *curp, int fd, const char *file, int re) 667 { 668 const char *svfile; 669 670 if (-1 == fd) 671 if (-1 == (fd = open(file, O_RDONLY, 0))) { 672 perror(file); 673 curp->file_status = MANDOCLEVEL_SYSERR; 674 return; 675 } 676 677 svfile = curp->file; 678 curp->file = file; 679 680 pdesc(curp, file, fd); 681 682 if (0 == re && MANDOCLEVEL_FATAL > curp->file_status) 683 mparse_end(curp); 684 685 if (STDIN_FILENO != fd && -1 == close(fd)) 686 perror(file); 687 688 curp->file = svfile; 689 } 690 691 enum mandoclevel 692 mparse_readfd(struct mparse *curp, int fd, const char *file) 693 { 694 695 mparse_readfd_r(curp, fd, file, 0); 696 return(curp->file_status); 697 } 698 699 struct mparse * 700 mparse_alloc(enum mparset inttype, enum mandoclevel wlevel, 701 mandocmsg mmsg, void *arg, char *defos) 702 { 703 struct mparse *curp; 704 705 assert(wlevel <= MANDOCLEVEL_FATAL); 706 707 curp = mandoc_calloc(1, sizeof(struct mparse)); 708 709 curp->wlevel = wlevel; 710 curp->mmsg = mmsg; 711 curp->arg = arg; 712 curp->inttype = inttype; 713 curp->defos = defos; 714 715 curp->roff = roff_alloc(inttype, curp); 716 return(curp); 717 } 718 719 void 720 mparse_reset(struct mparse *curp) 721 { 722 723 roff_reset(curp->roff); 724 725 if (curp->mdoc) 726 mdoc_reset(curp->mdoc); 727 if (curp->man) 728 man_reset(curp->man); 729 if (curp->secondary) 730 curp->secondary->sz = 0; 731 732 curp->file_status = MANDOCLEVEL_OK; 733 curp->mdoc = NULL; 734 curp->man = NULL; 735 } 736 737 void 738 mparse_free(struct mparse *curp) 739 { 740 741 if (curp->pmdoc) 742 mdoc_free(curp->pmdoc); 743 if (curp->pman) 744 man_free(curp->pman); 745 if (curp->roff) 746 roff_free(curp->roff); 747 if (curp->secondary) 748 free(curp->secondary->buf); 749 750 free(curp->secondary); 751 free(curp); 752 } 753 754 void 755 mparse_result(struct mparse *curp, struct mdoc **mdoc, struct man **man) 756 { 757 758 if (mdoc) 759 *mdoc = curp->mdoc; 760 if (man) 761 *man = curp->man; 762 } 763 764 void 765 mandoc_vmsg(enum mandocerr t, struct mparse *m, 766 int ln, int pos, const char *fmt, ...) 767 { 768 char buf[256]; 769 va_list ap; 770 771 va_start(ap, fmt); 772 vsnprintf(buf, sizeof(buf) - 1, fmt, ap); 773 va_end(ap); 774 775 mandoc_msg(t, m, ln, pos, buf); 776 } 777 778 void 779 mandoc_msg(enum mandocerr er, struct mparse *m, 780 int ln, int col, const char *msg) 781 { 782 enum mandoclevel level; 783 784 level = MANDOCLEVEL_FATAL; 785 while (er < mandoclimits[level]) 786 level--; 787 788 if (level < m->wlevel) 789 return; 790 791 if (m->mmsg) 792 (*m->mmsg)(er, level, m->file, ln, col, msg); 793 794 if (m->file_status < level) 795 m->file_status = level; 796 } 797 798 const char * 799 mparse_strerror(enum mandocerr er) 800 { 801 802 return(mandocerrs[er]); 803 } 804 805 const char * 806 mparse_strlevel(enum mandoclevel lvl) 807 { 808 return(mandoclevels[lvl]); 809 } 810 811 void 812 mparse_keep(struct mparse *p) 813 { 814 815 assert(NULL == p->secondary); 816 p->secondary = mandoc_calloc(1, sizeof(struct buf)); 817 } 818 819 const char * 820 mparse_getkeep(const struct mparse *p) 821 { 822 823 assert(p->secondary); 824 return(p->secondary->sz ? p->secondary->buf : NULL); 825 } 826