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