]> git.cameronkatri.com Git - mandoc.git/blobdiff - read.c
Cleanup regarding -offset and -width:
[mandoc.git] / read.c
diff --git a/read.c b/read.c
index f78d2983d0beb4c0a3488f26c8338db1c4b40426..5ad294f979a866621183489ed9468774c971fdf1 100644 (file)
--- a/read.c
+++ b/read.c
@@ -1,7 +1,8 @@
-/*     $Id: read.c,v 1.5 2011/03/22 09:50:11 kristaps Exp $ */
+/*     $Id: read.c,v 1.62 2014/07/05 01:12:20 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
- * Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2010-2014 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2010, 2012 Joerg Sonnenberger <joerg@netbsd.org>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#include <sys/stat.h>
-#include <sys/mman.h>
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_MMAP
+# include <sys/stat.h>
+# include <sys/mman.h>
+#endif
 
 #include <assert.h>
 #include <ctype.h>
+#include <errno.h>
 #include <fcntl.h>
 #include <stdarg.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 
 #include "mandoc.h"
+#include "mandoc_aux.h"
 #include "libmandoc.h"
 #include "mdoc.h"
 #include "man.h"
-
-#ifndef MAP_FILE
-#define        MAP_FILE        0
-#endif
+#include "main.h"
 
 #define        REPARSE_LIMIT   1000
 
 struct buf {
-       char             *buf; /* binary input buffer */
+       char             *buf; /* binary input buffer */
        size_t            sz; /* size of binary buffer */
 };
 
@@ -47,26 +54,28 @@ struct      mparse {
        enum mandoclevel  file_status; /* status of current parse */
        enum mandoclevel  wlevel; /* ignore messages below this */
        int               line; /* line number in the file */
-       enum mparset      inttype; /* which parser to use */
+       int               options; /* parser options */
        struct man       *pman; /* persistent man parser */
        struct mdoc      *pmdoc; /* persistent mdoc parser */
        struct man       *man; /* man parser */
        struct mdoc      *mdoc; /* mdoc parser */
        struct roff      *roff; /* roff parser (!NULL) */
-       struct regset     regs; /* roff registers */
+       char             *sodest; /* filename pointed to by .so */
        int               reparse_count; /* finite interp. stack */
        mandocmsg         mmsg; /* warning/error message handler */
-       void             *arg; /* argument to mmsg */
-       const char       *file; 
+       const char       *file;
+       struct buf       *secondary;
+       char             *defos; /* default operating system */
 };
 
 static void      resize_buf(struct buf *, size_t);
 static void      mparse_buf_r(struct mparse *, struct buf, int);
-static void      mparse_readfd_r(struct mparse *, int, const char *, int);
 static void      pset(const char *, int, struct mparse *);
-static void      pdesc(struct mparse *, const char *, int);
-static int       read_whole_file(const char *, int, struct buf *, int *);
+static int       read_whole_file(struct mparse *, const char *, int,
+                               struct buf *, int *);
 static void      mparse_end(struct mparse *);
+static void      mparse_parse_buffer(struct mparse *, struct buf,
+                       const char *);
 
 static const enum mandocerr    mandoclimits[MANDOCLEVEL_MAX] = {
        MANDOCERR_OK,
@@ -78,6 +87,148 @@ static      const enum mandocerr    mandoclimits[MANDOCLEVEL_MAX] = {
        MANDOCERR_MAX
 };
 
+static const char * const      mandocerrs[MANDOCERR_MAX] = {
+       "ok",
+
+       "generic warning",
+
+       /* related to the prologue */
+       "missing .TH macro, using \"unknown 1\"",
+       "lower case character in document title",
+       "unknown manual section",
+       "unknown manual volume or arch",
+       "missing date, using today's date",
+       "cannot parse date, using it verbatim",
+       "prologue macros out of order",
+       "duplicate prologue macro",
+       "incomplete prologue, terminated by",
+       "skipping prologue macro in body",
+
+       /* related to document structure */
+       ".so is fragile, better use ln(1)",
+       "no document body",
+       "content before first section header",
+       "first section is not \"NAME\"",
+       "bad NAME section contents",
+       "sections out of conventional order",
+       "duplicate section title",
+       "unexpected section",
+
+       /* related to macros and nesting */
+       "obsolete macro",
+       "skipping paragraph macro",
+       "moving paragraph macro out of list",
+       "skipping no-space macro",
+       "blocks badly nested",
+       "nested displays are not portable",
+       "moving content out of list",
+       ".Vt block has child macro",
+       "fill mode already enabled, skipping .fi",
+       "fill mode already disabled, skipping .nf",
+       "line scope broken",
+
+       /* related to missing macro arguments */
+       "skipping empty request",
+       "conditional request controls empty scope",
+       "skipping empty macro",
+       "empty argument, using 0n",
+       "argument count wrong",
+       "missing display type, using -ragged",
+       "list type is not the first argument",
+       "missing -width in -tag list, using 8n",
+       "empty head in list item",
+       "empty list item",
+       "missing font type, using \\fR",
+       "unknown font type, using \\fR",
+       "missing -std argument, adding it",
+
+       /* related to bad macro arguments */
+       "skipping argument",
+       "duplicate argument",
+       "duplicate display type",
+       "duplicate list type",
+       "unknown AT&T UNIX version",
+       "bad Boolean value",
+       "unknown font",
+       "unknown standard specifier",
+       "bad width argument",
+
+       /* related to plain text */
+       "blank line in non-literal context",
+       "tab in non-literal context",
+       "end of line whitespace",
+       "bad comment style",
+       "bad escape sequence",
+       "unterminated quoted string",
+
+       "generic error",
+
+       /* related to equations */
+       "unexpected equation scope closure",
+       "equation scope open on exit",
+       "overlapping equation scopes",
+       "unexpected end of equation",
+       "equation syntax error",
+
+       /* related to tables */
+       "bad table syntax",
+       "bad table option",
+       "bad table layout",
+       "no table layout cells specified",
+       "no table data cells specified",
+       "ignore data in cell",
+       "data block still open",
+       "ignoring extra data cells",
+
+       "input stack limit exceeded, infinite loop?",
+       "skipping bad character",
+       "escaped character not allowed in a name",
+       "manual name not yet set",
+       "skipping text before first section header",
+       "skipping unknown macro",
+       "NOT IMPLEMENTED, please use groff: skipping request",
+       "argument count wrong",
+       "skipping invalid content in .Rs block",
+       "skipping column outside column list",
+       "skipping end of block that is not open",
+       "missing end of block",
+       "scope open on exit",
+       "uname(3) system call failed",
+       "request requires a numeric argument",
+       "missing list type, using -item",
+       "skipping all arguments",
+       "skipping excess arguments",
+
+       "generic fatal error",
+
+       "input too large",
+       "not a manual",
+       "column syntax is inconsistent",
+       "NOT IMPLEMENTED: .Bd -file",
+       "child violates parent syntax",
+       "argument count wrong, violates syntax",
+       "NOT IMPLEMENTED: .so with absolute path or \"..\"",
+       ".so request failed",
+       "no document prologue",
+       "static buffer exhausted",
+
+       /* system errors */
+       NULL,
+       "cannot stat file",
+       "cannot read file",
+};
+
+static const char * const      mandoclevels[MANDOCLEVEL_MAX] = {
+       "SUCCESS",
+       "RESERVED",
+       "WARNING",
+       "ERROR",
+       "FATAL",
+       "BADARG",
+       "SYSERR"
+};
+
+
 static void
 resize_buf(struct buf *buf, size_t initial)
 {
@@ -110,33 +261,36 @@ pset(const char *buf, int pos, struct mparse *curp)
                        return;
        }
 
-       switch (curp->inttype) {
-       case (MPARSE_MDOC):
-               if (NULL == curp->pmdoc) 
-                       curp->pmdoc = mdoc_alloc(&curp->regs, curp);
+       if (MPARSE_MDOC & curp->options) {
+               if (NULL == curp->pmdoc)
+                       curp->pmdoc = mdoc_alloc(
+                           curp->roff, curp, curp->defos,
+                           MPARSE_QUICK & curp->options ? 1 : 0);
                assert(curp->pmdoc);
                curp->mdoc = curp->pmdoc;
                return;
-       case (MPARSE_MAN):
-               if (NULL == curp->pman) 
-                       curp->pman = man_alloc(&curp->regs, curp);
+       } else if (MPARSE_MAN & curp->options) {
+               if (NULL == curp->pman)
+                       curp->pman = man_alloc(curp->roff, curp,
+                           MPARSE_QUICK & curp->options ? 1 : 0);
                assert(curp->pman);
                curp->man = curp->pman;
                return;
-       default:
-               break;
        }
 
        if (pos >= 3 && 0 == memcmp(buf, ".Dd", 3))  {
-               if (NULL == curp->pmdoc) 
-                       curp->pmdoc = mdoc_alloc(&curp->regs, curp);
+               if (NULL == curp->pmdoc)
+                       curp->pmdoc = mdoc_alloc(
+                           curp->roff, curp, curp->defos,
+                           MPARSE_QUICK & curp->options ? 1 : 0);
                assert(curp->pmdoc);
                curp->mdoc = curp->pmdoc;
                return;
-       } 
+       }
 
-       if (NULL == curp->pman) 
-               curp->pman = man_alloc(&curp->regs, curp);
+       if (NULL == curp->pman)
+               curp->pman = man_alloc(curp->roff, curp,
+                   MPARSE_QUICK & curp->options ? 1 : 0);
        assert(curp->pman);
        curp->man = curp->pman;
 }
@@ -159,8 +313,8 @@ mparse_buf_r(struct mparse *curp, struct buf blk, int start)
 
        memset(&ln, 0, sizeof(struct buf));
 
-       lnn = curp->line; 
-       pos = 0; 
+       lnn = curp->line;
+       pos = 0;
 
        for (i = 0; i < (int)blk.sz; ) {
                if (0 == pos && '\0' == blk.buf[i])
@@ -188,30 +342,38 @@ mparse_buf_r(struct mparse *curp, struct buf blk, int start)
                                break;
                        }
 
-                       /* 
+                       /*
+                        * Make sure we have space for at least
+                        * one backslash and one other character
+                        * and the trailing NUL byte.
+                        */
+
+                       if (pos + 2 >= (int)ln.sz)
+                               resize_buf(&ln, 256);
+
+                       /*
                         * Warn about bogus characters.  If you're using
                         * non-ASCII encoding, you're screwing your
                         * readers.  Since I'd rather this not happen,
-                        * I'll be helpful and drop these characters so
-                        * we don't display gibberish.  Note to manual
-                        * writers: use special characters.
+                        * I'll be helpful and replace these characters
+                        * with "?", so we don't display gibberish.
+                        * Note to manual writers: use special characters.
                         */
 
                        c = (unsigned char) blk.buf[i];
 
-                       if ( ! (isascii(c) && 
-                                       (isgraph(c) || isblank(c)))) {
+                       if ( ! (isascii(c) &&
+                           (isgraph(c) || isblank(c)))) {
                                mandoc_msg(MANDOCERR_BADCHAR, curp,
-                                               curp->line, pos, "ignoring byte");
+                                   curp->line, pos, NULL);
                                i++;
+                               ln.buf[pos++] = '?';
                                continue;
                        }
 
                        /* Trailing backslash = a plain char. */
 
                        if ('\\' != blk.buf[i] || i + 1 == (int)blk.sz) {
-                               if (pos >= (int)ln.sz)
-                                       resize_buf(&ln, 256);
                                ln.buf[pos++] = blk.buf[i++];
                                continue;
                        }
@@ -232,7 +394,7 @@ mparse_buf_r(struct mparse *curp, struct buf blk, int start)
                                continue;
                        }
 
-                       if ('"' == blk.buf[i + 1]) {
+                       if ('"' == blk.buf[i + 1] || '#' == blk.buf[i + 1]) {
                                i += 2;
                                /* Comment, skip to end of line */
                                for (; i < (int)blk.sz; ++i) {
@@ -253,16 +415,26 @@ mparse_buf_r(struct mparse *curp, struct buf blk, int start)
                                break;
                        }
 
-                       /* Some other escape sequence, copy & cont. */
+                       /* Catch escaped bogus characters. */
 
-                       if (pos + 1 >= (int)ln.sz)
-                               resize_buf(&ln, 256);
+                       c = (unsigned char) blk.buf[i+1];
+
+                       if ( ! (isascii(c) &&
+                           (isgraph(c) || isblank(c)))) {
+                               mandoc_msg(MANDOCERR_BADCHAR, curp,
+                                   curp->line, pos, NULL);
+                               i += 2;
+                               ln.buf[pos++] = '?';
+                               continue;
+                       }
+
+                       /* Some other escape sequence, copy & cont. */
 
                        ln.buf[pos++] = blk.buf[i++];
                        ln.buf[pos++] = blk.buf[i++];
                }
 
-               if (pos >= (int)ln.sz)
+               if (pos >= (int)ln.sz)
                        resize_buf(&ln, 256);
 
                ln.buf[pos] = '\0';
@@ -278,35 +450,71 @@ mparse_buf_r(struct mparse *curp, struct buf blk, int start)
 
                of = 0;
 
+               /*
+                * Maintain a lookaside buffer of all parsed lines.  We
+                * only do this if mparse_keep() has been invoked (the
+                * buffer may be accessed with mparse_getkeep()).
+                */
+
+               if (curp->secondary) {
+                       curp->secondary->buf = mandoc_realloc(
+                           curp->secondary->buf,
+                           curp->secondary->sz + pos + 2);
+                       memcpy(curp->secondary->buf +
+                           curp->secondary->sz,
+                           ln.buf, pos);
+                       curp->secondary->sz += pos;
+                       curp->secondary->buf
+                               [curp->secondary->sz] = '\n';
+                       curp->secondary->sz++;
+                       curp->secondary->buf
+                               [curp->secondary->sz] = '\0';
+               }
 rerun:
-               rr = roff_parseln
-                       (curp->roff, curp->line, 
-                        &ln.buf, &ln.sz, of, &of);
+               rr = roff_parseln(curp->roff, curp->line,
+                   &ln.buf, &ln.sz, of, &of);
 
                switch (rr) {
-               case (ROFF_REPARSE):
+               case ROFF_REPARSE:
                        if (REPARSE_LIMIT >= ++curp->reparse_count)
                                mparse_buf_r(curp, ln, 0);
                        else
                                mandoc_msg(MANDOCERR_ROFFLOOP, curp,
-                                       curp->line, pos, NULL);
+                                   curp->line, pos, NULL);
                        pos = 0;
                        continue;
-               case (ROFF_APPEND):
+               case ROFF_APPEND:
                        pos = (int)strlen(ln.buf);
                        continue;
-               case (ROFF_RERUN):
+               case ROFF_RERUN:
                        goto rerun;
-               case (ROFF_IGN):
+               case ROFF_IGN:
                        pos = 0;
                        continue;
-               case (ROFF_ERR):
+               case ROFF_ERR:
                        assert(MANDOCLEVEL_FATAL <= curp->file_status);
                        break;
-               case (ROFF_SO):
-                       mparse_readfd_r(curp, -1, ln.buf + of, 1);
-                       if (MANDOCLEVEL_FATAL <= curp->file_status)
+               case ROFF_SO:
+                       if (0 == (MPARSE_SO & curp->options) &&
+                           (i >= (int)blk.sz || '\0' == blk.buf[i])) {
+                               curp->sodest = mandoc_strdup(ln.buf + of);
+                               free(ln.buf);
+                               return;
+                       }
+                       /*
+                        * We remove `so' clauses from our lookaside
+                        * buffer because we're going to descend into
+                        * the file recursively.
+                        */
+                       if (curp->secondary)
+                               curp->secondary->sz -= pos + 1;
+                       mparse_readfd(curp, -1, ln.buf + of);
+                       if (MANDOCLEVEL_FATAL <= curp->file_status) {
+                               mandoc_vmsg(MANDOCERR_SO_FAIL,
+                                   curp, curp->line, pos,
+                                   ".so %s", ln.buf + of);
                                break;
+                       }
                        pos = 0;
                        continue;
                default:
@@ -323,7 +531,7 @@ rerun:
 
                /*
                 * If input parsers have not been allocated, do so now.
-                * We keep these instanced betwen parsers, but set them
+                * We keep these instanced between parsers, but set them
                 * locally per parse routine since we can use different
                 * parsers with each one.
                 */
@@ -331,7 +539,7 @@ rerun:
                if ( ! (curp->man || curp->mdoc))
                        pset(ln.buf + of, pos - of, curp);
 
-               /* 
+               /*
                 * Lastly, push down into the parsers themselves.  One
                 * of these will have already been set in the pset()
                 * routine.
@@ -347,28 +555,29 @@ rerun:
                if (ROFF_TBL == rr)
                        while (NULL != (span = roff_span(curp->roff))) {
                                rc = curp->man ?
-                                       man_addspan(curp->man, span) :
-                                       mdoc_addspan(curp->mdoc, span);
+                                   man_addspan(curp->man, span) :
+                                   mdoc_addspan(curp->mdoc, span);
                                if (0 == rc)
                                        break;
                        }
                else if (ROFF_EQN == rr)
-                       rc = curp->mdoc ? 
-                               mdoc_addeqn(curp->mdoc, 
-                                       roff_eqn(curp->roff)) :
-                               man_addeqn(curp->man,
-                                       roff_eqn(curp->roff));
+                       rc = curp->mdoc ?
+                           mdoc_addeqn(curp->mdoc,
+                               roff_eqn(curp->roff)) :
+                           man_addeqn(curp->man,
+                               roff_eqn(curp->roff));
                else if (curp->man || curp->mdoc)
                        rc = curp->man ?
-                               man_parseln(curp->man, 
-                                       curp->line, ln.buf, of) :
-                               mdoc_parseln(curp->mdoc, 
-                                       curp->line, ln.buf, of);
+                           man_parseln(curp->man,
+                               curp->line, ln.buf, of) :
+                           mdoc_parseln(curp->mdoc,
+                               curp->line, ln.buf, of);
 
                if (0 == rc) {
                        assert(MANDOCLEVEL_FATAL <= curp->file_status);
                        break;
-               }
+               } else if (2 == rc)
+                       break;
 
                /* Temporary buffers typically are not full. */
 
@@ -383,45 +592,20 @@ rerun:
        free(ln.buf);
 }
 
-static void
-pdesc(struct mparse *curp, const char *file, int fd)
-{
-       struct buf       blk;
-       int              with_mmap;
-
-       /*
-        * Run for each opened file; may be called more than once for
-        * each full parse sequence if the opened file is nested (i.e.,
-        * from `so').  Simply sucks in the whole file and moves into
-        * the parse phase for the file.
-        */
-
-       if ( ! read_whole_file(file, fd, &blk, &with_mmap)) {
-               curp->file_status = MANDOCLEVEL_SYSERR;
-               return;
-       }
-
-       /* Line number is per-file. */
-
-       curp->line = 1;
-
-       mparse_buf_r(curp, blk, 1);
-
-       if (with_mmap)
-               munmap(blk.buf, blk.sz);
-       else
-               free(blk.buf);
-}
-
 static int
-read_whole_file(const char *file, int fd, struct buf *fb, int *with_mmap)
+read_whole_file(struct mparse *curp, const char *file, int fd,
+               struct buf *fb, int *with_mmap)
 {
-       struct stat      st;
        size_t           off;
        ssize_t          ssz;
 
+#ifdef HAVE_MMAP
+       struct stat      st;
        if (-1 == fstat(fd, &st)) {
-               perror(file);
+               curp->file_status = MANDOCLEVEL_SYSERR;
+               if (curp->mmsg)
+                       (*curp->mmsg)(MANDOCERR_SYSSTAT, curp->file_status,
+                           file, 0, 0, strerror(errno));
                return(0);
        }
 
@@ -434,16 +618,19 @@ read_whole_file(const char *file, int fd, struct buf *fb, int *with_mmap)
 
        if (S_ISREG(st.st_mode)) {
                if (st.st_size >= (1U << 31)) {
-                       fprintf(stderr, "%s: input too large\n", file);
+                       curp->file_status = MANDOCLEVEL_FATAL;
+                       if (curp->mmsg)
+                               (*curp->mmsg)(MANDOCERR_TOOLARGE,
+                                   curp->file_status, file, 0, 0, NULL);
                        return(0);
                }
                *with_mmap = 1;
                fb->sz = (size_t)st.st_size;
-               fb->buf = mmap(NULL, fb->sz, PROT_READ, 
-                               MAP_FILE|MAP_SHARED, fd, 0);
+               fb->buf = mmap(NULL, fb->sz, PROT_READ, MAP_SHARED, fd, 0);
                if (fb->buf != MAP_FAILED)
                        return(1);
        }
+#endif
 
        /*
         * If this isn't a regular file (like, say, stdin), then we must
@@ -457,7 +644,11 @@ read_whole_file(const char *file, int fd, struct buf *fb, int *with_mmap)
        for (;;) {
                if (off == fb->sz) {
                        if (fb->sz == (1U << 31)) {
-                               fprintf(stderr, "%s: input too large\n", file);
+                               curp->file_status = MANDOCLEVEL_FATAL;
+                               if (curp->mmsg)
+                                       (*curp->mmsg)(MANDOCERR_TOOLARGE,
+                                           curp->file_status,
+                                           file, 0, 0, NULL);
                                break;
                        }
                        resize_buf(fb, 65536);
@@ -468,7 +659,11 @@ read_whole_file(const char *file, int fd, struct buf *fb, int *with_mmap)
                        return(1);
                }
                if (ssz == -1) {
-                       perror(file);
+                       curp->file_status = MANDOCLEVEL_SYSERR;
+                       if (curp->mmsg)
+                               (*curp->mmsg)(MANDOCERR_SYSREAD,
+                                   curp->file_status, file, 0, 0,
+                                   strerror(errno));
                        break;
                }
                off += (size_t)ssz;
@@ -496,67 +691,109 @@ mparse_end(struct mparse *curp)
                return;
        }
 
-#if 0
-       /* FIXME: NOTE a parser may not have been assigned, yet. */
-
-       if ( ! (curp->man || curp->mdoc)) {
-               /* FIXME: make into an mandoc.h error. */
-               fprintf(stderr, "%s: Not a manual\n", curp->file);
+       if ( ! (curp->mdoc || curp->man || curp->sodest)) {
+               mandoc_msg(MANDOCERR_NOTMANUAL, curp, 0, 0, NULL);
                curp->file_status = MANDOCLEVEL_FATAL;
-               goto cleanup;
+               return;
        }
-#endif
 
        roff_endparse(curp->roff);
 }
 
 static void
-mparse_readfd_r(struct mparse *curp, int fd, const char *file, int re)
+mparse_parse_buffer(struct mparse *curp, struct buf blk, const char *file)
 {
        const char      *svfile;
+       static int       recursion_depth;
 
-       if (-1 == fd)
-               if (-1 == (fd = open(file, O_RDONLY, 0))) {
-                       perror(file);
-                       curp->file_status = MANDOCLEVEL_SYSERR;
-                       return;
-               }
+       if (64 < recursion_depth) {
+               mandoc_msg(MANDOCERR_ROFFLOOP, curp, curp->line, 0, NULL);
+               return;
+       }
 
+       /* Line number is per-file. */
        svfile = curp->file;
        curp->file = file;
+       curp->line = 1;
+       recursion_depth++;
 
-       pdesc(curp, file, fd);
+       mparse_buf_r(curp, blk, 1);
 
-       if (0 == re && MANDOCLEVEL_FATAL > curp->file_status)
+       if (0 == --recursion_depth && MANDOCLEVEL_FATAL > curp->file_status)
                mparse_end(curp);
 
-       if (STDIN_FILENO != fd && -1 == close(fd))
-               perror(file);
-
        curp->file = svfile;
 }
 
+enum mandoclevel
+mparse_readmem(struct mparse *curp, const void *buf, size_t len,
+               const char *file)
+{
+       struct buf blk;
+
+       blk.buf = UNCONST(buf);
+       blk.sz = len;
+
+       mparse_parse_buffer(curp, blk, file);
+       return(curp->file_status);
+}
+
 enum mandoclevel
 mparse_readfd(struct mparse *curp, int fd, const char *file)
 {
+       struct buf       blk;
+       int              with_mmap;
 
-       mparse_readfd_r(curp, fd, file, 0);
+       if (-1 == fd && -1 == (fd = open(file, O_RDONLY, 0))) {
+               curp->file_status = MANDOCLEVEL_SYSERR;
+               if (curp->mmsg)
+                       (*curp->mmsg)(MANDOCERR_SYSOPEN,
+                           curp->file_status,
+                           file, 0, 0, strerror(errno));
+               goto out;
+       }
+
+       /*
+        * Run for each opened file; may be called more than once for
+        * each full parse sequence if the opened file is nested (i.e.,
+        * from `so').  Simply sucks in the whole file and moves into
+        * the parse phase for the file.
+        */
+
+       if ( ! read_whole_file(curp, file, fd, &blk, &with_mmap))
+               goto out;
+
+       mparse_parse_buffer(curp, blk, file);
+
+#ifdef HAVE_MMAP
+       if (with_mmap)
+               munmap(blk.buf, blk.sz);
+       else
+#endif
+               free(blk.buf);
+
+       if (STDIN_FILENO != fd && -1 == close(fd))
+               perror(file);
+out:
        return(curp->file_status);
 }
 
 struct mparse *
-mparse_alloc(enum mparset inttype, enum mandoclevel wlevel, mandocmsg mmsg, void *arg)
+mparse_alloc(int options, enum mandoclevel wlevel,
+               mandocmsg mmsg, char *defos)
 {
        struct mparse   *curp;
 
+       assert(wlevel <= MANDOCLEVEL_FATAL);
+
        curp = mandoc_calloc(1, sizeof(struct mparse));
 
+       curp->options = options;
        curp->wlevel = wlevel;
        curp->mmsg = mmsg;
-       curp->arg = arg;
-       curp->inttype = inttype;
+       curp->defos = defos;
 
-       curp->roff = roff_alloc(&curp->regs, curp);
+       curp->roff = roff_alloc(curp, options);
        return(curp);
 }
 
@@ -564,18 +801,21 @@ void
 mparse_reset(struct mparse *curp)
 {
 
-       memset(&curp->regs, 0, sizeof(struct regset));
-
        roff_reset(curp->roff);
 
        if (curp->mdoc)
                mdoc_reset(curp->mdoc);
        if (curp->man)
                man_reset(curp->man);
+       if (curp->secondary)
+               curp->secondary->sz = 0;
 
        curp->file_status = MANDOCLEVEL_OK;
        curp->mdoc = NULL;
        curp->man = NULL;
+
+       free(curp->sodest);
+       curp->sodest = NULL;
 }
 
 void
@@ -588,16 +828,28 @@ mparse_free(struct mparse *curp)
                man_free(curp->pman);
        if (curp->roff)
                roff_free(curp->roff);
+       if (curp->secondary)
+               free(curp->secondary->buf);
 
+       free(curp->secondary);
+       free(curp->sodest);
        free(curp);
 }
 
 void
-mparse_result(struct mparse *curp, struct mdoc **mdoc, struct man **man)
+mparse_result(struct mparse *curp,
+       struct mdoc **mdoc, struct man **man, char **sodest)
 {
 
-       *mdoc = curp->mdoc;
-       *man = curp->man;
+       if (sodest && NULL != (*sodest = curp->sodest)) {
+               *mdoc = NULL;
+               *man = NULL;
+               return;
+       }
+       if (mdoc)
+               *mdoc = curp->mdoc;
+       if (man)
+               *man = curp->man;
 }
 
 void
@@ -608,14 +860,14 @@ mandoc_vmsg(enum mandocerr t, struct mparse *m,
        va_list          ap;
 
        va_start(ap, fmt);
-       vsnprintf(buf, sizeof(buf) - 1, fmt, ap);
+       (void)vsnprintf(buf, sizeof(buf), fmt, ap);
        va_end(ap);
 
        mandoc_msg(t, m, ln, pos, buf);
 }
 
 void
-mandoc_msg(enum mandocerr er, struct mparse *m, 
+mandoc_msg(enum mandocerr er, struct mparse *m,
                int ln, int col, const char *msg)
 {
        enum mandoclevel level;
@@ -627,8 +879,38 @@ mandoc_msg(enum mandocerr er, struct mparse *m,
        if (level < m->wlevel)
                return;
 
-       (*m->mmsg)(er, level, m->file, ln, col, msg);
+       if (m->mmsg)
+               (*m->mmsg)(er, level, m->file, ln, col, msg);
 
        if (m->file_status < level)
                m->file_status = level;
 }
+
+const char *
+mparse_strerror(enum mandocerr er)
+{
+
+       return(mandocerrs[er]);
+}
+
+const char *
+mparse_strlevel(enum mandoclevel lvl)
+{
+       return(mandoclevels[lvl]);
+}
+
+void
+mparse_keep(struct mparse *p)
+{
+
+       assert(NULL == p->secondary);
+       p->secondary = mandoc_calloc(1, sizeof(struct buf));
+}
+
+const char *
+mparse_getkeep(const struct mparse *p)
+{
+
+       assert(p->secondary);
+       return(p->secondary->sz ? p->secondary->buf : NULL);
+}