-/* $Id: read.c,v 1.57 2014/07/02 13:10:45 schwarze Exp $ */
+/* $Id: read.c,v 1.93 2014/10/25 01:03:52 schwarze Exp $ */
/*
* Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
* Copyright (c) 2010-2014 Ingo Schwarze <schwarze@openbsd.org>
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#ifdef HAVE_CONFIG_H
#include "config.h"
-#endif
-#ifdef HAVE_MMAP
-# include <sys/stat.h>
-# include <sys/mman.h>
+#include <sys/types.h>
+#if HAVE_MMAP
+#include <sys/mman.h>
+#include <sys/stat.h>
#endif
+#include <sys/wait.h>
#include <assert.h>
#include <ctype.h>
#define REPARSE_LIMIT 1000
-struct buf {
- char *buf; /* binary input buffer */
- size_t sz; /* size of binary buffer */
-};
-
struct mparse {
- enum mandoclevel file_status; /* status of current parse */
- enum mandoclevel wlevel; /* ignore messages below this */
- int line; /* line number in the file */
- 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) */
char *sodest; /* filename pointed to by .so */
- int reparse_count; /* finite interp. stack */
+ 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 */
- const char *file;
- struct buf *secondary;
- char *defos; /* default operating system */
+ enum mandoclevel file_status; /* status of current parse */
+ enum mandoclevel wlevel; /* ignore messages below this */
+ int options; /* parser options */
+ int filenc; /* encoding of the current file */
+ int reparse_count; /* finite interp. stack */
+ int line; /* line number in the file */
};
+static void choose_parser(struct mparse *);
static void resize_buf(struct buf *, size_t);
static void mparse_buf_r(struct mparse *, struct buf, int);
-static void pset(const char *, int, struct mparse *);
static int read_whole_file(struct mparse *, const char *, int,
struct buf *, int *);
static void mparse_end(struct mparse *);
"generic warning",
/* 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",
+ "missing manual section, using \"\"",
"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",
+ "missing Os macro, using \"\"",
"duplicate prologue macro",
- "incomplete prologue, terminated by",
- "skipping prologue macro in body",
+ "late prologue macro",
+ "skipping late title macro",
+ "prologue macros out of order",
/* related to document structure */
".so is fragile, better use ln(1)",
"sections out of conventional order",
"duplicate section title",
"unexpected section",
+ "unusual Xr order",
+ "unusual Xr punctuation",
+ "AUTHORS section without An macro",
/* related to macros and nesting */
"obsolete macro",
"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",
+ "fill mode already enabled, skipping",
+ "fill mode already disabled, skipping",
"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",
- "list type must come first",
- "tag lists require a width argument",
- "missing font type",
+ "missing display type, using -ragged",
+ "list type is not the first argument",
+ "missing -width in -tag list, using 8n",
+ "missing utility name, using \"\"",
+ "empty head in list item",
+ "empty list item",
+ "missing font type, using \\fR",
+ "unknown font type, using \\fR",
+ "missing -std argument, adding it",
+ "missing eqn box, using \"\"",
/* related to bad macro arguments */
- "skipping argument",
+ "unterminated quoted argument",
"duplicate argument",
- "duplicate display type",
- "duplicate list type",
+ "skipping duplicate argument",
+ "skipping duplicate display type",
+ "skipping duplicate list type",
+ "skipping -width argument",
"unknown AT&T UNIX version",
- "bad Boolean value",
- "unknown font",
- "unknown standard specifier",
- "bad width argument",
+ "comma in function argument",
+ "parenthesis in function name",
+ "invalid content in Rs block",
+ "invalid Boolean argument",
+ "unknown font, skipping request",
/* related to plain text */
- "blank line in non-literal context",
- "tab in non-literal context",
- "end of line whitespace",
+ "blank line in fill mode, using .sp",
+ "tab in filled text",
+ "whitespace at end of input line",
"bad comment style",
- "bad escape sequence",
- "unterminated quoted string",
+ "invalid escape sequence",
+ "undefined string, using \"\"",
"generic error",
"equation scope open on exit",
"overlapping equation scopes",
"unexpected end of equation",
- "equation syntax error",
/* related to tables */
"bad table syntax",
"data block still open",
"ignoring extra data cells",
+ /* related to document structure and macros */
"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 item outside list",
"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",
- "macro requires line argument(s)",
- "macro requires body argument(s)",
- "macro requires argument(s)",
- "request requires a numeric argument",
- "missing list type",
- "line argument(s) will be lost",
+ "inserting missing end of block",
+ "appending missing end of block",
+
+ /* related to request and macro arguments */
+ "escaped character not allowed in a name",
+ "argument count wrong",
+ "missing list type, using -item",
+ "missing manual name, using \"\"",
+ "uname(3) system call failed, using UNKNOWN",
+ "unknown standard specifier",
+ "skipping request without numeric argument",
+ "skipping all arguments",
+ "skipping excess arguments",
+ "divide by zero",
"generic fatal error",
"input too large",
- "not a manual",
- "column syntax is inconsistent",
- "NOT IMPLEMENTED: .Bd -file",
- "argument count wrong, violates syntax",
- "child violates parent syntax",
- "argument count wrong, violates syntax",
+ "NOT IMPLEMENTED: Bd -file",
"NOT IMPLEMENTED: .so with absolute path or \"..\"",
".so request failed",
- "no document prologue",
- "static buffer exhausted",
/* system errors */
+ "cannot dup file descriptor",
+ "cannot exec",
+ "gunzip failed with code",
+ "cannot fork",
NULL,
- "cannot stat file",
+ "cannot open pipe",
"cannot read file",
+ "gunzip died from signal",
+ "cannot stat file",
+ "wait failed",
};
static const char * const mandoclevels[MANDOCLEVEL_MAX] = {
}
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
- * passed in by command-line (-man, -mdoc), then use that
- * explicitly. If passed as -mandoc, then try to guess from the
- * 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 neither command line arguments -mdoc or -man select
+ * a parser nor the roff parser found a .Dd or .TH macro
+ * yet, look ahead in the main input buffer.
*/
- if ('.' == buf[0] || '\'' == buf[0]) {
- for (i = 1; buf[i]; i++)
- if (' ' != buf[i] && '\t' != buf[i])
+ if ((format = roff_getformat(curp->roff)) == 0) {
+ cp = curp->primary->buf;
+ 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;
- if ('\0' == buf[i])
- return;
+ cp++;
+ }
}
- if (MPARSE_MDOC & curp->options) {
+ if (format == MPARSE_MDOC) {
if (NULL == curp->pmdoc)
curp->pmdoc = mdoc_alloc(
curp->roff, curp, curp->defos,
assert(curp->pmdoc);
curp->mdoc = curp->pmdoc;
return;
- } 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;
}
- 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;
- }
+ /* Fall back to man(7) as a last resort. */
if (NULL == curp->pman)
curp->pman = man_alloc(curp->roff, curp,
lnn = curp->line;
pos = 0;
- for (i = 0; i < (int)blk.sz; ) {
+ for (i = blk.offs; i < (int)blk.sz; ) {
if (0 == pos && '\0' == blk.buf[i])
break;
if (start) {
curp->line = lnn;
curp->reparse_count = 0;
+
+ if (lnn < 3 &&
+ curp->filenc & MPARSE_UTF8 &&
+ curp->filenc & MPARSE_LATIN1) {
+ blk.offs = i;
+ curp->filenc = preconv_cue(&blk);
+ }
}
while (i < (int)blk.sz && (start || '\0' != blk.buf[i])) {
}
/*
- * Make sure we have space for at least
- * one backslash and one other character
- * and the trailing NUL byte.
+ * Make sure we have space for the worst
+ * case of 11 bytes: "\\[u10ffff]\0"
*/
- if (pos + 2 >= (int)ln.sz)
+ if (pos + 11 > (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 replace these characters
- * with "?", so we don't display gibberish.
- * Note to manual writers: use special characters.
+ * Encode 8-bit input.
*/
- c = (unsigned char) blk.buf[i];
+ c = blk.buf[i];
+ if (c & 0x80) {
+ blk.offs = i;
+ ln.offs = pos;
+ if (curp->filenc && preconv_encode(
+ &blk, &ln, &curp->filenc)) {
+ pos = ln.offs;
+ i = blk.offs;
+ } else {
+ mandoc_vmsg(MANDOCERR_BADCHAR,
+ curp, curp->line, pos,
+ "0x%x", c);
+ ln.buf[pos++] = '?';
+ i++;
+ }
+ continue;
+ }
- if ( ! (isascii(c) &&
- (isgraph(c) || isblank(c)))) {
- mandoc_msg(MANDOCERR_BADCHAR, curp,
- curp->line, pos, NULL);
+ /*
+ * Exclude control characters.
+ */
+
+ if (c == 0x7f || (c < 0x20 && c != 0x09)) {
+ mandoc_vmsg(MANDOCERR_BADCHAR, curp,
+ curp->line, pos, "0x%x", c);
i++;
ln.buf[pos++] = '?';
continue;
if ( ! (isascii(c) &&
(isgraph(c) || isblank(c)))) {
- mandoc_msg(MANDOCERR_BADCHAR, curp,
- curp->line, pos, NULL);
+ mandoc_vmsg(MANDOCERR_BADCHAR, curp,
+ curp->line, pos, "0x%x", c);
i += 2;
ln.buf[pos++] = '?';
continue;
*/
if ( ! (curp->man || curp->mdoc))
- pset(ln.buf + of, pos - of, curp);
+ choose_parser(curp);
/*
- * Lastly, push down into the parsers themselves. One
- * of these will have already been set in the pset()
- * routine.
+ * 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
size_t off;
ssize_t ssz;
-#ifdef HAVE_MMAP
+#if HAVE_MMAP
struct stat st;
if (-1 == fstat(fd, &st)) {
curp->file_status = MANDOCLEVEL_SYSERR;
return(0);
}
*with_mmap = 1;
+ fb->offs = 0;
fb->sz = (size_t)st.st_size;
fb->buf = mmap(NULL, fb->sz, PROT_READ, MAP_SHARED, fd, 0);
if (fb->buf != MAP_FAILED)
ssz = read(fd, fb->buf + (int)off, fb->sz - off);
if (ssz == 0) {
fb->sz = off;
+ fb->offs = 0;
return(1);
}
if (ssz == -1) {
if (MANDOCLEVEL_FATAL <= curp->file_status)
return;
+ if (curp->mdoc == NULL &&
+ curp->man == NULL &&
+ curp->sodest == NULL) {
+ if (curp->options & MPARSE_MDOC)
+ curp->mdoc = curp->pmdoc;
+ else {
+ if (curp->pman == NULL)
+ curp->pman = man_alloc(curp->roff, curp,
+ curp->options & MPARSE_QUICK ? 1 : 0);
+ curp->man = curp->pman;
+ }
+ }
+
if (curp->mdoc && ! mdoc_endparse(curp->mdoc)) {
assert(MANDOCLEVEL_FATAL <= curp->file_status);
return;
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);
}
static void
mparse_parse_buffer(struct mparse *curp, struct buf blk, const char *file)
{
+ struct buf *svprimary;
const char *svfile;
static int recursion_depth;
/* Line number is per-file. */
svfile = curp->file;
curp->file = file;
+ svprimary = curp->primary;
+ curp->primary = &blk;
curp->line = 1;
recursion_depth++;
+ /* 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) {
+ blk.offs = 3;
+ curp->filenc &= ~MPARSE_LATIN1;
+ }
+
mparse_buf_r(curp, blk, 1);
if (0 == --recursion_depth && MANDOCLEVEL_FATAL > curp->file_status)
mparse_end(curp);
+ curp->primary = svprimary;
curp->file = svfile;
}
blk.buf = UNCONST(buf);
blk.sz = len;
+ blk.offs = 0;
mparse_parse_buffer(curp, blk, file);
return(curp->file_status);
{
struct buf blk;
int with_mmap;
+ int save_filenc;
if (-1 == fd && -1 == (fd = open(file, O_RDONLY, 0))) {
curp->file_status = MANDOCLEVEL_SYSERR;
(*curp->mmsg)(MANDOCERR_SYSOPEN,
curp->file_status,
file, 0, 0, strerror(errno));
- goto out;
+ return(curp->file_status);
}
/*
* 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
+ if (read_whole_file(curp, file, fd, &blk, &with_mmap)) {
+ save_filenc = curp->filenc;
+ curp->filenc = curp->options &
+ (MPARSE_UTF8 | MPARSE_LATIN1);
+ mparse_parse_buffer(curp, blk, file);
+ curp->filenc = save_filenc;
+#if HAVE_MMAP
+ if (with_mmap)
+ munmap(blk.buf, blk.sz);
+ else
#endif
- free(blk.buf);
+ free(blk.buf);
+ }
if (STDIN_FILENO != fd && -1 == close(fd))
perror(file);
+
+ return(curp->file_status);
+}
+
+enum mandoclevel
+mparse_open(struct mparse *curp, int *fd, const char *file,
+ pid_t *child_pid)
+{
+ int pfd[2];
+ char *cp;
+ enum mandocerr err;
+
+ pfd[1] = -1;
+ curp->file = file;
+ if ((cp = strrchr(file, '.')) == NULL ||
+ strcmp(cp + 1, "gz")) {
+ *child_pid = 0;
+ if ((*fd = open(file, O_RDONLY)) == -1) {
+ err = MANDOCERR_SYSOPEN;
+ goto out;
+ }
+ return(MANDOCLEVEL_OK);
+ }
+
+ if (pipe(pfd) == -1) {
+ err = MANDOCERR_SYSPIPE;
+ goto out;
+ }
+
+ switch (*child_pid = fork()) {
+ case -1:
+ err = MANDOCERR_SYSFORK;
+ close(pfd[0]);
+ close(pfd[1]);
+ pfd[1] = -1;
+ break;
+ case 0:
+ close(pfd[0]);
+ if (dup2(pfd[1], STDOUT_FILENO) == -1) {
+ err = MANDOCERR_SYSDUP;
+ break;
+ }
+ execlp("gunzip", "gunzip", "-c", file, NULL);
+ err = MANDOCERR_SYSEXEC;
+ break;
+ default:
+ close(pfd[1]);
+ *fd = pfd[0];
+ return(MANDOCLEVEL_OK);
+ }
+
out:
+ *fd = -1;
+ *child_pid = 0;
+ curp->file_status = MANDOCLEVEL_SYSERR;
+ if (curp->mmsg)
+ (*curp->mmsg)(err, curp->file_status, file,
+ 0, 0, strerror(errno));
+ if (pfd[1] != -1)
+ exit(1);
return(curp->file_status);
}
+enum mandoclevel
+mparse_wait(struct mparse *curp, pid_t child_pid)
+{
+ int status;
+
+ if (waitpid(child_pid, &status, 0) == -1) {
+ mandoc_msg(MANDOCERR_SYSWAIT, curp, 0, 0,
+ strerror(errno));
+ curp->file_status = MANDOCLEVEL_SYSERR;
+ return(curp->file_status);
+ }
+ if (WIFSIGNALED(status)) {
+ mandoc_vmsg(MANDOCERR_SYSSIG, curp, 0, 0,
+ "%d", WTERMSIG(status));
+ curp->file_status = MANDOCLEVEL_SYSERR;
+ return(curp->file_status);
+ }
+ if (WEXITSTATUS(status)) {
+ mandoc_vmsg(MANDOCERR_SYSEXIT, curp, 0, 0,
+ "%d", WEXITSTATUS(status));
+ curp->file_status = MANDOCLEVEL_SYSERR;
+ return(curp->file_status);
+ }
+ return(MANDOCLEVEL_OK);
+}
+
struct mparse *
mparse_alloc(int options, enum mandoclevel wlevel,
- mandocmsg mmsg, char *defos)
+ mandocmsg mmsg, const char *defos)
{
struct mparse *curp;
curp->defos = defos;
curp->roff = roff_alloc(curp, options);
+ if (curp->options & MPARSE_MDOC)
+ curp->pmdoc = mdoc_alloc(
+ curp->roff, curp, curp->defos,
+ curp->options & MPARSE_QUICK ? 1 : 0);
+ if (curp->options & MPARSE_MAN)
+ curp->pman = man_alloc(curp->roff, curp,
+ curp->options & MPARSE_QUICK ? 1 : 0);
+
return(curp);
}