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