Return to read.c CVS log | Up to [cvsweb.bsd.lv] / mandoc |
version 1.59, 2014/07/03 23:24:56 | version 1.178, 2017/06/17 23:07:00 | ||
---|---|---|---|
|
|
||
/* $Id$ */ | /* $Id$ */ | ||
/* | /* | ||
* Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> | * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> | ||
* Copyright (c) 2010-2014 Ingo Schwarze <schwarze@openbsd.org> | * Copyright (c) 2010-2017 Ingo Schwarze <schwarze@openbsd.org> | ||
* Copyright (c) 2010, 2012 Joerg Sonnenberger <joerg@netbsd.org> | * Copyright (c) 2010, 2012 Joerg Sonnenberger <joerg@netbsd.org> | ||
* | * | ||
* Permission to use, copy, modify, and distribute this software for any | * Permission to use, copy, modify, and distribute this software for any | ||
* purpose with or without fee is hereby granted, provided that the above | * purpose with or without fee is hereby granted, provided that the above | ||
* copyright notice and this permission notice appear in all copies. | * copyright notice and this permission notice appear in all copies. | ||
* | * | ||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES | ||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR | ||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
*/ | */ | ||
#ifdef HAVE_CONFIG_H | |||
#include "config.h" | #include "config.h" | ||
#endif | |||
#ifdef HAVE_MMAP | #include <sys/types.h> | ||
# include <sys/stat.h> | #include <sys/mman.h> | ||
# include <sys/mman.h> | #include <sys/stat.h> | ||
#endif | |||
#include <assert.h> | #include <assert.h> | ||
#include <ctype.h> | #include <ctype.h> | ||
#if HAVE_ERR | |||
#include <err.h> | |||
#endif | |||
#include <errno.h> | #include <errno.h> | ||
#include <fcntl.h> | #include <fcntl.h> | ||
#include <stdarg.h> | #include <stdarg.h> | ||
#include <stdint.h> | |||
#include <stdio.h> | #include <stdio.h> | ||
#include <stdlib.h> | #include <stdlib.h> | ||
#include <string.h> | #include <string.h> | ||
#include <unistd.h> | #include <unistd.h> | ||
#include <zlib.h> | |||
#include "mandoc.h" | |||
#include "mandoc_aux.h" | #include "mandoc_aux.h" | ||
#include "libmandoc.h" | #include "mandoc.h" | ||
#include "roff.h" | |||
#include "mdoc.h" | #include "mdoc.h" | ||
#include "man.h" | #include "man.h" | ||
#include "main.h" | #include "libmandoc.h" | ||
#include "roff_int.h" | |||
#define REPARSE_LIMIT 1000 | #define REPARSE_LIMIT 1000 | ||
struct buf { | |||
char *buf; /* binary input buffer */ | |||
size_t sz; /* size of binary buffer */ | |||
}; | |||
struct mparse { | struct mparse { | ||
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 */ | |||
mandocmsg mmsg; /* warning/error message handler */ | |||
enum mandoclevel file_status; /* status of current parse */ | enum mandoclevel file_status; /* status of current parse */ | ||
enum mandoclevel wlevel; /* ignore messages below this */ | enum mandoclevel wlevel; /* ignore messages below this */ | ||
int line; /* line number in the file */ | |||
int options; /* parser options */ | int options; /* parser options */ | ||
struct man *pman; /* persistent man parser */ | int gzip; /* current input file is gzipped */ | ||
struct mdoc *pmdoc; /* persistent mdoc parser */ | int filenc; /* encoding of the current file */ | ||
struct man *man; /* man parser */ | |||
struct mdoc *mdoc; /* mdoc parser */ | |||
struct roff *roff; /* roff parser (!NULL) */ | |||
char *sodest; /* filename pointed to by .so */ | |||
int reparse_count; /* finite interp. stack */ | int reparse_count; /* finite interp. stack */ | ||
mandocmsg mmsg; /* warning/error message handler */ | int line; /* line number in the file */ | ||
const char *file; | |||
struct buf *secondary; | |||
char *defos; /* default operating system */ | |||
}; | }; | ||
static void choose_parser(struct mparse *); | |||
static void resize_buf(struct buf *, size_t); | static void resize_buf(struct buf *, size_t); | ||
static void mparse_buf_r(struct mparse *, struct buf, int); | static int mparse_buf_r(struct mparse *, struct buf, size_t, int); | ||
static void pset(const char *, int, struct mparse *); | |||
static int read_whole_file(struct mparse *, const char *, int, | static int read_whole_file(struct mparse *, const char *, int, | ||
struct buf *, int *); | struct buf *, int *); | ||
static void mparse_end(struct mparse *); | static void mparse_end(struct mparse *); | ||
|
|
||
static const enum mandocerr mandoclimits[MANDOCLEVEL_MAX] = { | static const enum mandocerr mandoclimits[MANDOCLEVEL_MAX] = { | ||
MANDOCERR_OK, | MANDOCERR_OK, | ||
MANDOCERR_STYLE, | |||
MANDOCERR_WARNING, | MANDOCERR_WARNING, | ||
MANDOCERR_WARNING, | |||
MANDOCERR_ERROR, | MANDOCERR_ERROR, | ||
MANDOCERR_FATAL, | MANDOCERR_UNSUPP, | ||
MANDOCERR_MAX, | MANDOCERR_MAX, | ||
MANDOCERR_MAX | MANDOCERR_MAX | ||
}; | }; | ||
|
|
||
static const char * const mandocerrs[MANDOCERR_MAX] = { | static const char * const mandocerrs[MANDOCERR_MAX] = { | ||
"ok", | "ok", | ||
"generic style suggestion", | |||
"Mdocdate found", | |||
"Mdocdate missing", | |||
"legacy man(7) date format", | |||
"RCS id missing", | |||
"duplicate RCS id", | |||
"useless macro", | |||
"consider using OS macro", | |||
"errnos out of order", | |||
"duplicate errno", | |||
"description line ends with a full stop", | |||
"no blank before trailing delimiter", | |||
"function name without markup", | |||
"generic warning", | "generic warning", | ||
/* related to the prologue */ | /* related to the prologue */ | ||
"missing .TH macro, using \"unknown 1\"", | "missing manual title, using UNTITLED", | ||
"missing manual title, using \"\"", | |||
"lower case character in document title", | "lower case character in document title", | ||
"missing manual section, using \"\"", | |||
"unknown manual section", | "unknown manual section", | ||
"unknown manual volume or arch", | |||
"missing date, using today's date", | "missing date, using today's date", | ||
"cannot parse date, using it verbatim", | "cannot parse date, using it verbatim", | ||
"prologue macros out of order", | "missing Os macro, using \"\"", | ||
"duplicate prologue macro", | "duplicate prologue macro", | ||
"incomplete prologue, terminated by", | "late prologue macro", | ||
"skipping prologue macro in body", | "skipping late title macro", | ||
"prologue macros out of order", | |||
/* related to document structure */ | /* related to document structure */ | ||
".so is fragile, better use ln(1)", | ".so is fragile, better use ln(1)", | ||
"no document body", | "no document body", | ||
"content before first section header", | "content before first section header", | ||
"first section is not \"NAME\"", | "first section is not \"NAME\"", | ||
"bad NAME section contents", | "NAME section without Nm before Nd", | ||
"NAME section without description", | |||
"description not at the end of NAME", | |||
"bad NAME section content", | |||
"missing comma before name", | |||
"missing description line, using \"\"", | |||
"description line outside NAME section", | |||
"sections out of conventional order", | "sections out of conventional order", | ||
"duplicate section title", | "duplicate section title", | ||
"unexpected section", | "unexpected section", | ||
"unusual Xr order", | |||
"unusual Xr punctuation", | |||
"AUTHORS section without An macro", | |||
/* related to macros and nesting */ | /* related to macros and nesting */ | ||
"obsolete macro", | "obsolete macro", | ||
"macro neither callable nor escaped", | |||
"skipping paragraph macro", | "skipping paragraph macro", | ||
"moving paragraph macro out of list", | "moving paragraph macro out of list", | ||
"skipping no-space macro", | "skipping no-space macro", | ||
"blocks badly nested", | "blocks badly nested", | ||
"nested displays are not portable", | "nested displays are not portable", | ||
"moving content out of list", | "moving content out of list", | ||
".Vt block has child macro", | "fill mode already enabled, skipping", | ||
"fill mode already enabled, skipping .fi", | "fill mode already disabled, skipping", | ||
"fill mode already disabled, skipping .nf", | |||
"line scope broken", | "line scope broken", | ||
"skipping blank line in line scope", | |||
/* related to missing macro arguments */ | /* related to missing macro arguments */ | ||
"skipping empty request", | "skipping empty request", | ||
"conditional request controls empty scope", | "conditional request controls empty scope", | ||
"skipping empty macro", | "skipping empty macro", | ||
"argument count wrong", | "empty block", | ||
"missing display type", | "empty argument, using 0n", | ||
"list type must come first", | "missing display type, using -ragged", | ||
"tag lists require a width argument", | "list type is not the first argument", | ||
"missing head in list item", | "missing -width in -tag list, using 6n", | ||
"missing font type", | "missing utility name, using \"\"", | ||
"missing function name, using \"\"", | |||
"empty head in list item", | |||
"empty list item", | |||
"missing font type, using \\fR", | |||
"unknown font type, using \\fR", | |||
"nothing follows prefix", | |||
"empty reference block", | |||
"missing section argument", | |||
"missing -std argument, adding it", | |||
"missing option string, using \"\"", | |||
"missing resource identifier, using \"\"", | |||
"missing eqn box, using \"\"", | |||
/* related to bad macro arguments */ | /* related to bad macro arguments */ | ||
"skipping argument", | "unterminated quoted argument", | ||
"duplicate argument", | "duplicate argument", | ||
"duplicate display type", | "skipping duplicate argument", | ||
"duplicate list type", | "skipping duplicate display type", | ||
"skipping duplicate list type", | |||
"skipping -width argument", | |||
"wrong number of cells", | |||
"unknown AT&T UNIX version", | "unknown AT&T UNIX version", | ||
"bad Boolean value", | "comma in function argument", | ||
"unknown font", | "parenthesis in function name", | ||
"unknown standard specifier", | "unknown library name", | ||
"bad width argument", | "invalid content in Rs block", | ||
"invalid Boolean argument", | |||
"unknown font, skipping request", | |||
"odd number of characters in request", | |||
/* related to plain text */ | /* related to plain text */ | ||
"blank line in non-literal context", | "blank line in fill mode, using .sp", | ||
"tab in non-literal context", | "tab in filled text", | ||
"end of line whitespace", | "whitespace at end of input line", | ||
"new sentence, new line", | |||
"bad comment style", | "bad comment style", | ||
"bad escape sequence", | "invalid escape sequence", | ||
"unterminated quoted string", | "undefined string, using \"\"", | ||
/* related to tables */ | |||
"tbl line starts with span", | |||
"tbl column starts with span", | |||
"skipping vertical bar in tbl layout", | |||
"generic error", | "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 */ | /* related to tables */ | ||
"bad table syntax", | "non-alphabetic character in tbl options", | ||
"bad table option", | "skipping unknown tbl option", | ||
"bad table layout", | "missing tbl option argument", | ||
"no table layout cells specified", | "wrong tbl option argument size", | ||
"no table data cells specified", | "empty tbl layout", | ||
"ignore data in cell", | "invalid character in tbl layout", | ||
"data block still open", | "unmatched parenthesis in tbl layout", | ||
"ignoring extra data cells", | "tbl without any data cells", | ||
"ignoring data in spanned tbl cell", | |||
"ignoring extra tbl data cells", | |||
"data block open at end of tbl", | |||
/* related to document structure and macros */ | |||
NULL, | |||
"input stack limit exceeded, infinite loop?", | "input stack limit exceeded, infinite loop?", | ||
"skipping bad character", | "skipping bad character", | ||
"escaped character not allowed in a name", | |||
"manual name not yet set", | |||
"skipping text before first section header", | |||
"skipping unknown macro", | "skipping unknown macro", | ||
"NOT IMPLEMENTED, please use groff: skipping request", | "skipping insecure request", | ||
"argument count wrong", | "skipping item outside list", | ||
"skipping invalid content in .Rs block", | |||
"skipping column outside column list", | "skipping column outside column list", | ||
"skipping end of block that is not open", | "skipping end of block that is not open", | ||
"missing end of block", | "fewer RS blocks open, skipping", | ||
"scope open on exit", | "inserting missing end of block", | ||
"uname(3) system call failed", | "appending missing end of block", | ||
"macro requires body argument(s)", | |||
"macro requires argument(s)", | |||
"request requires a numeric argument", | |||
"missing list type", | |||
"line argument(s) will be lost", | |||
"generic fatal error", | /* related to request and macro arguments */ | ||
"escaped character not allowed in a name", | |||
"input too large", | "NOT IMPLEMENTED: Bd -file", | ||
"not a manual", | "skipping display without arguments", | ||
"column syntax is inconsistent", | "missing list type, using -item", | ||
"NOT IMPLEMENTED: .Bd -file", | "argument is not numeric, using 1", | ||
"argument count wrong, violates syntax", | "missing manual name, using \"\"", | ||
"child violates parent syntax", | "uname(3) system call failed, using UNKNOWN", | ||
"argument count wrong, violates syntax", | "unknown standard specifier", | ||
"skipping request without numeric argument", | |||
"NOT IMPLEMENTED: .so with absolute path or \"..\"", | "NOT IMPLEMENTED: .so with absolute path or \"..\"", | ||
".so request failed", | ".so request failed", | ||
"no document prologue", | "skipping all arguments", | ||
"static buffer exhausted", | "skipping excess arguments", | ||
"divide by zero", | |||
/* system errors */ | "unsupported feature", | ||
NULL, | "input too large", | ||
"cannot stat file", | "unsupported control character", | ||
"cannot read file", | "unsupported roff request", | ||
"eqn delim option in tbl", | |||
"unsupported tbl layout modifier", | |||
"ignoring macro in table", | |||
}; | }; | ||
static const char * const mandoclevels[MANDOCLEVEL_MAX] = { | static const char * const mandoclevels[MANDOCLEVEL_MAX] = { | ||
"SUCCESS", | "SUCCESS", | ||
"RESERVED", | "STYLE", | ||
"WARNING", | "WARNING", | ||
"ERROR", | "ERROR", | ||
"FATAL", | "UNSUPP", | ||
"BADARG", | "BADARG", | ||
"SYSERR" | "SYSERR" | ||
}; | }; | ||
|
|
||
} | } | ||
static void | static void | ||
pset(const char *buf, int pos, struct mparse *curp) | choose_parser(struct mparse *curp) | ||
{ | { | ||
int i; | char *cp, *ep; | ||
int format; | |||
/* | /* | ||
* Try to intuit which kind of manual parser should be used. If | * If neither command line arguments -mdoc or -man select | ||
* passed in by command-line (-man, -mdoc), then use that | * a parser nor the roff parser found a .Dd or .TH macro | ||
* explicitly. If passed as -mandoc, then try to guess from the | * yet, look ahead in the main input buffer. | ||
* line: either skip dot-lines, use -mdoc when finding `.Dt', or | |||
* default to -man, which is more lenient. | |||
* | |||
* Separate out pmdoc/pman from mdoc/man: the first persists | |||
* through all parsers, while the latter is used per-parse. | |||
*/ | */ | ||
if ('.' == buf[0] || '\'' == buf[0]) { | if ((format = roff_getformat(curp->roff)) == 0) { | ||
for (i = 1; buf[i]; i++) | cp = curp->primary->buf; | ||
if (' ' != buf[i] && '\t' != buf[i]) | ep = cp + curp->primary->sz; | ||
while (cp < ep) { | |||
if (*cp == '.' || *cp == '\'') { | |||
cp++; | |||
if (cp[0] == 'D' && cp[1] == 'd') { | |||
format = MPARSE_MDOC; | |||
break; | |||
} | |||
if (cp[0] == 'T' && cp[1] == 'H') { | |||
format = MPARSE_MAN; | |||
break; | |||
} | |||
} | |||
cp = memchr(cp, '\n', ep - cp); | |||
if (cp == NULL) | |||
break; | break; | ||
if ('\0' == buf[i]) | cp++; | ||
return; | } | ||
} | } | ||
if (MPARSE_MDOC & curp->options) { | if (format == MPARSE_MDOC) { | ||
if (NULL == curp->pmdoc) | curp->man->macroset = MACROSET_MDOC; | ||
curp->pmdoc = mdoc_alloc( | if (curp->man->mdocmac == NULL) | ||
curp->roff, curp, curp->defos, | curp->man->mdocmac = roffhash_alloc(MDOC_Dd, MDOC_MAX); | ||
MPARSE_QUICK & curp->options ? 1 : 0); | } else { | ||
assert(curp->pmdoc); | curp->man->macroset = MACROSET_MAN; | ||
curp->mdoc = curp->pmdoc; | if (curp->man->manmac == NULL) | ||
return; | curp->man->manmac = roffhash_alloc(MAN_TH, MAN_MAX); | ||
} 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; | |||
} | } | ||
curp->man->first->tok = TOKEN_NONE; | |||
if (pos >= 3 && 0 == memcmp(buf, ".Dd", 3)) { | |||
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->roff, curp, | |||
MPARSE_QUICK & curp->options ? 1 : 0); | |||
assert(curp->pman); | |||
curp->man = curp->pman; | |||
} | } | ||
/* | /* | ||
* Main parse routine for an opened file. This is called for each | * Main parse routine for a buffer. | ||
* opened file and simply loops around the full input file, possibly | * It assumes encoding and line numbering are already set up. | ||
* nesting (i.e., with `so'). | * It can recurse directly (for invocations of user-defined | ||
* 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, int start) | mparse_buf_r(struct mparse *curp, struct buf blk, size_t i, int start) | ||
{ | { | ||
const struct tbl_span *span; | const struct tbl_span *span; | ||
struct buf ln; | struct buf ln; | ||
const char *save_file; | |||
char *cp; | |||
size_t pos; /* byte number in the ln buffer */ | |||
enum rofferr rr; | enum rofferr rr; | ||
int i, of, rc; | int of; | ||
int pos; /* byte number in the ln buffer */ | |||
int lnn; /* line number in the real file */ | int lnn; /* line number in the real file */ | ||
int fd; | |||
unsigned char c; | unsigned char c; | ||
memset(&ln, 0, sizeof(struct buf)); | memset(&ln, 0, sizeof(ln)); | ||
lnn = curp->line; | lnn = curp->line; | ||
pos = 0; | pos = 0; | ||
for (i = 0; i < (int)blk.sz; ) { | while (i < blk.sz) { | ||
if (0 == pos && '\0' == blk.buf[i]) | if (0 == pos && '\0' == blk.buf[i]) | ||
break; | break; | ||
if (start) { | if (start) { | ||
curp->line = lnn; | curp->line = lnn; | ||
curp->reparse_count = 0; | curp->reparse_count = 0; | ||
if (lnn < 3 && | |||
curp->filenc & MPARSE_UTF8 && | |||
curp->filenc & MPARSE_LATIN1) | |||
curp->filenc = preconv_cue(&blk, i); | |||
} | } | ||
while (i < (int)blk.sz && (start || '\0' != blk.buf[i])) { | while (i < blk.sz && (start || blk.buf[i] != '\0')) { | ||
/* | /* | ||
* When finding an unescaped newline character, | * When finding an unescaped newline character, | ||
|
|
||
* Skip a preceding carriage return, if any. | * Skip a preceding carriage return, if any. | ||
*/ | */ | ||
if ('\r' == blk.buf[i] && i + 1 < (int)blk.sz && | if ('\r' == blk.buf[i] && i + 1 < blk.sz && | ||
'\n' == blk.buf[i + 1]) | '\n' == blk.buf[i + 1]) | ||
++i; | ++i; | ||
if ('\n' == blk.buf[i]) { | if ('\n' == blk.buf[i]) { | ||
|
|
||
} | } | ||
/* | /* | ||
* Make sure we have space for at least | * Make sure we have space for the worst | ||
* one backslash and one other character | * case of 11 bytes: "\\[u10ffff]\0" | ||
* and the trailing NUL byte. | |||
*/ | */ | ||
if (pos + 2 >= (int)ln.sz) | if (pos + 11 > ln.sz) | ||
resize_buf(&ln, 256); | resize_buf(&ln, 256); | ||
/* | /* | ||
* Warn about bogus characters. If you're using | * Encode 8-bit input. | ||
* non-ASCII encoding, you're screwing your | |||
* readers. Since I'd rather this not happen, | |||
* 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]; | c = blk.buf[i]; | ||
if (c & 0x80) { | |||
if ( ! (isascii(c) && | if ( ! (curp->filenc && preconv_encode( | ||
(isgraph(c) || isblank(c)))) { | &blk, &i, &ln, &pos, &curp->filenc))) { | ||
mandoc_msg(MANDOCERR_BADCHAR, curp, | mandoc_vmsg(MANDOCERR_CHAR_BAD, curp, | ||
curp->line, pos, NULL); | curp->line, pos, "0x%x", c); | ||
i++; | ln.buf[pos++] = '?'; | ||
ln.buf[pos++] = '?'; | i++; | ||
} | |||
continue; | continue; | ||
} | } | ||
/* Trailing backslash = a plain char. */ | |||
if ('\\' != blk.buf[i] || i + 1 == (int)blk.sz) { | |||
ln.buf[pos++] = blk.buf[i++]; | |||
continue; | |||
} | |||
/* | /* | ||
* Found escape and at least one other character. | * Exclude control characters. | ||
* 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 < (int)blk.sz && | if (c == 0x7f || (c < 0x20 && c != 0x09)) { | ||
'\n' == blk.buf[i + 2]) | mandoc_vmsg(c == 0x00 || c == 0x04 || | ||
++i; | c > 0x0a ? MANDOCERR_CHAR_BAD : | ||
if ('\n' == blk.buf[i + 1]) { | MANDOCERR_CHAR_UNSUPP, | ||
i += 2; | curp, curp->line, pos, "0x%x", c); | ||
++lnn; | i++; | ||
if (c != '\r') | |||
ln.buf[pos++] = '?'; | |||
continue; | continue; | ||
} | } | ||
if ('"' == blk.buf[i + 1] || '#' == blk.buf[i + 1]) { | |||
i += 2; | |||
/* Comment, skip to end of line */ | |||
for (; i < (int)blk.sz; ++i) { | |||
if ('\n' == blk.buf[i]) { | |||
++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_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++]; | ||
ln.buf[pos++] = blk.buf[i++]; | |||
} | } | ||
if (pos >= (int)ln.sz) | if (pos + 1 >= ln.sz) | ||
resize_buf(&ln, 256); | resize_buf(&ln, 256); | ||
if (i == blk.sz || blk.buf[i] == '\0') | |||
ln.buf[pos++] = '\n'; | |||
ln.buf[pos] = '\0'; | ln.buf[pos] = '\0'; | ||
/* | /* | ||
|
|
||
[curp->secondary->sz] = '\0'; | [curp->secondary->sz] = '\0'; | ||
} | } | ||
rerun: | rerun: | ||
rr = roff_parseln(curp->roff, curp->line, | rr = roff_parseln(curp->roff, curp->line, &ln, &of); | ||
&ln.buf, &ln.sz, of, &of); | |||
switch (rr) { | switch (rr) { | ||
case ROFF_REPARSE: | case ROFF_REPARSE: | ||
if (REPARSE_LIMIT >= ++curp->reparse_count) | if (++curp->reparse_count > REPARSE_LIMIT) | ||
mparse_buf_r(curp, ln, 0); | |||
else | |||
mandoc_msg(MANDOCERR_ROFFLOOP, curp, | mandoc_msg(MANDOCERR_ROFFLOOP, curp, | ||
curp->line, pos, NULL); | curp->line, pos, NULL); | ||
pos = 0; | else if (mparse_buf_r(curp, ln, of, 0) == 1 || | ||
continue; | start == 1) { | ||
pos = 0; | |||
continue; | |||
} | |||
free(ln.buf); | |||
return 0; | |||
case ROFF_APPEND: | case ROFF_APPEND: | ||
pos = (int)strlen(ln.buf); | pos = strlen(ln.buf); | ||
continue; | continue; | ||
case ROFF_RERUN: | case ROFF_RERUN: | ||
goto rerun; | goto rerun; | ||
case ROFF_IGN: | case ROFF_IGN: | ||
pos = 0; | pos = 0; | ||
continue; | continue; | ||
case ROFF_ERR: | |||
assert(MANDOCLEVEL_FATAL <= curp->file_status); | |||
break; | |||
case ROFF_SO: | case ROFF_SO: | ||
if (0 == (MPARSE_SO & curp->options) && | if ( ! (curp->options & MPARSE_SO) && | ||
(i >= (int)blk.sz || '\0' == blk.buf[i])) { | (i >= blk.sz || blk.buf[i] == '\0')) { | ||
curp->sodest = mandoc_strdup(ln.buf + of); | curp->sodest = mandoc_strdup(ln.buf + of); | ||
free(ln.buf); | free(ln.buf); | ||
return; | return 1; | ||
} | } | ||
/* | /* | ||
* We remove `so' clauses from our lookaside | * We remove `so' clauses from our lookaside | ||
|
|
||
*/ | */ | ||
if (curp->secondary) | if (curp->secondary) | ||
curp->secondary->sz -= pos + 1; | curp->secondary->sz -= pos + 1; | ||
mparse_readfd(curp, -1, ln.buf + of); | save_file = curp->file; | ||
if (MANDOCLEVEL_FATAL <= curp->file_status) { | if ((fd = mparse_open(curp, ln.buf + of)) != -1) { | ||
mparse_readfd(curp, fd, ln.buf + of); | |||
close(fd); | |||
curp->file = save_file; | |||
} else { | |||
curp->file = save_file; | |||
mandoc_vmsg(MANDOCERR_SO_FAIL, | mandoc_vmsg(MANDOCERR_SO_FAIL, | ||
curp, curp->line, pos, | curp, curp->line, pos, | ||
".so %s", ln.buf + of); | ".so %s", ln.buf + of); | ||
break; | ln.sz = mandoc_asprintf(&cp, | ||
".sp\nSee the file %s.\n.sp", | |||
ln.buf + of); | |||
free(ln.buf); | |||
ln.buf = cp; | |||
of = 0; | |||
mparse_buf_r(curp, ln, of, 0); | |||
} | } | ||
pos = 0; | pos = 0; | ||
continue; | continue; | ||
|
|
||
break; | break; | ||
} | } | ||
/* | if (curp->man->macroset == MACROSET_NONE) | ||
* If we encounter errors in the recursive parse, make | choose_parser(curp); | ||
* sure we don't continue parsing. | |||
*/ | |||
if (MANDOCLEVEL_FATAL <= curp->file_status) | |||
break; | |||
/* | /* | ||
* If input parsers have not been allocated, do so now. | * Lastly, push down into the parsers themselves. | ||
* We keep these instanced between parsers, but set them | |||
* locally per parse routine since we can use different | |||
* parsers with each one. | |||
*/ | |||
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. | |||
* If libroff returns ROFF_TBL, then add it to the | * If libroff returns ROFF_TBL, then add it to the | ||
* currently open parse. Since we only get here if | * currently open parse. Since we only get here if | ||
* there does exist data (see tbl_data.c), we're | * there does exist data (see tbl_data.c), we're | ||
|
|
||
* Do the same for ROFF_EQN. | * Do the same for ROFF_EQN. | ||
*/ | */ | ||
rc = -1; | 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; | |||
if (ROFF_TBL == rr) | |||
while (NULL != (span = roff_span(curp->roff))) { | |||
rc = curp->man ? | |||
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)); | |||
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); | |||
if (0 == rc) { | |||
assert(MANDOCLEVEL_FATAL <= curp->file_status); | |||
break; | |||
} else if (2 == rc) | |||
break; | |||
/* Temporary buffers typically are not full. */ | /* Temporary buffers typically are not full. */ | ||
if (0 == start && '\0' == blk.buf[i]) | if (0 == start && '\0' == blk.buf[i]) | ||
|
|
||
} | } | ||
free(ln.buf); | free(ln.buf); | ||
return 1; | |||
} | } | ||
static int | static int | ||
read_whole_file(struct mparse *curp, const char *file, int fd, | read_whole_file(struct mparse *curp, const char *file, int fd, | ||
struct buf *fb, int *with_mmap) | struct buf *fb, int *with_mmap) | ||
{ | { | ||
struct stat st; | |||
gzFile gz; | |||
size_t off; | size_t off; | ||
ssize_t ssz; | ssize_t ssz; | ||
#ifdef HAVE_MMAP | if (fstat(fd, &st) == -1) | ||
struct stat st; | err((int)MANDOCLEVEL_SYSERR, "%s", file); | ||
if (-1 == fstat(fd, &st)) { | |||
curp->file_status = MANDOCLEVEL_SYSERR; | |||
if (curp->mmsg) | |||
(*curp->mmsg)(MANDOCERR_SYSSTAT, curp->file_status, | |||
file, 0, 0, strerror(errno)); | |||
return(0); | |||
} | |||
/* | /* | ||
* If we're a regular file, try just reading in the whole entry | * If we're a regular file, try just reading in the whole entry | ||
|
|
||
* concerned that this is going to tank any machines. | * concerned that this is going to tank any machines. | ||
*/ | */ | ||
if (S_ISREG(st.st_mode)) { | if (curp->gzip == 0 && S_ISREG(st.st_mode)) { | ||
if (st.st_size >= (1U << 31)) { | if (st.st_size > 0x7fffffff) { | ||
curp->file_status = MANDOCLEVEL_FATAL; | mandoc_msg(MANDOCERR_TOOLARGE, curp, 0, 0, NULL); | ||
if (curp->mmsg) | return 0; | ||
(*curp->mmsg)(MANDOCERR_TOOLARGE, | |||
curp->file_status, file, 0, 0, NULL); | |||
return(0); | |||
} | } | ||
*with_mmap = 1; | *with_mmap = 1; | ||
fb->sz = (size_t)st.st_size; | fb->sz = (size_t)st.st_size; | ||
fb->buf = mmap(NULL, fb->sz, PROT_READ, MAP_SHARED, fd, 0); | fb->buf = mmap(NULL, fb->sz, PROT_READ, MAP_SHARED, fd, 0); | ||
if (fb->buf != MAP_FAILED) | if (fb->buf != MAP_FAILED) | ||
return(1); | return 1; | ||
} | } | ||
#endif | |||
if (curp->gzip) { | |||
if ((gz = gzdopen(fd, "rb")) == NULL) | |||
err((int)MANDOCLEVEL_SYSERR, "%s", file); | |||
} else | |||
gz = NULL; | |||
/* | /* | ||
* If this isn't a regular file (like, say, stdin), then we must | * If this isn't a regular file (like, say, stdin), then we must | ||
* go the old way and just read things in bit by bit. | * go the old way and just read things in bit by bit. | ||
|
|
||
for (;;) { | for (;;) { | ||
if (off == fb->sz) { | if (off == fb->sz) { | ||
if (fb->sz == (1U << 31)) { | if (fb->sz == (1U << 31)) { | ||
curp->file_status = MANDOCLEVEL_FATAL; | mandoc_msg(MANDOCERR_TOOLARGE, curp, | ||
if (curp->mmsg) | 0, 0, NULL); | ||
(*curp->mmsg)(MANDOCERR_TOOLARGE, | |||
curp->file_status, | |||
file, 0, 0, NULL); | |||
break; | break; | ||
} | } | ||
resize_buf(fb, 65536); | resize_buf(fb, 65536); | ||
} | } | ||
ssz = read(fd, fb->buf + (int)off, fb->sz - off); | ssz = curp->gzip ? | ||
gzread(gz, fb->buf + (int)off, fb->sz - off) : | |||
read(fd, fb->buf + (int)off, fb->sz - off); | |||
if (ssz == 0) { | if (ssz == 0) { | ||
fb->sz = off; | fb->sz = off; | ||
return(1); | return 1; | ||
} | } | ||
if (ssz == -1) { | if (ssz == -1) | ||
curp->file_status = MANDOCLEVEL_SYSERR; | err((int)MANDOCLEVEL_SYSERR, "%s", file); | ||
if (curp->mmsg) | |||
(*curp->mmsg)(MANDOCERR_SYSREAD, | |||
curp->file_status, file, 0, 0, | |||
strerror(errno)); | |||
break; | |||
} | |||
off += (size_t)ssz; | off += (size_t)ssz; | ||
} | } | ||
free(fb->buf); | free(fb->buf); | ||
fb->buf = NULL; | fb->buf = NULL; | ||
return(0); | return 0; | ||
} | } | ||
static void | static void | ||
mparse_end(struct mparse *curp) | mparse_end(struct mparse *curp) | ||
{ | { | ||
if (curp->man->macroset == MACROSET_NONE) | |||
if (MANDOCLEVEL_FATAL <= curp->file_status) | curp->man->macroset = MACROSET_MAN; | ||
return; | if (curp->man->macroset == MACROSET_MDOC) | ||
mdoc_endparse(curp->man); | |||
if (curp->mdoc && ! mdoc_endparse(curp->mdoc)) { | else | ||
assert(MANDOCLEVEL_FATAL <= curp->file_status); | man_endparse(curp->man); | ||
return; | |||
} | |||
if (curp->man && ! man_endparse(curp->man)) { | |||
assert(MANDOCLEVEL_FATAL <= curp->file_status); | |||
return; | |||
} | |||
if ( ! (curp->mdoc || curp->man || curp->sodest)) { | |||
mandoc_msg(MANDOCERR_NOTMANUAL, curp, 0, 0, NULL); | |||
curp->file_status = MANDOCLEVEL_FATAL; | |||
return; | |||
} | |||
roff_endparse(curp->roff); | roff_endparse(curp->roff); | ||
} | } | ||
static void | static void | ||
mparse_parse_buffer(struct mparse *curp, struct buf blk, const char *file) | mparse_parse_buffer(struct mparse *curp, struct buf blk, const char *file) | ||
{ | { | ||
struct buf *svprimary; | |||
const char *svfile; | const char *svfile; | ||
size_t offset; | |||
static int recursion_depth; | static int recursion_depth; | ||
if (64 < recursion_depth) { | if (64 < recursion_depth) { | ||
|
|
||
/* Line number is per-file. */ | /* Line number is per-file. */ | ||
svfile = curp->file; | svfile = curp->file; | ||
curp->file = file; | curp->file = file; | ||
svprimary = curp->primary; | |||
curp->primary = &blk; | |||
curp->line = 1; | curp->line = 1; | ||
recursion_depth++; | recursion_depth++; | ||
mparse_buf_r(curp, blk, 1); | /* Skip an UTF-8 byte order mark. */ | ||
if (curp->filenc & MPARSE_UTF8 && blk.sz > 2 && | |||
(unsigned char)blk.buf[0] == 0xef && | |||
(unsigned char)blk.buf[1] == 0xbb && | |||
(unsigned char)blk.buf[2] == 0xbf) { | |||
offset = 3; | |||
curp->filenc &= ~MPARSE_LATIN1; | |||
} else | |||
offset = 0; | |||
if (0 == --recursion_depth && MANDOCLEVEL_FATAL > curp->file_status) | mparse_buf_r(curp, blk, offset, 1); | ||
if (--recursion_depth == 0) | |||
mparse_end(curp); | mparse_end(curp); | ||
curp->primary = svprimary; | |||
curp->file = svfile; | curp->file = svfile; | ||
} | } | ||
enum mandoclevel | enum mandoclevel | ||
mparse_readmem(struct mparse *curp, const void *buf, size_t len, | mparse_readmem(struct mparse *curp, void *buf, size_t len, | ||
const char *file) | const char *file) | ||
{ | { | ||
struct buf blk; | struct buf blk; | ||
blk.buf = UNCONST(buf); | blk.buf = buf; | ||
blk.sz = len; | blk.sz = len; | ||
mparse_parse_buffer(curp, blk, file); | mparse_parse_buffer(curp, blk, file); | ||
return(curp->file_status); | return curp->file_status; | ||
} | } | ||
/* | |||
* Read the whole file into memory and call the parsers. | |||
* Called recursively when an .so request is encountered. | |||
*/ | |||
enum mandoclevel | enum mandoclevel | ||
mparse_readfd(struct mparse *curp, int fd, const char *file) | mparse_readfd(struct mparse *curp, int fd, const char *file) | ||
{ | { | ||
struct buf blk; | struct buf blk; | ||
int with_mmap; | int with_mmap; | ||
int save_filenc; | |||
if (-1 == fd && -1 == (fd = open(file, O_RDONLY, 0))) { | if (read_whole_file(curp, file, fd, &blk, &with_mmap)) { | ||
curp->file_status = MANDOCLEVEL_SYSERR; | save_filenc = curp->filenc; | ||
if (curp->mmsg) | curp->filenc = curp->options & | ||
(*curp->mmsg)(MANDOCERR_SYSOPEN, | (MPARSE_UTF8 | MPARSE_LATIN1); | ||
curp->file_status, | mparse_parse_buffer(curp, blk, file); | ||
file, 0, 0, strerror(errno)); | curp->filenc = save_filenc; | ||
goto out; | if (with_mmap) | ||
munmap(blk.buf, blk.sz); | |||
else | |||
free(blk.buf); | |||
} | } | ||
return curp->file_status; | |||
} | |||
int | |||
mparse_open(struct mparse *curp, const char *file) | |||
{ | |||
char *cp; | |||
int fd; | |||
curp->file = file; | |||
cp = strrchr(file, '.'); | |||
curp->gzip = (cp != NULL && ! strcmp(cp + 1, "gz")); | |||
/* First try to use the filename as it is. */ | |||
if ((fd = open(file, O_RDONLY)) != -1) | |||
return fd; | |||
/* | /* | ||
* Run for each opened file; may be called more than once for | * If that doesn't work and the filename doesn't | ||
* each full parse sequence if the opened file is nested (i.e., | * already end in .gz, try appending .gz. | ||
* 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)) | if ( ! curp->gzip) { | ||
goto out; | mandoc_asprintf(&cp, "%s.gz", file); | ||
fd = open(cp, O_RDONLY); | |||
free(cp); | |||
if (fd != -1) { | |||
curp->gzip = 1; | |||
return fd; | |||
} | |||
} | |||
mparse_parse_buffer(curp, blk, file); | /* Neither worked, give up. */ | ||
#ifdef HAVE_MMAP | mandoc_msg(MANDOCERR_FILE, curp, 0, 0, strerror(errno)); | ||
if (with_mmap) | return -1; | ||
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 * | struct mparse * | ||
mparse_alloc(int options, enum mandoclevel wlevel, | mparse_alloc(int options, enum mandoclevel wlevel, mandocmsg mmsg, | ||
mandocmsg mmsg, char *defos) | const char *defos) | ||
{ | { | ||
struct mparse *curp; | struct mparse *curp; | ||
assert(wlevel <= MANDOCLEVEL_FATAL); | |||
curp = mandoc_calloc(1, sizeof(struct mparse)); | curp = mandoc_calloc(1, sizeof(struct mparse)); | ||
curp->options = options; | curp->options = options; | ||
|
|
||
curp->defos = defos; | curp->defos = defos; | ||
curp->roff = roff_alloc(curp, options); | curp->roff = roff_alloc(curp, options); | ||
return(curp); | curp->man = roff_man_alloc( curp->roff, curp, curp->defos, | ||
curp->options & MPARSE_QUICK ? 1 : 0); | |||
if (curp->options & MPARSE_MDOC) { | |||
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) { | |||
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; | |||
return curp; | |||
} | } | ||
void | void | ||
mparse_reset(struct mparse *curp) | mparse_reset(struct mparse *curp) | ||
{ | { | ||
roff_reset(curp->roff); | roff_reset(curp->roff); | ||
roff_man_reset(curp->man); | |||
if (curp->mdoc) | free(curp->sodest); | ||
mdoc_reset(curp->mdoc); | curp->sodest = NULL; | ||
if (curp->man) | |||
man_reset(curp->man); | |||
if (curp->secondary) | if (curp->secondary) | ||
curp->secondary->sz = 0; | curp->secondary->sz = 0; | ||
curp->file_status = MANDOCLEVEL_OK; | curp->file_status = MANDOCLEVEL_OK; | ||
curp->mdoc = NULL; | curp->gzip = 0; | ||
curp->man = NULL; | |||
free(curp->sodest); | |||
curp->sodest = NULL; | |||
} | } | ||
void | void | ||
mparse_free(struct mparse *curp) | mparse_free(struct mparse *curp) | ||
{ | { | ||
if (curp->pmdoc) | roffhash_free(curp->man->mdocmac); | ||
mdoc_free(curp->pmdoc); | roffhash_free(curp->man->manmac); | ||
if (curp->pman) | roff_man_free(curp->man); | ||
man_free(curp->pman); | roff_free(curp->roff); | ||
if (curp->roff) | |||
roff_free(curp->roff); | |||
if (curp->secondary) | if (curp->secondary) | ||
free(curp->secondary->buf); | free(curp->secondary->buf); | ||
|
|
||
} | } | ||
void | void | ||
mparse_result(struct mparse *curp, | mparse_result(struct mparse *curp, struct roff_man **man, | ||
struct mdoc **mdoc, struct man **man, char **sodest) | char **sodest) | ||
{ | { | ||
if (sodest && NULL != (*sodest = curp->sodest)) { | if (sodest && NULL != (*sodest = curp->sodest)) { | ||
*mdoc = NULL; | |||
*man = NULL; | *man = NULL; | ||
return; | return; | ||
} | } | ||
if (mdoc) | |||
*mdoc = curp->mdoc; | |||
if (man) | if (man) | ||
*man = curp->man; | *man = curp->man; | ||
} | } | ||
void | 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, | mandoc_vmsg(enum mandocerr t, struct mparse *m, | ||
int ln, int pos, const char *fmt, ...) | int ln, int pos, const char *fmt, ...) | ||
{ | { | ||
|
|
||
{ | { | ||
enum mandoclevel level; | enum mandoclevel level; | ||
level = MANDOCLEVEL_FATAL; | level = MANDOCLEVEL_UNSUPP; | ||
while (er < mandoclimits[level]) | while (er < mandoclimits[level]) | ||
level--; | level--; | ||
if (level < m->wlevel) | if (level < m->wlevel && er != MANDOCERR_FILE) | ||
return; | return; | ||
if (m->mmsg) | if (m->mmsg) | ||
|
|
||
mparse_strerror(enum mandocerr er) | mparse_strerror(enum mandocerr er) | ||
{ | { | ||
return(mandocerrs[er]); | return mandocerrs[er]; | ||
} | } | ||
const char * | const char * | ||
mparse_strlevel(enum mandoclevel lvl) | mparse_strlevel(enum mandoclevel lvl) | ||
{ | { | ||
return(mandoclevels[lvl]); | return mandoclevels[lvl]; | ||
} | } | ||
void | void | ||
|
|
||
{ | { | ||
assert(p->secondary); | assert(p->secondary); | ||
return(p->secondary->sz ? p->secondary->buf : NULL); | return p->secondary->sz ? p->secondary->buf : NULL; | ||
} | } |