-/* $Id: read.c,v 1.156 2017/01/08 00:11:24 schwarze Exp $ */
+/* $Id: read.c,v 1.201 2018/12/13 06:18:20 schwarze Exp $ */
/*
* Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
- * Copyright (c) 2010-2017 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2010-2018 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
#include <assert.h>
#include <ctype.h>
-#if HAVE_ERR
-#include <err.h>
-#endif
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
-#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define REPARSE_LIMIT 1000
struct mparse {
- struct roff_man *man; /* man parser */
struct roff *roff; /* roff parser (!NULL) */
+ struct roff_man *man; /* man parser */
char *sodest; /* filename pointed to by .so */
const char *file; /* filename of current input file */
struct buf *primary; /* buffer currently being parsed */
- struct buf *secondary; /* preprocessed copy of input */
- const char *defos; /* default operating system */
+ struct buf *secondary; /* copy of top level input */
+ struct buf *loop; /* open .while request line */
+ const char *os_s; /* default operating system */
mandocmsg mmsg; /* warning/error message handler */
enum mandoclevel file_status; /* status of current parse */
- enum mandoclevel wlevel; /* ignore messages below this */
+ enum mandocerr mmin; /* ignore messages below this */
int options; /* parser options */
int gzip; /* current input file is gzipped */
int filenc; /* encoding of the current file */
};
static void choose_parser(struct mparse *);
+static void free_buf_list(struct buf *);
static void resize_buf(struct buf *, size_t);
-static void mparse_buf_r(struct mparse *, struct buf, size_t, int);
+static int mparse_buf_r(struct mparse *, struct buf, size_t, int);
static int read_whole_file(struct mparse *, const char *, int,
struct buf *, int *);
static void mparse_end(struct mparse *);
static const enum mandocerr mandoclimits[MANDOCLEVEL_MAX] = {
MANDOCERR_OK,
- MANDOCERR_WARNING,
+ MANDOCERR_OK,
MANDOCERR_WARNING,
MANDOCERR_ERROR,
MANDOCERR_UNSUPP,
static const char * const mandocerrs[MANDOCERR_MAX] = {
"ok",
+ "base system convention",
+
+ "Mdocdate found",
+ "Mdocdate missing",
+ "unknown architecture",
+ "operating system explicitly specified",
+ "RCS id missing",
+ "referenced manual not found",
+
+ "generic style suggestion",
+
+ "legacy man(7) date format",
+ "normalizing date format to",
+ "lower case character in document title",
+ "duplicate RCS id",
+ "possible typo in section name",
+ "unterminated quoted argument",
+ "useless macro",
+ "consider using OS macro",
+ "errnos out of order",
+ "duplicate errno",
+ "trailing delimiter",
+ "no blank before trailing delimiter",
+ "fill mode already enabled, skipping",
+ "fill mode already disabled, skipping",
+ "verbatim \"--\", maybe consider using \\(em",
+ "function name without markup",
+ "whitespace at end of input line",
+ "bad comment style",
+
"generic warning",
/* related to the prologue */
"missing manual title, using UNTITLED",
"missing manual title, using \"\"",
- "lower case character in document title",
"missing manual section, using \"\"",
"unknown manual section",
"missing date, using today's date",
"cannot parse date, using it verbatim",
+ "date in the future, using it anyway",
"missing Os macro, using \"\"",
- "duplicate prologue macro",
"late prologue macro",
- "skipping late title macro",
"prologue macros out of order",
/* related to document structure */
"bad NAME section content",
"missing comma before name",
"missing description line, using \"\"",
+ "description line outside NAME section",
"sections out of conventional order",
"duplicate section title",
"unexpected section",
+ "cross reference to self",
"unusual Xr order",
"unusual Xr punctuation",
"AUTHORS section without An macro",
"blocks badly nested",
"nested displays are not portable",
"moving content out of list",
- "fill mode already enabled, skipping",
- "fill mode already disabled, skipping",
+ "first macro on line",
"line scope broken",
+ "skipping blank line in line scope",
/* related to missing macro arguments */
"skipping empty request",
"missing function name, using \"\"",
"empty head in list item",
"empty list item",
+ "missing argument, using next line",
"missing font type, using \\fR",
"unknown font type, using \\fR",
"nothing follows prefix",
"missing eqn box, using \"\"",
/* related to bad macro arguments */
- "unterminated quoted argument",
"duplicate argument",
"skipping duplicate argument",
"skipping duplicate display type",
"unknown AT&T UNIX version",
"comma in function argument",
"parenthesis in function name",
+ "unknown library name",
"invalid content in Rs block",
"invalid Boolean argument",
+ "argument contains two font escapes",
"unknown font, skipping request",
"odd number of characters in request",
/* related to plain text */
"blank line in fill mode, using .sp",
"tab in filled text",
- "whitespace at end of input line",
- "bad comment style",
+ "new sentence, new line",
"invalid escape sequence",
"undefined string, using \"\"",
/* related to document structure and macros */
NULL,
+ "duplicate prologue macro",
+ "skipping late title macro",
"input stack limit exceeded, infinite loop?",
"skipping bad character",
"skipping unknown macro",
+ "ignoring request outside macro",
"skipping insecure request",
"skipping item outside list",
"skipping column outside column list",
/* related to request and macro arguments */
"escaped character not allowed in a name",
+ "using macro argument outside macro",
+ "argument number is not numeric",
"NOT IMPLEMENTED: Bd -file",
"skipping display without arguments",
"missing list type, using -item",
+ "argument is not numeric, using 1",
+ "argument is not a character",
"missing manual name, using \"\"",
"uname(3) system call failed, using UNKNOWN",
"unknown standard specifier",
"skipping request without numeric argument",
+ "excessive shift",
"NOT IMPLEMENTED: .so with absolute path or \"..\"",
".so request failed",
"skipping all arguments",
"input too large",
"unsupported control character",
"unsupported roff request",
+ "nested .while loops",
+ "end of scope with open .while loop",
+ "end of .while loop in inner scope",
+ "cannot continue this .while loop",
"eqn delim option in tbl",
"unsupported tbl layout modifier",
"ignoring macro in table",
static const char * const mandoclevels[MANDOCLEVEL_MAX] = {
"SUCCESS",
- "RESERVED",
+ "STYLE",
"WARNING",
"ERROR",
"UNSUPP",
buf->buf = mandoc_realloc(buf->buf, buf->sz);
}
+static void
+free_buf_list(struct buf *buf)
+{
+ struct buf *tmp;
+
+ while (buf != NULL) {
+ tmp = buf;
+ buf = tmp->next;
+ free(tmp->buf);
+ free(tmp);
+ }
+}
+
static void
choose_parser(struct mparse *curp)
{
}
if (format == MPARSE_MDOC) {
- mdoc_hash_init();
curp->man->macroset = MACROSET_MDOC;
- curp->man->first->tok = TOKEN_NONE;
+ if (curp->man->mdocmac == NULL)
+ curp->man->mdocmac = roffhash_alloc(MDOC_Dd, MDOC_MAX);
} else {
- man_hash_init();
curp->man->macroset = MACROSET_MAN;
- curp->man->first->tok = TOKEN_NONE;
+ if (curp->man->manmac == NULL)
+ curp->man->manmac = roffhash_alloc(MAN_TH, MAN_MAX);
}
+ curp->man->first->tok = TOKEN_NONE;
}
/*
* macros, inline equations, and input line traps)
* and indirectly (for .so file inclusion).
*/
-static void
+static int
mparse_buf_r(struct mparse *curp, struct buf blk, size_t i, int start)
{
- const struct tbl_span *span;
struct buf ln;
+ struct buf *firstln, *lastln, *thisln, *loop;
const char *save_file;
char *cp;
size_t pos; /* byte number in the ln buffer */
- size_t j; /* auxiliary byte number in the blk buffer */
- enum rofferr rr;
+ int line_result, result;
int of;
int lnn; /* line number in the real file */
int fd;
+ int inloop; /* Saw .while on this level. */
unsigned char c;
- memset(&ln, 0, sizeof(ln));
-
+ ln.sz = 256;
+ ln.buf = mandoc_malloc(ln.sz);
+ ln.next = NULL;
+ firstln = loop = NULL;
lnn = curp->line;
pos = 0;
+ inloop = 0;
+ result = ROFF_CONT;
- while (i < blk.sz) {
- if (0 == pos && '\0' == blk.buf[i])
- break;
-
+ while (i < blk.sz && (blk.buf[i] != '\0' || pos != 0)) {
if (start) {
curp->line = lnn;
curp->reparse_count = 0;
/*
* Make sure we have space for the worst
- * case of 11 bytes: "\\[u10ffff]\0"
+ * case of 12 bytes: "\\[u10ffff]\n\0"
*/
- if (pos + 11 > ln.sz)
+ if (pos + 12 > ln.sz)
resize_buf(&ln, 256);
/*
continue;
}
- /* Trailing backslash = a plain char. */
-
- if (blk.buf[i] != '\\' || i + 1 == blk.sz) {
- ln.buf[pos++] = blk.buf[i++];
- continue;
- }
-
- /*
- * Found escape and at least one other character.
- * When it's a newline character, skip it.
- * When there is a carriage return in between,
- * skip that one as well.
- */
-
- if ('\r' == blk.buf[i + 1] && i + 2 < blk.sz &&
- '\n' == blk.buf[i + 2])
- ++i;
- if ('\n' == blk.buf[i + 1]) {
- i += 2;
- ++lnn;
- continue;
- }
-
- if ('"' == blk.buf[i + 1] || '#' == blk.buf[i + 1]) {
- j = i;
- i += 2;
- /* Comment, skip to end of line */
- for (; i < blk.sz; ++i) {
- if (blk.buf[i] != '\n')
- continue;
- if (blk.buf[i - 1] == ' ' ||
- blk.buf[i - 1] == '\t')
- mandoc_msg(
- MANDOCERR_SPACE_EOL,
- curp, curp->line,
- pos + i-1 - j, NULL);
- ++i;
- ++lnn;
- break;
- }
-
- /* Backout trailing whitespaces */
- for (; pos > 0; --pos) {
- if (ln.buf[pos - 1] != ' ')
- break;
- if (pos > 2 && ln.buf[pos - 2] == '\\')
- break;
- }
- break;
- }
-
- /* Catch escaped bogus characters. */
-
- c = (unsigned char) blk.buf[i+1];
-
- if ( ! (isascii(c) &&
- (isgraph(c) || isblank(c)))) {
- mandoc_vmsg(MANDOCERR_CHAR_BAD, curp,
- curp->line, pos, "0x%x", c);
- i += 2;
- ln.buf[pos++] = '?';
- continue;
- }
+ ln.buf[pos++] = blk.buf[i++];
+ }
+ ln.buf[pos] = '\0';
- /* Some other escape sequence, copy & cont. */
+ /*
+ * Maintain a lookaside buffer of all lines.
+ * parsed from this input source.
+ */
- ln.buf[pos++] = blk.buf[i++];
- ln.buf[pos++] = blk.buf[i++];
+ thisln = mandoc_malloc(sizeof(*thisln));
+ thisln->buf = mandoc_strdup(ln.buf);
+ thisln->sz = strlen(ln.buf) + 1;
+ thisln->next = NULL;
+ if (firstln == NULL) {
+ firstln = lastln = thisln;
+ if (curp->secondary == NULL)
+ curp->secondary = firstln;
+ } else {
+ lastln->next = thisln;
+ lastln = thisln;
}
- if (pos >= ln.sz)
- resize_buf(&ln, 256);
+ /* XXX Ugly hack to mark the end of the input. */
- ln.buf[pos] = '\0';
+ if (i == blk.sz || blk.buf[i] == '\0') {
+ ln.buf[pos++] = '\n';
+ ln.buf[pos] = '\0';
+ }
/*
* A significant amount of complexity is contained by
*/
of = 0;
+rerun:
+ line_result = roff_parseln(curp->roff, curp->line, &ln, &of);
- /*
- * 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()).
- */
+ /* Process options. */
+
+ if (line_result & ROFF_APPEND)
+ assert(line_result == (ROFF_IGN | ROFF_APPEND));
- 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';
+ if (line_result & ROFF_USERCALL)
+ assert((line_result & ROFF_MASK) == ROFF_REPARSE);
+
+ if (line_result & ROFF_USERRET) {
+ assert(line_result == (ROFF_IGN | ROFF_USERRET));
+ if (start == 0) {
+ /* Return from the current macro. */
+ result = ROFF_USERRET;
+ goto out;
+ }
}
-rerun:
- rr = roff_parseln(curp->roff, curp->line, &ln, &of);
- switch (rr) {
+ switch (line_result & ROFF_LOOPMASK) {
+ case ROFF_IGN:
+ break;
+ case ROFF_WHILE:
+ if (curp->loop != NULL) {
+ if (loop == curp->loop)
+ break;
+ mandoc_msg(MANDOCERR_WHILE_NEST,
+ curp, curp->line, pos, NULL);
+ }
+ curp->loop = thisln;
+ loop = NULL;
+ inloop = 1;
+ break;
+ case ROFF_LOOPCONT:
+ case ROFF_LOOPEXIT:
+ if (curp->loop == NULL) {
+ mandoc_msg(MANDOCERR_WHILE_FAIL,
+ curp, curp->line, pos, NULL);
+ break;
+ }
+ if (inloop == 0) {
+ mandoc_msg(MANDOCERR_WHILE_INTO,
+ curp, curp->line, pos, NULL);
+ curp->loop = loop = NULL;
+ break;
+ }
+ if (line_result & ROFF_LOOPCONT)
+ loop = curp->loop;
+ else {
+ curp->loop = loop = NULL;
+ inloop = 0;
+ }
+ break;
+ default:
+ abort();
+ }
+
+ /* Process the main instruction from the roff parser. */
+
+ switch (line_result & ROFF_MASK) {
+ case ROFF_IGN:
+ break;
+ case ROFF_CONT:
+ if (curp->man->macroset == MACROSET_NONE)
+ choose_parser(curp);
+ if ((curp->man->macroset == MACROSET_MDOC ?
+ mdoc_parseln(curp->man, curp->line, ln.buf, of) :
+ man_parseln(curp->man, curp->line, ln.buf, of)
+ ) == 2)
+ goto out;
+ break;
+ case ROFF_RERUN:
+ goto rerun;
case ROFF_REPARSE:
- if (REPARSE_LIMIT >= ++curp->reparse_count)
- mparse_buf_r(curp, ln, of, 0);
- else
+ if (++curp->reparse_count > REPARSE_LIMIT) {
+ /* Abort and return to the top level. */
+ result = ROFF_IGN;
mandoc_msg(MANDOCERR_ROFFLOOP, curp,
curp->line, pos, NULL);
- pos = 0;
- continue;
- case ROFF_APPEND:
- pos = strlen(ln.buf);
- continue;
- case ROFF_RERUN:
- goto rerun;
- case ROFF_IGN:
- pos = 0;
- continue;
+ goto out;
+ }
+ result = mparse_buf_r(curp, ln, of, 0);
+ if (line_result & ROFF_USERCALL) {
+ roff_userret(curp->roff);
+ /* Continue normally. */
+ if (result & ROFF_USERRET)
+ result = ROFF_CONT;
+ }
+ if (start == 0 && result != ROFF_CONT)
+ goto out;
+ break;
case ROFF_SO:
if ( ! (curp->options & MPARSE_SO) &&
(i >= blk.sz || blk.buf[i] == '\0')) {
curp->sodest = mandoc_strdup(ln.buf + of);
- free(ln.buf);
- return;
+ goto out;
}
- /*
- * 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;
save_file = curp->file;
if ((fd = mparse_open(curp, ln.buf + of)) != -1) {
mparse_readfd(curp, fd, ln.buf + of);
of = 0;
mparse_buf_r(curp, ln, of, 0);
}
- pos = 0;
- continue;
- default:
break;
+ default:
+ abort();
}
- if (curp->man->macroset == MACROSET_NONE)
- choose_parser(curp);
-
- /*
- * Lastly, push down into the parsers themselves.
- * If libroff returns ROFF_TBL, then add it to the
- * currently open parse. Since we only get here if
- * there does exist data (see tbl_data.c), we're
- * guaranteed that something's been allocated.
- * Do the same for ROFF_EQN.
- */
-
- if (rr == ROFF_TBL)
- while ((span = roff_span(curp->roff)) != NULL)
- roff_addtbl(curp->man, span);
- else if (rr == ROFF_EQN)
- roff_addeqn(curp->man, roff_eqn(curp->roff));
- else if ((curp->man->macroset == MACROSET_MDOC ?
- mdoc_parseln(curp->man, curp->line, ln.buf, of) :
- man_parseln(curp->man, curp->line, ln.buf, of)) == 2)
- break;
-
- /* Temporary buffers typically are not full. */
-
- if (0 == start && '\0' == blk.buf[i])
- break;
-
/* Start the next input line. */
- pos = 0;
- }
+ if (loop != NULL &&
+ (line_result & ROFF_LOOPMASK) == ROFF_IGN)
+ loop = loop->next;
+
+ if (loop != NULL) {
+ if ((line_result & ROFF_APPEND) == 0)
+ *ln.buf = '\0';
+ if (ln.sz < loop->sz)
+ resize_buf(&ln, loop->sz);
+ (void)strlcat(ln.buf, loop->buf, ln.sz);
+ of = 0;
+ goto rerun;
+ }
+ pos = (line_result & ROFF_APPEND) ? strlen(ln.buf) : 0;
+ }
+out:
+ if (inloop) {
+ if (result != ROFF_USERRET)
+ mandoc_msg(MANDOCERR_WHILE_OUTOF, curp,
+ curp->line, pos, NULL);
+ curp->loop = NULL;
+ }
free(ln.buf);
+ if (firstln != curp->secondary)
+ free_buf_list(firstln);
+ return result;
}
static int
read_whole_file(struct mparse *curp, const char *file, int fd,
struct buf *fb, int *with_mmap)
{
+ struct stat st;
gzFile gz;
size_t off;
ssize_t ssz;
+ int gzerrnum, retval;
- struct stat st;
-
- if (fstat(fd, &st) == -1)
- err((int)MANDOCLEVEL_SYSERR, "%s", file);
+ if (fstat(fd, &st) == -1) {
+ mandoc_vmsg(MANDOCERR_FILE, curp, 0, 0,
+ "fstat: %s", strerror(errno));
+ return 0;
+ }
/*
* If we're a regular file, try just reading in the whole entry
}
if (curp->gzip) {
- if ((gz = gzdopen(fd, "rb")) == NULL)
- err((int)MANDOCLEVEL_SYSERR, "%s", file);
+ /*
+ * Duplicating the file descriptor is required
+ * because we will have to call gzclose(3)
+ * to free memory used internally by zlib,
+ * but that will also close the file descriptor,
+ * which this function must not do.
+ */
+ if ((fd = dup(fd)) == -1) {
+ mandoc_vmsg(MANDOCERR_FILE, curp, 0, 0,
+ "dup: %s", strerror(errno));
+ return 0;
+ }
+ if ((gz = gzdopen(fd, "rb")) == NULL) {
+ mandoc_vmsg(MANDOCERR_FILE, curp, 0, 0,
+ "gzdopen: %s", strerror(errno));
+ close(fd);
+ return 0;
+ }
} else
gz = NULL;
*with_mmap = 0;
off = 0;
+ retval = 0;
fb->sz = 0;
fb->buf = NULL;
for (;;) {
read(fd, fb->buf + (int)off, fb->sz - off);
if (ssz == 0) {
fb->sz = off;
- return 1;
+ retval = 1;
+ break;
+ }
+ if (ssz == -1) {
+ if (curp->gzip)
+ (void)gzerror(gz, &gzerrnum);
+ mandoc_vmsg(MANDOCERR_FILE, curp, 0, 0, "read: %s",
+ curp->gzip && gzerrnum != Z_ERRNO ?
+ zError(gzerrnum) : strerror(errno));
+ break;
}
- if (ssz == -1)
- err((int)MANDOCLEVEL_SYSERR, "%s", file);
off += (size_t)ssz;
}
- free(fb->buf);
- fb->buf = NULL;
- return 0;
+ if (curp->gzip && (gzerrnum = gzclose(gz)) != Z_OK)
+ mandoc_vmsg(MANDOCERR_FILE, curp, 0, 0, "gzclose: %s",
+ gzerrnum == Z_ERRNO ? strerror(errno) :
+ zError(gzerrnum));
+ if (retval == 0) {
+ free(fb->buf);
+ fb->buf = NULL;
+ }
+ return retval;
}
static void
}
struct mparse *
-mparse_alloc(int options, enum mandoclevel wlevel, mandocmsg mmsg,
- const char *defos)
+mparse_alloc(int options, enum mandocerr mmin, mandocmsg mmsg,
+ enum mandoc_os os_e, const char *os_s)
{
struct mparse *curp;
curp = mandoc_calloc(1, sizeof(struct mparse));
curp->options = options;
- curp->wlevel = wlevel;
+ curp->mmin = mmin;
curp->mmsg = mmsg;
- curp->defos = defos;
+ curp->os_s = os_s;
curp->roff = roff_alloc(curp, options);
- curp->man = roff_man_alloc( curp->roff, curp, curp->defos,
+ curp->man = roff_man_alloc(curp->roff, curp, curp->os_s,
curp->options & MPARSE_QUICK ? 1 : 0);
if (curp->options & MPARSE_MDOC) {
- mdoc_hash_init();
curp->man->macroset = MACROSET_MDOC;
+ if (curp->man->mdocmac == NULL)
+ curp->man->mdocmac = roffhash_alloc(MDOC_Dd, MDOC_MAX);
} else if (curp->options & MPARSE_MAN) {
- man_hash_init();
curp->man->macroset = MACROSET_MAN;
+ if (curp->man->manmac == NULL)
+ curp->man->manmac = roffhash_alloc(MAN_TH, MAN_MAX);
}
curp->man->first->tok = TOKEN_NONE;
+ curp->man->meta.os_e = os_e;
return curp;
}
{
roff_reset(curp->roff);
roff_man_reset(curp->man);
- if (curp->secondary)
- curp->secondary->sz = 0;
-
- curp->file_status = MANDOCLEVEL_OK;
+ free_buf_list(curp->secondary);
+ curp->secondary = NULL;
free(curp->sodest);
curp->sodest = NULL;
+
+ curp->file_status = MANDOCLEVEL_OK;
+ curp->gzip = 0;
}
void
mparse_free(struct mparse *curp)
{
-
+ roffhash_free(curp->man->mdocmac);
+ roffhash_free(curp->man->manmac);
roff_man_free(curp->man);
- if (curp->roff)
- roff_free(curp->roff);
- if (curp->secondary)
- free(curp->secondary->buf);
-
- free(curp->secondary);
+ roff_free(curp->roff);
+ free_buf_list(curp->secondary);
free(curp->sodest);
free(curp);
}
*man = curp->man;
}
+void
+mparse_updaterc(struct mparse *curp, enum mandoclevel *rc)
+{
+ if (curp->file_status > *rc)
+ *rc = curp->file_status;
+}
+
void
mandoc_vmsg(enum mandocerr t, struct mparse *m,
int ln, int pos, const char *fmt, ...)
{
enum mandoclevel level;
+ if (er < m->mmin && er != MANDOCERR_FILE)
+ return;
+
level = MANDOCLEVEL_UNSUPP;
while (er < mandoclimits[level])
level--;
- if (level < m->wlevel && er != MANDOCERR_FILE)
- return;
-
if (m->mmsg)
(*m->mmsg)(er, level, m->file, ln, col, msg);
}
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)
+mparse_copy(const struct mparse *p)
{
+ struct buf *buf;
- assert(p->secondary);
- return p->secondary->sz ? p->secondary->buf : NULL;
+ for (buf = p->secondary; buf != NULL; buf = buf->next)
+ puts(buf->buf);
}