1/*	$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			/*
357			 * Found escape and at least one other character.
358			 * When it's a newline character, skip it.
359			 * When there is a carriage return in between,
360			 * skip that one as well.
361			 */
362
363			if ('\r' == blk.buf[i + 1] && i + 2 < (int)blk.sz &&
364			    '\n' == blk.buf[i + 2])
365				++i;
366			if ('\n' == blk.buf[i + 1]) {
367				i += 2;
368				++lnn;
369				continue;
370			}
371
372			if ('"' == blk.buf[i + 1] || '#' == blk.buf[i + 1]) {
373				i += 2;
374				/* Comment, skip to end of line */
375				for (; i < (int)blk.sz; ++i) {
376					if ('\n' == blk.buf[i]) {
377						++i;
378						++lnn;
379						break;
380					}
381				}
382
383				/* Backout trailing whitespaces */
384				for (; pos > 0; --pos) {
385					if (ln.buf[pos - 1] != ' ')
386						break;
387					if (pos > 2 && ln.buf[pos - 2] == '\\')
388						break;
389				}
390				break;
391			}
392
393			/* Some other escape sequence, copy & cont. */
394
395			if (pos + 1 >= (int)ln.sz)
396				resize_buf(&ln, 256);
397
398			ln.buf[pos++] = blk.buf[i++];
399			ln.buf[pos++] = blk.buf[i++];
400		}
401
402 		if (pos >= (int)ln.sz)
403			resize_buf(&ln, 256);
404
405		ln.buf[pos] = '\0';
406
407		/*
408		 * A significant amount of complexity is contained by
409		 * the roff preprocessor.  It's line-oriented but can be
410		 * expressed on one line, so we need at times to
411		 * readjust our starting point and re-run it.  The roff
412		 * preprocessor can also readjust the buffers with new
413		 * data, so we pass them in wholesale.
414		 */
415
416		of = 0;
417
418		/*
419		 * Maintain a lookaside buffer of all parsed lines.  We
420		 * only do this if mparse_keep() has been invoked (the
421		 * buffer may be accessed with mparse_getkeep()).
422		 */
423
424		if (curp->secondary) {
425			curp->secondary->buf =
426				mandoc_realloc
427				(curp->secondary->buf,
428				 curp->secondary->sz + pos + 2);
429			memcpy(curp->secondary->buf +
430					curp->secondary->sz,
431					ln.buf, pos);
432			curp->secondary->sz += pos;
433			curp->secondary->buf
434				[curp->secondary->sz] = '\n';
435			curp->secondary->sz++;
436			curp->secondary->buf
437				[curp->secondary->sz] = '\0';
438		}
439rerun:
440		rr = roff_parseln
441			(curp->roff, curp->line,
442			 &ln.buf, &ln.sz, of, &of);
443
444		switch (rr) {
445		case (ROFF_REPARSE):
446			if (REPARSE_LIMIT >= ++curp->reparse_count)
447				mparse_buf_r(curp, ln, 0);
448			else
449				mandoc_msg(MANDOCERR_ROFFLOOP, curp,
450					curp->line, pos, NULL);
451			pos = 0;
452			continue;
453		case (ROFF_APPEND):
454			pos = (int)strlen(ln.buf);
455			continue;
456		case (ROFF_RERUN):
457			goto rerun;
458		case (ROFF_IGN):
459			pos = 0;
460			continue;
461		case (ROFF_ERR):
462			assert(MANDOCLEVEL_FATAL <= curp->file_status);
463			break;
464		case (ROFF_SO):
465			/*
466			 * We remove `so' clauses from our lookaside
467			 * buffer because we're going to descend into
468			 * the file recursively.
469			 */
470			if (curp->secondary)
471				curp->secondary->sz -= pos + 1;
472			mparse_readfd_r(curp, -1, ln.buf + of, 1);
473			if (MANDOCLEVEL_FATAL <= curp->file_status)
474				break;
475			pos = 0;
476			continue;
477		default:
478			break;
479		}
480
481		/*
482		 * If we encounter errors in the recursive parse, make
483		 * sure we don't continue parsing.
484		 */
485
486		if (MANDOCLEVEL_FATAL <= curp->file_status)
487			break;
488
489		/*
490		 * If input parsers have not been allocated, do so now.
491		 * We keep these instanced between parsers, but set them
492		 * locally per parse routine since we can use different
493		 * parsers with each one.
494		 */
495
496		if ( ! (curp->man || curp->mdoc))
497			pset(ln.buf + of, pos - of, curp);
498
499		/*
500		 * Lastly, push down into the parsers themselves.  One
501		 * of these will have already been set in the pset()
502		 * routine.
503		 * If libroff returns ROFF_TBL, then add it to the
504		 * currently open parse.  Since we only get here if
505		 * there does exist data (see tbl_data.c), we're
506		 * guaranteed that something's been allocated.
507		 * Do the same for ROFF_EQN.
508		 */
509
510		rc = -1;
511
512		if (ROFF_TBL == rr)
513			while (NULL != (span = roff_span(curp->roff))) {
514				rc = curp->man ?
515					man_addspan(curp->man, span) :
516					mdoc_addspan(curp->mdoc, span);
517				if (0 == rc)
518					break;
519			}
520		else if (ROFF_EQN == rr)
521			rc = curp->mdoc ?
522				mdoc_addeqn(curp->mdoc,
523					roff_eqn(curp->roff)) :
524				man_addeqn(curp->man,
525					roff_eqn(curp->roff));
526		else if (curp->man || curp->mdoc)
527			rc = curp->man ?
528				man_parseln(curp->man,
529					curp->line, ln.buf, of) :
530				mdoc_parseln(curp->mdoc,
531					curp->line, ln.buf, of);
532
533		if (0 == rc) {
534			assert(MANDOCLEVEL_FATAL <= curp->file_status);
535			break;
536		}
537
538		/* Temporary buffers typically are not full. */
539
540		if (0 == start && '\0' == blk.buf[i])
541			break;
542
543		/* Start the next input line. */
544
545		pos = 0;
546	}
547
548	free(ln.buf);
549}
550
551static int
552read_whole_file(const char *file, int fd, struct buf *fb, int *with_mmap)
553{
554	size_t		 off;
555	ssize_t		 ssz;
556
557#ifdef	HAVE_MMAP
558	struct stat	 st;
559	if (-1 == fstat(fd, &st)) {
560		perror(file);
561		return(0);
562	}
563
564	/*
565	 * If we're a regular file, try just reading in the whole entry
566	 * via mmap().  This is faster than reading it into blocks, and
567	 * since each file is only a few bytes to begin with, I'm not
568	 * concerned that this is going to tank any machines.
569	 */
570
571	if (S_ISREG(st.st_mode)) {
572		if (st.st_size >= (1U << 31)) {
573			fprintf(stderr, "%s: input too large\n", file);
574			return(0);
575		}
576		*with_mmap = 1;
577		fb->sz = (size_t)st.st_size;
578		fb->buf = mmap(NULL, fb->sz, PROT_READ,
579				MAP_FILE|MAP_SHARED, fd, 0);
580		if (fb->buf != MAP_FAILED)
581			return(1);
582	}
583#endif
584
585	/*
586	 * If this isn't a regular file (like, say, stdin), then we must
587	 * go the old way and just read things in bit by bit.
588	 */
589
590	*with_mmap = 0;
591	off = 0;
592	fb->sz = 0;
593	fb->buf = NULL;
594	for (;;) {
595		if (off == fb->sz) {
596			if (fb->sz == (1U << 31)) {
597				fprintf(stderr, "%s: input too large\n", file);
598				break;
599			}
600			resize_buf(fb, 65536);
601		}
602		ssz = read(fd, fb->buf + (int)off, fb->sz - off);
603		if (ssz == 0) {
604			fb->sz = off;
605			return(1);
606		}
607		if (ssz == -1) {
608			perror(file);
609			break;
610		}
611		off += (size_t)ssz;
612	}
613
614	free(fb->buf);
615	fb->buf = NULL;
616	return(0);
617}
618
619static void
620mparse_end(struct mparse *curp)
621{
622
623	if (MANDOCLEVEL_FATAL <= curp->file_status)
624		return;
625
626	if (curp->mdoc && ! mdoc_endparse(curp->mdoc)) {
627		assert(MANDOCLEVEL_FATAL <= curp->file_status);
628		return;
629	}
630
631	if (curp->man && ! man_endparse(curp->man)) {
632		assert(MANDOCLEVEL_FATAL <= curp->file_status);
633		return;
634	}
635
636	if ( ! (curp->man || curp->mdoc)) {
637		mandoc_msg(MANDOCERR_NOTMANUAL, curp, 1, 0, NULL);
638		curp->file_status = MANDOCLEVEL_FATAL;
639		return;
640	}
641
642	roff_endparse(curp->roff);
643}
644
645static void
646mparse_parse_buffer(struct mparse *curp, struct buf blk, const char *file,
647		int re)
648{
649	const char	*svfile;
650
651	/* Line number is per-file. */
652	svfile = curp->file;
653	curp->file = file;
654	curp->line = 1;
655
656	mparse_buf_r(curp, blk, 1);
657
658	if (0 == re && MANDOCLEVEL_FATAL > curp->file_status)
659		mparse_end(curp);
660
661	curp->file = svfile;
662}
663
664enum mandoclevel
665mparse_readmem(struct mparse *curp, const void *buf, size_t len,
666		const char *file)
667{
668	struct buf blk;
669
670	blk.buf = UNCONST(buf);
671	blk.sz = len;
672
673	mparse_parse_buffer(curp, blk, file, 0);
674	return(curp->file_status);
675}
676
677static void
678mparse_readfd_r(struct mparse *curp, int fd, const char *file, int re)
679{
680	struct buf	 blk;
681	int		 with_mmap;
682
683	if (-1 == fd)
684		if (-1 == (fd = open(file, O_RDONLY, 0))) {
685			perror(file);
686			curp->file_status = MANDOCLEVEL_SYSERR;
687			return;
688		}
689	/*
690	 * Run for each opened file; may be called more than once for
691	 * each full parse sequence if the opened file is nested (i.e.,
692	 * from `so').  Simply sucks in the whole file and moves into
693	 * the parse phase for the file.
694	 */
695
696	if ( ! read_whole_file(file, fd, &blk, &with_mmap)) {
697		curp->file_status = MANDOCLEVEL_SYSERR;
698		return;
699	}
700
701	mparse_parse_buffer(curp, blk, file, re);
702
703#ifdef	HAVE_MMAP
704	if (with_mmap)
705		munmap(blk.buf, blk.sz);
706	else
707#endif
708		free(blk.buf);
709
710	if (STDIN_FILENO != fd && -1 == close(fd))
711		perror(file);
712}
713
714enum mandoclevel
715mparse_readfd(struct mparse *curp, int fd, const char *file)
716{
717
718	mparse_readfd_r(curp, fd, file, 0);
719	return(curp->file_status);
720}
721
722struct mparse *
723mparse_alloc(enum mparset inttype, enum mandoclevel wlevel, mandocmsg mmsg, void *arg)
724{
725	struct mparse	*curp;
726
727	assert(wlevel <= MANDOCLEVEL_FATAL);
728
729	curp = mandoc_calloc(1, sizeof(struct mparse));
730
731	curp->wlevel = wlevel;
732	curp->mmsg = mmsg;
733	curp->arg = arg;
734	curp->inttype = inttype;
735
736	curp->roff = roff_alloc(curp);
737	return(curp);
738}
739
740void
741mparse_reset(struct mparse *curp)
742{
743
744	roff_reset(curp->roff);
745
746	if (curp->mdoc)
747		mdoc_reset(curp->mdoc);
748	if (curp->man)
749		man_reset(curp->man);
750	if (curp->secondary)
751		curp->secondary->sz = 0;
752
753	curp->file_status = MANDOCLEVEL_OK;
754	curp->mdoc = NULL;
755	curp->man = NULL;
756}
757
758void
759mparse_free(struct mparse *curp)
760{
761
762	if (curp->pmdoc)
763		mdoc_free(curp->pmdoc);
764	if (curp->pman)
765		man_free(curp->pman);
766	if (curp->roff)
767		roff_free(curp->roff);
768	if (curp->secondary)
769		free(curp->secondary->buf);
770
771	free(curp->secondary);
772	free(curp);
773}
774
775void
776mparse_result(struct mparse *curp, struct mdoc **mdoc, struct man **man)
777{
778
779	if (mdoc)
780		*mdoc = curp->mdoc;
781	if (man)
782		*man = curp->man;
783}
784
785void
786mandoc_vmsg(enum mandocerr t, struct mparse *m,
787		int ln, int pos, const char *fmt, ...)
788{
789	char		 buf[256];
790	va_list		 ap;
791
792	va_start(ap, fmt);
793	vsnprintf(buf, sizeof(buf) - 1, fmt, ap);
794	va_end(ap);
795
796	mandoc_msg(t, m, ln, pos, buf);
797}
798
799void
800mandoc_msg(enum mandocerr er, struct mparse *m,
801		int ln, int col, const char *msg)
802{
803	enum mandoclevel level;
804
805	level = MANDOCLEVEL_FATAL;
806	while (er < mandoclimits[level])
807		level--;
808
809	if (level < m->wlevel)
810		return;
811
812	if (m->mmsg)
813		(*m->mmsg)(er, level, m->file, ln, col, msg);
814
815	if (m->file_status < level)
816		m->file_status = level;
817}
818
819const char *
820mparse_strerror(enum mandocerr er)
821{
822
823	return(mandocerrs[er]);
824}
825
826const char *
827mparse_strlevel(enum mandoclevel lvl)
828{
829	return(mandoclevels[lvl]);
830}
831
832void
833mparse_keep(struct mparse *p)
834{
835
836	assert(NULL == p->secondary);
837	p->secondary = mandoc_calloc(1, sizeof(struct buf));
838}
839
840const char *
841mparse_getkeep(const struct mparse *p)
842{
843
844	assert(p->secondary);
845	return(p->secondary->sz ? p->secondary->buf : NULL);
846}
847