+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "main.h"
+#include "manpath.h"
+#include "mansearch.h"
+#include "cgi.h"
+
+/*
+ * A query as passed to the search function.
+ */
+struct query {
+ char *manpath; /* desired manual directory */
+ char *arch; /* architecture */
+ char *sec; /* manual section */
+ char *query; /* unparsed query expression */
+ int equal; /* match whole names, not substrings */
+};
+
+struct req {
+ struct query q;
+ char **p; /* array of available manpaths */
+ size_t psz; /* number of available manpaths */
+};
+
+static void catman(const struct req *, const char *);
+static void format(const struct req *, const char *);
+static void html_print(const char *);
+static void html_putchar(char);
+static int http_decode(char *);
+static void http_parse(struct req *, const char *);
+static void http_print(const char *);
+static void http_putchar(char);
+static void http_printquery(const struct req *, const char *);
+static void pathgen(struct req *);
+static void pg_error_badrequest(const char *);
+static void pg_error_internal(void);
+static void pg_index(const struct req *);
+static void pg_noresult(const struct req *, const char *);
+static void pg_search(const struct req *);
+static void pg_searchres(const struct req *,
+ struct manpage *, size_t);
+static void pg_show(struct req *, const char *);
+static void resp_begin_html(int, const char *);
+static void resp_begin_http(int, const char *);
+static void resp_end_html(void);
+static void resp_searchform(const struct req *);
+static void resp_show(const struct req *, const char *);
+static void set_query_attr(char **, char **);
+static int validate_filename(const char *);
+static int validate_manpath(const struct req *, const char *);
+static int validate_urifrag(const char *);
+
+static const char *scriptname; /* CGI script name */
+
+static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
+static const char *const sec_numbers[] = {
+ "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
+};
+static const char *const sec_names[] = {
+ "All Sections",
+ "1 - General Commands",
+ "2 - System Calls",
+ "3 - Subroutines",
+ "3p - Perl Subroutines",
+ "4 - Special Files",
+ "5 - File Formats",
+ "6 - Games",
+ "7 - Macros and Conventions",
+ "8 - Maintenance Commands",
+ "9 - Kernel Interface"
+};
+static const int sec_MAX = sizeof(sec_names) / sizeof(char *);
+
+static const char *const arch_names[] = {
+ "amd64", "alpha", "armish", "armv7",
+ "aviion", "hppa", "hppa64", "i386",
+ "ia64", "landisk", "loongson", "luna88k",
+ "macppc", "mips64", "octeon", "sgi",
+ "socppc", "solbourne", "sparc", "sparc64",
+ "vax", "zaurus",
+ "amiga", "arc", "arm32", "atari",
+ "beagle", "cats", "hp300", "mac68k",
+ "mvme68k", "mvme88k", "mvmeppc", "palm",
+ "pc532", "pegasos", "pmax", "powerpc",
+ "sun3", "wgrisc", "x68k"
+};
+static const int arch_MAX = sizeof(arch_names) / sizeof(char *);
+
+/*
+ * Print a character, escaping HTML along the way.
+ * This will pass non-ASCII straight to output: be warned!
+ */
+static void
+html_putchar(char c)
+{
+
+ switch (c) {
+ case ('"'):
+ printf(""e;");
+ break;
+ case ('&'):
+ printf("&");
+ break;
+ case ('>'):
+ printf(">");
+ break;
+ case ('<'):
+ printf("<");
+ break;
+ default:
+ putchar((unsigned char)c);
+ break;
+ }
+}
+
+static void
+http_printquery(const struct req *req, const char *sep)
+{
+
+ if (NULL != req->q.query) {
+ printf("query=");
+ http_print(req->q.query);
+ }
+ if (0 == req->q.equal)
+ printf("%sapropos=1", sep);
+ if (NULL != req->q.sec) {
+ printf("%ssec=", sep);
+ http_print(req->q.sec);
+ }
+ if (NULL != req->q.arch) {
+ printf("%sarch=", sep);
+ http_print(req->q.arch);
+ }
+ if (NULL != req->q.manpath &&
+ strcmp(req->q.manpath, req->p[0])) {
+ printf("%smanpath=", sep);
+ http_print(req->q.manpath);
+ }
+}
+
+static void
+http_print(const char *p)
+{
+
+ if (NULL == p)
+ return;
+ while ('\0' != *p)
+ http_putchar(*p++);
+}
+
+/*
+ * Call through to html_putchar().
+ * Accepts NULL strings.
+ */
+static void
+html_print(const char *p)
+{
+
+ if (NULL == p)
+ return;
+ while ('\0' != *p)
+ html_putchar(*p++);
+}
+
+/*
+ * Transfer the responsibility for the allocated string *val
+ * to the query structure.
+ */
+static void
+set_query_attr(char **attr, char **val)
+{
+
+ free(*attr);
+ if (**val == '\0') {
+ *attr = NULL;
+ free(*val);
+ } else
+ *attr = *val;
+ *val = NULL;
+}
+
+/*
+ * Parse the QUERY_STRING for key-value pairs
+ * and store the values into the query structure.
+ */
+static void
+http_parse(struct req *req, const char *qs)
+{
+ char *key, *val;
+ size_t keysz, valsz;
+
+ req->q.manpath = NULL;
+ req->q.arch = NULL;
+ req->q.sec = NULL;
+ req->q.query = NULL;
+ req->q.equal = 1;
+
+ key = val = NULL;
+ while (*qs != '\0') {
+
+ /* Parse one key. */
+
+ keysz = strcspn(qs, "=;&");
+ key = mandoc_strndup(qs, keysz);
+ qs += keysz;
+ if (*qs != '=')
+ goto next;
+
+ /* Parse one value. */
+
+ valsz = strcspn(++qs, ";&");
+ val = mandoc_strndup(qs, valsz);
+ qs += valsz;
+
+ /* Decode and catch encoding errors. */
+
+ if ( ! (http_decode(key) && http_decode(val)))
+ goto next;
+
+ /* Handle key-value pairs. */
+
+ if ( ! strcmp(key, "query"))
+ set_query_attr(&req->q.query, &val);
+
+ else if ( ! strcmp(key, "apropos"))
+ req->q.equal = !strcmp(val, "0");
+
+ else if ( ! strcmp(key, "manpath")) {
+#ifdef COMPAT_OLDURI
+ if ( ! strncmp(val, "OpenBSD ", 8)) {
+ val[7] = '-';
+ if ('C' == val[8])
+ val[8] = 'c';
+ }
+#endif
+ set_query_attr(&req->q.manpath, &val);
+ }
+
+ else if ( ! (strcmp(key, "sec")
+#ifdef COMPAT_OLDURI
+ && strcmp(key, "sektion")
+#endif
+ )) {
+ if ( ! strcmp(val, "0"))
+ *val = '\0';
+ set_query_attr(&req->q.sec, &val);
+ }
+
+ else if ( ! strcmp(key, "arch")) {
+ if ( ! strcmp(val, "default"))
+ *val = '\0';
+ set_query_attr(&req->q.arch, &val);
+ }
+
+ /*
+ * The key must be freed in any case.
+ * The val may have been handed over to the query
+ * structure, in which case it is now NULL.
+ */
+next:
+ free(key);
+ key = NULL;
+ free(val);
+ val = NULL;
+
+ if (*qs != '\0')
+ qs++;
+ }
+
+ /* Fall back to the default manpath. */
+
+ if (req->q.manpath == NULL)
+ req->q.manpath = mandoc_strdup(req->p[0]);
+}
+
+static void
+http_putchar(char c)
+{
+
+ if (isalnum((unsigned char)c)) {
+ putchar((unsigned char)c);
+ return;
+ } else if (' ' == c) {
+ putchar('+');
+ return;
+ }
+ printf("%%%.2x", c);
+}
+
+/*
+ * HTTP-decode a string. The standard explanation is that this turns
+ * "%4e+foo" into "n foo" in the regular way. This is done in-place
+ * over the allocated string.
+ */
+static int
+http_decode(char *p)
+{
+ char hex[3];
+ char *q;
+ int c;
+
+ hex[2] = '\0';
+
+ q = p;
+ for ( ; '\0' != *p; p++, q++) {
+ if ('%' == *p) {
+ if ('\0' == (hex[0] = *(p + 1)))
+ return(0);
+ if ('\0' == (hex[1] = *(p + 2)))
+ return(0);
+ if (1 != sscanf(hex, "%x", &c))
+ return(0);
+ if ('\0' == c)
+ return(0);
+
+ *q = (char)c;
+ p += 2;
+ } else
+ *q = '+' == *p ? ' ' : *p;
+ }
+
+ *q = '\0';
+ return(1);
+}
+
+static void
+resp_begin_http(int code, const char *msg)
+{
+
+ if (200 != code)
+ printf("Status: %d %s\r\n", code, msg);
+
+ printf("Content-Type: text/html; charset=utf-8\r\n"
+ "Cache-Control: no-cache\r\n"
+ "Pragma: no-cache\r\n"
+ "\r\n");
+
+ fflush(stdout);
+}
+
+static void
+resp_begin_html(int code, const char *msg)
+{
+
+ resp_begin_http(code, msg);
+
+ printf("\n"
+ "\n"
+ "\n"
+ " \n"
+ " \n"
+ " \n"
+ "%s \n"
+ "\n"
+ "\n"
+ "\n",
+ CSS_DIR, CSS_DIR, CUSTOMIZE_TITLE);
+}
+
+static void
+resp_end_html(void)
+{
+
+ puts("\n"
+ "");
+}
+
+static void
+resp_searchform(const struct req *req)
+{
+ int i;
+
+ puts(CUSTOMIZE_BEGIN);
+ puts("");
+ printf("");
+ puts("");
+}
+
+static int
+validate_urifrag(const char *frag)
+{
+
+ while ('\0' != *frag) {
+ if ( ! (isalnum((unsigned char)*frag) ||
+ '-' == *frag || '.' == *frag ||
+ '/' == *frag || '_' == *frag))
+ return(0);
+ frag++;
+ }
+ return(1);
+}
+
+static int
+validate_manpath(const struct req *req, const char* manpath)
+{
+ size_t i;
+
+ if ( ! strcmp(manpath, "mandoc"))
+ return(1);
+
+ for (i = 0; i < req->psz; i++)
+ if ( ! strcmp(manpath, req->p[i]))
+ return(1);
+
+ return(0);
+}
+
+static int
+validate_filename(const char *file)
+{
+
+ if ('.' == file[0] && '/' == file[1])
+ file += 2;
+
+ return ( ! (strstr(file, "../") || strstr(file, "/..") ||
+ (strncmp(file, "man", 3) && strncmp(file, "cat", 3))));
+}
+
+static void
+pg_index(const struct req *req)
+{
+
+ resp_begin_html(200, NULL);
+ resp_searchform(req);
+ printf("\n"
+ "This web interface is documented in the\n"
+ "man.cgi \n"
+ "manual, and the\n"
+ "apropos \n"
+ "manual explains the query syntax.\n"
+ "
\n",
+ scriptname, scriptname);
+ resp_end_html();
+}
+
+static void
+pg_noresult(const struct req *req, const char *msg)
+{
+ resp_begin_html(200, NULL);
+ resp_searchform(req);
+ puts("");
+ puts(msg);
+ puts("
");
+ resp_end_html();
+}
+
+static void
+pg_error_badrequest(const char *msg)
+{
+
+ resp_begin_html(400, "Bad Request");
+ puts("Bad Request \n"
+ "\n");
+ puts(msg);
+ printf("Try again from the\n"
+ "main page .\n"
+ "
", scriptname);
+ resp_end_html();
+}
+
+static void
+pg_error_internal(void)
+{
+ resp_begin_html(500, "Internal Server Error");
+ puts("Internal Server Error
");
+ resp_end_html();
+}
+
+static void
+pg_searchres(const struct req *req, struct manpage *r, size_t sz)
+{
+ char *arch, *archend;
+ size_t i, iuse, isec;
+ int archprio, archpriouse;
+ int prio, priouse;
+ char sec;
+
+ for (i = 0; i < sz; i++) {
+ if (validate_filename(r[i].file))
+ continue;
+ fprintf(stderr, "invalid filename %s in %s database\n",
+ r[i].file, req->q.manpath);
+ pg_error_internal();
+ return;
+ }
+
+ if (1 == sz) {
+ /*
+ * If we have just one result, then jump there now
+ * without any delay.
+ */
+ printf("Status: 303 See Other\r\n");
+ printf("Location: http://%s%s/%s/%s?",
+ HTTP_HOST, scriptname, req->q.manpath, r[0].file);
+ http_printquery(req, "&");
+ printf("\r\n"
+ "Content-Type: text/html; charset=utf-8\r\n"
+ "\r\n");
+ return;
+ }
+
+ resp_begin_html(200, NULL);
+ resp_searchform(req);
+ puts("");
+
+ /*
+ * In man(1) mode, show one of the pages
+ * even if more than one is found.
+ */
+
+ if (req->q.equal) {
+ puts(" ");
+ iuse = 0;
+ priouse = 10;
+ archpriouse = 3;
+ for (i = 0; i < sz; i++) {
+ isec = strcspn(r[i].file, "123456789");
+ sec = r[i].file[isec];
+ if ('\0' == sec)
+ continue;
+ prio = sec_prios[sec - '1'];
+ if (NULL == req->q.arch) {
+ archprio =
+ (NULL == (arch = strchr(
+ r[i].file + isec, '/'))) ? 3 :
+ (NULL == (archend = strchr(
+ arch + 1, '/'))) ? 0 :
+ strncmp(arch, "amd64/",
+ archend - arch) ? 2 : 1;
+ if (archprio < archpriouse) {
+ archpriouse = archprio;
+ priouse = prio;
+ iuse = i;
+ continue;
+ }
+ if (archprio > archpriouse)
+ continue;
+ }
+ if (prio >= priouse)
+ continue;
+ priouse = prio;
+ iuse = i;
+ }
+ resp_show(req, r[iuse].file);
+ }
+
+ resp_end_html();
+}
+
+static void
+catman(const struct req *req, const char *file)
+{
+ FILE *f;
+ size_t len;
+ int i;
+ char *p;
+ int italic, bold;
+
+ if (NULL == (f = fopen(file, "r"))) {
+ puts("You specified an invalid manual file.
");
+ return;
+ }
+
+ puts("\n"
+ "
");
+
+ while (NULL != (p = fgetln(f, &len))) {
+ bold = italic = 0;
+ for (i = 0; i < (int)len - 1; i++) {
+ /*
+ * This means that the catpage is out of state.
+ * Ignore it and keep going (although the
+ * catpage is bogus).
+ */
+
+ if ('\b' == p[i] || '\n' == p[i])
+ continue;
+
+ /*
+ * Print a regular character.
+ * Close out any bold/italic scopes.
+ * If we're in back-space mode, make sure we'll
+ * have something to enter when we backspace.
+ */
+
+ if ('\b' != p[i + 1]) {
+ if (italic)
+ printf("");
+ if (bold)
+ printf("");
+ italic = bold = 0;
+ html_putchar(p[i]);
+ continue;
+ } else if (i + 2 >= (int)len)
+ continue;
+
+ /* Italic mode. */
+
+ if ('_' == p[i]) {
+ if (bold)
+ printf("");
+ if ( ! italic)
+ printf("");
+ bold = 0;
+ italic = 1;
+ i += 2;
+ html_putchar(p[i]);
+ continue;
+ }
+
+ /*
+ * Handle funny behaviour troff-isms.
+ * These grok'd from the original man2html.c.
+ */
+
+ if (('+' == p[i] && 'o' == p[i + 2]) ||
+ ('o' == p[i] && '+' == p[i + 2]) ||
+ ('|' == p[i] && '=' == p[i + 2]) ||
+ ('=' == p[i] && '|' == p[i + 2]) ||
+ ('*' == p[i] && '=' == p[i + 2]) ||
+ ('=' == p[i] && '*' == p[i + 2]) ||
+ ('*' == p[i] && '|' == p[i + 2]) ||
+ ('|' == p[i] && '*' == p[i + 2])) {
+ if (italic)
+ printf(" ");
+ if (bold)
+ printf("");
+ italic = bold = 0;
+ putchar('*');
+ i += 2;
+ continue;
+ } else if (('|' == p[i] && '-' == p[i + 2]) ||
+ ('-' == p[i] && '|' == p[i + 1]) ||
+ ('+' == p[i] && '-' == p[i + 1]) ||
+ ('-' == p[i] && '+' == p[i + 1]) ||
+ ('+' == p[i] && '|' == p[i + 1]) ||
+ ('|' == p[i] && '+' == p[i + 1])) {
+ if (italic)
+ printf("");
+ if (bold)
+ printf("");
+ italic = bold = 0;
+ putchar('+');
+ i += 2;
+ continue;
+ }
+
+ /* Bold mode. */
+
+ if (italic)
+ printf("");
+ if ( ! bold)
+ printf("");
+ bold = 1;
+ italic = 0;
+ i += 2;
+ html_putchar(p[i]);
+ }
+
+ /*
+ * Clean up the last character.
+ * We can get to a newline; don't print that.
+ */
+
+ if (italic)
+ printf("");
+ if (bold)
+ printf(" ");
+
+ if (i == (int)len - 1 && '\n' != p[i])
+ html_putchar(p[i]);
+
+ putchar('\n');
+ }
+
+ puts(" \n"
+ "
");
+
+ fclose(f);
+}
+
+static void
+format(const struct req *req, const char *file)
+{
+ struct mparse *mp;
+ struct mdoc *mdoc;
+ struct man *man;
+ void *vp;
+ char *opts;
+ enum mandoclevel rc;
+ int fd;
+ int usepath;
+
+ if (-1 == (fd = open(file, O_RDONLY, 0))) {
+ puts("You specified an invalid manual file.
");
+ return;
+ }
+
+ mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_FATAL, NULL,
+ req->q.manpath);
+ rc = mparse_readfd(mp, fd, file);
+ close(fd);
+
+ if (rc >= MANDOCLEVEL_FATAL) {
+ fprintf(stderr, "fatal mandoc error: %s/%s\n",
+ req->q.manpath, file);
+ pg_error_internal();
+ return;
+ }
+
+ usepath = strcmp(req->q.manpath, req->p[0]);
+ mandoc_asprintf(&opts,
+ "fragment,man=%s?query=%%N&sec=%%S%s%s%s%s",
+ scriptname,
+ req->q.arch ? "&arch=" : "",
+ req->q.arch ? req->q.arch : "",
+ usepath ? "&manpath=" : "",
+ usepath ? req->q.manpath : "");
+
+ mparse_result(mp, &mdoc, &man, NULL);
+ if (NULL == man && NULL == mdoc) {
+ fprintf(stderr, "fatal mandoc error: %s/%s\n",
+ req->q.manpath, file);
+ pg_error_internal();
+ mparse_free(mp);
+ return;
+ }
+
+ vp = html_alloc(opts);
+
+ if (NULL != mdoc)
+ html_mdoc(vp, mdoc);
+ else
+ html_man(vp, man);
+
+ html_free(vp);
+ mparse_free(mp);
+ free(opts);
+}
+
+static void
+resp_show(const struct req *req, const char *file)
+{
+
+ if ('.' == file[0] && '/' == file[1])
+ file += 2;
+
+ if ('c' == *file)
+ catman(req, file);
+ else
+ format(req, file);
+}
+
+static void
+pg_show(struct req *req, const char *fullpath)
+{
+ char *manpath;
+ const char *file;
+
+ if ((file = strchr(fullpath, '/')) == NULL) {
+ pg_error_badrequest(
+ "You did not specify a page to show.");
+ return;
+ }
+ manpath = mandoc_strndup(fullpath, file - fullpath);
+ file++;
+
+ if ( ! validate_manpath(req, manpath)) {
+ pg_error_badrequest(
+ "You specified an invalid manpath.");
+ free(manpath);
+ return;
+ }
+
+ /*
+ * Begin by chdir()ing into the manpath.
+ * This way we can pick up the database files, which are
+ * relative to the manpath root.
+ */
+
+ if (chdir(manpath) == -1) {
+ fprintf(stderr, "chdir %s: %s\n",
+ manpath, strerror(errno));
+ pg_error_internal();
+ free(manpath);
+ return;
+ }
+
+ if (strcmp(manpath, "mandoc")) {
+ free(req->q.manpath);
+ req->q.manpath = manpath;
+ } else
+ free(manpath);
+
+ if ( ! validate_filename(file)) {
+ pg_error_badrequest(
+ "You specified an invalid manual file.");
+ return;
+ }
+
+ resp_begin_html(200, NULL);
+ resp_searchform(req);
+ resp_show(req, file);
+ resp_end_html();
+}
+
+static void
+pg_search(const struct req *req)
+{
+ struct mansearch search;
+ struct manpaths paths;
+ struct manpage *res;
+ char **cp;
+ const char *ep, *start;
+ size_t ressz;
+ int i, sz;
+
+ /*
+ * Begin by chdir()ing into the root of the manpath.
+ * This way we can pick up the database files, which are
+ * relative to the manpath root.
+ */
+
+ if (-1 == (chdir(req->q.manpath))) {
+ fprintf(stderr, "chdir %s: %s\n",
+ req->q.manpath, strerror(errno));
+ pg_error_internal();
+ return;
+ }
+
+ search.arch = req->q.arch;
+ search.sec = req->q.sec;
+ search.deftype = req->q.equal ? TYPE_Nm : (TYPE_Nm | TYPE_Nd);
+ search.flags = req->q.equal ? MANSEARCH_MAN : 0;
+
+ paths.sz = 1;
+ paths.paths = mandoc_malloc(sizeof(char *));
+ paths.paths[0] = mandoc_strdup(".");
+
+ /*
+ * Poor man's tokenisation: just break apart by spaces.
+ * Yes, this is half-ass. But it works for now.
+ */
+
+ ep = req->q.query;
+ while (ep && isspace((unsigned char)*ep))
+ ep++;
+
+ sz = 0;
+ cp = NULL;
+ while (ep && '\0' != *ep) {
+ cp = mandoc_reallocarray(cp, sz + 1, sizeof(char *));
+ start = ep;
+ while ('\0' != *ep && ! isspace((unsigned char)*ep))
+ ep++;
+ cp[sz] = mandoc_malloc((ep - start) + 1);
+ memcpy(cp[sz], start, ep - start);
+ cp[sz++][ep - start] = '\0';
+ while (isspace((unsigned char)*ep))
+ ep++;
+ }
+
+ if (0 == mansearch(&search, &paths, sz, cp, "Nd", &res, &ressz))
+ pg_noresult(req, "You entered an invalid query.");
+ else if (0 == ressz)
+ pg_noresult(req, "No results found.");
+ else
+ pg_searchres(req, res, ressz);
+
+ for (i = 0; i < sz; i++)
+ free(cp[i]);
+ free(cp);
+
+ for (i = 0; i < (int)ressz; i++) {
+ free(res[i].file);
+ free(res[i].names);
+ free(res[i].output);
+ }
+ free(res);
+
+ free(paths.paths[0]);
+ free(paths.paths);
+}
+
+int
+main(void)
+{
+ struct req req;
+ const char *path;
+ const char *querystring;
+ int i;
+
+ /* Scan our run-time environment. */
+
+ if (NULL == (scriptname = getenv("SCRIPT_NAME")))
+ scriptname = "";
+
+ if ( ! validate_urifrag(scriptname)) {
+ fprintf(stderr, "unsafe SCRIPT_NAME \"%s\"\n",
+ scriptname);
+ pg_error_internal();
+ return(EXIT_FAILURE);
+ }
+
+ /*
+ * First we change directory into the MAN_DIR so that
+ * subsequent scanning for manpath directories is rooted
+ * relative to the same position.
+ */
+
+ if (-1 == chdir(MAN_DIR)) {
+ fprintf(stderr, "MAN_DIR: %s: %s\n",
+ MAN_DIR, strerror(errno));
+ pg_error_internal();
+ return(EXIT_FAILURE);
+ }
+
+ memset(&req, 0, sizeof(struct req));
+ pathgen(&req);
+
+ /* Next parse out the query string. */
+
+ if (NULL != (querystring = getenv("QUERY_STRING")))
+ http_parse(&req, querystring);
+
+ if ( ! (NULL == req.q.manpath ||
+ validate_manpath(&req, req.q.manpath))) {
+ pg_error_badrequest(
+ "You specified an invalid manpath.");
+ return(EXIT_FAILURE);
+ }
+
+ if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) {
+ pg_error_badrequest(
+ "You specified an invalid architecture.");
+ return(EXIT_FAILURE);
+ }
+
+ /* Dispatch to the three different pages. */
+
+ path = getenv("PATH_INFO");
+ if (NULL == path)
+ path = "";
+ else if ('/' == *path)
+ path++;
+
+ if ('\0' != *path)
+ pg_show(&req, path);
+ else if (NULL != req.q.query)
+ pg_search(&req);
+ else
+ pg_index(&req);
+
+ free(req.q.manpath);
+ free(req.q.arch);
+ free(req.q.sec);
+ free(req.q.query);
+ for (i = 0; i < (int)req.psz; i++)
+ free(req.p[i]);
+ free(req.p);
+ return(EXIT_SUCCESS);
+}
+
+/*
+ * Scan for indexable paths.
+ */
+static void
+pathgen(struct req *req)
+{
+ FILE *fp;
+ char *dp;
+ size_t dpsz;
+
+ if (NULL == (fp = fopen("manpath.conf", "r"))) {
+ fprintf(stderr, "%s/manpath.conf: %s\n",
+ MAN_DIR, strerror(errno));
+ pg_error_internal();
+ exit(EXIT_FAILURE);
+ }
+
+ while (NULL != (dp = fgetln(fp, &dpsz))) {
+ if ('\n' == dp[dpsz - 1])
+ dpsz--;
+ req->p = mandoc_realloc(req->p,
+ (req->psz + 1) * sizeof(char *));
+ dp = mandoc_strndup(dp, dpsz);
+ if ( ! validate_urifrag(dp)) {
+ fprintf(stderr, "%s/manpath.conf contains "
+ "unsafe path \"%s\"\n", MAN_DIR, dp);
+ pg_error_internal();
+ exit(EXIT_FAILURE);
+ }
+ if (NULL != strchr(dp, '/')) {
+ fprintf(stderr, "%s/manpath.conf contains "
+ "path with slash \"%s\"\n", MAN_DIR, dp);
+ pg_error_internal();
+ exit(EXIT_FAILURE);
+ }
+ req->p[req->psz++] = dp;
+ }
+
+ if ( req->p == NULL ) {
+ fprintf(stderr, "%s/manpath.conf is empty\n", MAN_DIR);
+ pg_error_internal();
+ exit(EXIT_FAILURE);
+ }
+}
diff --git a/contrib/mdocml/cgi.h.example b/contrib/mdocml/cgi.h.example
new file mode 100644
index 000000000000..f4c783186751
--- /dev/null
+++ b/contrib/mdocml/cgi.h.example
@@ -0,0 +1,9 @@
+/* Example compile-time configuration file for man.cgi(8). */
+
+#define HTTP_HOST "mdocml.bsd.lv"
+#define MAN_DIR "/var/www/man"
+#define CSS_DIR ""
+#define CUSTOMIZE_TITLE "Manual pages with mandoc"
+#define CUSTOMIZE_BEGIN "\nManual pages with " \
+ "mandoc \n "
+#define COMPAT_OLDURI Yes
diff --git a/contrib/mdocml/chars.c b/contrib/mdocml/chars.c
index 3ad1f57471c6..d758d0ccbd1b 100644
--- a/contrib/mdocml/chars.c
+++ b/contrib/mdocml/chars.c
@@ -1,4 +1,4 @@
-/* $Id: chars.c,v 1.54 2013/06/20 22:39:30 schwarze Exp $ */
+/* $Id: chars.c,v 1.58 2014/07/23 15:00:08 schwarze Exp $ */
/*
* Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons
* Copyright (c) 2011 Ingo Schwarze
@@ -25,6 +25,7 @@
#include
#include "mandoc.h"
+#include "mandoc_aux.h"
#include "libmandoc.h"
#define PRINT_HI 126
@@ -37,7 +38,7 @@ struct ln {
int unicode;
};
-#define LINES_MAX 329
+#define LINES_MAX 330
#define CHAR(in, ch, code) \
{ NULL, (in), (ch), (code) },
@@ -51,9 +52,10 @@ struct mchars {
struct ln **htab;
};
-static const struct ln *find(const struct mchars *,
+static const struct ln *find(const struct mchars *,
const char *, size_t);
+
void
mchars_free(struct mchars *arg)
{
@@ -110,27 +112,38 @@ mchars_spec2cp(const struct mchars *arg, const char *p, size_t sz)
char
mchars_num2char(const char *p, size_t sz)
{
- int i;
+ int i;
if ((i = mandoc_strntoi(p, sz, 10)) < 0)
return('\0');
- return(i > 0 && i < 256 && isprint(i) ?
- /* LINTED */ i : '\0');
+
+ return(i > 0 && i < 256 && isprint(i) ? i : '\0');
}
int
mchars_num2uc(const char *p, size_t sz)
{
- int i;
+ int i;
if ((i = mandoc_strntoi(p, sz, 16)) < 0)
return('\0');
- /* FIXME: make sure we're not in a bogus range. */
+
+ /*
+ * Security warning:
+ * Never extend the range of accepted characters
+ * to overlap with the ASCII range, 0x00-0x7F
+ * without re-auditing the callers of this function.
+ * Some callers might relay on the fact that we never
+ * return ASCII characters for their escaping decisions.
+ *
+ * XXX Code is missing here to exclude bogus ranges.
+ */
+
return(i > 0x80 && i <= 0x10FFFF ? i : '\0');
}
const char *
-mchars_spec2str(const struct mchars *arg,
+mchars_spec2str(const struct mchars *arg,
const char *p, size_t sz, size_t *rsz)
{
const struct ln *ln;
@@ -159,8 +172,8 @@ find(const struct mchars *tab, const char *p, size_t sz)
hash = (int)p[0] - PRINT_LO;
for (pp = tab->htab[hash]; pp; pp = pp->next)
- if (0 == strncmp(pp->code, p, sz) &&
- '\0' == pp->code[(int)sz])
+ if (0 == strncmp(pp->code, p, sz) &&
+ '\0' == pp->code[(int)sz])
return(pp);
return(NULL);
diff --git a/contrib/mdocml/chars.in b/contrib/mdocml/chars.in
index cc6549e7e5be..098504fa1648 100644
--- a/contrib/mdocml/chars.in
+++ b/contrib/mdocml/chars.in
@@ -1,6 +1,7 @@
-/* $Id: chars.in,v 1.43 2013/06/20 22:39:30 schwarze Exp $ */
+/* $Id: chars.in,v 1.46 2014/04/20 16:46:04 schwarze Exp $ */
/*
* Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons
+ * Copyright (c) 2014 Ingo Schwarze
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -16,7 +17,7 @@
*/
/*
- * The ASCII translation tables.
+ * The ASCII translation tables.
*
* The left-hand side corresponds to the input sequence (\x, \(xx, \*(xx
* and so on) whose length is listed second element. The right-hand
@@ -27,39 +28,42 @@
* XXX - update LINES_MAX if adding more!
*/
-/* Non-breaking, non-collapsing space uses unit separator. */
+/* Special break control characters. */
static const char ascii_nbrsp[2] = { ASCII_NBRSP, '\0' };
+static const char ascii_break[2] = { ASCII_BREAK, '\0' };
CHAR_TBL_START
/* Spacing. */
-CHAR("c", "", 0)
-CHAR("0", " ", 8194)
CHAR(" ", ascii_nbrsp, 160)
CHAR("~", ascii_nbrsp, 160)
-CHAR("%", "", 0)
-CHAR("&", "", 0)
-CHAR("^", "", 0)
+CHAR("0", " ", 8194)
CHAR("|", "", 0)
-CHAR("}", "", 0)
+CHAR("^", "", 0)
+CHAR("&", "", 0)
+CHAR("%", "", 0)
+CHAR(":", ascii_break, 0)
+/* XXX The following three do not really belong into this file. */
CHAR("t", "", 0)
+CHAR("c", "", 0)
+CHAR("}", "", 0)
/* Accents. */
-CHAR("a\"", "\"", 779)
+CHAR("a\"", "\"", 733)
CHAR("a-", "-", 175)
CHAR("a.", ".", 729)
-CHAR("a^", "^", 770)
-CHAR("\'", "\'", 769)
-CHAR("aa", "\'", 769)
-CHAR("ga", "`", 768)
-CHAR("`", "`", 768)
-CHAR("ab", "`", 774)
-CHAR("ac", ",", 807)
-CHAR("ad", "\"", 776)
+CHAR("a^", "^", 94)
+CHAR("\'", "\'", 180)
+CHAR("aa", "\'", 180)
+CHAR("ga", "`", 96)
+CHAR("`", "`", 96)
+CHAR("ab", "`", 728)
+CHAR("ac", ",", 184)
+CHAR("ad", "\"", 168)
CHAR("ah", "v", 711)
CHAR("ao", "o", 730)
-CHAR("a~", "~", 771)
-CHAR("ho", ",", 808)
+CHAR("a~", "~", 126)
+CHAR("ho", ",", 731)
CHAR("ha", "^", 94)
CHAR("ti", "~", 126)
diff --git a/contrib/mdocml/compat_ohash.c b/contrib/mdocml/compat_ohash.c
new file mode 100644
index 000000000000..0992b3657dde
--- /dev/null
+++ b/contrib/mdocml/compat_ohash.c
@@ -0,0 +1,339 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_OHASH
+
+int dummy;
+
+#else
+
+/* $OpenBSD: ohash.c,v 1.1 2014/06/02 18:52:03 deraadt Exp $ */
+
+/* Copyright (c) 1999, 2004 Marc Espie
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include "compat_ohash.h"
+
+struct _ohash_record {
+ uint32_t hv;
+ const char *p;
+};
+
+#define DELETED ((const char *)h)
+#define NONE (h->size)
+
+/* Don't bother changing the hash table if the change is small enough. */
+#define MINSIZE (1UL << 4)
+#define MINDELETED 4
+
+static void ohash_resize(struct ohash *);
+
+
+/* This handles the common case of variable length keys, where the
+ * key is stored at the end of the record.
+ */
+void *
+ohash_create_entry(struct ohash_info *i, const char *start, const char **end)
+{
+ char *p;
+
+ if (!*end)
+ *end = start + strlen(start);
+ p = (i->alloc)(i->key_offset + (*end - start) + 1, i->data);
+ if (p) {
+ memcpy(p+i->key_offset, start, *end-start);
+ p[i->key_offset + (*end - start)] = '\0';
+ }
+ return (void *)p;
+}
+
+/* hash_delete only frees the hash structure. Use hash_first/hash_next
+ * to free entries as well. */
+void
+ohash_delete(struct ohash *h)
+{
+ (h->info.free)(h->t, h->info.data);
+#ifndef NDEBUG
+ h->t = NULL;
+#endif
+}
+
+static void
+ohash_resize(struct ohash *h)
+{
+ struct _ohash_record *n;
+ size_t ns;
+ unsigned int j;
+ unsigned int i, incr;
+
+ if (4 * h->deleted < h->total) {
+ if (h->size >= (UINT_MAX >> 1U))
+ ns = UINT_MAX;
+ else
+ ns = h->size << 1U;
+ } else if (3 * h->deleted > 2 * h->total)
+ ns = h->size >> 1U;
+ else
+ ns = h->size;
+ if (ns < MINSIZE)
+ ns = MINSIZE;
+#ifdef STATS_HASH
+ STAT_HASH_EXPAND++;
+ STAT_HASH_SIZE += ns - h->size;
+#endif
+
+ n = (h->info.calloc)(ns, sizeof(struct _ohash_record), h->info.data);
+ if (!n)
+ return;
+
+ for (j = 0; j < h->size; j++) {
+ if (h->t[j].p != NULL && h->t[j].p != DELETED) {
+ i = h->t[j].hv % ns;
+ incr = ((h->t[j].hv % (ns - 2)) & ~1) + 1;
+ while (n[i].p != NULL) {
+ i += incr;
+ if (i >= ns)
+ i -= ns;
+ }
+ n[i].hv = h->t[j].hv;
+ n[i].p = h->t[j].p;
+ }
+ }
+ (h->info.free)(h->t, h->info.data);
+ h->t = n;
+ h->size = ns;
+ h->total -= h->deleted;
+ h->deleted = 0;
+}
+
+void *
+ohash_remove(struct ohash *h, unsigned int i)
+{
+ void *result = (void *)h->t[i].p;
+
+ if (result == NULL || result == DELETED)
+ return NULL;
+
+#ifdef STATS_HASH
+ STAT_HASH_ENTRIES--;
+#endif
+ h->t[i].p = DELETED;
+ h->deleted++;
+ if (h->deleted >= MINDELETED && 4 * h->deleted > h->total)
+ ohash_resize(h);
+ return result;
+}
+
+void *
+ohash_find(struct ohash *h, unsigned int i)
+{
+ if (h->t[i].p == DELETED)
+ return NULL;
+ else
+ return (void *)h->t[i].p;
+}
+
+void *
+ohash_insert(struct ohash *h, unsigned int i, void *p)
+{
+#ifdef STATS_HASH
+ STAT_HASH_ENTRIES++;
+#endif
+ if (h->t[i].p == DELETED) {
+ h->deleted--;
+ h->t[i].p = p;
+ } else {
+ h->t[i].p = p;
+ /* Arbitrary resize boundary. Tweak if not efficient enough. */
+ if (++h->total * 4 > h->size * 3)
+ ohash_resize(h);
+ }
+ return p;
+}
+
+unsigned int
+ohash_entries(struct ohash *h)
+{
+ return h->total - h->deleted;
+}
+
+void *
+ohash_first(struct ohash *h, unsigned int *pos)
+{
+ *pos = 0;
+ return ohash_next(h, pos);
+}
+
+void *
+ohash_next(struct ohash *h, unsigned int *pos)
+{
+ for (; *pos < h->size; (*pos)++)
+ if (h->t[*pos].p != DELETED && h->t[*pos].p != NULL)
+ return (void *)h->t[(*pos)++].p;
+ return NULL;
+}
+
+void
+ohash_init(struct ohash *h, unsigned int size, struct ohash_info *info)
+{
+ h->size = 1UL << size;
+ if (h->size < MINSIZE)
+ h->size = MINSIZE;
+#ifdef STATS_HASH
+ STAT_HASH_CREATION++;
+ STAT_HASH_SIZE += h->size;
+#endif
+ /* Copy info so that caller may free it. */
+ h->info.key_offset = info->key_offset;
+ h->info.calloc = info->calloc;
+ h->info.free = info->free;
+ h->info.alloc = info->alloc;
+ h->info.data = info->data;
+ h->t = (h->info.calloc)(h->size, sizeof(struct _ohash_record),
+ h->info.data);
+ h->total = h->deleted = 0;
+}
+
+uint32_t
+ohash_interval(const char *s, const char **e)
+{
+ uint32_t k;
+
+ if (!*e)
+ *e = s + strlen(s);
+ if (s == *e)
+ k = 0;
+ else
+ k = *s++;
+ while (s != *e)
+ k = ((k << 2) | (k >> 30)) ^ *s++;
+ return k;
+}
+
+unsigned int
+ohash_lookup_interval(struct ohash *h, const char *start, const char *end,
+ uint32_t hv)
+{
+ unsigned int i, incr;
+ unsigned int empty;
+
+#ifdef STATS_HASH
+ STAT_HASH_LOOKUP++;
+#endif
+ empty = NONE;
+ i = hv % h->size;
+ incr = ((hv % (h->size-2)) & ~1) + 1;
+ while (h->t[i].p != NULL) {
+#ifdef STATS_HASH
+ STAT_HASH_LENGTH++;
+#endif
+ if (h->t[i].p == DELETED) {
+ if (empty == NONE)
+ empty = i;
+ } else if (h->t[i].hv == hv &&
+ strncmp(h->t[i].p+h->info.key_offset, start,
+ end - start) == 0 &&
+ (h->t[i].p+h->info.key_offset)[end-start] == '\0') {
+ if (empty != NONE) {
+ h->t[empty].hv = hv;
+ h->t[empty].p = h->t[i].p;
+ h->t[i].p = DELETED;
+ return empty;
+ } else {
+#ifdef STATS_HASH
+ STAT_HASH_POSITIVE++;
+#endif
+ return i;
+ }
+ }
+ i += incr;
+ if (i >= h->size)
+ i -= h->size;
+ }
+
+ /* Found an empty position. */
+ if (empty != NONE)
+ i = empty;
+ h->t[i].hv = hv;
+ return i;
+}
+
+unsigned int
+ohash_lookup_memory(struct ohash *h, const char *k, size_t size, uint32_t hv)
+{
+ unsigned int i, incr;
+ unsigned int empty;
+
+#ifdef STATS_HASH
+ STAT_HASH_LOOKUP++;
+#endif
+ empty = NONE;
+ i = hv % h->size;
+ incr = ((hv % (h->size-2)) & ~1) + 1;
+ while (h->t[i].p != NULL) {
+#ifdef STATS_HASH
+ STAT_HASH_LENGTH++;
+#endif
+ if (h->t[i].p == DELETED) {
+ if (empty == NONE)
+ empty = i;
+ } else if (h->t[i].hv == hv &&
+ memcmp(h->t[i].p+h->info.key_offset, k, size) == 0) {
+ if (empty != NONE) {
+ h->t[empty].hv = hv;
+ h->t[empty].p = h->t[i].p;
+ h->t[i].p = DELETED;
+ return empty;
+ } else {
+#ifdef STATS_HASH
+ STAT_HASH_POSITIVE++;
+#endif
+ } return i;
+ }
+ i += incr;
+ if (i >= h->size)
+ i -= h->size;
+ }
+
+ /* Found an empty position. */
+ if (empty != NONE)
+ i = empty;
+ h->t[i].hv = hv;
+ return i;
+}
+
+unsigned int
+ohash_qlookup(struct ohash *h, const char *s)
+{
+ const char *e = NULL;
+ return ohash_qlookupi(h, s, &e);
+}
+
+unsigned int
+ohash_qlookupi(struct ohash *h, const char *s, const char **e)
+{
+ uint32_t hv;
+
+ hv = ohash_interval(s, e);
+ return ohash_lookup_interval(h, s, *e, hv);
+}
+
+#endif /*!HAVE_OHASH*/
diff --git a/contrib/mdocml/compat_ohash.h b/contrib/mdocml/compat_ohash.h
new file mode 100644
index 000000000000..e3124c96b124
--- /dev/null
+++ b/contrib/mdocml/compat_ohash.h
@@ -0,0 +1,73 @@
+/* $OpenBSD: ohash.h,v 1.2 2014/06/02 18:52:03 deraadt Exp $ */
+
+/* Copyright (c) 1999, 2004 Marc Espie
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef OHASH_H
+#define OHASH_H
+
+/* Open hashing support.
+ * Open hashing was chosen because it is much lighter than other hash
+ * techniques, and more efficient in most cases.
+ */
+
+/* user-visible data structure */
+struct ohash_info {
+ ptrdiff_t key_offset;
+ void *data; /* user data */
+ void *(*calloc)(size_t, size_t, void *);
+ void (*free)(void *, void *);
+ void *(*alloc)(size_t, void *);
+};
+
+struct _ohash_record;
+
+/* private structure. It's there just so you can do a sizeof */
+struct ohash {
+ struct _ohash_record *t;
+ struct ohash_info info;
+ unsigned int size;
+ unsigned int total;
+ unsigned int deleted;
+};
+
+/* For this to be tweakable, we use small primitives, and leave part of the
+ * logic to the client application. e.g., hashing is left to the client
+ * application. We also provide a simple table entry lookup that yields
+ * a hashing table index (opaque) to be used in find/insert/remove.
+ * The keys are stored at a known position in the client data.
+ */
+__BEGIN_DECLS
+void ohash_init(struct ohash *, unsigned, struct ohash_info *);
+void ohash_delete(struct ohash *);
+
+unsigned int ohash_lookup_interval(struct ohash *, const char *,
+ const char *, uint32_t);
+unsigned int ohash_lookup_memory(struct ohash *, const char *,
+ size_t, uint32_t);
+void *ohash_find(struct ohash *, unsigned int);
+void *ohash_remove(struct ohash *, unsigned int);
+void *ohash_insert(struct ohash *, unsigned int, void *);
+void *ohash_first(struct ohash *, unsigned int *);
+void *ohash_next(struct ohash *, unsigned int *);
+unsigned int ohash_entries(struct ohash *);
+
+void *ohash_create_entry(struct ohash_info *, const char *, const char **);
+uint32_t ohash_interval(const char *, const char **);
+
+unsigned int ohash_qlookupi(struct ohash *, const char *, const char **);
+unsigned int ohash_qlookup(struct ohash *, const char *);
+__END_DECLS
+#endif
diff --git a/contrib/mdocml/compat_reallocarray.c b/contrib/mdocml/compat_reallocarray.c
new file mode 100644
index 000000000000..e25d8374bd53
--- /dev/null
+++ b/contrib/mdocml/compat_reallocarray.c
@@ -0,0 +1,45 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_REALLOCARRAY
+
+int dummy;
+
+#else
+
+/* $OpenBSD: malloc.c,v 1.158 2014/04/23 15:07:27 tedu Exp $ */
+/*
+ * Copyright (c) 2008 Otto Moerbeek
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include
+#include
+#include
+#include
+
+#define MUL_NO_OVERFLOW (1UL << (sizeof(size_t) * 4))
+
+void *
+reallocarray(void *optr, size_t nmemb, size_t size)
+{
+ if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+ nmemb > 0 && SIZE_MAX / nmemb < size) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ return realloc(optr, size * nmemb);
+}
+
+#endif /*!HAVE_REALLOCARRAY*/
diff --git a/contrib/mdocml/compat_sqlite3_errstr.c b/contrib/mdocml/compat_sqlite3_errstr.c
new file mode 100644
index 000000000000..b8d6eb58f1cc
--- /dev/null
+++ b/contrib/mdocml/compat_sqlite3_errstr.c
@@ -0,0 +1,18 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_SQLITE3_ERRSTR
+
+int dummy;
+
+#else
+
+const char *
+sqlite3_errstr(int rc)
+{
+
+ return(rc ? "unknown error" : "not an error");
+}
+
+#endif
diff --git a/contrib/mdocml/compat_strcasestr.c b/contrib/mdocml/compat_strcasestr.c
new file mode 100644
index 000000000000..5216d0215753
--- /dev/null
+++ b/contrib/mdocml/compat_strcasestr.c
@@ -0,0 +1,74 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_STRCASESTR
+
+int dummy;
+
+#else
+
+/* ($)NetBSD: strcasestr.c,v 1.2 2005/02/09 21:35:47 kleink Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include
+#include
+#include
+
+#define __UNCONST(a) ((void *)(unsigned long)(const void *)(a))
+
+/*
+ * Find the first occurrence of find in s, ignore case.
+ */
+char *
+strcasestr(const char *s, const char *find)
+{
+ char c, sc;
+ size_t len;
+
+ if ((c = *find++) != 0) {
+ c = tolower((unsigned char)c);
+ len = strlen(find);
+ do {
+ do {
+ if ((sc = *s++) == 0)
+ return (NULL);
+ } while ((char)tolower((unsigned char)sc) != c);
+ } while (strncasecmp(s, find, len) != 0);
+ s--;
+ }
+ return __UNCONST(s);
+}
+
+#endif
diff --git a/contrib/mdocml/compat_strsep.c b/contrib/mdocml/compat_strsep.c
new file mode 100644
index 000000000000..a5c58c625326
--- /dev/null
+++ b/contrib/mdocml/compat_strsep.c
@@ -0,0 +1,80 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_STRSEP
+
+int dummy;
+
+#else
+
+/* ($)OpenBSD: strsep.c,v 1.6 2005/08/08 08:05:37 espie Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Get next token from string *stringp, where tokens are possibly-empty
+ * strings separated by characters from delim.
+ *
+ * Writes NULs into the string at *stringp to end tokens.
+ * delim need not remain constant from call to call.
+ * On return, *stringp points past the last NUL written (if there might
+ * be further tokens), or is NULL (if there are definitely no more tokens).
+ *
+ * If *stringp is NULL, strsep returns NULL.
+ */
+char *
+strsep(char **stringp, const char *delim)
+{
+ char *s;
+ const char *spanp;
+ int c, sc;
+ char *tok;
+
+ if ((s = *stringp) == NULL)
+ return (NULL);
+ for (tok = s;;) {
+ c = *s++;
+ spanp = delim;
+ do {
+ if ((sc = *spanp++) == c) {
+ if (c == 0)
+ s = NULL;
+ else
+ s[-1] = 0;
+ *stringp = s;
+ return (tok);
+ }
+ } while (sc != 0);
+ }
+ /* NOTREACHED */
+}
+
+#endif
diff --git a/contrib/mdocml/config.h b/contrib/mdocml/config.h
index 0b070d587bc5..c1d5951240da 100644
--- a/contrib/mdocml/config.h
+++ b/contrib/mdocml/config.h
@@ -2,20 +2,21 @@
#define MANDOC_CONFIG_H
#if defined(__linux__) || defined(__MINT__)
-# define _GNU_SOURCE /* strptime(), getsubopt() */
+# define _GNU_SOURCE /* getsubopt(), strcasestr(), strptime() */
#endif
+#include
#include
-#define VERSION "1.12.3"
+#define VERSION "1.13.1"
#define HAVE_FGETLN
-#define HAVE_STRPTIME
#define HAVE_GETSUBOPT
-#define HAVE_STRLCAT
#define HAVE_MMAP
+#define HAVE_STRCASESTR
+#define HAVE_STRLCAT
#define HAVE_STRLCPY
-
-#include
+#define HAVE_STRPTIME
+#define HAVE_STRSEP
#if !defined(__BEGIN_DECLS)
# ifdef __cplusplus
@@ -32,30 +33,30 @@
# endif
#endif
-#ifndef HAVE_BETOH64
-# if defined(__APPLE__)
-# define betoh64(x) OSSwapBigToHostInt64(x)
-# define htobe64(x) OSSwapHostToBigInt64(x)
-# elif defined(__sun)
-# define betoh64(x) BE_64(x)
-# define htobe64(x) BE_64(x)
-# else
-# define betoh64(x) be64toh(x)
-# endif
+#ifndef HAVE_FGETLN
+extern char *fgetln(FILE *, size_t *);
+#endif
+#ifndef HAVE_GETSUBOPT
+extern int getsubopt(char **, char * const *, char **);
+extern char *suboptarg;
+#endif
+#ifndef HAVE_REALLOCARRAY
+extern void *reallocarray(void *, size_t, size_t);
+#endif
+#ifndef HAVE_SQLITE3_ERRSTR
+extern const char *sqlite3_errstr(int);
+#endif
+#ifndef HAVE_STRCASESTR
+extern char *strcasestr(const char *, const char *);
#endif
-
#ifndef HAVE_STRLCAT
extern size_t strlcat(char *, const char *, size_t);
#endif
#ifndef HAVE_STRLCPY
extern size_t strlcpy(char *, const char *, size_t);
#endif
-#ifndef HAVE_GETSUBOPT
-extern int getsubopt(char **, char * const *, char **);
-extern char *suboptarg;
-#endif
-#ifndef HAVE_FGETLN
-extern char *fgetln(FILE *, size_t *);
+#ifndef HAVE_STRSEP
+extern char *strsep(char **, const char *);
#endif
#endif /* MANDOC_CONFIG_H */
diff --git a/contrib/mdocml/config.h.post b/contrib/mdocml/config.h.post
new file mode 100644
index 000000000000..e95f5f5311d4
--- /dev/null
+++ b/contrib/mdocml/config.h.post
@@ -0,0 +1,42 @@
+#if !defined(__BEGIN_DECLS)
+# ifdef __cplusplus
+# define __BEGIN_DECLS extern "C" {
+# else
+# define __BEGIN_DECLS
+# endif
+#endif
+#if !defined(__END_DECLS)
+# ifdef __cplusplus
+# define __END_DECLS }
+# else
+# define __END_DECLS
+# endif
+#endif
+
+#ifndef HAVE_FGETLN
+extern char *fgetln(FILE *, size_t *);
+#endif
+#ifndef HAVE_GETSUBOPT
+extern int getsubopt(char **, char * const *, char **);
+extern char *suboptarg;
+#endif
+#ifndef HAVE_REALLOCARRAY
+extern void *reallocarray(void *, size_t, size_t);
+#endif
+#ifndef HAVE_SQLITE3_ERRSTR
+extern const char *sqlite3_errstr(int);
+#endif
+#ifndef HAVE_STRCASESTR
+extern char *strcasestr(const char *, const char *);
+#endif
+#ifndef HAVE_STRLCAT
+extern size_t strlcat(char *, const char *, size_t);
+#endif
+#ifndef HAVE_STRLCPY
+extern size_t strlcpy(char *, const char *, size_t);
+#endif
+#ifndef HAVE_STRSEP
+extern char *strsep(char **, const char *);
+#endif
+
+#endif /* MANDOC_CONFIG_H */
diff --git a/contrib/mdocml/config.h.pre b/contrib/mdocml/config.h.pre
new file mode 100644
index 000000000000..1c3940de5cc0
--- /dev/null
+++ b/contrib/mdocml/config.h.pre
@@ -0,0 +1,9 @@
+#ifndef MANDOC_CONFIG_H
+#define MANDOC_CONFIG_H
+
+#if defined(__linux__) || defined(__MINT__)
+# define _GNU_SOURCE /* getsubopt(), strcasestr(), strptime() */
+#endif
+
+#include
+#include
diff --git a/contrib/mdocml/configure b/contrib/mdocml/configure
new file mode 100755
index 000000000000..5b987ebef60d
--- /dev/null
+++ b/contrib/mdocml/configure
@@ -0,0 +1,49 @@
+#!/bin/sh
+#
+# Copyright (c) 2014 Ingo Schwarze
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+echo "/* RUNNING ./CONFIGURE - SHOULD BE USED ONLY VIA MAKE, READ INSTALL */"
+
+set -e
+exec > config.h 2> config.log
+
+CFLAGS="${CFLAGS} -Wno-unused -Werror"
+
+runtest() {
+ echo ${CC} ${CFLAGS} ${3} -o test-${1} test-${1}.c 1>&2
+ ${CC} ${CFLAGS} ${3} -o "test-${1}" "test-${1}.c" 1>&2 || return 0
+ "./test-${1}" && echo "#define HAVE_${2}" \
+ || echo FAILURE: test-${1} returned $? 1>&2
+ rm "test-${1}"
+}
+
+cat config.h.pre
+echo
+echo "#define VERSION \"${VERSION}\""
+runtest fgetln FGETLN
+runtest getsubopt GETSUBOPT
+runtest mmap MMAP
+runtest ohash OHASH "${DBLIB}"
+runtest reallocarray REALLOCARRAY
+runtest sqlite3_errstr SQLITE3_ERRSTR "${DBLIB}"
+runtest strcasestr STRCASESTR
+runtest strlcat STRLCAT
+runtest strlcpy STRLCPY
+runtest strptime STRPTIME
+runtest strsep STRSEP
+echo
+cat config.h.post
+
+exit 0
diff --git a/contrib/mdocml/demandoc.c b/contrib/mdocml/demandoc.c
new file mode 100644
index 000000000000..4a7b979e9225
--- /dev/null
+++ b/contrib/mdocml/demandoc.c
@@ -0,0 +1,257 @@
+/* $Id: demandoc.c,v 1.10 2014/03/19 22:20:43 schwarze Exp $ */
+/*
+ * Copyright (c) 2011 Kristaps Dzonsons
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * 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
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "man.h"
+#include "mdoc.h"
+#include "mandoc.h"
+
+static void pline(int, int *, int *, int);
+static void pman(const struct man_node *, int *, int *, int);
+static void pmandoc(struct mparse *, int, const char *, int);
+static void pmdoc(const struct mdoc_node *, int *, int *, int);
+static void pstring(const char *, int, int *, int);
+static void usage(void);
+
+static const char *progname;
+
+int
+main(int argc, char *argv[])
+{
+ struct mparse *mp;
+ int ch, i, list;
+ extern int optind;
+
+ progname = strrchr(argv[0], '/');
+ if (progname == NULL)
+ progname = argv[0];
+ else
+ ++progname;
+
+ mp = NULL;
+ list = 0;
+
+ while (-1 != (ch = getopt(argc, argv, "ikm:pw")))
+ switch (ch) {
+ case ('i'):
+ /* FALLTHROUGH */
+ case ('k'):
+ /* FALLTHROUGH */
+ case ('m'):
+ /* FALLTHROUGH */
+ case ('p'):
+ break;
+ case ('w'):
+ list = 1;
+ break;
+ default:
+ usage();
+ return((int)MANDOCLEVEL_BADARG);
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_FATAL, NULL, NULL);
+ assert(mp);
+
+ if (0 == argc)
+ pmandoc(mp, STDIN_FILENO, "", list);
+
+ for (i = 0; i < argc; i++) {
+ mparse_reset(mp);
+ pmandoc(mp, -1, argv[i], list);
+ }
+
+ mparse_free(mp);
+ return((int)MANDOCLEVEL_OK);
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: %s [-w] [files...]\n", progname);
+}
+
+static void
+pmandoc(struct mparse *mp, int fd, const char *fn, int list)
+{
+ struct mdoc *mdoc;
+ struct man *man;
+ int line, col;
+
+ if (mparse_readfd(mp, fd, fn) >= MANDOCLEVEL_FATAL) {
+ fprintf(stderr, "%s: Parse failure\n", fn);
+ return;
+ }
+
+ mparse_result(mp, &mdoc, &man, NULL);
+ line = 1;
+ col = 0;
+
+ if (mdoc)
+ pmdoc(mdoc_node(mdoc), &line, &col, list);
+ else if (man)
+ pman(man_node(man), &line, &col, list);
+ else
+ return;
+
+ if ( ! list)
+ putchar('\n');
+}
+
+/*
+ * Strip the escapes out of a string, emitting the results.
+ */
+static void
+pstring(const char *p, int col, int *colp, int list)
+{
+ enum mandoc_esc esc;
+ const char *start, *end;
+ int emit;
+
+ /*
+ * Print as many column spaces til we achieve parity with the
+ * input document.
+ */
+
+again:
+ if (list && '\0' != *p) {
+ while (isspace((unsigned char)*p))
+ p++;
+
+ while ('\'' == *p || '(' == *p || '"' == *p)
+ p++;
+
+ emit = isalpha((unsigned char)p[0]) &&
+ isalpha((unsigned char)p[1]);
+
+ for (start = p; '\0' != *p; p++)
+ if ('\\' == *p) {
+ p++;
+ esc = mandoc_escape(&p, NULL, NULL);
+ if (ESCAPE_ERROR == esc)
+ return;
+ emit = 0;
+ } else if (isspace((unsigned char)*p))
+ break;
+
+ end = p - 1;
+
+ while (end > start)
+ if ('.' == *end || ',' == *end ||
+ '\'' == *end || '"' == *end ||
+ ')' == *end || '!' == *end ||
+ '?' == *end || ':' == *end ||
+ ';' == *end)
+ end--;
+ else
+ break;
+
+ if (emit && end - start >= 1) {
+ for ( ; start <= end; start++)
+ if (ASCII_HYPH == *start)
+ putchar('-');
+ else
+ putchar((unsigned char)*start);
+ putchar('\n');
+ }
+
+ if (isspace((unsigned char)*p))
+ goto again;
+
+ return;
+ }
+
+ while (*colp < col) {
+ putchar(' ');
+ (*colp)++;
+ }
+
+ /*
+ * Print the input word, skipping any special characters.
+ */
+ while ('\0' != *p)
+ if ('\\' == *p) {
+ p++;
+ esc = mandoc_escape(&p, NULL, NULL);
+ if (ESCAPE_ERROR == esc)
+ break;
+ } else {
+ putchar((unsigned char )*p++);
+ (*colp)++;
+ }
+}
+
+static void
+pline(int line, int *linep, int *col, int list)
+{
+
+ if (list)
+ return;
+
+ /*
+ * Print out as many lines as needed to reach parity with the
+ * original input.
+ */
+
+ while (*linep < line) {
+ putchar('\n');
+ (*linep)++;
+ }
+
+ *col = 0;
+}
+
+static void
+pmdoc(const struct mdoc_node *p, int *line, int *col, int list)
+{
+
+ for ( ; p; p = p->next) {
+ if (MDOC_LINE & p->flags)
+ pline(p->line, line, col, list);
+ if (MDOC_TEXT == p->type)
+ pstring(p->string, p->pos, col, list);
+ if (p->child)
+ pmdoc(p->child, line, col, list);
+ }
+}
+
+static void
+pman(const struct man_node *p, int *line, int *col, int list)
+{
+
+ for ( ; p; p = p->next) {
+ if (MAN_LINE & p->flags)
+ pline(p->line, line, col, list);
+ if (MAN_TEXT == p->type)
+ pstring(p->string, p->pos, col, list);
+ if (p->child)
+ pman(p->child, line, col, list);
+ }
+}
diff --git a/contrib/mdocml/eqn.c b/contrib/mdocml/eqn.c
index 37f01bcb5b6e..cda0db5d26e4 100644
--- a/contrib/mdocml/eqn.c
+++ b/contrib/mdocml/eqn.c
@@ -1,4 +1,4 @@
-/* $Id: eqn.c,v 1.38 2011/07/25 15:37:00 kristaps Exp $ */
+/* $Id: eqn.c,v 1.44 2014/07/06 19:09:00 schwarze Exp $ */
/*
* Copyright (c) 2011 Kristaps Dzonsons
*
@@ -26,6 +26,7 @@
#include
#include "mandoc.h"
+#include "mandoc_aux.h"
#include "libmandoc.h"
#include "libroff.h"
@@ -137,12 +138,11 @@ struct eqnsym {
const char *sym;
};
-
static enum eqn_rest eqn_box(struct eqn_node *, struct eqn_box *);
-static struct eqn_box *eqn_box_alloc(struct eqn_node *,
+static struct eqn_box *eqn_box_alloc(struct eqn_node *,
struct eqn_box *);
static void eqn_box_free(struct eqn_box *);
-static struct eqn_def *eqn_def_find(struct eqn_node *,
+static struct eqn_def *eqn_def_find(struct eqn_node *,
const char *, size_t);
static int eqn_do_gfont(struct eqn_node *);
static int eqn_do_gsize(struct eqn_node *);
@@ -156,7 +156,7 @@ static enum eqn_rest eqn_list(struct eqn_node *, struct eqn_box *);
static enum eqn_rest eqn_matrix(struct eqn_node *, struct eqn_box *);
static const char *eqn_nexttok(struct eqn_node *, size_t *);
static const char *eqn_nextrawtok(struct eqn_node *, size_t *);
-static const char *eqn_next(struct eqn_node *,
+static const char *eqn_next(struct eqn_node *,
char, size_t *, int);
static void eqn_rewind(struct eqn_node *);
@@ -277,9 +277,9 @@ static const struct eqnsym eqnsyms[EQNSYM__MAX] = {
{ { ">=", 2 }, ">=" }, /* EQNSYM_moreequal */
};
-/* ARGSUSED */
+
enum rofferr
-eqn_read(struct eqn_node **epp, int ln,
+eqn_read(struct eqn_node **epp, int ln,
const char *p, int pos, int *offs)
{
size_t sz;
@@ -298,9 +298,10 @@ eqn_read(struct eqn_node **epp, int ln,
p += 3;
while (' ' == *p || '\t' == *p)
p++;
- if ('\0' == *p)
+ if ('\0' == *p)
return(er);
- mandoc_msg(MANDOCERR_ARGSLOST, ep->parse, ln, pos, NULL);
+ mandoc_vmsg(MANDOCERR_ARG_SKIP, ep->parse,
+ ln, pos, "EN %s", p);
return(er);
}
@@ -413,11 +414,11 @@ eqn_matrix(struct eqn_node *ep, struct eqn_box *last)
while (EQN_OK == (c = eqn_box(ep, bp)))
switch (bp->last->pile) {
- case (EQNPILE_LCOL):
+ case EQNPILE_LCOL:
/* FALLTHROUGH */
- case (EQNPILE_CCOL):
+ case EQNPILE_CCOL:
/* FALLTHROUGH */
- case (EQNPILE_RCOL):
+ case EQNPILE_RCOL:
continue;
default:
EQN_MSG(MANDOCERR_EQNSYNT, ep);
@@ -512,9 +513,8 @@ eqn_box(struct eqn_node *ep, struct eqn_box *last)
for (i = 0; i < (int)EQN__MAX; i++) {
if ( ! EQNSTREQ(&eqnparts[i].str, start, sz))
continue;
- return((*eqnparts[i].fp)(ep) ?
- EQN_OK : EQN_ERR);
- }
+ return((*eqnparts[i].fp)(ep) ? EQN_OK : EQN_ERR);
+ }
if (STRNEQ(start, sz, "{", 1)) {
if (EQN_DESCOPE != (c = eqn_eqn(ep, last))) {
@@ -529,7 +529,7 @@ eqn_box(struct eqn_node *ep, struct eqn_box *last)
return(EQN_OK);
EQN_MSG(MANDOCERR_EQNBADSCOPE, ep);
return(EQN_ERR);
- }
+ }
for (i = 0; i < (int)EQNPILE__MAX; i++) {
if ( ! EQNSTREQ(&eqnpiles[i], start, sz))
@@ -575,7 +575,7 @@ eqn_box(struct eqn_node *ep, struct eqn_box *last)
if (NULL == last->last) {
EQN_MSG(MANDOCERR_EQNSYNT, ep);
return(EQN_ERR);
- }
+ }
last->last->pos = (enum eqn_post)i;
if (EQN_EOF == (c = eqn_box(ep, last))) {
EQN_MSG(MANDOCERR_EQNEOF, ep);
@@ -590,7 +590,7 @@ eqn_box(struct eqn_node *ep, struct eqn_box *last)
if (NULL == last->last) {
EQN_MSG(MANDOCERR_EQNSYNT, ep);
return(EQN_ERR);
- }
+ }
last->last->mark = (enum eqn_markt)i;
if (EQN_EOF == (c = eqn_box(ep, last))) {
EQN_MSG(MANDOCERR_EQNEOF, ep);
@@ -629,7 +629,7 @@ eqn_box(struct eqn_node *ep, struct eqn_box *last)
for (i = 0; i < (int)EQNSYM__MAX; i++)
if (EQNSTREQ(&eqnsyms[i].str, start, sz)) {
sym[63] = '\0';
- snprintf(sym, 62, "\\[%s]", eqnsyms[i].sym);
+ (void)snprintf(sym, 62, "\\[%s]", eqnsyms[i].sym);
bp->text = mandoc_strdup(sym);
return(EQN_OK);
}
@@ -762,13 +762,13 @@ eqn_next(struct eqn_node *ep, char quote, size_t *sz, int repl)
if (q)
ep->cur++;
while (' ' == ep->data[(int)ep->cur] ||
- '\t' == ep->data[(int)ep->cur] ||
- '^' == ep->data[(int)ep->cur] ||
- '~' == ep->data[(int)ep->cur])
+ '\t' == ep->data[(int)ep->cur] ||
+ '^' == ep->data[(int)ep->cur] ||
+ '~' == ep->data[(int)ep->cur])
ep->cur++;
} else {
if (q)
- EQN_MSG(MANDOCERR_BADQUOTE, ep);
+ EQN_MSG(MANDOCERR_ARG_QUOTE, ep);
next = strchr(start, '\0');
*sz = (size_t)(next - start);
ep->cur += *sz;
@@ -790,8 +790,8 @@ eqn_next(struct eqn_node *ep, char quote, size_t *sz, int repl)
}
diff = def->valsz - *sz;
- memmove(start + *sz + diff, start + *sz,
- (strlen(start) - *sz) + 1);
+ memmove(start + *sz + diff, start + *sz,
+ (strlen(start) - *sz) + 1);
memcpy(start, def->val, def->valsz);
goto again;
}
@@ -852,8 +852,8 @@ eqn_do_define(struct eqn_node *ep)
return(0);
}
- /*
- * Search for a key that already exists.
+ /*
+ * Search for a key that already exists.
* Create a new key if none is found.
*/
@@ -865,15 +865,14 @@ eqn_do_define(struct eqn_node *ep)
if (i == (int)ep->defsz) {
ep->defsz++;
- ep->defs = mandoc_realloc
- (ep->defs, ep->defsz *
- sizeof(struct eqn_def));
+ ep->defs = mandoc_reallocarray(ep->defs,
+ ep->defsz, sizeof(struct eqn_def));
ep->defs[i].key = ep->defs[i].val = NULL;
}
ep->defs[i].keysz = sz;
- ep->defs[i].key = mandoc_realloc
- (ep->defs[i].key, sz + 1);
+ ep->defs[i].key = mandoc_realloc(
+ ep->defs[i].key, sz + 1);
memcpy(ep->defs[i].key, start, sz);
ep->defs[i].key[(int)sz] = '\0';
@@ -901,7 +900,7 @@ eqn_do_gfont(struct eqn_node *ep)
if (NULL == eqn_nextrawtok(ep, NULL)) {
EQN_MSG(MANDOCERR_EQNEOF, ep);
return(0);
- }
+ }
return(1);
}
@@ -914,7 +913,7 @@ eqn_do_gsize(struct eqn_node *ep)
if (NULL == (start = eqn_nextrawtok(ep, &sz))) {
EQN_MSG(MANDOCERR_EQNEOF, ep);
return(0);
- }
+ }
ep->gsize = mandoc_strntoi(start, sz, 10);
return(1);
}
@@ -940,9 +939,9 @@ eqn_def_find(struct eqn_node *ep, const char *key, size_t sz)
{
int i;
- for (i = 0; i < (int)ep->defsz; i++)
- if (ep->defs[i].keysz && STRNEQ(ep->defs[i].key,
- ep->defs[i].keysz, key, sz))
+ for (i = 0; i < (int)ep->defsz; i++)
+ if (ep->defs[i].keysz && STRNEQ(ep->defs[i].key,
+ ep->defs[i].keysz, key, sz))
return(&ep->defs[i]);
return(NULL);
diff --git a/contrib/mdocml/eqn_html.c b/contrib/mdocml/eqn_html.c
index 80c82f1de5b5..3e58ab5880b5 100644
--- a/contrib/mdocml/eqn_html.c
+++ b/contrib/mdocml/eqn_html.c
@@ -1,4 +1,4 @@
-/* $Id: eqn_html.c,v 1.2 2011/07/24 10:09:03 kristaps Exp $ */
+/* $Id: eqn_html.c,v 1.3 2014/04/20 16:46:04 schwarze Exp $ */
/*
* Copyright (c) 2011 Kristaps Dzonsons
*
@@ -35,9 +35,9 @@ static const enum htmltag fontmap[EQNFONT__MAX] = {
TAG_I /* EQNFONT_ITALIC */
};
-
static void eqn_box(struct html *, const struct eqn_box *);
+
void
print_eqn(struct html *p, const struct eqn *ep)
{
@@ -59,12 +59,12 @@ eqn_box(struct html *p, const struct eqn_box *bp)
{
struct tag *t;
- t = EQNFONT_NONE == bp->font ? NULL :
- print_otag(p, fontmap[(int)bp->font], 0, NULL);
+ t = EQNFONT_NONE == bp->font ? NULL :
+ print_otag(p, fontmap[(int)bp->font], 0, NULL);
if (bp->left)
print_text(p, bp->left);
-
+
if (bp->text)
print_text(p, bp->text);
diff --git a/contrib/mdocml/eqn_term.c b/contrib/mdocml/eqn_term.c
index cfbd8d48f807..889c5c6586f0 100644
--- a/contrib/mdocml/eqn_term.c
+++ b/contrib/mdocml/eqn_term.c
@@ -1,4 +1,4 @@
-/* $Id: eqn_term.c,v 1.4 2011/07/24 10:09:03 kristaps Exp $ */
+/* $Id: eqn_term.c,v 1.5 2014/04/20 16:46:04 schwarze Exp $ */
/*
* Copyright (c) 2011 Kristaps Dzonsons
*
@@ -37,6 +37,7 @@ static const enum termfont fontmap[EQNFONT__MAX] = {
static void eqn_box(struct termp *, const struct eqn_box *);
+
void
term_eqn(struct termp *p, const struct eqn *ep)
{
@@ -68,7 +69,7 @@ eqn_box(struct termp *p, const struct eqn_box *bp)
term_word(p, ")");
if (bp->right)
term_word(p, bp->right);
- if (EQNFONT_NONE != bp->font)
+ if (EQNFONT_NONE != bp->font)
term_fontpop(p);
if (bp->next)
diff --git a/contrib/mdocml/external.png b/contrib/mdocml/external.png
deleted file mode 100644
index 419c06fb960b..000000000000
Binary files a/contrib/mdocml/external.png and /dev/null differ
diff --git a/contrib/mdocml/gmdiff b/contrib/mdocml/gmdiff
new file mode 100644
index 000000000000..2c7ba4b343ca
--- /dev/null
+++ b/contrib/mdocml/gmdiff
@@ -0,0 +1,38 @@
+#!/bin/sh
+# Copyright (c) 2013, 2014 Ingo Schwarze
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+if [ `id -u` -eq 0 ]; then
+ echo "$0: do not run me as root"
+ exit 1
+fi
+
+if [ $# -eq 0 ]; then
+ echo "usage: $0 manual_source_file ..."
+ exit 1
+fi
+
+while [ -n "$1" ]; do
+ file=$1
+ shift
+ echo " ========== $file ========== "
+ tbl $file | groff -mandoc -Tascii -P -c 2> /tmp/groff.err > /tmp/groff.out
+ mandoc -Ios='OpenBSD ports' -Werror $file 2> /tmp/mandoc.err > /tmp/mandoc.out
+ for i in groff mandoc; do
+ [[ -s /tmp/$i.err ]] && echo "$i errors:" && cat /tmp/$i.err
+ done
+ diff -au /tmp/groff.out /tmp/mandoc.out 2>&1
+done
+
+exit 0
diff --git a/contrib/mdocml/html.c b/contrib/mdocml/html.c
index 9d28b4270e4c..d4783ee06fdc 100644
--- a/contrib/mdocml/html.c
+++ b/contrib/mdocml/html.c
@@ -1,7 +1,7 @@
-/* $Id: html.c,v 1.152 2013/08/08 20:07:47 schwarze Exp $ */
+/* $Id: html.c,v 1.159 2014/07/23 15:00:08 schwarze Exp $ */
/*
* Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons
- * Copyright (c) 2011, 2012, 2013 Ingo Schwarze
+ * Copyright (c) 2011, 2012, 2013, 2014 Ingo Schwarze
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -31,6 +31,7 @@
#include
#include "mandoc.h"
+#include "mandoc_aux.h"
#include "libmandoc.h"
#include "out.h"
#include "html.h"
@@ -109,11 +110,13 @@ static const char *const roffscales[SCALE_MAX] = {
static void bufncat(struct html *, const char *, size_t);
static void print_ctag(struct html *, enum htmltag);
+static int print_escape(char);
static int print_encode(struct html *, const char *, int);
static void print_metaf(struct html *, enum mandoc_esc);
static void print_attr(struct html *, const char *, const char *);
static void *ml_alloc(char *, enum htmltype);
+
static void *
ml_alloc(char *outopts, enum htmltype type)
{
@@ -135,16 +138,16 @@ ml_alloc(char *outopts, enum htmltype type)
while (outopts && *outopts)
switch (getsubopt(&outopts, UNCONST(toks), &v)) {
- case (0):
+ case 0:
h->style = v;
break;
- case (1):
+ case 1:
h->base_man = v;
break;
- case (2):
+ case 2:
h->base_includes = v;
break;
- case (3):
+ case 3:
h->oflags |= HTML_FRAGMENT;
break;
default:
@@ -161,7 +164,6 @@ html_alloc(char *outopts)
return(ml_alloc(outopts, HTML_HTML_4_01_STRICT));
}
-
void *
xhtml_alloc(char *outopts)
{
@@ -169,7 +171,6 @@ xhtml_alloc(char *outopts)
return(ml_alloc(outopts, HTML_XHTML_1_0_STRICT));
}
-
void
html_free(void *p)
{
@@ -179,17 +180,16 @@ html_free(void *p)
h = (struct html *)p;
while ((tag = h->tags.head) != NULL) {
- h->tags.head = tag->next;
+ h->tags.head = tag->next;
free(tag);
}
-
+
if (h->symtab)
mchars_free(h->symtab);
free(h);
}
-
void
print_gen_head(struct html *h)
{
@@ -226,21 +226,21 @@ print_metaf(struct html *h, enum mandoc_esc deco)
enum htmlfont font;
switch (deco) {
- case (ESCAPE_FONTPREV):
+ case ESCAPE_FONTPREV:
font = h->metal;
break;
- case (ESCAPE_FONTITALIC):
+ case ESCAPE_FONTITALIC:
font = HTMLFONT_ITALIC;
break;
- case (ESCAPE_FONTBOLD):
+ case ESCAPE_FONTBOLD:
font = HTMLFONT_BOLD;
break;
- case (ESCAPE_FONTBI):
+ case ESCAPE_FONTBI:
font = HTMLFONT_BI;
break;
- case (ESCAPE_FONT):
+ case ESCAPE_FONT:
/* FALLTHROUGH */
- case (ESCAPE_FONTROMAN):
+ case ESCAPE_FONTROMAN:
font = HTMLFONT_NONE;
break;
default:
@@ -257,13 +257,13 @@ print_metaf(struct html *h, enum mandoc_esc deco)
h->metac = font;
switch (font) {
- case (HTMLFONT_ITALIC):
+ case HTMLFONT_ITALIC:
h->metaf = print_otag(h, TAG_I, 0, NULL);
break;
- case (HTMLFONT_BOLD):
+ case HTMLFONT_BOLD:
h->metaf = print_otag(h, TAG_B, 0, NULL);
break;
- case (HTMLFONT_BI):
+ case HTMLFONT_BI:
h->metaf = print_otag(h, TAG_B, 0, NULL);
print_otag(h, TAG_I, 0, NULL);
break;
@@ -302,19 +302,19 @@ html_strlen(const char *cp)
break;
cp++;
switch (mandoc_escape(&cp, NULL, NULL)) {
- case (ESCAPE_ERROR):
+ case ESCAPE_ERROR:
return(sz);
- case (ESCAPE_UNICODE):
+ case ESCAPE_UNICODE:
/* FALLTHROUGH */
- case (ESCAPE_NUMBERED):
+ case ESCAPE_NUMBERED:
/* FALLTHROUGH */
- case (ESCAPE_SPECIAL):
+ case ESCAPE_SPECIAL:
if (skip)
skip = 0;
else
sz++;
break;
- case (ESCAPE_SKIPCHAR):
+ case ESCAPE_SKIPCHAR:
skip = 1;
break;
default:
@@ -324,6 +324,37 @@ html_strlen(const char *cp)
return(sz);
}
+static int
+print_escape(char c)
+{
+
+ switch (c) {
+ case '<':
+ printf("<");
+ break;
+ case '>':
+ printf(">");
+ break;
+ case '&':
+ printf("&");
+ break;
+ case '"':
+ printf(""");
+ break;
+ case ASCII_NBRSP:
+ putchar('-');
+ break;
+ case ASCII_HYPH:
+ putchar('-');
+ /* FALLTHROUGH */
+ case ASCII_BREAK:
+ break;
+ default:
+ return(0);
+ }
+ return(1);
+}
+
static int
print_encode(struct html *h, const char *p, int norecurse)
{
@@ -331,7 +362,8 @@ print_encode(struct html *h, const char *p, int norecurse)
int c, len, nospace;
const char *seq;
enum mandoc_esc esc;
- static const char rejs[6] = { '\\', '<', '>', '&', ASCII_HYPH, '\0' };
+ static const char rejs[9] = { '\\', '<', '>', '&', '"',
+ ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' };
nospace = 0;
@@ -350,43 +382,29 @@ print_encode(struct html *h, const char *p, int norecurse)
if ('\0' == *p)
break;
- switch (*p++) {
- case ('<'):
- printf("<");
+ if (print_escape(*p++))
continue;
- case ('>'):
- printf(">");
- continue;
- case ('&'):
- printf("&");
- continue;
- case (ASCII_HYPH):
- putchar('-');
- continue;
- default:
- break;
- }
esc = mandoc_escape(&p, &seq, &len);
if (ESCAPE_ERROR == esc)
break;
switch (esc) {
- case (ESCAPE_FONT):
+ case ESCAPE_FONT:
/* FALLTHROUGH */
- case (ESCAPE_FONTPREV):
+ case ESCAPE_FONTPREV:
/* FALLTHROUGH */
- case (ESCAPE_FONTBOLD):
+ case ESCAPE_FONTBOLD:
/* FALLTHROUGH */
- case (ESCAPE_FONTITALIC):
+ case ESCAPE_FONTITALIC:
/* FALLTHROUGH */
- case (ESCAPE_FONTBI):
+ case ESCAPE_FONTBI:
/* FALLTHROUGH */
- case (ESCAPE_FONTROMAN):
+ case ESCAPE_FONTROMAN:
if (0 == norecurse)
print_metaf(h, esc);
continue;
- case (ESCAPE_SKIPCHAR):
+ case ESCAPE_SKIPCHAR:
h->flags |= HTML_SKIPCHAR;
continue;
default:
@@ -399,25 +417,26 @@ print_encode(struct html *h, const char *p, int norecurse)
}
switch (esc) {
- case (ESCAPE_UNICODE):
- /* Skip passed "u" header. */
+ case ESCAPE_UNICODE:
+ /* Skip past "u" header. */
c = mchars_num2uc(seq + 1, len - 1);
if ('\0' != c)
printf("%x;", c);
break;
- case (ESCAPE_NUMBERED):
+ case ESCAPE_NUMBERED:
c = mchars_num2char(seq, len);
- if ('\0' != c)
+ if ( ! ('\0' == c || print_escape(c)))
putchar(c);
break;
- case (ESCAPE_SPECIAL):
+ case ESCAPE_SPECIAL:
c = mchars_spec2cp(h->symtab, seq, len);
if (c > 0)
printf("%d;", c);
- else if (-1 == c && 1 == len)
+ else if (-1 == c && 1 == len &&
+ !print_escape(*seq))
putchar((int)*seq);
break;
- case (ESCAPE_NOSPACE):
+ case ESCAPE_NOSPACE:
if ('\0' == *p)
nospace = 1;
break;
@@ -429,7 +448,6 @@ print_encode(struct html *h, const char *p, int norecurse)
return(nospace);
}
-
static void
print_attr(struct html *h, const char *key, const char *val)
{
@@ -438,9 +456,8 @@ print_attr(struct html *h, const char *key, const char *val)
putchar('\"');
}
-
struct tag *
-print_otag(struct html *h, enum htmltag tag,
+print_otag(struct html *h, enum htmltag tag,
int sz, const struct htmlpair *p)
{
int i;
@@ -490,7 +507,7 @@ print_otag(struct html *h, enum htmltag tag,
if (HTML_AUTOCLOSE & htmltags[tag].flags)
switch (h->type) {
- case (HTML_XHTML_1_0_STRICT):
+ case HTML_XHTML_1_0_STRICT:
putchar('/');
break;
default:
@@ -507,16 +524,15 @@ print_otag(struct html *h, enum htmltag tag,
return(t);
}
-
static void
print_ctag(struct html *h, enum htmltag tag)
{
-
+
printf("%s>", htmltags[tag].name);
if (HTML_CLRLINE & htmltags[tag].flags) {
h->flags |= HTML_NOSPACE;
putchar('\n');
- }
+ }
}
void
@@ -527,7 +543,7 @@ print_gen_decls(struct html *h)
const char *name;
switch (h->type) {
- case (HTML_HTML_4_01_STRICT):
+ case HTML_HTML_4_01_STRICT:
name = "HTML";
doctype = "-//W3C//DTD HTML 4.01//EN";
dtd = "http://www.w3.org/TR/html4/strict.dtd";
@@ -540,8 +556,8 @@ print_gen_decls(struct html *h)
break;
}
- printf("\n",
- name, doctype, dtd);
+ printf("\n",
+ name, doctype, dtd);
}
void
@@ -560,13 +576,13 @@ print_text(struct html *h, const char *word)
assert(NULL == h->metaf);
switch (h->metac) {
- case (HTMLFONT_ITALIC):
+ case HTMLFONT_ITALIC:
h->metaf = print_otag(h, TAG_I, 0, NULL);
break;
- case (HTMLFONT_BOLD):
+ case HTMLFONT_BOLD:
h->metaf = print_otag(h, TAG_B, 0, NULL);
break;
- case (HTMLFONT_BI):
+ case HTMLFONT_BI:
h->metaf = print_otag(h, TAG_B, 0, NULL);
print_otag(h, TAG_I, 0, NULL);
break;
@@ -589,14 +605,13 @@ print_text(struct html *h, const char *word)
h->flags &= ~HTML_IGNDELIM;
}
-
void
print_tagq(struct html *h, const struct tag *until)
{
struct tag *tag;
while ((tag = h->tags.head) != NULL) {
- /*
+ /*
* Remember to close out and nullify the current
* meta-font and table, if applicable.
*/
@@ -612,7 +627,6 @@ print_tagq(struct html *h, const struct tag *until)
}
}
-
void
print_stagq(struct html *h, const struct tag *suntil)
{
@@ -621,7 +635,7 @@ print_stagq(struct html *h, const struct tag *suntil)
while ((tag = h->tags.head) != NULL) {
if (suntil && tag == suntil)
return;
- /*
+ /*
* Remember to close out and nullify the current
* meta-font and table, if applicable.
*/
@@ -657,6 +671,12 @@ void
bufcat(struct html *h, const char *p)
{
+ /*
+ * XXX This is broken and not easy to fix.
+ * When using the -Oincludes option, buffmt_includes()
+ * may pass in strings overrunning BUFSIZ, causing a crash.
+ */
+
h->buflen = strlcat(h->buf, p, BUFSIZ);
assert(h->buflen < BUFSIZ);
}
@@ -667,8 +687,8 @@ bufcat_fmt(struct html *h, const char *fmt, ...)
va_list ap;
va_start(ap, fmt);
- (void)vsnprintf(h->buf + (int)h->buflen,
- BUFSIZ - h->buflen - 1, fmt, ap);
+ (void)vsnprintf(h->buf + (int)h->buflen,
+ BUFSIZ - h->buflen - 1, fmt, ap);
va_end(ap);
h->buflen = strlen(h->buf);
}
@@ -688,12 +708,12 @@ buffmt_includes(struct html *h, const char *name)
const char *p, *pp;
pp = h->base_includes;
-
+
bufinit(h);
while (NULL != (p = strchr(pp, '%'))) {
bufncat(h, pp, (size_t)(p - pp));
switch (*(p + 1)) {
- case('I'):
+ case'I':
bufcat(h, name);
break;
default:
@@ -707,22 +727,21 @@ buffmt_includes(struct html *h, const char *name)
}
void
-buffmt_man(struct html *h,
- const char *name, const char *sec)
+buffmt_man(struct html *h, const char *name, const char *sec)
{
const char *p, *pp;
pp = h->base_man;
-
+
bufinit(h);
while (NULL != (p = strchr(pp, '%'))) {
bufncat(h, pp, (size_t)(p - pp));
switch (*(p + 1)) {
- case('S'):
+ case 'S':
bufcat(h, sec ? sec : "1");
break;
- case('N'):
- bufcat_fmt(h, name);
+ case 'N':
+ bufcat_fmt(h, "%s", name);
break;
default:
bufncat(h, p, 2);
diff --git a/contrib/mdocml/html.h b/contrib/mdocml/html.h
index 894cfc4cff47..ca15f0f32164 100644
--- a/contrib/mdocml/html.h
+++ b/contrib/mdocml/html.h
@@ -1,4 +1,4 @@
-/* $Id: html.h,v 1.49 2013/08/08 20:07:47 schwarze Exp $ */
+/* $Id: html.h,v 1.51 2014/04/20 16:46:04 schwarze Exp $ */
/*
* Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons
*
@@ -105,7 +105,7 @@ struct htmlpair {
#define PAIR_STYLE_INIT(p, h) PAIR_INIT(p, ATTR_STYLE, (h)->buf)
#define PAIR_SUMMARY_INIT(p, v) PAIR_INIT(p, ATTR_SUMMARY, v)
-enum htmltype {
+enum htmltype {
HTML_HTML_4_01_STRICT,
HTML_XHTML_1_0_STRICT
};
@@ -127,7 +127,7 @@ struct html {
char *base_includes; /* base for include href */
char *style; /* style-sheet URI */
char buf[BUFSIZ]; /* see bufcat and friends */
- size_t buflen;
+ size_t buflen;
struct tag *metaf; /* current open font scope */
enum htmlfont metal; /* last used font */
enum htmlfont metac; /* current font mode */
@@ -138,7 +138,7 @@ struct html {
void print_gen_decls(struct html *);
void print_gen_head(struct html *);
-struct tag *print_otag(struct html *, enum htmltag,
+struct tag *print_otag(struct html *, enum htmltag,
int, const struct htmlpair *);
void print_tagq(struct html *, const struct tag *);
void print_stagq(struct html *, const struct tag *);
@@ -147,15 +147,18 @@ void print_tblclose(struct html *);
void print_tbl(struct html *, const struct tbl_span *);
void print_eqn(struct html *, const struct eqn *);
+#if __GNUC__ - 0 >= 4
+__attribute__((__format__ (__printf__, 2, 3)))
+#endif
void bufcat_fmt(struct html *, const char *, ...);
void bufcat(struct html *, const char *);
void bufcat_id(struct html *, const char *);
-void bufcat_style(struct html *,
+void bufcat_style(struct html *,
const char *, const char *);
-void bufcat_su(struct html *, const char *,
+void bufcat_su(struct html *, const char *,
const struct roffsu *);
void bufinit(struct html *);
-void buffmt_man(struct html *,
+void buffmt_man(struct html *,
const char *, const char *);
void buffmt_includes(struct html *, const char *);
diff --git a/contrib/mdocml/lib.c b/contrib/mdocml/lib.c
index 7a18a5dd4fe6..8cc8a778a690 100644
--- a/contrib/mdocml/lib.c
+++ b/contrib/mdocml/lib.c
@@ -1,4 +1,4 @@
-/* $Id: lib.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */
+/* $Id: lib.c,v 1.10 2014/03/23 11:25:26 schwarze Exp $ */
/*
* Copyright (c) 2009 Kristaps Dzonsons
*
@@ -18,12 +18,9 @@
#include "config.h"
#endif
-#include
#include
-#include
#include "mdoc.h"
-#include "mandoc.h"
#include "libmdoc.h"
#define LINE(x, y) \
diff --git a/contrib/mdocml/lib.in b/contrib/mdocml/lib.in
index 81cb560128e2..a785c72e123e 100644
--- a/contrib/mdocml/lib.in
+++ b/contrib/mdocml/lib.in
@@ -1,6 +1,7 @@
-/* $Id: lib.in,v 1.17 2013/10/13 15:24:03 schwarze Exp $ */
+/* $Id: lib.in,v 1.18 2014/01/06 00:53:33 schwarze Exp $ */
/*
* Copyright (c) 2009 Kristaps Dzonsons
+ * Copyright (c) 2009, 2012 Joerg Sonnenberger
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
diff --git a/contrib/mdocml/libman.h b/contrib/mdocml/libman.h
index f2ba6a125630..e852927cd2cd 100644
--- a/contrib/mdocml/libman.h
+++ b/contrib/mdocml/libman.h
@@ -1,4 +1,4 @@
-/* $Id: libman.h,v 1.56 2012/11/17 00:26:33 schwarze Exp $ */
+/* $Id: libman.h,v 1.63 2014/08/01 21:24:17 schwarze Exp $ */
/*
* Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons
*
@@ -24,13 +24,11 @@ enum man_next {
struct man {
struct mparse *parse; /* parse pointer */
+ int quick; /* abort parse early */
int flags; /* parse flags */
-#define MAN_HALT (1 << 0) /* badness happened: die */
#define MAN_ELINE (1 << 1) /* Next-line element scope. */
#define MAN_BLINE (1 << 2) /* Next-line block scope. */
-#define MAN_ILINE (1 << 3) /* Ignored in next-line scope. */
#define MAN_LITERAL (1 << 4) /* Literal input. */
-#define MAN_BPLINE (1 << 5)
#define MAN_NEWLINE (1 << 6) /* first macro/text in a line */
enum man_next next; /* where to put the next node */
struct man_node *last; /* the last parsed node */
@@ -61,10 +59,6 @@ extern const struct man_macro *const man_macros;
__BEGIN_DECLS
-#define man_pmsg(man, l, p, t) \
- mandoc_msg((t), (man)->parse, (l), (p), NULL)
-#define man_nmsg(man, n, t) \
- mandoc_msg((t), (man)->parse, (n)->line, (n)->pos, NULL)
int man_word_alloc(struct man *, int, int, const char *);
int man_block_alloc(struct man *, int, int, enum mant);
int man_head_alloc(struct man *, int, int, enum mant);
@@ -76,9 +70,7 @@ void man_hash_init(void);
enum mant man_hash_find(const char *);
int man_macroend(struct man *);
int man_valid_post(struct man *);
-int man_valid_pre(struct man *, struct man_node *);
-int man_unscope(struct man *,
- const struct man_node *, enum mandocerr);
+int man_unscope(struct man *, const struct man_node *);
__END_DECLS
diff --git a/contrib/mdocml/libmandoc.h b/contrib/mdocml/libmandoc.h
index 3c005e106da9..1011cc502147 100644
--- a/contrib/mdocml/libmandoc.h
+++ b/contrib/mdocml/libmandoc.h
@@ -1,7 +1,7 @@
-/* $Id: libmandoc.h,v 1.35 2013/12/15 21:23:52 schwarze Exp $ */
+/* $Id: libmandoc.h,v 1.42 2014/07/09 11:31:43 schwarze Exp $ */
/*
* Copyright (c) 2009, 2010, 2011, 2012 Kristaps Dzonsons
- * Copyright (c) 2013 Ingo Schwarze
+ * Copyright (c) 2013, 2014 Ingo Schwarze
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -36,46 +36,50 @@ struct roff;
struct mdoc;
struct man;
-void mandoc_msg(enum mandocerr, struct mparse *,
+void mandoc_msg(enum mandocerr, struct mparse *,
int, int, const char *);
-void mandoc_vmsg(enum mandocerr, struct mparse *,
+#if __GNUC__ - 0 >= 4
+__attribute__((__format__ (__printf__, 5, 6)))
+#endif
+void mandoc_vmsg(enum mandocerr, struct mparse *,
int, int, const char *, ...);
char *mandoc_getarg(struct mparse *, char **, int, int *);
char *mandoc_normdate(struct mparse *, char *, int, int);
-int mandoc_eos(const char *, size_t, int);
+int mandoc_eos(const char *, size_t);
int mandoc_strntoi(const char *, size_t, int);
const char *mandoc_a2msec(const char*);
-void mdoc_free(struct mdoc *);
-struct mdoc *mdoc_alloc(struct roff *, struct mparse *, char *);
+void mdoc_free(struct mdoc *);
+struct mdoc *mdoc_alloc(struct roff *, struct mparse *,
+ const char *, int);
void mdoc_reset(struct mdoc *);
-int mdoc_parseln(struct mdoc *, int, char *, int);
+int mdoc_parseln(struct mdoc *, int, char *, int);
int mdoc_endparse(struct mdoc *);
int mdoc_addspan(struct mdoc *, const struct tbl_span *);
int mdoc_addeqn(struct mdoc *, const struct eqn *);
-void man_free(struct man *);
-struct man *man_alloc(struct roff *, struct mparse *);
+void man_free(struct man *);
+struct man *man_alloc(struct roff *, struct mparse *, int);
void man_reset(struct man *);
-int man_parseln(struct man *, int, char *, int);
+int man_parseln(struct man *, int, char *, int);
int man_endparse(struct man *);
int man_addspan(struct man *, const struct tbl_span *);
int man_addeqn(struct man *, const struct eqn *);
-void roff_free(struct roff *);
-struct roff *roff_alloc(enum mparset, struct mparse *);
+void roff_free(struct roff *);
+struct roff *roff_alloc(struct mparse *, int);
void roff_reset(struct roff *);
-enum rofferr roff_parseln(struct roff *, int,
+enum rofferr roff_parseln(struct roff *, int,
char **, size_t *, int, int *);
void roff_endparse(struct roff *);
void roff_setreg(struct roff *, const char *, int, char sign);
int roff_getreg(const struct roff *, const char *);
char *roff_strdup(const struct roff *, const char *);
-int roff_getcontrol(const struct roff *,
+int roff_getcontrol(const struct roff *,
const char *, int *);
#if 0
char roff_eqndelim(const struct roff *);
-void roff_openeqn(struct roff *, const char *,
+void roff_openeqn(struct roff *, const char *,
int, int, const char *);
int roff_closeeqn(struct roff *);
#endif
diff --git a/contrib/mdocml/libmdoc.h b/contrib/mdocml/libmdoc.h
index 3f14519d3b40..1507a8c26fbb 100644
--- a/contrib/mdocml/libmdoc.h
+++ b/contrib/mdocml/libmdoc.h
@@ -1,4 +1,4 @@
-/* $Id: libmdoc.h,v 1.82 2013/10/21 23:47:58 schwarze Exp $ */
+/* $Id: libmdoc.h,v 1.88 2014/08/01 17:40:34 schwarze Exp $ */
/*
* Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons
* Copyright (c) 2013 Ingo Schwarze
@@ -25,9 +25,9 @@ enum mdoc_next {
struct mdoc {
struct mparse *parse; /* parse pointer */
- char *defos; /* default argument for .Os */
+ const char *defos; /* default argument for .Os */
+ int quick; /* abort parse early */
int flags; /* parse flags */
-#define MDOC_HALT (1 << 0) /* error in parse: halt */
#define MDOC_LITERAL (1 << 1) /* in a literal scope */
#define MDOC_PBODY (1 << 2) /* in the document body */
#define MDOC_NEWLINE (1 << 3) /* first macro/text in a line */
@@ -40,6 +40,7 @@ struct mdoc {
enum mdoc_next next; /* where to put the next node */
struct mdoc_node *last; /* the last node parsed */
struct mdoc_node *first; /* the first node parsed */
+ struct mdoc_node *last_es; /* the most recent Es node */
struct mdoc_meta meta; /* document meta-data */
enum mdoc_sec lastnamed;
enum mdoc_sec lastsec;
@@ -103,17 +104,13 @@ extern const struct mdoc_macro *const mdoc_macros;
__BEGIN_DECLS
-#define mdoc_pmsg(mdoc, l, p, t) \
- mandoc_msg((t), (mdoc)->parse, (l), (p), NULL)
-#define mdoc_nmsg(mdoc, n, t) \
- mandoc_msg((t), (mdoc)->parse, (n)->line, (n)->pos, NULL)
int mdoc_macro(MACRO_PROT_ARGS);
-int mdoc_word_alloc(struct mdoc *,
+int mdoc_word_alloc(struct mdoc *,
int, int, const char *);
void mdoc_word_append(struct mdoc *, const char *);
-int mdoc_elem_alloc(struct mdoc *, int, int,
+int mdoc_elem_alloc(struct mdoc *, int, int,
enum mdoct, struct mdoc_arg *);
-int mdoc_block_alloc(struct mdoc *, int, int,
+int mdoc_block_alloc(struct mdoc *, int, int,
enum mdoct, struct mdoc_arg *);
int mdoc_head_alloc(struct mdoc *, int, int, enum mdoct);
int mdoc_tail_alloc(struct mdoc *, int, int, enum mdoct);
@@ -136,7 +133,7 @@ enum margverr mdoc_argv(struct mdoc *, int, enum mdoct,
void mdoc_argv_free(struct mdoc_arg *);
enum margserr mdoc_args(struct mdoc *, int,
int *, char *, enum mdoct, char **);
-enum margserr mdoc_zargs(struct mdoc *, int,
+enum margserr mdoc_zargs(struct mdoc *, int,
int *, char *, char **);
int mdoc_macroend(struct mdoc *);
enum mdelim mdoc_isdelim(const char *);
diff --git a/contrib/mdocml/libroff.h b/contrib/mdocml/libroff.h
index 5b84c5fc4541..f917b14926f9 100644
--- a/contrib/mdocml/libroff.h
+++ b/contrib/mdocml/libroff.h
@@ -1,4 +1,4 @@
-/* $Id: libroff.h,v 1.28 2013/05/31 21:37:17 schwarze Exp $ */
+/* $Id: libroff.h,v 1.29 2014/04/20 16:46:04 schwarze Exp $ */
/*
* Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons
*
@@ -66,7 +66,7 @@ struct tbl_node *tbl_alloc(int, int, struct mparse *);
void tbl_restart(int, int, struct tbl_node *);
void tbl_free(struct tbl_node *);
void tbl_reset(struct tbl_node *);
-enum rofferr tbl_read(struct tbl_node *, int, const char *, int);
+enum rofferr tbl_read(struct tbl_node *, int, const char *, int);
int tbl_option(struct tbl_node *, int, const char *);
int tbl_layout(struct tbl_node *, int, const char *);
int tbl_data(struct tbl_node *, int, const char *);
@@ -76,7 +76,7 @@ void tbl_end(struct tbl_node **);
struct eqn_node *eqn_alloc(const char *, int, int, struct mparse *);
enum rofferr eqn_end(struct eqn_node **);
void eqn_free(struct eqn_node *);
-enum rofferr eqn_read(struct eqn_node **, int,
+enum rofferr eqn_read(struct eqn_node **, int,
const char *, int, int *);
__END_DECLS
diff --git a/contrib/mdocml/main.c b/contrib/mdocml/main.c
index 7e5c7a98aef2..d97c8e2393b0 100644
--- a/contrib/mdocml/main.c
+++ b/contrib/mdocml/main.c
@@ -1,7 +1,8 @@
-/* $Id: main.c,v 1.167 2012/11/19 17:22:26 schwarze Exp $ */
+/* $Id: main.c,v 1.177 2014/06/21 22:24:01 schwarze Exp $ */
/*
* Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons
- * Copyright (c) 2010, 2011, 2012 Ingo Schwarze
+ * Copyright (c) 2010, 2011, 2012, 2014 Ingo Schwarze
+ * Copyright (c) 2010 Joerg Sonnenberger
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -27,6 +28,7 @@
#include
#include "mandoc.h"
+#include "mandoc_aux.h"
#include "main.h"
#include "mdoc.h"
#include "man.h"
@@ -58,18 +60,18 @@ struct curparse {
struct mparse *mp;
enum mandoclevel wlevel; /* ignore messages below this */
int wstop; /* stop after a file with a warning */
- enum outt outtype; /* which output to use */
+ enum outt outtype; /* which output to use */
out_mdoc outmdoc; /* mdoc output ptr */
- out_man outman; /* man output ptr */
+ out_man outman; /* man output ptr */
out_free outfree; /* free output ptr */
void *outdata; /* data for output */
char outopts[BUFSIZ]; /* buf of output opts */
};
-static int moptions(enum mparset *, char *);
+static int moptions(int *, char *);
static void mmsg(enum mandocerr, enum mandoclevel,
const char *, int, int, const char *);
-static void parse(struct curparse *, int,
+static void parse(struct curparse *, int,
const char *, enum mandoclevel *);
static int toptions(struct curparse *, char *);
static void usage(void) __attribute__((noreturn));
@@ -78,12 +80,13 @@ static int woptions(struct curparse *, char *);
static const char *progname;
+
int
main(int argc, char *argv[])
{
int c;
struct curparse curp;
- enum mparset type;
+ int options;
enum mandoclevel rc;
char *defos;
@@ -95,44 +98,45 @@ main(int argc, char *argv[])
memset(&curp, 0, sizeof(struct curparse));
- type = MPARSE_AUTO;
+ options = MPARSE_SO;
curp.outtype = OUTT_ASCII;
curp.wlevel = MANDOCLEVEL_FATAL;
defos = NULL;
- /* LINTED */
while (-1 != (c = getopt(argc, argv, "I:m:O:T:VW:")))
switch (c) {
- case ('I'):
+ case 'I':
if (strncmp(optarg, "os=", 3)) {
- fprintf(stderr, "-I%s: Bad argument\n",
- optarg);
+ fprintf(stderr,
+ "%s: -I%s: Bad argument\n",
+ progname, optarg);
return((int)MANDOCLEVEL_BADARG);
}
if (defos) {
- fprintf(stderr, "-I%s: Duplicate argument\n",
- optarg);
+ fprintf(stderr,
+ "%s: -I%s: Duplicate argument\n",
+ progname, optarg);
return((int)MANDOCLEVEL_BADARG);
}
defos = mandoc_strdup(optarg + 3);
break;
- case ('m'):
- if ( ! moptions(&type, optarg))
+ case 'm':
+ if ( ! moptions(&options, optarg))
return((int)MANDOCLEVEL_BADARG);
break;
- case ('O'):
+ case 'O':
(void)strlcat(curp.outopts, optarg, BUFSIZ);
(void)strlcat(curp.outopts, ",", BUFSIZ);
break;
- case ('T'):
+ case 'T':
if ( ! toptions(&curp, optarg))
return((int)MANDOCLEVEL_BADARG);
break;
- case ('W'):
+ case 'W':
if ( ! woptions(&curp, optarg))
return((int)MANDOCLEVEL_BADARG);
break;
- case ('V'):
+ case 'V':
version();
/* NOTREACHED */
default:
@@ -140,7 +144,7 @@ main(int argc, char *argv[])
/* NOTREACHED */
}
- curp.mp = mparse_alloc(type, curp.wlevel, mmsg, &curp, defos);
+ curp.mp = mparse_alloc(options, curp.wlevel, mmsg, defos);
/*
* Conditionally start up the lookaside buffer before parsing.
@@ -191,15 +195,15 @@ usage(void)
"[-Ooption] "
"[-Toutput] "
"[-Wlevel]\n"
- "\t [file ...]\n",
+ "\t [file ...]\n",
progname);
exit((int)MANDOCLEVEL_BADARG);
}
static void
-parse(struct curparse *curp, int fd,
- const char *file, enum mandoclevel *level)
+parse(struct curparse *curp, int fd, const char *file,
+ enum mandoclevel *level)
{
enum mandoclevel rc;
struct mdoc *mdoc;
@@ -229,31 +233,31 @@ parse(struct curparse *curp, int fd,
if ( ! (curp->outman && curp->outmdoc)) {
switch (curp->outtype) {
- case (OUTT_XHTML):
+ case OUTT_XHTML:
curp->outdata = xhtml_alloc(curp->outopts);
curp->outfree = html_free;
break;
- case (OUTT_HTML):
+ case OUTT_HTML:
curp->outdata = html_alloc(curp->outopts);
curp->outfree = html_free;
break;
- case (OUTT_UTF8):
+ case OUTT_UTF8:
curp->outdata = utf8_alloc(curp->outopts);
curp->outfree = ascii_free;
break;
- case (OUTT_LOCALE):
+ case OUTT_LOCALE:
curp->outdata = locale_alloc(curp->outopts);
curp->outfree = ascii_free;
break;
- case (OUTT_ASCII):
+ case OUTT_ASCII:
curp->outdata = ascii_alloc(curp->outopts);
curp->outfree = ascii_free;
break;
- case (OUTT_PDF):
+ case OUTT_PDF:
curp->outdata = pdf_alloc(curp->outopts);
curp->outfree = pspdf_free;
break;
- case (OUTT_PS):
+ case OUTT_PS:
curp->outdata = ps_alloc(curp->outopts);
curp->outfree = pspdf_free;
break;
@@ -262,29 +266,29 @@ parse(struct curparse *curp, int fd,
}
switch (curp->outtype) {
- case (OUTT_HTML):
+ case OUTT_HTML:
/* FALLTHROUGH */
- case (OUTT_XHTML):
+ case OUTT_XHTML:
curp->outman = html_man;
curp->outmdoc = html_mdoc;
break;
- case (OUTT_TREE):
+ case OUTT_TREE:
curp->outman = tree_man;
curp->outmdoc = tree_mdoc;
break;
- case (OUTT_MAN):
+ case OUTT_MAN:
curp->outmdoc = man_mdoc;
curp->outman = man_man;
break;
- case (OUTT_PDF):
+ case OUTT_PDF:
/* FALLTHROUGH */
- case (OUTT_ASCII):
+ case OUTT_ASCII:
/* FALLTHROUGH */
- case (OUTT_UTF8):
+ case OUTT_UTF8:
/* FALLTHROUGH */
- case (OUTT_LOCALE):
+ case OUTT_LOCALE:
/* FALLTHROUGH */
- case (OUTT_PS):
+ case OUTT_PS:
curp->outman = terminal_man;
curp->outmdoc = terminal_mdoc;
break;
@@ -293,7 +297,7 @@ parse(struct curparse *curp, int fd,
}
}
- mparse_result(curp->mp, &mdoc, &man);
+ mparse_result(curp->mp, &mdoc, &man, NULL);
/* Execute the out device, if it exists. */
@@ -311,17 +315,18 @@ parse(struct curparse *curp, int fd,
}
static int
-moptions(enum mparset *tflags, char *arg)
+moptions(int *options, char *arg)
{
if (0 == strcmp(arg, "doc"))
- *tflags = MPARSE_MDOC;
+ *options |= MPARSE_MDOC;
else if (0 == strcmp(arg, "andoc"))
- *tflags = MPARSE_AUTO;
+ /* nothing to do */;
else if (0 == strcmp(arg, "an"))
- *tflags = MPARSE_MAN;
+ *options |= MPARSE_MAN;
else {
- fprintf(stderr, "%s: Bad argument\n", arg);
+ fprintf(stderr, "%s: -m%s: Bad argument\n",
+ progname, arg);
return(0);
}
@@ -354,7 +359,8 @@ toptions(struct curparse *curp, char *arg)
else if (0 == strcmp(arg, "pdf"))
curp->outtype = OUTT_PDF;
else {
- fprintf(stderr, "%s: Bad argument\n", arg);
+ fprintf(stderr, "%s: -T%s: Bad argument\n",
+ progname, arg);
return(0);
}
@@ -365,7 +371,7 @@ static int
woptions(struct curparse *curp, char *arg)
{
char *v, *o;
- const char *toks[6];
+ const char *toks[6];
toks[0] = "stop";
toks[1] = "all";
@@ -377,22 +383,23 @@ woptions(struct curparse *curp, char *arg)
while (*arg) {
o = arg;
switch (getsubopt(&arg, UNCONST(toks), &v)) {
- case (0):
+ case 0:
curp->wstop = 1;
break;
- case (1):
+ case 1:
/* FALLTHROUGH */
- case (2):
+ case 2:
curp->wlevel = MANDOCLEVEL_WARNING;
break;
- case (3):
+ case 3:
curp->wlevel = MANDOCLEVEL_ERROR;
break;
- case (4):
+ case 4:
curp->wlevel = MANDOCLEVEL_FATAL;
break;
default:
- fprintf(stderr, "-W%s: Bad argument\n", o);
+ fprintf(stderr, "%s: -W%s: Bad argument\n",
+ progname, o);
return(0);
}
}
@@ -401,14 +408,20 @@ woptions(struct curparse *curp, char *arg)
}
static void
-mmsg(enum mandocerr t, enum mandoclevel lvl,
+mmsg(enum mandocerr t, enum mandoclevel lvl,
const char *file, int line, int col, const char *msg)
{
+ const char *mparse_msg;
- fprintf(stderr, "%s:%d:%d: %s: %s",
- file, line, col + 1,
- mparse_strlevel(lvl),
- mparse_strerror(t));
+ fprintf(stderr, "%s: %s:", progname, file);
+
+ if (line)
+ fprintf(stderr, "%d:%d:", line, col + 1);
+
+ fprintf(stderr, " %s", mparse_strlevel(lvl));
+
+ if (NULL != (mparse_msg = mparse_strerror(t)))
+ fprintf(stderr, ": %s", mparse_msg);
if (msg)
fprintf(stderr, ": %s", msg);
diff --git a/contrib/mdocml/main.h b/contrib/mdocml/main.h
index 79dcf489ae65..beb0481cab83 100644
--- a/contrib/mdocml/main.h
+++ b/contrib/mdocml/main.h
@@ -1,4 +1,4 @@
-/* $Id: main.h,v 1.15 2011/10/06 22:29:12 kristaps Exp $ */
+/* $Id: main.h,v 1.16 2014/04/20 16:46:04 schwarze Exp $ */
/*
* Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons
*
@@ -25,7 +25,7 @@ struct man;
#define UNCONST(a) ((void *)(uintptr_t)(const void *)(a))
-/*
+/*
* Definitions for main.c-visible output device functions, e.g., -Thtml
* and -Tascii. Note that ascii_alloc() is named as such in
* anticipation of latin1_alloc() and so on, all of which map into the
diff --git a/contrib/mdocml/makewhatis.8 b/contrib/mdocml/makewhatis.8
new file mode 100644
index 000000000000..02c4cc354bd6
--- /dev/null
+++ b/contrib/mdocml/makewhatis.8
@@ -0,0 +1,217 @@
+.\" $Id: makewhatis.8,v 1.2 2014/04/25 12:13:15 schwarze Exp $
+.\"
+.\" Copyright (c) 2011, 2012 Kristaps Dzonsons
+.\" Copyright (c) 2011, 2012 Ingo Schwarze
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: April 25 2014 $
+.Dt MAKEWHATIS 8
+.Os
+.Sh NAME
+.Nm makewhatis
+.Nd index UNIX manuals
+.Sh SYNOPSIS
+.Nm
+.Op Fl aDnpQ
+.Op Fl T Cm utf8
+.Op Fl C Ar file
+.Nm
+.Op Fl aDnpQ
+.Op Fl T Cm utf8
+.Ar dir ...
+.Nm
+.Op Fl DnpQ
+.Op Fl T Cm utf8
+.Fl d Ar dir
+.Op Ar
+.Nm
+.Op Fl Dnp
+.Op Fl T Cm utf8
+.Fl u Ar dir
+.Op Ar
+.Nm
+.Op Fl DQ
+.Fl t Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility extracts keywords from
+.Ux
+manuals and indexes them in a database for fast retrieval by
+.Xr apropos 1 ,
+.Xr whatis 1 ,
+and
+.Xr man 1 Ns 's
+.Fl k
+option.
+.Pp
+By default,
+.Nm
+creates a database in each
+.Ar dir
+using the files
+.Sm off
+.Sy man Ar section Li /
+.Op Ar arch Li /
+.Ar title . section
+.Sm on
+and
+.Sm off
+.Sy cat Ar section Li /
+.Op Ar arch Li /
+.Ar title . Sy 0
+.Sm on
+in that directory.
+Existing databases are replaced.
+If
+.Ar dir
+is not provided,
+.Nm
+uses the default paths stipulated by
+.Xr manpath 1 ,
+or
+.Xr man.conf 5 .
+.Pp
+The arguments are as follows:
+.Bl -tag -width "-C file"
+.It Fl a
+Use all directories and files found below
+.Ar dir ... .
+.It Fl C Ar file
+Specify an alternative configuration
+.Ar file
+in
+.Xr man.conf 5
+format.
+.It Fl D
+Display all files added or removed to the index.
+With a second
+.Fl D ,
+also show all keyswords added for each file.
+.It Fl d Ar dir
+Merge (remove and re-add)
+.Ar
+to the database in
+.Ar dir .
+.It Fl n
+Do not create or modify any database; scan and parse only,
+and print manual page names and descriptions to standard output.
+.It Fl p
+Print warnings about potential problems with manual pages
+to the standard error output.
+.It Fl Q
+Quickly build reduced-size databases
+by reading only the NAME sections of manuals.
+The resulting databases will usually contain names and descriptions only.
+.It Fl T Cm utf8
+Use UTF-8 encoding instead of ASCII for strings stored in the databases.
+.It Fl t Ar
+Check the given
+.Ar files
+for potential problems.
+Implies
+.Fl a ,
+.Fl n ,
+and
+.Fl p .
+All diagnostic messages are printed to the standard output;
+the standard error output is not used.
+.It Fl u Ar dir
+Remove
+.Ar
+from the database in
+.Ar dir .
+.El
+.Pp
+If fatal parse errors are encountered while parsing, the offending file
+is printed to stderr, omitted from the index, and the parse continues
+with the next input file.
+.Sh FILES
+.Bl -tag -width Ds
+.It Pa mandoc.db
+A database of manpages relative to the directory of the file.
+This file is portable across architectures and systems, so long as the
+manpage hierarchy it indexes does not change.
+.It Pa /etc/man.conf
+The default
+.Xr man 1
+configuration file.
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits with one of the following values:
+.Pp
+.Bl -tag -width Ds -compact
+.It 0
+No errors occurred.
+.It 5
+Invalid command line arguments were specified.
+No input files have been read.
+.It 6
+An operating system error occurred, for example memory exhaustion or an
+error accessing input files.
+Such errors cause
+.Nm
+to exit at once, possibly in the middle of parsing or formatting a file.
+The output databases are corrupt and should be removed.
+.El
+.Sh SEE ALSO
+.Xr apropos 1 ,
+.Xr man 1 ,
+.Xr whatis 1 ,
+.Xr man.conf 5
+.Sh HISTORY
+A
+.Nm
+utility first appeared in
+.Bx 2 .
+It was rewritten in
+.Xr perl 1
+for
+.Ox 2.7
+and in C for
+.Ox 5.6 .
+.Pp
+The
+.Ar dir
+argument first appeared in
+.Nx 1.0 ;
+the options
+.Fl dpt
+in
+.Ox 2.7 ;
+the option
+.Fl u
+in
+.Ox 3.4 ;
+and the options
+.Fl aCDnQT
+in
+.Ox 5.6 .
+.Sh AUTHORS
+.An -nosplit
+.An Bill Joy
+wrote the original
+.Bx
+.Nm
+in February 1979,
+.An Marc Espie
+started the Perl version in 2000,
+and the current version of
+.Nm
+was written by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
+and
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org .
diff --git a/contrib/mdocml/man.7 b/contrib/mdocml/man.7
index f2f4d1d8c099..4b64d7f442f4 100644
--- a/contrib/mdocml/man.7
+++ b/contrib/mdocml/man.7
@@ -1,7 +1,8 @@
-.\" $Id: man.7,v 1.120 2013/09/16 22:58:57 schwarze Exp $
+.\" $Id: man.7,v 1.127 2014/06/22 16:39:45 schwarze Exp $
.\"
.\" Copyright (c) 2009, 2010, 2011, 2012 Kristaps Dzonsons
-.\" Copyright (c) 2011, 2012 Ingo Schwarze
+.\" Copyright (c) 2011, 2012, 2013 Ingo Schwarze
+.\" Copyright (c) 2010 Joerg Sonnenberger
.\"
.\" Permission to use, copy, modify, and distribute this software for any
.\" purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +16,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
-.Dd $Mdocdate: September 16 2013 $
+.Dd $Mdocdate: June 22 2014 $
.Dt MAN 7
.Os
.Sh NAME
@@ -97,30 +98,32 @@ file for a utility
.Bd -literal -offset indent
\&.TH PROGNAME 1 2009-10-10
\&.SH NAME
-\efBprogname\efR \e(en a description goes here
+\efBprogname\efR \e(en one line about what it does
\&.\e\(dq .SH LIBRARY
-\&.\e\(dq For sections 2 & 3 only.
+\&.\e\(dq For sections 2, 3, and 9 only.
\&.\e\(dq Not used in OpenBSD.
\&.SH SYNOPSIS
-\efBprogname\efR [\efB\e-options\efR] arguments...
+\efBprogname\efR [\efB\e-options\efR] \efIfile ...\efR
\&.SH DESCRIPTION
-The \efBfoo\efR utility processes files...
+The \efBfoo\efR utility processes files ...
+\&.\e\(dq .Sh CONTEXT
+\&.\e\(dq For section 9 functions only.
\&.\e\(dq .SH IMPLEMENTATION NOTES
\&.\e\(dq Not used in OpenBSD.
\&.\e\(dq .SH RETURN VALUES
-\&.\e\(dq For sections 2, 3, & 9 only.
+\&.\e\(dq For sections 2, 3, and 9 function return values only.
\&.\e\(dq .SH ENVIRONMENT
-\&.\e\(dq For sections 1, 6, 7, & 8 only.
+\&.\e\(dq For sections 1, 6, 7, and 8 only.
\&.\e\(dq .SH FILES
\&.\e\(dq .SH EXIT STATUS
-\&.\e\(dq For sections 1, 6, & 8 only.
+\&.\e\(dq For sections 1, 6, and 8 only.
\&.\e\(dq .SH EXAMPLES
\&.\e\(dq .SH DIAGNOSTICS
-\&.\e\(dq For sections 1, 4, 6, 7, & 8 only.
+\&.\e\(dq For sections 1, 4, 6, 7, 8, and 9 printf/stderr messages only.
\&.\e\(dq .SH ERRORS
-\&.\e\(dq For sections 2, 3, & 9 only.
+\&.\e\(dq For sections 2, 3, 4, and 9 errno settings only.
\&.\e\(dq .SH SEE ALSO
-\&.\e\(dq .BR foo ( 1 )
+\&.\e\(dq .BR foobar ( 1 )
\&.\e\(dq .SH STANDARDS
\&.\e\(dq .SH HISTORY
\&.\e\(dq .SH AUTHORS
@@ -170,6 +173,9 @@ This expands upon the brief, one-line description in
.Em NAME .
It usually contains a break-down of the options (if documenting a
command).
+.It Em CONTEXT
+This section lists the contexts in which functions can be called in section 9.
+The contexts are autoconf, process, or interrupt.
.It Em IMPLEMENTATION NOTES
Implementation-specific notes should be kept here.
This is useful when implementing standard functions that may have side
@@ -196,13 +202,19 @@ well-tested invocations.
Make sure that examples work properly!
.It Em DIAGNOSTICS
Documents error conditions.
-This is most useful in section 4 manuals.
+In section 4 and 9 manuals, these are usually messages
+printed by the kernel to the console and to the kernel log.
+In section 1, 6, 7, and 8, these are usually messages
+printed by userland programs to the standard error output.
+.Pp
Historically, this section was used in place of
.Em EXIT STATUS
for manuals in sections 1, 6, and 8; however, this practise is
discouraged.
.It Em ERRORS
-Documents error handling in sections 2, 3, and 9.
+Documents
+.Xr errno 2
+settings in sections 2, 3, 4, and 9.
.It Em SEE ALSO
References other manuals with related topics.
This section should exist for most manuals.
@@ -280,7 +292,7 @@ For the scoping of individual macros, see
.Sx MACRO SYNTAX .
.Ss \&AT
Sets the volume for the footer for compatibility with man pages from
-.Tn AT&T UNIX
+.At
releases.
The optional arguments specify which release it is from.
.Ss \&B
@@ -656,6 +668,20 @@ Sets the volume for the footer for compatibility with man pages from
.Bx
releases.
The optional first argument specifies which release it is from.
+.Ss \&UE
+End a uniform resource identifier block.
+This is a non-standard GNU extension, included only for compatibility.
+See
+.Sx \&UE .
+.Ss \&UR
+Begin a uniform resource identifier block.
+This is a non-standard GNU extension, included only for compatibility.
+It has the following syntax:
+.Bd -literal -offset indent
+.Pf \. Sx \&UR Ar uri
+link description to be shown
+.Pf \. Sx UE
+.Ed
.Ss \&br
Breaks the current line.
Consecutive invocations have no further effect.
@@ -665,11 +691,6 @@ See also
.Ss \&fi
End literal mode begun by
.Sx \&nf .
-.Ss \&ft
-Change the current font mode.
-See
-.Sx Text Decoration
-for a listing of available font modes.
.Ss \&in
Indent relative to the current indentation:
.Pp
@@ -750,10 +771,13 @@ The syntax is as follows:
.It Sx \&BI Ta n Ta current Ta \&
.It Sx \&BR Ta n Ta current Ta \&
.It Sx \&DT Ta 0 Ta current Ta \&
+.It Sx \&EE Ta 0 Ta current Ta compat
+.It Sx \&EX Ta 0 Ta current Ta compat
.It Sx \&I Ta n Ta next-line Ta \&
.It Sx \&IB Ta n Ta current Ta \&
.It Sx \&IR Ta n Ta current Ta \&
.It Sx \&OP Ta 0, 1 Ta current Ta compat
+.It Sx \&PD Ta 1 Ta current Ta \&
.It Sx \&R Ta n Ta next-line Ta \&
.It Sx \&RB Ta n Ta current Ta \&
.It Sx \&RI Ta n Ta current Ta \&
@@ -763,7 +787,6 @@ The syntax is as follows:
.It Sx \&UC Ta <=1 Ta current Ta \&
.It Sx \&br Ta 0 Ta current Ta compat
.It Sx \&fi Ta 0 Ta current Ta compat
-.It Sx \&ft Ta 1 Ta current Ta compat
.It Sx \&in Ta 1 Ta current Ta compat
.It Sx \&na Ta 0 Ta current Ta compat
.It Sx \&nf Ta 0 Ta current Ta compat
@@ -823,6 +846,8 @@ implicitly closed, is syntactically incorrect.
.It Sx \&SH Ta >0 Ta next-line Ta section Ta \&
.It Sx \&SS Ta >0 Ta next-line Ta sub-section Ta \&
.It Sx \&TP Ta n Ta next-line Ta paragraph Ta \&
+.It Sx \&UE Ta 0 Ta current Ta none Ta compat
+.It Sx \&UR Ta 1 Ta current Ta part Ta compat
.El
.Pp
Macros marked
@@ -848,10 +873,11 @@ Note that macros like
.Sx \&BR
open and close a font scope for each argument.
.Sh COMPATIBILITY
-This section documents areas of questionable portability between
+This section mentions some areas of questionable portability between
implementations of the
.Nm
language.
+More incompatibilities exist.
.Pp
.Bl -dash -compact
.It
@@ -863,47 +889,12 @@ to close out a literal context opened with
.Sx \&nf .
This behaviour may not be portable.
.It
-In quoted literals, GNU troff allowed pair-wise double-quotes to produce
-a standalone double-quote in formatted output.
-It is not known whether this behaviour is exhibited by other formatters.
-.It
troff suppresses a newline before
.Sq \(aq
macro output; in mandoc, it is an alias for the standard
.Sq \&.
control character.
.It
-The
-.Sq \eh
-.Pq horizontal position ,
-.Sq \ev
-.Pq vertical position ,
-.Sq \em
-.Pq text colour ,
-.Sq \eM
-.Pq text filling colour ,
-.Sq \ez
-.Pq zero-length character ,
-.Sq \ew
-.Pq string length ,
-.Sq \ek
-.Pq horizontal position marker ,
-.Sq \eo
-.Pq text overstrike ,
-and
-.Sq \es
-.Pq text size
-escape sequences are all discarded in mandoc.
-.It
-The
-.Sq \ef
-scaling unit is accepted by mandoc, but rendered as the default unit.
-.It
-The
-.Sx \&sp
-macro does not accept negative values in mandoc.
-In GNU troff, this would result in strange behaviour.
-.It
In page header lines, GNU troff versions up to and including 1.21
only print
.Ar volume
@@ -919,8 +910,13 @@ is given, like in
.El
.Pp
The
-.Sx OP
-macro is part of the extended
+.Sx EE ,
+.Sx EX ,
+.Sx OP ,
+.Sx UE ,
+and
+.Sx UR
+macros are part of the GNU extended
.Nm
macro set, and may not be portable to non-GNU troff implementations.
.Sh SEE ALSO
diff --git a/contrib/mdocml/man.c b/contrib/mdocml/man.c
index e6e1c2899209..75a6350577ba 100644
--- a/contrib/mdocml/man.c
+++ b/contrib/mdocml/man.c
@@ -1,6 +1,8 @@
-/* $Id: man.c,v 1.121 2013/11/10 22:54:40 schwarze Exp $ */
+/* $Id: man.c,v 1.137 2014/08/01 21:24:17 schwarze Exp $ */
/*
* Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons
+ * Copyright (c) 2013, 2014 Ingo Schwarze
+ * Copyright (c) 2011 Joerg Sonnenberger
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -21,6 +23,7 @@
#include
#include
+#include
#include
#include
#include
@@ -28,12 +31,13 @@
#include "man.h"
#include "mandoc.h"
+#include "mandoc_aux.h"
#include "libman.h"
#include "libmandoc.h"
-const char *const __man_macronames[MAN_MAX] = {
+const char *const __man_macronames[MAN_MAX] = {
"br", "TH", "SH", "SS",
- "TP", "LP", "PP", "P",
+ "TP", "LP", "PP", "P",
"IP", "HP", "SM", "SB",
"BI", "IB", "BR", "RB",
"R", "B", "I", "IR",
@@ -41,17 +45,17 @@ const char *const __man_macronames[MAN_MAX] = {
"fi", "RE", "RS", "DT",
"UC", "PD", "AT", "in",
"ft", "OP", "EX", "EE",
- "UR", "UE"
+ "UR", "UE", "ll"
};
const char * const *man_macronames = __man_macronames;
-static struct man_node *man_node_alloc(struct man *, int, int,
+static struct man_node *man_node_alloc(struct man *, int, int,
enum man_type, enum mant);
-static int man_node_append(struct man *,
+static int man_node_append(struct man *,
struct man_node *);
static void man_node_free(struct man_node *);
-static void man_node_unlink(struct man *,
+static void man_node_unlink(struct man *,
struct man_node *);
static int man_ptext(struct man *, int, char *, int);
static int man_pmacro(struct man *, int, char *, int);
@@ -64,20 +68,16 @@ const struct man_node *
man_node(const struct man *man)
{
- assert( ! (MAN_HALT & man->flags));
return(man->first);
}
-
const struct man_meta *
man_meta(const struct man *man)
{
- assert( ! (MAN_HALT & man->flags));
return(&man->meta);
}
-
void
man_reset(struct man *man)
{
@@ -86,7 +86,6 @@ man_reset(struct man *man)
man_alloc1(man);
}
-
void
man_free(struct man *man)
{
@@ -95,9 +94,8 @@ man_free(struct man *man)
free(man);
}
-
struct man *
-man_alloc(struct roff *roff, struct mparse *parse)
+man_alloc(struct roff *roff, struct mparse *parse, int quick)
{
struct man *p;
@@ -105,39 +103,31 @@ man_alloc(struct roff *roff, struct mparse *parse)
man_hash_init();
p->parse = parse;
+ p->quick = quick;
p->roff = roff;
man_alloc1(p);
return(p);
}
-
int
man_endparse(struct man *man)
{
- assert( ! (MAN_HALT & man->flags));
- if (man_macroend(man))
- return(1);
- man->flags |= MAN_HALT;
- return(0);
+ return(man_macroend(man));
}
-
int
man_parseln(struct man *man, int ln, char *buf, int offs)
{
man->flags |= MAN_NEWLINE;
- assert( ! (MAN_HALT & man->flags));
-
return (roff_getcontrol(man->roff, buf, &offs) ?
- man_pmacro(man, ln, buf, offs) :
- man_ptext(man, ln, buf, offs));
+ man_pmacro(man, ln, buf, offs) :
+ man_ptext(man, ln, buf, offs));
}
-
static void
man_free1(struct man *man)
{
@@ -156,7 +146,6 @@ man_free1(struct man *man)
free(man->meta.msec);
}
-
static void
man_alloc1(struct man *man)
{
@@ -180,12 +169,12 @@ man_node_append(struct man *man, struct man_node *p)
assert(MAN_ROOT != p->type);
switch (man->next) {
- case (MAN_NEXT_SIBLING):
+ case MAN_NEXT_SIBLING:
man->last->next = p;
p->prev = man->last;
p->parent = man->last->parent;
break;
- case (MAN_NEXT_CHILD):
+ case MAN_NEXT_CHILD:
man->last->child = p;
p->parent = man->last;
break;
@@ -193,23 +182,24 @@ man_node_append(struct man *man, struct man_node *p)
abort();
/* NOTREACHED */
}
-
+
assert(p->parent);
p->parent->nchild++;
- if ( ! man_valid_pre(man, p))
- return(0);
-
switch (p->type) {
- case (MAN_HEAD):
+ case MAN_BLOCK:
+ if (p->tok == MAN_SH || p->tok == MAN_SS)
+ man->flags &= ~MAN_LITERAL;
+ break;
+ case MAN_HEAD:
assert(MAN_BLOCK == p->parent->type);
p->parent->head = p;
break;
- case (MAN_TAIL):
+ case MAN_TAIL:
assert(MAN_BLOCK == p->parent->type);
p->parent->tail = p;
break;
- case (MAN_BODY):
+ case MAN_BODY:
assert(MAN_BLOCK == p->parent->type);
p->parent->body = p;
break;
@@ -220,9 +210,9 @@ man_node_append(struct man *man, struct man_node *p)
man->last = p;
switch (p->type) {
- case (MAN_TBL):
+ case MAN_TBL:
/* FALLTHROUGH */
- case (MAN_TEXT):
+ case MAN_TEXT:
if ( ! man_valid_post(man))
return(0);
break;
@@ -233,9 +223,8 @@ man_node_append(struct man *man, struct man_node *p)
return(1);
}
-
static struct man_node *
-man_node_alloc(struct man *man, int line, int pos,
+man_node_alloc(struct man *man, int line, int pos,
enum man_type type, enum mant tok)
{
struct man_node *p;
@@ -252,7 +241,6 @@ man_node_alloc(struct man *man, int line, int pos,
return(p);
}
-
int
man_elem_alloc(struct man *man, int line, int pos, enum mant tok)
{
@@ -265,7 +253,6 @@ man_elem_alloc(struct man *man, int line, int pos, enum mant tok)
return(1);
}
-
int
man_tail_alloc(struct man *man, int line, int pos, enum mant tok)
{
@@ -278,7 +265,6 @@ man_tail_alloc(struct man *man, int line, int pos, enum mant tok)
return(1);
}
-
int
man_head_alloc(struct man *man, int line, int pos, enum mant tok)
{
@@ -291,7 +277,6 @@ man_head_alloc(struct man *man, int line, int pos, enum mant tok)
return(1);
}
-
int
man_body_alloc(struct man *man, int line, int pos, enum mant tok)
{
@@ -304,7 +289,6 @@ man_body_alloc(struct man *man, int line, int pos, enum mant tok)
return(1);
}
-
int
man_block_alloc(struct man *man, int line, int pos, enum mant tok)
{
@@ -332,7 +316,6 @@ man_word_alloc(struct man *man, int line, int pos, const char *word)
return(1);
}
-
/*
* Free all of the resources held by a node. This does NOT unlink a
* node from its context; for that, see man_node_unlink().
@@ -346,7 +329,6 @@ man_node_free(struct man_node *p)
free(p);
}
-
void
man_node_delete(struct man *man, struct man_node *p)
{
@@ -363,8 +345,6 @@ man_addeqn(struct man *man, const struct eqn *ep)
{
struct man_node *n;
- assert( ! (MAN_HALT & man->flags));
-
n = man_node_alloc(man, ep->ln, ep->pos, MAN_EQN, MAN_MAX);
n->eqn = ep;
@@ -380,8 +360,6 @@ man_addspan(struct man *man, const struct tbl_span *sp)
{
struct man_node *n;
- assert( ! (MAN_HALT & man->flags));
-
n = man_node_alloc(man, sp->line, 0, MAN_TBL, MAN_MAX);
n->span = sp;
@@ -403,7 +381,7 @@ man_descope(struct man *man, int line, int offs)
if (MAN_ELINE & man->flags) {
man->flags &= ~MAN_ELINE;
- if ( ! man_unscope(man, man->last->parent, MANDOCERR_MAX))
+ if ( ! man_unscope(man, man->last->parent))
return(0);
}
@@ -411,7 +389,7 @@ man_descope(struct man *man, int line, int offs)
return(1);
man->flags &= ~MAN_BLINE;
- if ( ! man_unscope(man, man->last->parent, MANDOCERR_MAX))
+ if ( ! man_unscope(man, man->last->parent))
return(0);
return(man_body_alloc(man, line, offs, man->last->tok));
}
@@ -448,9 +426,9 @@ man_ptext(struct man *man, int line, char *buf, int offs)
return(1);
}
- /*
+ /*
* Warn if the last un-escaped character is whitespace. Then
- * strip away the remaining spaces (tabs stay!).
+ * strip away the remaining spaces (tabs stay!).
*/
i = (int)strlen(buf);
@@ -458,7 +436,8 @@ man_ptext(struct man *man, int line, char *buf, int offs)
if (' ' == buf[i - 1] || '\t' == buf[i - 1]) {
if (i > 1 && '\\' != buf[i - 2])
- man_pmsg(man, line, i - 1, MANDOCERR_EOLNSPACE);
+ mandoc_msg(MANDOCERR_SPACE_EOL, man->parse,
+ line, i - 1, NULL);
for (--i; i && ' ' == buf[i]; i--)
/* Spin back to non-space. */ ;
@@ -479,7 +458,7 @@ man_ptext(struct man *man, int line, char *buf, int offs)
*/
assert(i);
- if (mandoc_eos(buf, (size_t)i, 0))
+ if (mandoc_eos(buf, (size_t)i))
man->last->flags |= MAN_EOS;
return(man_descope(man, line, offs));
@@ -488,13 +467,15 @@ man_ptext(struct man *man, int line, char *buf, int offs)
static int
man_pmacro(struct man *man, int ln, char *buf, int offs)
{
- int i, ppos;
- enum mant tok;
char mac[5];
struct man_node *n;
+ enum mant tok;
+ int i, ppos;
+ int bline;
if ('"' == buf[offs]) {
- man_pmsg(man, ln, offs, MANDOCERR_BADCOMMENT);
+ mandoc_msg(MANDOCERR_COMMENT_BAD, man->parse,
+ ln, offs, NULL);
return(1);
} else if ('\0' == buf[offs])
return(1);
@@ -507,8 +488,8 @@ man_pmacro(struct man *man, int ln, char *buf, int offs)
*/
i = 0;
- while (i < 4 && '\0' != buf[offs] &&
- ' ' != buf[offs] && '\t' != buf[offs])
+ while (i < 4 && '\0' != buf[offs] && ' ' != buf[offs] &&
+ '\t' != buf[offs])
mac[i++] = buf[offs++];
mac[i] = '\0';
@@ -516,8 +497,8 @@ man_pmacro(struct man *man, int ln, char *buf, int offs)
tok = (i > 0 && i < 4) ? man_hash_find(mac) : MAN_MAX;
if (MAN_MAX == tok) {
- mandoc_vmsg(MANDOCERR_MACRO, man->parse, ln,
- ppos, "%s", buf + ppos - 1);
+ mandoc_msg(MANDOCERR_MACRO, man->parse,
+ ln, ppos, buf + ppos - 1);
return(1);
}
@@ -526,15 +507,16 @@ man_pmacro(struct man *man, int ln, char *buf, int offs)
while (buf[offs] && ' ' == buf[offs])
offs++;
- /*
+ /*
* Trailing whitespace. Note that tabs are allowed to be passed
* into the parser as "text", so we only warn about spaces here.
*/
if ('\0' == buf[offs] && ' ' == buf[offs - 1])
- man_pmsg(man, ln, offs - 1, MANDOCERR_EOLNSPACE);
+ mandoc_msg(MANDOCERR_SPACE_EOL, man->parse,
+ ln, offs - 1, NULL);
- /*
+ /*
* Remove prior ELINE macro, as it's being clobbered by a new
* macro. Note that NSCOPED macros do not close out ELINE
* macros---they don't print text---so we let those slip by.
@@ -550,7 +532,7 @@ man_pmacro(struct man *man, int ln, char *buf, int offs)
if (MAN_NSCOPED & man_macros[n->tok].flags)
n = n->parent;
- mandoc_vmsg(MANDOCERR_LINESCOPE, man->parse, n->line,
+ mandoc_vmsg(MANDOCERR_BLK_LINE, man->parse, n->line,
n->pos, "%s breaks %s", man_macronames[tok],
man_macronames[n->tok]);
@@ -581,7 +563,7 @@ man_pmacro(struct man *man, int ln, char *buf, int offs)
assert(MAN_BLOCK == n->type);
assert(MAN_SCOPED & man_macros[n->tok].flags);
- mandoc_vmsg(MANDOCERR_LINESCOPE, man->parse, n->line,
+ mandoc_vmsg(MANDOCERR_BLK_LINE, man->parse, n->line,
n->pos, "%s breaks %s", man_macronames[tok],
man_macronames[n->tok]);
@@ -589,63 +571,41 @@ man_pmacro(struct man *man, int ln, char *buf, int offs)
man->flags &= ~MAN_BLINE;
}
- /*
- * Save the fact that we're in the next-line for a block. In
- * this way, embedded roff instructions can "remember" state
- * when they exit.
- */
+ /* Remember whether we are in next-line scope for a block head. */
- if (MAN_BLINE & man->flags)
- man->flags |= MAN_BPLINE;
+ bline = man->flags & MAN_BLINE;
/* Call to handler... */
assert(man_macros[tok].fp);
if ( ! (*man_macros[tok].fp)(man, tok, ln, ppos, &offs, buf))
- goto err;
+ return(0);
- /*
- * We weren't in a block-line scope when entering the
- * above-parsed macro, so return.
- */
+ /* In quick mode (for mandocdb), abort after the NAME section. */
- if ( ! (MAN_BPLINE & man->flags)) {
- man->flags &= ~MAN_ILINE;
- return(1);
+ if (man->quick && MAN_SH == tok) {
+ n = man->last;
+ if (MAN_BODY == n->type &&
+ strcmp(n->prev->child->string, "NAME"))
+ return(2);
}
- man->flags &= ~MAN_BPLINE;
/*
- * If we're in a block scope, then allow this macro to slip by
- * without closing scope around it.
+ * If we are in a next-line scope for a block head,
+ * close it out now and switch to the body,
+ * unless the next-line scope is allowed to continue.
*/
- if (MAN_ILINE & man->flags) {
- man->flags &= ~MAN_ILINE;
+ if ( ! bline || man->flags & MAN_ELINE ||
+ man_macros[tok].flags & MAN_NSCOPED)
return(1);
- }
-
- /*
- * If we've opened a new next-line element scope, then return
- * now, as the next line will close out the block scope.
- */
-
- if (MAN_ELINE & man->flags)
- return(1);
-
- /* Close out the block scope opened in the prior line. */
assert(MAN_BLINE & man->flags);
man->flags &= ~MAN_BLINE;
- if ( ! man_unscope(man, man->last->parent, MANDOCERR_MAX))
+ if ( ! man_unscope(man, man->last->parent))
return(0);
return(man_body_alloc(man, ln, ppos, man->last->tok));
-
-err: /* Error out. */
-
- man->flags |= MAN_HALT;
- return(0);
}
/*
@@ -696,3 +656,49 @@ man_mparse(const struct man *man)
assert(man && man->parse);
return(man->parse);
}
+
+void
+man_deroff(char **dest, const struct man_node *n)
+{
+ char *cp;
+ size_t sz;
+
+ if (MAN_TEXT != n->type) {
+ for (n = n->child; n; n = n->next)
+ man_deroff(dest, n);
+ return;
+ }
+
+ /* Skip leading whitespace and escape sequences. */
+
+ cp = n->string;
+ while ('\0' != *cp) {
+ if ('\\' == *cp) {
+ cp++;
+ mandoc_escape((const char **)&cp, NULL, NULL);
+ } else if (isspace((unsigned char)*cp))
+ cp++;
+ else
+ break;
+ }
+
+ /* Skip trailing whitespace. */
+
+ for (sz = strlen(cp); sz; sz--)
+ if (0 == isspace((unsigned char)cp[sz-1]))
+ break;
+
+ /* Skip empty strings. */
+
+ if (0 == sz)
+ return;
+
+ if (NULL == *dest) {
+ *dest = mandoc_strndup(cp, sz);
+ return;
+ }
+
+ mandoc_asprintf(&cp, "%s %*s", *dest, (int)sz, cp);
+ free(*dest);
+ *dest = cp;
+}
diff --git a/contrib/mdocml/man.cgi.8 b/contrib/mdocml/man.cgi.8
new file mode 100644
index 000000000000..69335e632ec9
--- /dev/null
+++ b/contrib/mdocml/man.cgi.8
@@ -0,0 +1,409 @@
+.\" $Id: man.cgi.8,v 1.9 2014/07/22 18:14:13 schwarze Exp $
+.\"
+.\" Copyright (c) 2014 Ingo Schwarze
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: July 22 2014 $
+.Dt MAN.CGI 8
+.Os
+.Sh NAME
+.Nm man.cgi
+.Nd CGI program to search and display manual pages
+.Sh DESCRIPTION
+The
+.Nm
+CGI program searches for manual pages on a WWW server
+and displays them to HTTP clients,
+providing functionality equivalent to the
+.Xr apropos 1
+and
+.Xr man 1
+utilities.
+It can use multiple manual trees in parallel.
+.Ss HTML search interface
+At the top of each generated HTML page,
+.Nm
+displays a search form containing these elements:
+.Bl -enum
+.It
+An input box for search queries, expecting
+either a name of a manual page or an
+.Ar expression
+using the syntax described in the
+.Xr apropos 1
+manual; filling this in is required for each search.
+.It
+A
+.Dq Submit
+button to send a search request from the client to the server.
+.It
+A
+.Dq Reset
+button to undo any changes to the input boxes and the dropdown menus
+and reset them to the values contained in the
+.Ev QUERY_STRING .
+.It
+Radio buttons to select pages either by name like in
+.Xr man 1
+or using
+.Xr apropos 1
+queries.
+.It
+A dropdown menu to optionally select a manual section.
+If one is provided, it has the same effect as the
+.Xr man 1
+and
+.Xr apropos 1
+.Fl s
+option.
+Otherwise, pages from all sections are shown.
+.It
+A dropdown menu to optionally select an architecture.
+If one is provided, it has the same effect as the
+.Xr man 1
+and
+.Xr apropos 1
+.Fl S
+option.
+By default, pages for all architectures are shown.
+.It
+A dropdown menu to select a manual tree.
+If the configuration file
+.Pa /var/www/man/manpath.conf
+contains only one manpath, the dropdown menu is not shown.
+By default, the first manpath given in the file is used.
+.El
+.Ss Program output
+The
+.Nm
+program generates five kinds of output pages:
+.Bl -tag -width Ds
+.It The index page.
+This is returned when calling
+.Nm
+without
+.Ev PATH_INFO
+and without a
+.Ev QUERY_STRING .
+It serves as a starting point for using the program
+and shows the search form only.
+.It A list page.
+Lists are returned when searches match more than one manual page.
+The first column shows the names and section numbers of manuals
+as clickable links.
+The second column shows the one-line descriptions of the manuals.
+.It A manual page.
+This output format is used when a search matches exactly one
+manual page, or when a link on a list page or an
+.Ic \&Xr
+link on another manual page is followed.
+.It A no-result page.
+This is shown when a search request returns no results -
+eiher because it violates the query syntax, or because
+the search does not match any manual pages.
+.It \&An error page.
+This cannot happen by merely clicking the
+.Dq Search
+button, but only by manually entering an invalid URI.
+It does not show the search form, but only an error message
+and a link back to the index page.
+.El
+.Ss Setup
+For each manual tree, create one first-level subdirectory below
+.Pa /var/www/man .
+The name of one of these directories is called a
+.Dq manpath
+in the context of
+.Nm .
+Create a single ASCII text file
+.Pa /var/www/man/manpath.conf
+containing the names of these directories, one per line.
+The directory given first is used as the default manpath.
+.Pp
+Inside each of these directories, use the same directory and file
+structure as found below
+.Pa /usr/share/man ,
+that is, second-level subdirectories
+.Pa /var/www/man/*/man1 , /var/www/man/*/man2
+etc. containing source
+.Xr mdoc 7
+and
+.Xr man 7
+manuals with file name extensions matching the section numbers,
+second-level subdirectories
+.Pa /var/www/man/*/cat1 , /var/www/man/*/cat2
+etc. containing preformatted manuals with the file name extension
+.Sq 0 ,
+and optional third-level subdirectories for architectures.
+Use
+.Xr makewhatis 8
+to create a
+.Xr mandoc.db 5
+database inside each manpath.
+.Pp
+Configure your web server to execute CGI programs located in
+.Pa /cgi-bin .
+When using
+.Xr nginx 8 ,
+the
+.Xr slowcgi 8
+proxy daemon is needed to translate FastCGI requests to plain old CGI.
+.Pp
+To compile
+.Nm ,
+first copy
+.Pa cgi.h.example
+to
+.Pa cgi.h
+and edit it according to your needs.
+It contains the following compile-time definitions:
+.Bl -tag -width Ds
+.It Ev COMPAT_OLDURI
+Only useful for running on www.openbsd.org to deal with old URIs containing
+.Qq "manpath=OpenBSD "
+where the blank character has to be translated to a hyphen.
+When compiling for other sites, this definition can be deleted.
+.It Ev CSS_DIR
+An optional path to the directory containing the CSS files,
+to be specified relative to the server's document root,
+and to be specified without a trailing slash.
+When not specified, the CSS files
+are assumed to be in the document root.
+This is used in generated HTML code.
+.It Ev CUSTOMIZE_BEGIN
+A HTML string to be inserted right after opening the
+.Aq BODY
+element.
+.It Ev CUSTOMIZE_TITLE
+An ASCII string to be used for the HTML
+.Aq TITLE
+element.
+.It Ev HTTP_HOST
+The FQDN of the (possibly virtual) host the HTTP server is running on.
+This is used for
+.Ic Location:
+headers in HTTP 303 responses.
+.It Ev MAN_DIR
+A path to the
+.Nm
+data directory to be used instead of
+.Pa /var/www/man ,
+relative to the web server
+.Xr chroot 2
+directory, to be specified without a trailing slash.
+This is prepended to the manpath when opening
+.Xr mandoc.db 5
+and manual page files.
+.El
+.Pp
+After editing
+.Pa cgi.h ,
+run
+.Pp
+.Dl make man.cgi
+.Pp
+and copy the files to the proper locations.
+Reading the
+.Cm installcgi
+target in the
+.Pa Makefile
+can help with that, but do not run it without carefully checking it
+because the directory layouts of web servers vary greatly.
+.Ss URI interface
+.Nm
+uniform resource identifiers are not needed for interactive use,
+but can be useful for deep linking.
+They consist of:
+.Bl -enum
+.It
+The
+.Cm http://
+protocol specifier.
+.It
+The host name and a following slash.
+.It
+The path to the program, normally
+.Pa cgi-bin/man.cgi/ .
+.It
+To show a single page, a slash, the manpath, another slash,
+and the name of the requested file, for example
+.Pa /OpenBSD-current/man1/mandoc.1 .
+.It
+For searches, a query string starting with a question mark
+and consisting of
+.Ar key Ns = Ns Ar value
+pairs, separated by ampersands, for example
+.Pa ?manpath=OpenBSD-current&query=mandoc .
+Supported keys are
+.Cm manpath ,
+.Cm query ,
+.Cm sec ,
+.Cm arch ,
+corresponding to
+.Xr apropos 1
+.Fl M ,
+.Ar expression ,
+.Fl s ,
+.Fl S ,
+respectively, and
+.Cm apropos ,
+which is a boolean parameter to select or deselect the
+.Xr apropos 1
+query mode.
+For backward compatibility with the traditional
+.Nm ,
+.Cm sektion
+is supported as an alias for
+.Cm sec .
+.El
+.Ss Restricted character set
+For security reasons, in particular to prevent cross site scripting
+attacks, some strings used by
+.Nm
+can only contain the following characters:
+.Pp
+.Bl -dash -compact -offset indent
+.It
+lower case and upper case ASCII letters
+.It
+the ten decimal digits
+.It
+the dash
+.Pq Sq -
+.It
+the dot
+.Pq Sq \&.
+.It
+the slash
+.Pq Sq /
+.It
+the underscore
+.Pq Sq _
+.El
+.Pp
+In particular, this applies to the
+.Ev SCRIPT_NAME ,
+to all manpaths, and to all architecture names.
+.Sh ENVIRONMENT
+The web server may pass the following CGI variables to
+.Nm :
+.Bl -tag -width Ds
+.It Ev PATH_INFO
+The final part of the URI path passed from the client to the server,
+starting after the
+.Ev SCRIPT_NAME
+and ending before the
+.Ev QUERY_STRING .
+It is used by the
+.Cm show
+page to aquire the manpath and filename it needs.
+.It Ev QUERY_STRING
+The HTTP query string passed from the client to the server.
+It is the final part of the URI, after the question mark.
+It is used by the
+.Cm search
+page to acquire the named parameters it needs.
+.It Ev SCRIPT_NAME
+The path to the
+.Nm
+binary relative to the server root, usually
+.Pa /cgi-bin/man.cgi .
+This is used for generating URIs to be embedded
+in generated HTML code and HTTP headers.
+If this contains any character not contained in the
+.Sx Restricted character set ,
+.Nm
+reports an internal server error and exits without doing anything.
+.El
+.Sh FILES
+.Bl -tag -width Ds
+.It Pa /var/www
+Default web server
+.Xr chroot 2
+directory.
+All the following paths are specified relative to this directory.
+.It Pa /cgi-bin/man.cgi
+The path to the
+.Nm
+program relative to the server root.
+Can be overridden by
+.Ev SCRIPT_NAME .
+.It Pa /htdocs
+The path to the server document root relative to the server root.
+This is part of the web server configuration and not specific to
+.Nm .
+.It Pa /htdocs/man-cgi.css
+A style sheet for general
+.Nm
+styling, referenced from each generated HTML page.
+.It Pa /htdocs/man.css
+A style sheet for
+.Xr mandoc 1
+HTML styling, referenced from each generated HTML page after
+.Pa man-cgi.css .
+.It Pa /man
+Default
+.Nm
+data directory containing all the manual trees.
+Can be overridden by
+.Ev MAN_DIR .
+.It Pa /man/mandoc/man1/apropos.1 , /man/mandoc/man8/man.cgi.8
+Manual pages documenting
+.Nm
+itself, linked from the index page.
+.It Pa /man/manpath.conf
+The list of available manpaths, one per line.
+If any of the lines in this file contains a slash
+.Pq Sq /
+or any character not contained in the
+.Sx Restricted character set ,
+.Nm
+reports an internal server error and exits without doing anything.
+.It Pa /man/OpenBSD-current/man1/mandoc.1
+An example
+.Xr mdoc 7
+source file located below the
+.Dq OpenBSD-current
+manpath.
+.El
+.Sh COMPATIBILITY
+The
+.Nm
+CGI program is call-compatible with queries from the traditional
+.Pa man.cgi
+script by Wolfram Schneider.
+However, the output may not be quite the same.
+.Sh SEE ALSO
+.Xr apropos 1 ,
+.Xr mandoc.db 5 ,
+.Xr makewhatis 8 ,
+.Xr slowcgi 8
+.Sh HISTORY
+A version of
+.Nm
+based on
+.Xr mandoc 1
+first appeared in mdocml-1.12.1 (March 2012).
+The current SQLite3-based version first appeared in
+.Ox 5.6 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+program was written by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
+and ported to the SQLite3-based
+.Xr mandoc.db 5
+backend by
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org .
diff --git a/contrib/mdocml/man.h b/contrib/mdocml/man.h
index ef9480f2768c..aa80b6732eae 100644
--- a/contrib/mdocml/man.h
+++ b/contrib/mdocml/man.h
@@ -1,6 +1,7 @@
-/* $Id: man.h,v 1.62 2013/10/17 20:54:58 schwarze Exp $ */
+/* $Id: man.h,v 1.65 2014/06/20 23:02:31 schwarze Exp $ */
/*
* Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons
+ * Copyright (c) 2014 Ingo Schwarze
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -56,6 +57,7 @@ enum mant {
MAN_EE,
MAN_UR,
MAN_UE,
+ MAN_ll,
MAN_MAX
};
@@ -77,6 +79,7 @@ struct man_meta {
char *vol; /* `TH' volume */
char *title; /* `TH' title (e.g., FOO) */
char *source; /* `TH' source (e.g., GNU) */
+ int hasbody; /* document is not empty */
};
struct man_node {
@@ -111,6 +114,7 @@ struct man;
const struct man_node *man_node(const struct man *);
const struct man_meta *man_meta(const struct man *);
const struct mparse *man_mparse(const struct man *);
+void man_deroff(char **, const struct man_node *);
__END_DECLS
diff --git a/contrib/mdocml/man_hash.c b/contrib/mdocml/man_hash.c
index 86c5c40a199b..ab887226b5de 100644
--- a/contrib/mdocml/man_hash.c
+++ b/contrib/mdocml/man_hash.c
@@ -1,4 +1,4 @@
-/* $Id: man_hash.c,v 1.25 2011/07/24 18:15:14 kristaps Exp $ */
+/* $Id: man_hash.c,v 1.27 2014/04/20 16:46:04 schwarze Exp $ */
/*
* Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons
*
@@ -23,7 +23,6 @@
#include
#include
#include
-#include
#include
#include "man.h"
@@ -49,6 +48,7 @@
*/
static unsigned char table[26 * HASH_DEPTH];
+
/*
* XXX - this hash has global scope, so if intended for use as a library
* with multiple callers, it will need re-invocation protection.
@@ -60,8 +60,7 @@ man_hash_init(void)
memset(table, UCHAR_MAX, sizeof(table));
- assert(/* LINTED */
- MAN_MAX < UCHAR_MAX);
+ assert(MAN_MAX < UCHAR_MAX);
for (i = 0; i < (int)MAN_MAX; i++) {
x = man_macronames[i][0];
@@ -80,7 +79,6 @@ man_hash_init(void)
}
}
-
enum mant
man_hash_find(const char *tmp)
{
diff --git a/contrib/mdocml/man_html.c b/contrib/mdocml/man_html.c
index 2c4e220a1181..9689cc261e5f 100644
--- a/contrib/mdocml/man_html.c
+++ b/contrib/mdocml/man_html.c
@@ -1,7 +1,7 @@
-/* $Id: man_html.c,v 1.90 2013/10/17 20:54:58 schwarze Exp $ */
+/* $Id: man_html.c,v 1.96 2014/08/01 19:25:52 schwarze Exp $ */
/*
* Copyright (c) 2008-2012 Kristaps Dzonsons
- * Copyright (c) 2013 Ingo Schwarze
+ * Copyright (c) 2013, 2014 Ingo Schwarze
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -28,6 +28,7 @@
#include
#include "mandoc.h"
+#include "mandoc_aux.h"
#include "out.h"
#include "html.h"
#include "man.h"
@@ -53,7 +54,7 @@ struct htmlman {
int (*post)(MAN_ARGS);
};
-static void print_bvspace(struct html *,
+static void print_bvspace(struct html *,
const struct man_node *);
static void print_man(MAN_ARGS);
static void print_man_head(MAN_ARGS);
@@ -90,7 +91,7 @@ static const struct htmlman mans[MAN_MAX] = {
{ man_PP_pre, NULL }, /* PP */
{ man_PP_pre, NULL }, /* P */
{ man_IP_pre, NULL }, /* IP */
- { man_HP_pre, NULL }, /* HP */
+ { man_HP_pre, NULL }, /* HP */
{ man_SM_pre, NULL }, /* SM */
{ man_SM_pre, NULL }, /* SB */
{ man_alt_pre, NULL }, /* BI */
@@ -119,8 +120,10 @@ static const struct htmlman mans[MAN_MAX] = {
{ man_literal_pre, NULL }, /* EE */
{ man_UR_pre, NULL }, /* UR */
{ NULL, NULL }, /* UE */
+ { man_ign_pre, NULL }, /* ll */
};
+
/*
* Printing leading vertical space before a block.
* This is used for the paragraph macros.
@@ -155,7 +158,7 @@ html_man(void *arg, const struct man *man)
}
static void
-print_man(MAN_ARGS)
+print_man(MAN_ARGS)
{
struct tag *t, *tt;
struct htmlpair tag;
@@ -170,15 +173,13 @@ print_man(MAN_ARGS)
print_tagq(h, tt);
print_otag(h, TAG_BODY, 0, NULL);
print_otag(h, TAG_DIV, 1, &tag);
- } else
+ } else
t = print_otag(h, TAG_DIV, 1, &tag);
print_man_nodelist(man, n, mh, h);
print_tagq(h, t);
}
-
-/* ARGSUSED */
static void
print_man_head(MAN_ARGS)
{
@@ -191,7 +192,6 @@ print_man_head(MAN_ARGS)
print_text(h, h->buf);
}
-
static void
print_man_nodelist(MAN_ARGS)
{
@@ -201,7 +201,6 @@ print_man_nodelist(MAN_ARGS)
print_man_nodelist(man, n->next, mh, h);
}
-
static void
print_man_node(MAN_ARGS)
{
@@ -212,10 +211,10 @@ print_man_node(MAN_ARGS)
t = h->tags.head;
switch (n->type) {
- case (MAN_ROOT):
+ case MAN_ROOT:
man_root_pre(man, n, mh, h);
break;
- case (MAN_TEXT):
+ case MAN_TEXT:
/*
* If we have a blank line, output a vertical space.
* If we have a space as the first character, break
@@ -233,10 +232,10 @@ print_man_node(MAN_ARGS)
print_text(h, n->string);
return;
- case (MAN_EQN):
+ case MAN_EQN:
print_eqn(h, n->eqn);
break;
- case (MAN_TBL):
+ case MAN_TBL:
/*
* This will take care of initialising all of the table
* state data for the first table, then tearing it down
@@ -245,7 +244,7 @@ print_man_node(MAN_ARGS)
print_tbl(h, n->span);
return;
default:
- /*
+ /*
* Close out scope of font prior to opening a macro
* scope.
*/
@@ -275,10 +274,10 @@ print_man_node(MAN_ARGS)
print_stagq(h, t);
switch (n->type) {
- case (MAN_ROOT):
+ case MAN_ROOT:
man_root_post(man, n, mh, h);
break;
- case (MAN_EQN):
+ case MAN_EQN:
break;
default:
if (mans[n->tok].post)
@@ -287,7 +286,6 @@ print_man_node(MAN_ARGS)
}
}
-
static int
a2width(const struct man_node *n, struct roffsu *su)
{
@@ -300,22 +298,16 @@ a2width(const struct man_node *n, struct roffsu *su)
return(0);
}
-
-/* ARGSUSED */
static void
man_root_pre(MAN_ARGS)
{
struct htmlpair tag[3];
struct tag *t, *tt;
- char b[BUFSIZ], title[BUFSIZ];
-
- b[0] = 0;
- if (man->vol)
- (void)strlcat(b, man->vol, BUFSIZ);
+ char *title;
assert(man->title);
assert(man->msec);
- snprintf(title, BUFSIZ - 1, "%s(%s)", man->title, man->msec);
+ mandoc_asprintf(&title, "%s(%s)", man->title, man->msec);
PAIR_SUMMARY_INIT(&tag[0], "Document Header");
PAIR_CLASS_INIT(&tag[1], "head");
@@ -338,7 +330,8 @@ man_root_pre(MAN_ARGS)
PAIR_CLASS_INIT(&tag[0], "head-vol");
PAIR_INIT(&tag[1], ATTR_ALIGN, "center");
print_otag(h, TAG_TD, 2, tag);
- print_text(h, b);
+ if (NULL != man->vol)
+ print_text(h, man->vol);
print_stagq(h, tt);
PAIR_CLASS_INIT(&tag[0], "head-rtitle");
@@ -346,10 +339,9 @@ man_root_pre(MAN_ARGS)
print_otag(h, TAG_TD, 2, tag);
print_text(h, title);
print_tagq(h, t);
+ free(title);
}
-
-/* ARGSUSED */
static void
man_root_post(MAN_ARGS)
{
@@ -383,7 +375,6 @@ man_root_post(MAN_ARGS)
}
-/* ARGSUSED */
static int
man_br_pre(MAN_ARGS)
{
@@ -397,7 +388,7 @@ man_br_pre(MAN_ARGS)
if ( ! a2roffsu(n->string, &su, SCALE_VS))
SCALE_VS_INIT(&su, atoi(n->string));
} else
- su.scale = 0;
+ su.scale = 0.0;
bufinit(h);
bufcat_su(h, "height", &su);
@@ -410,7 +401,6 @@ man_br_pre(MAN_ARGS)
return(0);
}
-/* ARGSUSED */
static int
man_SH_pre(MAN_ARGS)
{
@@ -428,7 +418,6 @@ man_SH_pre(MAN_ARGS)
return(1);
}
-/* ARGSUSED */
static int
man_alt_pre(MAN_ARGS)
{
@@ -437,7 +426,7 @@ man_alt_pre(MAN_ARGS)
enum htmltag fp;
struct tag *t;
- if ((savelit = mh->fl & MANH_LITERAL))
+ if ((savelit = mh->fl & MANH_LITERAL))
print_otag(h, TAG_BR, 0, NULL);
mh->fl &= ~MANH_LITERAL;
@@ -445,22 +434,22 @@ man_alt_pre(MAN_ARGS)
for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
t = NULL;
switch (n->tok) {
- case (MAN_BI):
+ case MAN_BI:
fp = i % 2 ? TAG_I : TAG_B;
break;
- case (MAN_IB):
+ case MAN_IB:
fp = i % 2 ? TAG_B : TAG_I;
break;
- case (MAN_RI):
+ case MAN_RI:
fp = i % 2 ? TAG_I : TAG_MAX;
break;
- case (MAN_IR):
+ case MAN_IR:
fp = i % 2 ? TAG_MAX : TAG_I;
break;
- case (MAN_BR):
+ case MAN_BR:
fp = i % 2 ? TAG_MAX : TAG_B;
break;
- case (MAN_RB):
+ case MAN_RB:
fp = i % 2 ? TAG_B : TAG_MAX;
break;
default:
@@ -486,18 +475,16 @@ man_alt_pre(MAN_ARGS)
return(0);
}
-/* ARGSUSED */
static int
man_SM_pre(MAN_ARGS)
{
-
+
print_otag(h, TAG_SMALL, 0, NULL);
if (MAN_SB == n->tok)
print_otag(h, TAG_B, 0, NULL);
return(1);
}
-/* ARGSUSED */
static int
man_SS_pre(MAN_ARGS)
{
@@ -515,7 +502,6 @@ man_SS_pre(MAN_ARGS)
return(1);
}
-/* ARGSUSED */
static int
man_PP_pre(MAN_ARGS)
{
@@ -528,13 +514,12 @@ man_PP_pre(MAN_ARGS)
return(1);
}
-/* ARGSUSED */
static int
man_IP_pre(MAN_ARGS)
{
const struct man_node *nn;
- if (MAN_BODY == n->type) {
+ if (MAN_BODY == n->type) {
print_otag(h, TAG_DD, 0, NULL);
return(1);
} else if (MAN_HEAD != n->type) {
@@ -553,15 +538,19 @@ man_IP_pre(MAN_ARGS)
/* For TP, only print next-line header elements. */
- if (MAN_TP == n->tok)
- for (nn = n->child; nn; nn = nn->next)
- if (nn->line > n->line)
- print_man_node(man, nn, mh, h);
+ if (MAN_TP == n->tok) {
+ nn = n->child;
+ while (NULL != nn && 0 == (MAN_LINE & nn->flags))
+ nn = nn->next;
+ while (NULL != nn) {
+ print_man_node(man, nn, mh, h);
+ nn = nn->next;
+ }
+ }
return(0);
}
-/* ARGSUSED */
static int
man_HP_pre(MAN_ARGS)
{
@@ -590,7 +579,6 @@ man_HP_pre(MAN_ARGS)
return(1);
}
-/* ARGSUSED */
static int
man_OP_pre(MAN_ARGS)
{
@@ -620,8 +608,6 @@ man_OP_pre(MAN_ARGS)
return(0);
}
-
-/* ARGSUSED */
static int
man_B_pre(MAN_ARGS)
{
@@ -630,16 +616,14 @@ man_B_pre(MAN_ARGS)
return(1);
}
-/* ARGSUSED */
static int
man_I_pre(MAN_ARGS)
{
-
+
print_otag(h, TAG_I, 0, NULL);
return(1);
}
-/* ARGSUSED */
static int
man_literal_pre(MAN_ARGS)
{
@@ -653,7 +637,6 @@ man_literal_pre(MAN_ARGS)
return(0);
}
-/* ARGSUSED */
static int
man_in_pre(MAN_ARGS)
{
@@ -662,7 +645,6 @@ man_in_pre(MAN_ARGS)
return(0);
}
-/* ARGSUSED */
static int
man_ign_pre(MAN_ARGS)
{
@@ -670,7 +652,6 @@ man_ign_pre(MAN_ARGS)
return(0);
}
-/* ARGSUSED */
static int
man_RS_pre(MAN_ARGS)
{
@@ -693,7 +674,6 @@ man_RS_pre(MAN_ARGS)
return(1);
}
-/* ARGSUSED */
static int
man_UR_pre(MAN_ARGS)
{
diff --git a/contrib/mdocml/man_macro.c b/contrib/mdocml/man_macro.c
index 479d0484c41f..ea45a504db4c 100644
--- a/contrib/mdocml/man_macro.c
+++ b/contrib/mdocml/man_macro.c
@@ -1,4 +1,4 @@
-/* $Id: man_macro.c,v 1.79 2013/12/25 00:50:05 schwarze Exp $ */
+/* $Id: man_macro.c,v 1.87 2014/07/30 23:01:39 schwarze Exp $ */
/*
* Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons
* Copyright (c) 2012, 2013 Ingo Schwarze
@@ -40,17 +40,15 @@ static int blk_close(MACRO_PROT_ARGS);
static int blk_exp(MACRO_PROT_ARGS);
static int blk_imp(MACRO_PROT_ARGS);
static int in_line_eoln(MACRO_PROT_ARGS);
-static int man_args(struct man *, int,
+static int man_args(struct man *, int,
int *, char *, char **);
-static int rew_scope(enum man_type,
+static int rew_scope(enum man_type,
struct man *, enum mant);
-static enum rew rew_dohalt(enum mant, enum man_type,
+static enum rew rew_dohalt(enum mant, enum man_type,
const struct man_node *);
-static enum rew rew_block(enum mant, enum man_type,
+static enum rew rew_block(enum mant, enum man_type,
const struct man_node *);
-static void rew_warn(struct man *,
- struct man_node *, enum mandocerr);
const struct man_macro __man_macros[MAN_MAX] = {
{ in_line_eoln, MAN_NSCOPED }, /* br */
@@ -91,85 +89,80 @@ const struct man_macro __man_macros[MAN_MAX] = {
{ in_line_eoln, MAN_BSCOPE }, /* EE */
{ blk_exp, MAN_BSCOPE | MAN_EXPLICIT }, /* UR */
{ blk_close, 0 }, /* UE */
+ { in_line_eoln, 0 }, /* ll */
};
const struct man_macro * const man_macros = __man_macros;
-/*
- * Warn when "n" is an explicit non-roff macro.
- */
-static void
-rew_warn(struct man *man, struct man_node *n, enum mandocerr er)
-{
-
- if (er == MANDOCERR_MAX || MAN_BLOCK != n->type)
- return;
- if (MAN_VALID & n->flags)
- return;
- if ( ! (MAN_EXPLICIT & man_macros[n->tok].flags))
- return;
-
- assert(er < MANDOCERR_FATAL);
- man_nmsg(man, n, er);
-}
-
-
-/*
- * Rewind scope. If a code "er" != MANDOCERR_MAX has been provided, it
- * will be used if an explicit block scope is being closed out.
- */
int
-man_unscope(struct man *man, const struct man_node *to,
- enum mandocerr er)
+man_unscope(struct man *man, const struct man_node *to)
{
struct man_node *n;
- assert(to);
-
man->next = MAN_NEXT_SIBLING;
+ to = to->parent;
+ n = man->last;
+ while (n != to) {
+
+ /* Reached the end of the document? */
+
+ if (to == NULL && ! (n->flags & MAN_VALID)) {
+ if (man->flags & (MAN_BLINE | MAN_ELINE) &&
+ man_macros[n->tok].flags & MAN_SCOPED) {
+ mandoc_vmsg(MANDOCERR_BLK_LINE,
+ man->parse, n->line, n->pos,
+ "EOF breaks %s",
+ man_macronames[n->tok]);
+ if (man->flags & MAN_ELINE)
+ man->flags &= ~MAN_ELINE;
+ else {
+ assert(n->type == MAN_HEAD);
+ n = n->parent;
+ man->flags &= ~MAN_BLINE;
+ }
+ man->last = n;
+ n = n->parent;
+ man_node_delete(man, man->last);
+ continue;
+ }
+ if (n->type == MAN_BLOCK &&
+ man_macros[n->tok].flags & MAN_EXPLICIT)
+ mandoc_msg(MANDOCERR_BLK_NOEND,
+ man->parse, n->line, n->pos,
+ man_macronames[n->tok]);
+ }
- /* LINTED */
- while (man->last != to) {
/*
- * Save the parent here, because we may delete the
- * man->last node in the post-validation phase and reset
- * it to man->last->parent, causing a step in the closing
- * out to be lost.
+ * We might delete the man->last node
+ * in the post-validation phase.
+ * Save a pointer to the parent such that
+ * we know where to continue the iteration.
*/
- n = man->last->parent;
- rew_warn(man, man->last, er);
+ man->last = n;
+ n = n->parent;
if ( ! man_valid_post(man))
return(0);
- man->last = n;
- assert(man->last);
}
-
- rew_warn(man, man->last, er);
- if ( ! man_valid_post(man))
- return(0);
-
return(1);
}
-
static enum rew
rew_block(enum mant ntok, enum man_type type, const struct man_node *n)
{
- if (MAN_BLOCK == type && ntok == n->parent->tok &&
- MAN_BODY == n->parent->type)
+ if (MAN_BLOCK == type && ntok == n->parent->tok &&
+ MAN_BODY == n->parent->type)
return(REW_REWIND);
return(ntok == n->tok ? REW_HALT : REW_NOHALT);
}
-
/*
* There are three scope levels: scoped to the root (all), scoped to the
* section (all less sections), and scoped to subsections (all less
* sections and subsections).
*/
-static enum rew
+static enum rew
rew_dohalt(enum mant tok, enum man_type type, const struct man_node *n)
{
enum rew c;
@@ -196,20 +189,20 @@ rew_dohalt(enum mant tok, enum man_type type, const struct man_node *n)
return(REW_REWIND);
}
- /*
+ /*
* Next follow the implicit scope-smashings as defined by man.7:
* section, sub-section, etc.
*/
switch (tok) {
- case (MAN_SH):
+ case MAN_SH:
break;
- case (MAN_SS):
+ case MAN_SS:
/* Rewind to a section, if a block. */
if (REW_NOHALT != (c = rew_block(MAN_SH, type, n)))
return(c);
break;
- case (MAN_RS):
+ case MAN_RS:
/* Preserve empty paragraphs before RS. */
if (0 == n->nchild && (MAN_P == n->tok ||
MAN_PP == n->tok || MAN_LP == n->tok))
@@ -237,7 +230,6 @@ rew_dohalt(enum mant tok, enum man_type type, const struct man_node *n)
return(REW_NOHALT);
}
-
/*
* Rewinding entails ascending the parse tree until a coherent point,
* for example, the `SH' macro will close out any intervening `SS'
@@ -249,9 +241,8 @@ rew_scope(enum man_type type, struct man *man, enum mant tok)
struct man_node *n;
enum rew c;
- /* LINTED */
for (n = man->last; n; n = n->parent) {
- /*
+ /*
* Whether we should stop immediately (REW_HALT), stop
* and rewind until this point (REW_REWIND), or keep
* rewinding (REW_NOHALT).
@@ -263,31 +254,30 @@ rew_scope(enum man_type type, struct man *man, enum mant tok)
break;
}
- /*
+ /*
* Rewind until the current point. Warn if we're a roff
* instruction that's mowing over explicit scopes.
*/
assert(n);
- return(man_unscope(man, n, MANDOCERR_MAX));
+ return(man_unscope(man, n));
}
/*
* Close out a generic explicit macro.
*/
-/* ARGSUSED */
int
blk_close(MACRO_PROT_ARGS)
{
- enum mant ntok;
+ enum mant ntok;
const struct man_node *nn;
switch (tok) {
- case (MAN_RE):
+ case MAN_RE:
ntok = MAN_RS;
break;
- case (MAN_UE):
+ case MAN_UE:
ntok = MAN_UR;
break;
default:
@@ -300,17 +290,16 @@ blk_close(MACRO_PROT_ARGS)
break;
if (NULL == nn) {
- man_pmsg(man, line, ppos, MANDOCERR_NOSCOPE);
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, man->parse,
+ line, ppos, man_macronames[tok]);
if ( ! rew_scope(MAN_BLOCK, man, MAN_PP))
return(0);
- } else
- man_unscope(man, nn, MANDOCERR_MAX);
+ } else
+ man_unscope(man, nn);
return(1);
}
-
-/* ARGSUSED */
int
blk_exp(MACRO_PROT_ARGS)
{
@@ -343,22 +332,19 @@ blk_exp(MACRO_PROT_ARGS)
if (n->tok != tok)
continue;
assert(MAN_HEAD == n->type);
- man_unscope(man, n, MANDOCERR_MAX);
+ man_unscope(man, n);
break;
}
return(man_body_alloc(man, line, ppos, tok));
}
-
-
/*
* Parse an implicit-block macro. These contain a MAN_HEAD and a
* MAN_BODY contained within a MAN_BLOCK. Rules for closing out other
* scopes, such as `SH' closing out an `SS', are defined in the rew
* routines.
*/
-/* ARGSUSED */
int
blk_imp(MACRO_PROT_ARGS)
{
@@ -410,8 +396,6 @@ blk_imp(MACRO_PROT_ARGS)
return(man_body_alloc(man, line, ppos, tok));
}
-
-/* ARGSUSED */
int
in_line_eoln(MACRO_PROT_ARGS)
{
@@ -438,7 +422,7 @@ in_line_eoln(MACRO_PROT_ARGS)
*/
if (n != man->last &&
- mandoc_eos(man->last->string, strlen(man->last->string), 0))
+ mandoc_eos(man->last->string, strlen(man->last->string)))
man->last->flags |= MAN_EOS;
/*
@@ -451,18 +435,11 @@ in_line_eoln(MACRO_PROT_ARGS)
assert( ! (MAN_NSCOPED & man_macros[tok].flags));
man->flags |= MAN_ELINE;
return(1);
- }
-
- /* Set ignorable context, if applicable. */
-
- if (MAN_NSCOPED & man_macros[tok].flags) {
- assert( ! (MAN_SCOPED & man_macros[tok].flags));
- man->flags |= MAN_ILINE;
}
assert(MAN_ROOT != man->last->type);
man->next = MAN_NEXT_SIBLING;
-
+
/*
* Rewind our element scope. Note that when TH is pruned, we'll
* be back at the root, so make sure that we don't clobber as
@@ -481,7 +458,7 @@ in_line_eoln(MACRO_PROT_ARGS)
assert(man->last);
/*
- * Same here regarding whether we're back at the root.
+ * Same here regarding whether we're back at the root.
*/
if (man->last->type != MAN_ROOT && ! man_valid_post(man))
@@ -495,7 +472,7 @@ int
man_macroend(struct man *man)
{
- return(man_unscope(man, man->first, MANDOCERR_SCOPEEXIT));
+ return(man_unscope(man, man->first));
}
static int
diff --git a/contrib/mdocml/man_term.c b/contrib/mdocml/man_term.c
index 4bd62443b430..c91c0746201a 100644
--- a/contrib/mdocml/man_term.c
+++ b/contrib/mdocml/man_term.c
@@ -1,7 +1,7 @@
-/* $Id: man_term.c,v 1.139 2013/12/22 23:34:13 schwarze Exp $ */
+/* $Id: man_term.c,v 1.149 2014/06/20 23:02:31 schwarze Exp $ */
/*
* Copyright (c) 2008-2012 Kristaps Dzonsons
- * Copyright (c) 2010, 2011, 2012, 2013 Ingo Schwarze
+ * Copyright (c) 2010-2014 Ingo Schwarze
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -28,6 +28,7 @@
#include
#include "mandoc.h"
+#include "mandoc_aux.h"
#include "out.h"
#include "man.h"
#include "term.h"
@@ -45,7 +46,7 @@ struct mtermp {
int pardist; /* vert. space before par., unit: [v] */
};
-#define DECL_ARGS struct termp *p, \
+#define DECL_ARGS struct termp *p, \
struct mtermp *mt, \
const struct man_node *n, \
const struct man_meta *meta
@@ -64,7 +65,7 @@ static void print_man_nodelist(DECL_ARGS);
static void print_man_node(DECL_ARGS);
static void print_man_head(struct termp *, const void *);
static void print_man_foot(struct termp *, const void *);
-static void print_bvspace(struct termp *,
+static void print_bvspace(struct termp *,
const struct man_node *, int);
static int pre_B(DECL_ARGS);
@@ -84,6 +85,7 @@ static int pre_ft(DECL_ARGS);
static int pre_ign(DECL_ARGS);
static int pre_in(DECL_ARGS);
static int pre_literal(DECL_ARGS);
+static int pre_ll(DECL_ARGS);
static int pre_sp(DECL_ARGS);
static void post_IP(DECL_ARGS);
@@ -104,7 +106,7 @@ static const struct termact termacts[MAN_MAX] = {
{ pre_PP, NULL, 0 }, /* PP */
{ pre_PP, NULL, 0 }, /* P */
{ pre_IP, post_IP, 0 }, /* IP */
- { pre_HP, post_HP, 0 }, /* HP */
+ { pre_HP, post_HP, 0 }, /* HP */
{ NULL, NULL, 0 }, /* SM */
{ pre_B, NULL, 0 }, /* SB */
{ pre_alternate, NULL, 0 }, /* BI */
@@ -133,10 +135,10 @@ static const struct termact termacts[MAN_MAX] = {
{ pre_literal, NULL, 0 }, /* EE */
{ pre_UR, post_UR, 0 }, /* UR */
{ NULL, NULL, 0 }, /* UE */
+ { pre_ll, NULL, MAN_NOTEXT }, /* ll */
};
-
void
terminal_man(void *arg, const struct man *man)
{
@@ -187,7 +189,6 @@ a2height(const struct termp *p, const char *cp)
return(term_vspan(p, &su));
}
-
static int
a2width(const struct termp *p, const char *cp)
{
@@ -226,7 +227,7 @@ print_bvspace(struct termp *p, const struct man_node *n, int pardist)
term_vspace(p);
}
-/* ARGSUSED */
+
static int
pre_ign(DECL_ARGS)
{
@@ -234,8 +235,14 @@ pre_ign(DECL_ARGS)
return(0);
}
+static int
+pre_ll(DECL_ARGS)
+{
+
+ term_setwidth(p, n->nchild ? n->child->string : NULL);
+ return(0);
+}
-/* ARGSUSED */
static int
pre_I(DECL_ARGS)
{
@@ -244,8 +251,6 @@ pre_I(DECL_ARGS)
return(1);
}
-
-/* ARGSUSED */
static int
pre_literal(DECL_ARGS)
{
@@ -266,14 +271,13 @@ pre_literal(DECL_ARGS)
p->offset = p->rmargin;
p->rmargin = p->maxrmargin;
p->trailspace = 0;
- p->flags &= ~TERMP_NOBREAK;
+ p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
p->flags |= TERMP_NOSPACE;
}
return(0);
}
-/* ARGSUSED */
static int
pre_PD(DECL_ARGS)
{
@@ -288,7 +292,6 @@ pre_PD(DECL_ARGS)
return(0);
}
-/* ARGSUSED */
static int
pre_alternate(DECL_ARGS)
{
@@ -297,27 +300,27 @@ pre_alternate(DECL_ARGS)
int savelit, i;
switch (n->tok) {
- case (MAN_RB):
+ case MAN_RB:
font[0] = TERMFONT_NONE;
font[1] = TERMFONT_BOLD;
break;
- case (MAN_RI):
+ case MAN_RI:
font[0] = TERMFONT_NONE;
font[1] = TERMFONT_UNDER;
break;
- case (MAN_BR):
+ case MAN_BR:
font[0] = TERMFONT_BOLD;
font[1] = TERMFONT_NONE;
break;
- case (MAN_BI):
+ case MAN_BI:
font[0] = TERMFONT_BOLD;
font[1] = TERMFONT_UNDER;
break;
- case (MAN_IR):
+ case MAN_IR:
font[0] = TERMFONT_UNDER;
font[1] = TERMFONT_NONE;
break;
- case (MAN_IB):
+ case MAN_IB:
font[0] = TERMFONT_UNDER;
font[1] = TERMFONT_BOLD;
break;
@@ -340,7 +343,6 @@ pre_alternate(DECL_ARGS)
return(0);
}
-/* ARGSUSED */
static int
pre_B(DECL_ARGS)
{
@@ -349,7 +351,6 @@ pre_B(DECL_ARGS)
return(1);
}
-/* ARGSUSED */
static int
pre_OP(DECL_ARGS)
{
@@ -372,7 +373,6 @@ pre_OP(DECL_ARGS)
return(0);
}
-/* ARGSUSED */
static int
pre_ft(DECL_ARGS)
{
@@ -385,26 +385,26 @@ pre_ft(DECL_ARGS)
cp = n->child->string;
switch (*cp) {
- case ('4'):
+ case '4':
/* FALLTHROUGH */
- case ('3'):
+ case '3':
/* FALLTHROUGH */
- case ('B'):
+ case 'B':
term_fontrepl(p, TERMFONT_BOLD);
break;
- case ('2'):
+ case '2':
/* FALLTHROUGH */
- case ('I'):
+ case 'I':
term_fontrepl(p, TERMFONT_UNDER);
break;
- case ('P'):
+ case 'P':
term_fontlast(p);
break;
- case ('1'):
+ case '1':
/* FALLTHROUGH */
- case ('C'):
+ case 'C':
/* FALLTHROUGH */
- case ('R'):
+ case 'R':
term_fontrepl(p, TERMFONT_NONE);
break;
default:
@@ -413,7 +413,6 @@ pre_ft(DECL_ARGS)
return(0);
}
-/* ARGSUSED */
static int
pre_in(DECL_ARGS)
{
@@ -447,7 +446,7 @@ pre_in(DECL_ARGS)
p->offset -= p->offset > v ? v : p->offset;
else if (less > 0)
p->offset += v;
- else
+ else
p->offset = v;
/* Don't let this creep beyond the right margin. */
@@ -458,8 +457,6 @@ pre_in(DECL_ARGS)
return(0);
}
-
-/* ARGSUSED */
static int
pre_sp(DECL_ARGS)
{
@@ -469,15 +466,15 @@ pre_sp(DECL_ARGS)
if ((NULL == n->prev && n->parent)) {
switch (n->parent->tok) {
- case (MAN_SH):
+ case MAN_SH:
/* FALLTHROUGH */
- case (MAN_SS):
+ case MAN_SS:
/* FALLTHROUGH */
- case (MAN_PP):
+ case MAN_PP:
/* FALLTHROUGH */
- case (MAN_LP):
+ case MAN_LP:
/* FALLTHROUGH */
- case (MAN_P):
+ case MAN_P:
/* FALLTHROUGH */
return(0);
default:
@@ -487,7 +484,7 @@ pre_sp(DECL_ARGS)
neg = 0;
switch (n->tok) {
- case (MAN_br):
+ case MAN_br:
len = 0;
break;
default:
@@ -515,8 +512,6 @@ pre_sp(DECL_ARGS)
return(0);
}
-
-/* ARGSUSED */
static int
pre_HP(DECL_ARGS)
{
@@ -525,17 +520,17 @@ pre_HP(DECL_ARGS)
const struct man_node *nn;
switch (n->type) {
- case (MAN_BLOCK):
+ case MAN_BLOCK:
print_bvspace(p, n, mt->pardist);
return(1);
- case (MAN_BODY):
+ case MAN_BODY:
break;
default:
return(0);
}
if ( ! (MANT_LITERAL & mt->fl)) {
- p->flags |= TERMP_NOBREAK;
+ p->flags |= TERMP_NOBREAK | TERMP_BRIND;
p->trailspace = 2;
}
@@ -561,16 +556,14 @@ pre_HP(DECL_ARGS)
return(1);
}
-
-/* ARGSUSED */
static void
post_HP(DECL_ARGS)
{
switch (n->type) {
- case (MAN_BODY):
+ case MAN_BODY:
term_newln(p);
- p->flags &= ~TERMP_NOBREAK;
+ p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
p->trailspace = 0;
p->offset = mt->offset;
p->rmargin = p->maxrmargin;
@@ -580,14 +573,12 @@ post_HP(DECL_ARGS)
}
}
-
-/* ARGSUSED */
static int
pre_PP(DECL_ARGS)
{
switch (n->type) {
- case (MAN_BLOCK):
+ case MAN_BLOCK:
mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
print_bvspace(p, n, mt->pardist);
break;
@@ -599,8 +590,6 @@ pre_PP(DECL_ARGS)
return(MAN_HEAD != n->type);
}
-
-/* ARGSUSED */
static int
pre_IP(DECL_ARGS)
{
@@ -609,14 +598,14 @@ pre_IP(DECL_ARGS)
int savelit, ival;
switch (n->type) {
- case (MAN_BODY):
+ case MAN_BODY:
p->flags |= TERMP_NOSPACE;
break;
- case (MAN_HEAD):
+ case MAN_HEAD:
p->flags |= TERMP_NOBREAK;
p->trailspace = 1;
break;
- case (MAN_BLOCK):
+ case MAN_BLOCK:
print_bvspace(p, n, mt->pardist);
/* FALLTHROUGH */
default:
@@ -633,7 +622,7 @@ pre_IP(DECL_ARGS)
len = (size_t)ival;
switch (n->type) {
- case (MAN_HEAD):
+ case MAN_HEAD:
/* Handle zero-width lengths. */
if (0 == len)
len = term_len(p, 1);
@@ -656,9 +645,10 @@ pre_IP(DECL_ARGS)
mt->fl |= MANT_LITERAL;
return(0);
- case (MAN_BODY):
+ case MAN_BODY:
p->offset = mt->offset + len;
- p->rmargin = p->maxrmargin;
+ p->rmargin = p->maxrmargin > p->offset ?
+ p->maxrmargin : p->offset;
break;
default:
break;
@@ -667,20 +657,18 @@ pre_IP(DECL_ARGS)
return(1);
}
-
-/* ARGSUSED */
static void
post_IP(DECL_ARGS)
{
switch (n->type) {
- case (MAN_HEAD):
+ case MAN_HEAD:
term_flushln(p);
p->flags &= ~TERMP_NOBREAK;
p->trailspace = 0;
p->rmargin = p->maxrmargin;
break;
- case (MAN_BODY):
+ case MAN_BODY:
term_newln(p);
p->offset = mt->offset;
break;
@@ -689,8 +677,6 @@ post_IP(DECL_ARGS)
}
}
-
-/* ARGSUSED */
static int
pre_TP(DECL_ARGS)
{
@@ -699,14 +685,14 @@ pre_TP(DECL_ARGS)
int savelit, ival;
switch (n->type) {
- case (MAN_HEAD):
+ case MAN_HEAD:
p->flags |= TERMP_NOBREAK;
p->trailspace = 1;
break;
- case (MAN_BODY):
+ case MAN_BODY:
p->flags |= TERMP_NOSPACE;
break;
- case (MAN_BLOCK):
+ case MAN_BLOCK:
print_bvspace(p, n, mt->pardist);
/* FALLTHROUGH */
default:
@@ -719,12 +705,12 @@ pre_TP(DECL_ARGS)
/* Calculate offset. */
if (NULL != (nn = n->parent->head->child))
- if (nn->string && nn->parent->line == nn->line)
+ if (nn->string && 0 == (MAN_LINE & nn->flags))
if ((ival = a2width(p, nn->string)) >= 0)
len = (size_t)ival;
switch (n->type) {
- case (MAN_HEAD):
+ case MAN_HEAD:
/* Handle zero-length properly. */
if (0 == len)
len = term_len(p, 1);
@@ -736,9 +722,14 @@ pre_TP(DECL_ARGS)
mt->fl &= ~MANT_LITERAL;
/* Don't print same-line elements. */
- for (nn = n->child; nn; nn = nn->next)
- if (nn->line > n->line)
- print_man_node(p, mt, nn, meta);
+ nn = n->child;
+ while (NULL != nn && 0 == (MAN_LINE & nn->flags))
+ nn = nn->next;
+
+ while (NULL != nn) {
+ print_man_node(p, mt, nn, meta);
+ nn = nn->next;
+ }
if (savelit)
mt->fl |= MANT_LITERAL;
@@ -746,9 +737,10 @@ pre_TP(DECL_ARGS)
mt->lmargin[mt->lmargincur] = (size_t)ival;
return(0);
- case (MAN_BODY):
+ case MAN_BODY:
p->offset = mt->offset + len;
- p->rmargin = p->maxrmargin;
+ p->rmargin = p->maxrmargin > p->offset ?
+ p->maxrmargin : p->offset;
p->trailspace = 0;
p->flags &= ~TERMP_NOBREAK;
break;
@@ -759,17 +751,15 @@ pre_TP(DECL_ARGS)
return(1);
}
-
-/* ARGSUSED */
static void
post_TP(DECL_ARGS)
{
switch (n->type) {
- case (MAN_HEAD):
+ case MAN_HEAD:
term_flushln(p);
break;
- case (MAN_BODY):
+ case MAN_BODY:
term_newln(p);
p->offset = mt->offset;
break;
@@ -778,15 +768,13 @@ post_TP(DECL_ARGS)
}
}
-
-/* ARGSUSED */
static int
pre_SS(DECL_ARGS)
{
int i;
switch (n->type) {
- case (MAN_BLOCK):
+ case MAN_BLOCK:
mt->fl &= ~MANT_LITERAL;
mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
mt->offset = term_len(p, p->defindent);
@@ -799,11 +787,11 @@ pre_SS(DECL_ARGS)
for (i = 0; i < mt->pardist; i++)
term_vspace(p);
break;
- case (MAN_HEAD):
+ case MAN_HEAD:
term_fontrepl(p, TERMFONT_BOLD);
p->offset = term_len(p, 3);
break;
- case (MAN_BODY):
+ case MAN_BODY:
p->offset = mt->offset;
break;
default:
@@ -813,17 +801,15 @@ pre_SS(DECL_ARGS)
return(1);
}
-
-/* ARGSUSED */
static void
post_SS(DECL_ARGS)
{
-
+
switch (n->type) {
- case (MAN_HEAD):
+ case MAN_HEAD:
term_newln(p);
break;
- case (MAN_BODY):
+ case MAN_BODY:
term_newln(p);
break;
default:
@@ -831,15 +817,13 @@ post_SS(DECL_ARGS)
}
}
-
-/* ARGSUSED */
static int
pre_SH(DECL_ARGS)
{
int i;
switch (n->type) {
- case (MAN_BLOCK):
+ case MAN_BLOCK:
mt->fl &= ~MANT_LITERAL;
mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
mt->offset = term_len(p, p->defindent);
@@ -853,11 +837,11 @@ pre_SH(DECL_ARGS)
for (i = 0; i < mt->pardist; i++)
term_vspace(p);
break;
- case (MAN_HEAD):
+ case MAN_HEAD:
term_fontrepl(p, TERMFONT_BOLD);
p->offset = 0;
break;
- case (MAN_BODY):
+ case MAN_BODY:
p->offset = mt->offset;
break;
default:
@@ -867,17 +851,15 @@ pre_SH(DECL_ARGS)
return(1);
}
-
-/* ARGSUSED */
static void
post_SH(DECL_ARGS)
{
-
+
switch (n->type) {
- case (MAN_HEAD):
+ case MAN_HEAD:
term_newln(p);
break;
- case (MAN_BODY):
+ case MAN_BODY:
term_newln(p);
break;
default:
@@ -885,7 +867,6 @@ post_SH(DECL_ARGS)
}
}
-/* ARGSUSED */
static int
pre_RS(DECL_ARGS)
{
@@ -893,10 +874,10 @@ pre_RS(DECL_ARGS)
size_t sz;
switch (n->type) {
- case (MAN_BLOCK):
+ case MAN_BLOCK:
term_newln(p);
return(1);
- case (MAN_HEAD):
+ case MAN_HEAD:
return(0);
default:
break;
@@ -905,12 +886,13 @@ pre_RS(DECL_ARGS)
sz = term_len(p, p->defindent);
if (NULL != (n = n->parent->head->child))
- if ((ival = a2width(p, n->string)) >= 0)
+ if ((ival = a2width(p, n->string)) >= 0)
sz = (size_t)ival;
mt->offset += sz;
- p->rmargin = p->maxrmargin;
- p->offset = mt->offset < p->rmargin ? mt->offset : p->rmargin;
+ p->offset = mt->offset;
+ p->rmargin = p->maxrmargin > p->offset ?
+ p->maxrmargin : p->offset;
if (++mt->lmarginsz < MAXMARGINS)
mt->lmargincur = mt->lmarginsz;
@@ -919,7 +901,6 @@ pre_RS(DECL_ARGS)
return(1);
}
-/* ARGSUSED */
static void
post_RS(DECL_ARGS)
{
@@ -927,9 +908,9 @@ post_RS(DECL_ARGS)
size_t sz;
switch (n->type) {
- case (MAN_BLOCK):
+ case MAN_BLOCK:
return;
- case (MAN_HEAD):
+ case MAN_HEAD:
return;
default:
term_newln(p);
@@ -938,8 +919,8 @@ post_RS(DECL_ARGS)
sz = term_len(p, p->defindent);
- if (NULL != (n = n->parent->head->child))
- if ((ival = a2width(p, n->string)) >= 0)
+ if (NULL != (n = n->parent->head->child))
+ if ((ival = a2width(p, n->string)) >= 0)
sz = (size_t)ival;
mt->offset = mt->offset < sz ? 0 : mt->offset - sz;
@@ -949,7 +930,6 @@ post_RS(DECL_ARGS)
mt->lmargincur = mt->lmarginsz;
}
-/* ARGSUSED */
static int
pre_UR(DECL_ARGS)
{
@@ -957,7 +937,6 @@ pre_UR(DECL_ARGS)
return (MAN_HEAD != n->type);
}
-/* ARGSUSED */
static void
post_UR(DECL_ARGS)
{
@@ -982,7 +961,7 @@ print_man_node(DECL_ARGS)
int c;
switch (n->type) {
- case(MAN_TEXT):
+ case MAN_TEXT:
/*
* If we have a blank line, output a vertical space.
* If we have a space as the first character, break
@@ -997,15 +976,15 @@ print_man_node(DECL_ARGS)
term_word(p, n->string);
goto out;
- case (MAN_EQN):
+ case MAN_EQN:
term_eqn(p, n->eqn);
return;
- case (MAN_TBL):
+ case MAN_TBL:
/*
* Tables are preceded by a newline. Then process a
* table line, which will cause line termination,
*/
- if (TBL_SPAN_FIRST & n->span->flags)
+ if (TBL_SPAN_FIRST & n->span->flags)
term_newln(p);
term_tbl(p, n->span);
return;
@@ -1037,7 +1016,7 @@ print_man_node(DECL_ARGS)
* more specific than this.
*/
if (MANT_LITERAL & mt->fl && ! (TERMP_NOBREAK & p->flags) &&
- (NULL == n->next || n->next->line > n->line)) {
+ (NULL == n->next || MAN_LINE & n->next->flags)) {
rm = p->rmargin;
rmax = p->maxrmargin;
p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
@@ -1068,13 +1047,12 @@ print_man_nodelist(DECL_ARGS)
print_man_nodelist(p, mt, n->next, meta);
}
-
static void
print_man_foot(struct termp *p, const void *arg)
{
- char title[BUFSIZ];
- size_t datelen;
- const struct man_meta *meta;
+ const struct man_meta *meta;
+ char *title;
+ size_t datelen;
meta = (const struct man_meta *)arg;
assert(meta->title);
@@ -1083,7 +1061,8 @@ print_man_foot(struct termp *p, const void *arg)
term_fontrepl(p, TERMFONT_NONE);
- term_vspace(p);
+ if (meta->hasbody)
+ term_vspace(p);
/*
* Temporary, undocumented option to imitate mdoc(7) output.
@@ -1092,13 +1071,16 @@ print_man_foot(struct termp *p, const void *arg)
*/
if ( ! p->mdocstyle) {
- term_vspace(p);
- term_vspace(p);
- snprintf(title, BUFSIZ, "%s(%s)", meta->title, meta->msec);
+ if (meta->hasbody) {
+ term_vspace(p);
+ term_vspace(p);
+ }
+ mandoc_asprintf(&title, "%s(%s)",
+ meta->title, meta->msec);
} else if (meta->source) {
- strlcpy(title, meta->source, BUFSIZ);
+ title = mandoc_strdup(meta->source);
} else {
- title[0] = '\0';
+ title = mandoc_strdup("");
}
datelen = term_strlen(p, meta->date);
@@ -1134,38 +1116,35 @@ print_man_foot(struct termp *p, const void *arg)
term_word(p, title);
term_flushln(p);
+ free(title);
}
-
static void
print_man_head(struct termp *p, const void *arg)
{
- char buf[BUFSIZ], title[BUFSIZ];
- size_t buflen, titlen;
- const struct man_meta *meta;
+ const struct man_meta *meta;
+ const char *volume;
+ char *title;
+ size_t vollen, titlen;
meta = (const struct man_meta *)arg;
assert(meta->title);
assert(meta->msec);
- if (meta->vol)
- strlcpy(buf, meta->vol, BUFSIZ);
- else
- buf[0] = '\0';
- buflen = term_strlen(p, buf);
+ volume = NULL == meta->vol ? "" : meta->vol;
+ vollen = term_strlen(p, volume);
/* Top left corner: manual title and section. */
- snprintf(title, BUFSIZ, "%s(%s)", meta->title, meta->msec);
+ mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec);
titlen = term_strlen(p, title);
p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
p->trailspace = 1;
p->offset = 0;
- p->rmargin = 2 * (titlen+1) + buflen < p->maxrmargin ?
- (p->maxrmargin -
- term_strlen(p, buf) + term_len(p, 1)) / 2 :
- p->maxrmargin - buflen;
+ p->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ?
+ (p->maxrmargin - vollen + term_len(p, 1)) / 2 :
+ p->maxrmargin - vollen;
term_word(p, title);
term_flushln(p);
@@ -1174,10 +1153,10 @@ print_man_head(struct termp *p, const void *arg)
p->flags |= TERMP_NOSPACE;
p->offset = p->rmargin;
- p->rmargin = p->offset + buflen + titlen < p->maxrmargin ?
+ p->rmargin = p->offset + vollen + titlen < p->maxrmargin ?
p->maxrmargin - titlen : p->maxrmargin;
- term_word(p, buf);
+ term_word(p, volume);
term_flushln(p);
/* Top right corner: title and section, again. */
@@ -1196,7 +1175,7 @@ print_man_head(struct termp *p, const void *arg)
p->offset = 0;
p->rmargin = p->maxrmargin;
- /*
+ /*
* Groff prints three blank lines before the content.
* Do the same, except in the temporary, undocumented
* mode imitating mdoc(7) output.
@@ -1207,4 +1186,5 @@ print_man_head(struct termp *p, const void *arg)
term_vspace(p);
term_vspace(p);
}
+ free(title);
}
diff --git a/contrib/mdocml/man_validate.c b/contrib/mdocml/man_validate.c
index da2e557ebb5e..c17eb9ecdac6 100644
--- a/contrib/mdocml/man_validate.c
+++ b/contrib/mdocml/man_validate.c
@@ -1,7 +1,7 @@
-/* $Id: man_validate.c,v 1.86 2013/10/17 20:54:58 schwarze Exp $ */
+/* $Id: man_validate.c,v 1.105 2014/08/06 15:09:05 schwarze Exp $ */
/*
* Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons
- * Copyright (c) 2010, 2012, 2013 Ingo Schwarze
+ * Copyright (c) 2010, 2012, 2013, 2014 Ingo Schwarze
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -32,6 +32,7 @@
#include "man.h"
#include "mandoc.h"
+#include "mandoc_aux.h"
#include "libman.h"
#include "libmandoc.h"
@@ -39,21 +40,14 @@
typedef int (*v_check)(CHKARGS);
-struct man_valid {
- v_check *pres;
- v_check *posts;
-};
-
static int check_eq0(CHKARGS);
static int check_eq2(CHKARGS);
static int check_le1(CHKARGS);
-static int check_ge2(CHKARGS);
static int check_le5(CHKARGS);
-static int check_head1(CHKARGS);
static int check_par(CHKARGS);
static int check_part(CHKARGS);
static int check_root(CHKARGS);
-static void check_text(CHKARGS);
+static int check_text(CHKARGS);
static int post_AT(CHKARGS);
static int post_IP(CHKARGS);
@@ -61,174 +55,122 @@ static int post_vs(CHKARGS);
static int post_fi(CHKARGS);
static int post_ft(CHKARGS);
static int post_nf(CHKARGS);
-static int post_sec(CHKARGS);
static int post_TH(CHKARGS);
static int post_UC(CHKARGS);
-static int pre_sec(CHKARGS);
+static int post_UR(CHKARGS);
-static v_check posts_at[] = { post_AT, NULL };
-static v_check posts_br[] = { post_vs, check_eq0, NULL };
-static v_check posts_eq0[] = { check_eq0, NULL };
-static v_check posts_eq2[] = { check_eq2, NULL };
-static v_check posts_fi[] = { check_eq0, post_fi, NULL };
-static v_check posts_ft[] = { post_ft, NULL };
-static v_check posts_ip[] = { post_IP, NULL };
-static v_check posts_le1[] = { check_le1, NULL };
-static v_check posts_nf[] = { check_eq0, post_nf, NULL };
-static v_check posts_par[] = { check_par, NULL };
-static v_check posts_part[] = { check_part, NULL };
-static v_check posts_sec[] = { post_sec, NULL };
-static v_check posts_sp[] = { post_vs, check_le1, NULL };
-static v_check posts_th[] = { check_ge2, check_le5, post_TH, NULL };
-static v_check posts_uc[] = { post_UC, NULL };
-static v_check posts_ur[] = { check_head1, check_part, NULL };
-static v_check pres_sec[] = { pre_sec, NULL };
-
-static const struct man_valid man_valids[MAN_MAX] = {
- { NULL, posts_br }, /* br */
- { NULL, posts_th }, /* TH */
- { pres_sec, posts_sec }, /* SH */
- { pres_sec, posts_sec }, /* SS */
- { NULL, NULL }, /* TP */
- { NULL, posts_par }, /* LP */
- { NULL, posts_par }, /* PP */
- { NULL, posts_par }, /* P */
- { NULL, posts_ip }, /* IP */
- { NULL, NULL }, /* HP */
- { NULL, NULL }, /* SM */
- { NULL, NULL }, /* SB */
- { NULL, NULL }, /* BI */
- { NULL, NULL }, /* IB */
- { NULL, NULL }, /* BR */
- { NULL, NULL }, /* RB */
- { NULL, NULL }, /* R */
- { NULL, NULL }, /* B */
- { NULL, NULL }, /* I */
- { NULL, NULL }, /* IR */
- { NULL, NULL }, /* RI */
- { NULL, posts_eq0 }, /* na */
- { NULL, posts_sp }, /* sp */
- { NULL, posts_nf }, /* nf */
- { NULL, posts_fi }, /* fi */
- { NULL, NULL }, /* RE */
- { NULL, posts_part }, /* RS */
- { NULL, NULL }, /* DT */
- { NULL, posts_uc }, /* UC */
- { NULL, posts_le1 }, /* PD */
- { NULL, posts_at }, /* AT */
- { NULL, NULL }, /* in */
- { NULL, posts_ft }, /* ft */
- { NULL, posts_eq2 }, /* OP */
- { NULL, posts_nf }, /* EX */
- { NULL, posts_fi }, /* EE */
- { NULL, posts_ur }, /* UR */
- { NULL, NULL }, /* UE */
+static v_check man_valids[MAN_MAX] = {
+ post_vs, /* br */
+ post_TH, /* TH */
+ NULL, /* SH */
+ NULL, /* SS */
+ NULL, /* TP */
+ check_par, /* LP */
+ check_par, /* PP */
+ check_par, /* P */
+ post_IP, /* IP */
+ NULL, /* HP */
+ NULL, /* SM */
+ NULL, /* SB */
+ NULL, /* BI */
+ NULL, /* IB */
+ NULL, /* BR */
+ NULL, /* RB */
+ NULL, /* R */
+ NULL, /* B */
+ NULL, /* I */
+ NULL, /* IR */
+ NULL, /* RI */
+ check_eq0, /* na */
+ post_vs, /* sp */
+ post_nf, /* nf */
+ post_fi, /* fi */
+ NULL, /* RE */
+ check_part, /* RS */
+ NULL, /* DT */
+ post_UC, /* UC */
+ check_le1, /* PD */
+ post_AT, /* AT */
+ NULL, /* in */
+ post_ft, /* ft */
+ check_eq2, /* OP */
+ post_nf, /* EX */
+ post_fi, /* EE */
+ post_UR, /* UR */
+ NULL, /* UE */
+ NULL, /* ll */
};
-int
-man_valid_pre(struct man *man, struct man_node *n)
-{
- v_check *cp;
-
- switch (n->type) {
- case (MAN_TEXT):
- /* FALLTHROUGH */
- case (MAN_ROOT):
- /* FALLTHROUGH */
- case (MAN_EQN):
- /* FALLTHROUGH */
- case (MAN_TBL):
- return(1);
- default:
- break;
- }
-
- if (NULL == (cp = man_valids[n->tok].pres))
- return(1);
- for ( ; *cp; cp++)
- if ( ! (*cp)(man, n))
- return(0);
- return(1);
-}
-
-
int
man_valid_post(struct man *man)
{
+ struct man_node *n;
v_check *cp;
- if (MAN_VALID & man->last->flags)
+ n = man->last;
+ if (n->flags & MAN_VALID)
return(1);
- man->last->flags |= MAN_VALID;
+ n->flags |= MAN_VALID;
- switch (man->last->type) {
- case (MAN_TEXT):
- check_text(man, man->last);
- return(1);
- case (MAN_ROOT):
- return(check_root(man, man->last));
- case (MAN_EQN):
+ switch (n->type) {
+ case MAN_TEXT:
+ return(check_text(man, n));
+ case MAN_ROOT:
+ return(check_root(man, n));
+ case MAN_EQN:
/* FALLTHROUGH */
- case (MAN_TBL):
+ case MAN_TBL:
return(1);
default:
- break;
+ cp = man_valids + n->tok;
+ return(*cp ? (*cp)(man, n) : 1);
}
-
- if (NULL == (cp = man_valids[man->last->tok].posts))
- return(1);
- for ( ; *cp; cp++)
- if ( ! (*cp)(man, man->last))
- return(0);
-
- return(1);
}
-
static int
-check_root(CHKARGS)
+check_root(CHKARGS)
{
- if (MAN_BLINE & man->flags)
- man_nmsg(man, n, MANDOCERR_SCOPEEXIT);
- else if (MAN_ELINE & man->flags)
- man_nmsg(man, n, MANDOCERR_SCOPEEXIT);
+ assert((man->flags & (MAN_BLINE | MAN_ELINE)) == 0);
- man->flags &= ~MAN_BLINE;
- man->flags &= ~MAN_ELINE;
+ if (NULL == man->first->child)
+ mandoc_msg(MANDOCERR_DOC_EMPTY, man->parse,
+ n->line, n->pos, NULL);
+ else
+ man->meta.hasbody = 1;
- if (NULL == man->first->child) {
- man_nmsg(man, n, MANDOCERR_NODOCBODY);
- return(0);
- } else if (NULL == man->meta.title) {
- man_nmsg(man, n, MANDOCERR_NOTITLE);
+ if (NULL == man->meta.title) {
+ mandoc_msg(MANDOCERR_TH_NOTITLE, man->parse,
+ n->line, n->pos, NULL);
/*
* If a title hasn't been set, do so now (by
* implication, date and section also aren't set).
*/
- man->meta.title = mandoc_strdup("unknown");
- man->meta.msec = mandoc_strdup("1");
- man->meta.date = mandoc_normdate
- (man->parse, NULL, n->line, n->pos);
+ man->meta.title = mandoc_strdup("");
+ man->meta.msec = mandoc_strdup("");
+ man->meta.date = man->quick ? mandoc_strdup("") :
+ mandoc_normdate(man->parse, NULL, n->line, n->pos);
}
return(1);
}
-static void
+static int
check_text(CHKARGS)
{
char *cp, *p;
if (MAN_LITERAL & man->flags)
- return;
+ return(1);
cp = n->string;
for (p = cp; NULL != (p = strchr(p, '\t')); p++)
- man_pmsg(man, n->line, (int)(p - cp), MANDOCERR_BADTAB);
+ mandoc_msg(MANDOCERR_FI_TAB, man->parse,
+ n->line, n->pos + (p - cp), NULL);
+ return(1);
}
#define INEQ_DEFINE(x, ineq, name) \
@@ -238,26 +180,25 @@ check_##name(CHKARGS) \
if (n->nchild ineq (x)) \
return(1); \
mandoc_vmsg(MANDOCERR_ARGCOUNT, man->parse, n->line, n->pos, \
- "line arguments %s %d (have %d)", \
- #ineq, (x), n->nchild); \
+ "line arguments %s %d (have %d)", \
+ #ineq, (x), n->nchild); \
return(1); \
}
INEQ_DEFINE(0, ==, eq0)
INEQ_DEFINE(2, ==, eq2)
INEQ_DEFINE(1, <=, le1)
-INEQ_DEFINE(2, >=, ge2)
INEQ_DEFINE(5, <=, le5)
static int
-check_head1(CHKARGS)
+post_UR(CHKARGS)
{
if (MAN_HEAD == n->type && 1 != n->nchild)
mandoc_vmsg(MANDOCERR_ARGCOUNT, man->parse, n->line,
n->pos, "line arguments eq 1 (have %d)", n->nchild);
- return(1);
+ return(check_part(man, n));
}
static int
@@ -272,27 +213,27 @@ post_ft(CHKARGS)
ok = 0;
cp = n->child->string;
switch (*cp) {
- case ('1'):
+ case '1':
/* FALLTHROUGH */
- case ('2'):
+ case '2':
/* FALLTHROUGH */
- case ('3'):
+ case '3':
/* FALLTHROUGH */
- case ('4'):
+ case '4':
/* FALLTHROUGH */
- case ('I'):
+ case 'I':
/* FALLTHROUGH */
- case ('P'):
+ case 'P':
/* FALLTHROUGH */
- case ('R'):
+ case 'R':
if ('\0' == cp[1])
ok = 1;
break;
- case ('B'):
+ case 'B':
if ('\0' == cp[1] || ('I' == cp[1] && '\0' == cp[2]))
ok = 1;
break;
- case ('C'):
+ case 'C':
if ('W' == cp[1] && '\0' == cp[2])
ok = 1;
break;
@@ -301,69 +242,51 @@ post_ft(CHKARGS)
}
if (0 == ok) {
- mandoc_vmsg
- (MANDOCERR_BADFONT, man->parse,
- n->line, n->pos, "%s", cp);
+ mandoc_vmsg(MANDOCERR_FT_BAD, man->parse,
+ n->line, n->pos, "ft %s", cp);
*cp = '\0';
}
if (1 < n->nchild)
- mandoc_vmsg
- (MANDOCERR_ARGCOUNT, man->parse, n->line,
- n->pos, "want one child (have %d)",
- n->nchild);
+ mandoc_vmsg(MANDOCERR_ARGCOUNT, man->parse, n->line,
+ n->pos, "want one child (have %d)", n->nchild);
return(1);
}
-static int
-pre_sec(CHKARGS)
-{
-
- if (MAN_BLOCK == n->type)
- man->flags &= ~MAN_LITERAL;
- return(1);
-}
-
-static int
-post_sec(CHKARGS)
-{
-
- if ( ! (MAN_HEAD == n->type && 0 == n->nchild))
- return(1);
-
- man_nmsg(man, n, MANDOCERR_SYNTARGCOUNT);
- return(0);
-}
-
static int
check_part(CHKARGS)
{
if (MAN_BODY == n->type && 0 == n->nchild)
- mandoc_msg(MANDOCERR_ARGCWARN, man->parse, n->line,
- n->pos, "want children (have none)");
+ mandoc_msg(MANDOCERR_ARGCWARN, man->parse, n->line,
+ n->pos, "want children (have none)");
return(1);
}
-
static int
check_par(CHKARGS)
{
switch (n->type) {
- case (MAN_BLOCK):
+ case MAN_BLOCK:
if (0 == n->body->nchild)
man_node_delete(man, n);
break;
- case (MAN_BODY):
+ case MAN_BODY:
if (0 == n->nchild)
- man_nmsg(man, n, MANDOCERR_IGNPAR);
+ mandoc_vmsg(MANDOCERR_PAR_SKIP,
+ man->parse, n->line, n->pos,
+ "%s empty", man_macronames[n->tok]);
break;
- case (MAN_HEAD):
+ case MAN_HEAD:
if (n->nchild)
- man_nmsg(man, n, MANDOCERR_ARGSLOST);
+ mandoc_vmsg(MANDOCERR_ARG_SKIP,
+ man->parse, n->line, n->pos,
+ "%s %s%s", man_macronames[n->tok],
+ n->child->string,
+ n->nchild > 1 ? " ..." : "");
break;
default:
break;
@@ -377,13 +300,15 @@ post_IP(CHKARGS)
{
switch (n->type) {
- case (MAN_BLOCK):
+ case MAN_BLOCK:
if (0 == n->head->nchild && 0 == n->body->nchild)
man_node_delete(man, n);
break;
- case (MAN_BODY):
+ case MAN_BODY:
if (0 == n->parent->head->nchild && 0 == n->nchild)
- man_nmsg(man, n, MANDOCERR_IGNPAR);
+ mandoc_vmsg(MANDOCERR_PAR_SKIP,
+ man->parse, n->line, n->pos,
+ "%s empty", man_macronames[n->tok]);
break;
default:
break;
@@ -394,8 +319,10 @@ post_IP(CHKARGS)
static int
post_TH(CHKARGS)
{
+ struct man_node *nb;
const char *p;
- int line, pos;
+
+ check_le5(man, n);
free(man->meta.title);
free(man->meta.vol);
@@ -403,10 +330,10 @@ post_TH(CHKARGS)
free(man->meta.msec);
free(man->meta.date);
- line = n->line;
- pos = n->pos;
man->meta.title = man->meta.vol = man->meta.date =
- man->meta.msec = man->meta.source = NULL;
+ man->meta.msec = man->meta.source = NULL;
+
+ nb = n;
/* ->TITLE<- MSEC DATE SOURCE VOL */
@@ -414,15 +341,21 @@ post_TH(CHKARGS)
if (n && n->string) {
for (p = n->string; '\0' != *p; p++) {
/* Only warn about this once... */
- if (isalpha((unsigned char)*p) &&
- ! isupper((unsigned char)*p)) {
- man_nmsg(man, n, MANDOCERR_UPPERCASE);
+ if (isalpha((unsigned char)*p) &&
+ ! isupper((unsigned char)*p)) {
+ mandoc_vmsg(MANDOCERR_TITLE_CASE,
+ man->parse, n->line,
+ n->pos + (p - n->string),
+ "TH %s", n->string);
break;
}
}
man->meta.title = mandoc_strdup(n->string);
- } else
+ } else {
man->meta.title = mandoc_strdup("");
+ mandoc_msg(MANDOCERR_TH_NOTITLE, man->parse,
+ nb->line, nb->pos, "TH");
+ }
/* TITLE ->MSEC<- DATE SOURCE VOL */
@@ -430,19 +363,27 @@ post_TH(CHKARGS)
n = n->next;
if (n && n->string)
man->meta.msec = mandoc_strdup(n->string);
- else
+ else {
man->meta.msec = mandoc_strdup("");
+ mandoc_vmsg(MANDOCERR_MSEC_MISSING, man->parse,
+ nb->line, nb->pos, "TH %s", man->meta.title);
+ }
/* TITLE MSEC ->DATE<- SOURCE VOL */
if (n)
n = n->next;
if (n && n->string && '\0' != n->string[0]) {
- pos = n->pos;
- man->meta.date = mandoc_normdate
- (man->parse, n->string, line, pos);
- } else
+ man->meta.date = man->quick ?
+ mandoc_strdup(n->string) :
+ mandoc_normdate(man->parse, n->string,
+ n->line, n->pos);
+ } else {
man->meta.date = mandoc_strdup("");
+ mandoc_msg(MANDOCERR_DATE_MISSING, man->parse,
+ n ? n->line : nb->line,
+ n ? n->pos : nb->pos, "TH");
+ }
/* TITLE MSEC DATE ->SOURCE<- VOL */
@@ -470,8 +411,11 @@ static int
post_nf(CHKARGS)
{
+ check_eq0(man, n);
+
if (MAN_LITERAL & man->flags)
- man_nmsg(man, n, MANDOCERR_SCOPEREP);
+ mandoc_msg(MANDOCERR_NF_SKIP, man->parse,
+ n->line, n->pos, "nf");
man->flags |= MAN_LITERAL;
return(1);
@@ -481,8 +425,11 @@ static int
post_fi(CHKARGS)
{
+ check_eq0(man, n);
+
if ( ! (MAN_LITERAL & man->flags))
- man_nmsg(man, n, MANDOCERR_WNOSCOPE);
+ mandoc_msg(MANDOCERR_FI_SKIP, man->parse,
+ n->line, n->pos, "fi");
man->flags &= ~MAN_LITERAL;
return(1);
@@ -568,17 +515,24 @@ static int
post_vs(CHKARGS)
{
+ if (n->tok == MAN_br)
+ check_eq0(man, n);
+ else
+ check_le1(man, n);
+
if (NULL != n->prev)
return(1);
switch (n->parent->tok) {
- case (MAN_SH):
+ case MAN_SH:
/* FALLTHROUGH */
- case (MAN_SS):
- man_nmsg(man, n, MANDOCERR_IGNPAR);
+ case MAN_SS:
+ mandoc_vmsg(MANDOCERR_PAR_SKIP, man->parse, n->line, n->pos,
+ "%s after %s", man_macronames[n->tok],
+ man_macronames[n->parent->tok]);
/* FALLTHROUGH */
- case (MAN_MAX):
- /*
+ case MAN_MAX:
+ /*
* Don't warn about this because it occurs in pod2man
* and would cause considerable (unfixable) warnage.
*/
diff --git a/contrib/mdocml/mandoc.1 b/contrib/mdocml/mandoc.1
index 0657bc66c559..e9f7375777bc 100644
--- a/contrib/mdocml/mandoc.1
+++ b/contrib/mdocml/mandoc.1
@@ -1,7 +1,7 @@
-.\" $Id: mandoc.1,v 1.103 2013/07/13 19:41:16 schwarze Exp $
+.\" $Id: mandoc.1,v 1.106 2014/08/08 01:50:59 schwarze Exp $
.\"
.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons
-.\" Copyright (c) 2012 Ingo Schwarze
+.\" Copyright (c) 2012, 2014 Ingo Schwarze
.\"
.\" Permission to use, copy, modify, and distribute this software for any
.\" purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
-.Dd $Mdocdate: July 13 2013 $
+.Dd $Mdocdate: August 8 2014 $
.Dt MANDOC 1
.Os
.Sh NAME
@@ -496,30 +496,28 @@ parser:
.Pp
.Dl $ mandoc \-Tman foo.mdoc \*(Gt foo.man
.Sh DIAGNOSTICS
-Standard error messages reporting parsing errors are prefixed by
+Messages displayed by
+.Nm
+follow this format:
.Pp
-.Sm off
-.D1 Ar file : line : column : \ level :
-.Sm on
+.D1 Nm Ns : Ar file : Ns Ar line : Ns Ar column : level : message : macro args
.Pp
-where the fields have the following meanings:
-.Bl -tag -width "column"
-.It Ar file
-The name of the input file causing the message.
-.It Ar line
-The line number in that input file.
-Line numbering starts at 1.
-.It Ar column
-The column number in that input file.
-Column numbering starts at 1.
-If the issue is caused by a word, the column number usually
-points to the first character of the word.
-.It Ar level
-The message level, printed in capital letters.
-.El
+Line and column numbers start at 1.
+Both are omitted for messages referring to an input file as a whole.
+Macro names and arguments are omitted where meaningless.
+Fatal messages about invalid command line arguments
+or operating system errors, for example when memory is exhausted,
+may also omit the
+.Ar file
+and
+.Ar level
+fields.
.Pp
Message levels have the following meanings:
.Bl -tag -width "warning"
+.It Cm syserr
+Opening or reading an input file failed, so the parser cannot
+even be started and no output is produced from that input file.
.It Cm fatal
The parser is unable to parse a given input file at all.
No formatted output is produced from that input file.
@@ -551,13 +549,836 @@ levels are hidden unless their level, or a lower level, is requested using a
option or
.Fl T Ns Cm lint
output mode.
-.Pp
+.Ss Warnings related to the document prologue
+.Bl -ohang
+.It Sy "missing manual title, using UNTITLED"
+.Pq mdoc
+A
+.Ic \&Dt
+macro has no arguments, or there is no
+.Ic \&Dt
+macro before the first non-prologue macro.
+.It Sy "missing manual title, using \(dq\(dq"
+.Pq man
+There is no
+.Ic \&TH
+macro, or it has no arguments.
+.It Sy "lower case character in document title"
+.Pq mdoc , man
+The title is still used as given in the
+.Ic \&Dt
+or
+.Ic \&TH
+macro.
+.It Sy "missing manual section, using \(dq\(dq"
+.Pq mdoc , man
+A
+.Ic \&Dt
+or
+.Ic \&TH
+macro lacks the mandatory section argument.
+.It Sy "unknown manual section"
+.Pq mdoc
+The section number in a
+.Ic \&Dt
+line is invalid, but still used.
+.It Sy "unknown manual volume or arch"
+.Pq mdoc
+The volume name in a
+.Ic \&Dt
+line is invalid, but still used.
+The manual is assumed to be architecture-independent.
+.It Sy "missing date, using today's date"
+.Pq mdoc, man
+The document was parsed as
+.Xr mdoc 7
+and it has no
+.Ic \&Dd
+macro, or the
+.Ic \&Dd
+macro has no arguments or only empty arguments;
+or the document was parsed as
+.Xr man 7
+and it has no
+.Ic \&TH
+macro, or the
+.Ic \&TH
+macro has less than three arguments or its third argument is empty.
+.It Sy "cannot parse date, using it verbatim"
+.Pq mdoc , man
+The date given in a
+.Ic \&Dd
+or
+.Ic \&TH
+macro does not follow the conventional format.
+.It Sy "missing Os macro, using \(dq\(dq"
+.Pq mdoc
+The default or current system is not shown in this case.
+.It Sy "duplicate prologue macro"
+.Pq mdoc
+One of the prologue macros occurs more than once.
+The last instance overrides all previous ones.
+.It Sy "late prologue macro"
+.Pq mdoc
+A
+.Ic \&Dd
+or
+.Ic \&Os
+macro occurs after some non-prologue macro, but still takes effect.
+.It Sy "skipping late title macro"
+.Pq mdoc
+The
+.Ic \&Dt
+macro can only occur before the first non-prologue macro
+because traditional formatters write the page header
+before parsing the document body.
+Even though this technical restriction does not apply to
+.Nm ,
+traditional semantics is preserved.
+The late macro is discarded including its arguments.
+.It Sy "prologue macros out of order"
+.Pq mdoc
+The prologue macros are not given in the conventional order
+.Ic \&Dd ,
+.Ic \&Dt ,
+.Ic \&Os .
+All three macros are used even when given in another order.
+.El
+.Ss Warnings regarding document structure
+.Bl -ohang
+.It Sy ".so is fragile, better use ln(1)"
+.Pq roff
+Including files only works when the parser program runs with the correct
+current working directory.
+.It Sy "no document body"
+.Pq mdoc , man
+The document body contains neither text nor macros.
+An empty document is shown, consisting only of a header and a footer line.
+.It Sy "content before first section header"
+.Pq mdoc , man
+Some macros or text precede the first
+.Ic \&Sh
+or
+.Ic \&SH
+section header.
+The offending macros and text are parsed and added to the top level
+of the syntax tree, outside any section block.
+.It Sy "first section is not NAME"
+.Pq mdoc
+The argument of the first
+.Ic \&Sh
+macro is not
+.Sq NAME .
+This may confuse
+.Xr makewhatis 8
+and
+.Xr apropos 1 .
+.It Sy "bad NAME section contents"
+.Pq mdoc
+The last node in the NAME section is not an
+.Ic \&Nd
+macro, or any preceding macro is not
+.Ic \&Nm ,
+or the NAME section is completely empty.
+This may confuse
+.Xr makewhatis 8
+and
+.Xr apropos 1 .
+.It Sy "sections out of conventional order"
+.Pq mdoc
+A standard section occurs after another section it usually precedes.
+All section titles are used as given,
+and the order of sections is not changed.
+.It Sy "duplicate section title"
+.Pq mdoc
+The same standard section title occurs more than once.
+.It Sy "unexpected section"
+.Pq mdoc
+A standard section header occurs in a section of the manual
+where it normally isn't useful.
+.El
+.Ss "Warnings related to macros and nesting"
+.Bl -ohang
+.It Sy "obsolete macro"
+.Pq mdoc
+See the
+.Xr mdoc 7
+manual for replacements.
+.It Sy "skipping paragraph macro"
+In
+.Xr mdoc 7
+documents, this happens
+.Bl -dash -compact
+.It
+at the beginning and end of sections and subsections
+.It
+right before non-compact lists and displays
+.It
+at the end of items in non-column, non-compact lists
+.It
+and for multiple consecutive paragraph macros.
+.El
+In
+.Xr man 7
+documents, it happens
+.Bl -dash -compact
+.It
+for empty
+.Ic \&P ,
+.Ic \&PP ,
+and
+.Ic \&LP
+macros
+.It
+for
+.Ic \&IP
+macros having neither head nor body arguments
+.It
+for
+.Ic \&br
+or
+.Ic \&sp
+right after
+.Ic \&SH
+or
+.Ic \&SS
+.El
+.It Sy "moving paragraph macro out of list"
+.Pq mdoc
+A list item in a
+.Ic \&Bl
+list contains a trailing paragraph macro.
+The paragraph macro is moved after the end of the list.
+.It Sy "skipping no-space macro"
+.Pq mdoc
+An input line begins with an
+.Ic \&Ns
+macro.
+The macro is ignored.
+.It Sy "blocks badly nested"
+.Pq mdoc
+If two blocks intersect, one should completely contain the other.
+Otherwise, rendered output is likely to look strange in any output
+format, and rendering in SGML-based output formats is likely to be
+outright wrong because such languages do not support badly nested
+blocks at all.
+Typical examples of badly nested blocks are
+.Qq Ic \&Ao \&Bo \&Ac \&Bc
+and
+.Qq Ic \&Ao \&Bq \&Ac .
+In these examples,
+.Ic \&Ac
+breaks
+.Ic \&Bo
+and
+.Ic \&Bq ,
+respectively.
+.It Sy "nested displays are not portable"
+.Pq mdoc
+A
+.Ic \&Bd ,
+.Ic \&D1 ,
+or
+.Ic \&Dl
+display occurs nested inside another
+.Ic \&Bd
+display.
+This works with
+.Nm ,
+but fails with most other implementations.
+.It Sy "moving content out of list"
+.Pq mdoc
+A
+.Ic \&Bl
+list block contains text or macros before the first
+.Ic \&It
+macro.
+The offending children are moved before the beginning of the list.
+.It Sy ".Vt block has child macro"
+.Pq mdoc
+The
+.Ic \&Vt
+macro supports plain text arguments only.
+Formatting may be ugly and semantic searching
+for the affected content might not work.
+.It Sy "fill mode already enabled, skipping"
+.Pq man
+A
+.Ic \&fi
+request occurs even though the document is still in fill mode,
+or already switched back to fill mode.
+It has no effect.
+.It Sy "fill mode already disabled, skipping"
+.Pq man
+An
+.Ic \&nf
+request occurs even though the document already switched to no-fill mode
+and did not switch back to fill mode yet.
+It has no effect.
+.It Sy "line scope broken"
+.Pq man
+While parsing the next-line scope of the previous macro,
+another macro is found that prematurely terminates the previous one.
+The previous, interrupted macro is deleted from the parse tree.
+.El
+.Ss "Warnings related to missing arguments"
+.Bl -ohang
+.It Sy "skipping empty request"
+.Pq roff
+The macro name is missing from a macro definition request.
+.It Sy "conditional request controls empty scope"
+.Pq roff
+A conditional request is only useful if any of the following
+follows it on the same logical input line:
+.Bl -dash -compact
+.It
+The
+.Sq \e{
+keyword to open a multi-line scope.
+.It
+A request or macro or some text, resulting in a single-line scope.
+.It
+The immediate end of the logical line without any intervening whitespace,
+resulting in next-line scope.
+.El
+Here, a conditional request is followed by trailing whitespace only,
+and there is no other content on its logical input line.
+Note that it doesn't matter whether the logical input line is split
+across multiple physical input lines using
+.Sq \e
+line continuation characters.
+This is one of the rare cases
+where trailing whitespace is syntactically significant.
+The conditional request controls a scope containing whitespace only,
+so it is unlikely to have a significant effect,
+except that it may control a following
+.Ic \&el
+clause.
+.It Sy "skipping empty macro"
+.Pq mdoc
+The indicated macro has no arguments and hence no effect.
+.It Sy "empty argument, using 0n"
+.Pq mdoc
+The required width is missing after
+.Ic \&Bd
+or
+.Ic \&Bl
+.Fl offset
+or
+.Fl width.
+.It Sy "argument count wrong"
+.Pq mdoc , man
+The indicated macro has too few or too many arguments.
+The syntax tree will contain the wrong number of arguments as given.
+Formatting behaviour depends on the specific macro in question.
+Note that the same message may also occur as an ERROR, see below.
+.It Sy "missing display type, using -ragged"
+.Pq mdoc
+The
+.Ic \&Bd
+macro is invoked without the required display type.
+.It Sy "list type is not the first argument"
+.Pq mdoc
+In a
+.Ic \&Bl
+macro, at least one other argument precedes the type argument.
The
.Nm
-utility may also print messages related to invalid command line arguments
-or operating system errors, for example when memory is exhausted or
-input files cannot be read.
-Such messages do not carry the prefix described above.
+utility copes with any argument order, but some other
+.Xr mdoc 7
+implementations do not.
+.It Sy "missing -width in -tag list, using 8n"
+.Pq mdoc
+Every
+.Ic \&Bl
+macro having the
+.Fl tag
+argument requires
+.Fl width ,
+too.
+.It Sy "missing utility name, using \(dq\(dq"
+.Pq mdoc
+The
+.Ic \&Ex Fl std
+macro is called without an argument before
+.Ic \&Nm
+has first been called with an argument.
+.It Sy "empty head in list item"
+.Pq mdoc
+In a
+.Ic \&Bl
+.Fl diag ,
+.Fl hang ,
+.Fl inset ,
+.Fl ohang ,
+or
+.Fl tag
+list, an
+.Ic \&It
+macro lacks the required argument.
+The item head is left empty.
+.It Sy "empty list item"
+.Pq mdoc
+In a
+.Ic \&Bl
+.Fl bullet ,
+.Fl dash ,
+.Fl enum ,
+or
+.Fl hyphen
+list, an
+.Ic \&It
+block is empty.
+An empty list item is shown.
+.It Sy "missing font type"
+.Pq mdoc
+A
+.Ic \&Bf
+macro has no argument.
+It switches to the default font,
+.Cm \efR .
+.It Sy "unknown font type"
+.Pq mdoc
+The
+.Ic \&Bf
+argument is invalid.
+The default font
+.Cm \efR
+is used instead.
+.It Sy "missing -std argument, adding it"
+.Pq mdoc
+An
+.Ic \&Ex
+or
+.Ic \&Rv
+macro lacks the required
+.Fl std
+argument.
+The
+.Nm
+utility assumes
+.Fl std
+even when it is not specified, but other implementations may not.
+.El
+.Ss "Warnings related to bad macro arguments"
+.Bl -ohang
+.It Sy "unterminated quoted argument"
+.Pq roff
+Macro arguments can be enclosed in double quote characters
+such that space characters and macro names contained in the quoted
+argument need not be escaped.
+The closing quote of the last argument of a macro can be omitted.
+However, omitting it is not recommended because it makes the code
+harder to read.
+.It Sy "duplicate argument"
+.Pq mdoc
+A
+.Ic \&Bd
+or
+.Ic \&Bl
+macro has more than one
+.Fl compact ,
+more than one
+.Fl offset ,
+or more than one
+.Fl width
+argument.
+All but the last instances of these arguments are ignored.
+.It Sy "skipping duplicate argument"
+.Pq mdoc
+An
+.Ic \&An
+macro has more than one
+.Fl split
+or
+.Fl nosplit
+argument.
+All but the first of these arguments are ignored.
+.It Sy "skipping duplicate display type"
+.Pq mdoc
+A
+.Ic \&Bd
+macro has more than one type argument; the first one is used.
+.It Sy "skipping duplicate list type"
+.Pq mdoc
+A
+.Ic \&Bl
+macro has more than one type argument; the first one is used.
+.It Sy "skipping -width argument"
+.Pq mdoc
+A
+.Ic \&Bl
+.Fl column ,
+.Fl diag ,
+.Fl ohang ,
+.Fl inset ,
+or
+.Fl item
+list has a
+.Fl width
+argument.
+That has no effect.
+.It Sy "unknown AT&T UNIX version"
+.Pq mdoc
+An
+.Ic \&At
+macro has an invalid argument.
+It is used verbatim, with
+.Qq "AT&T UNIX "
+prefixed to it.
+.It Sy "invalid content in Rs block"
+.Pq mdoc
+An
+.Ic \&Rs
+block contains plain text or non-% macros.
+The bogus content is left in the syntax tree.
+Formatting may be poor.
+.It Sy "invalid Boolean argument"
+.Pq mdoc
+An
+.Ic \&Sm
+macro has an argument other than
+.Cm on
+or
+.Cm off .
+The invalid argument is moved out of the macro, which leaves the macro
+empty, causing it to toggle the spacing mode.
+.It Sy "unknown font, skipping request"
+.Pq man
+A
+.Xr roff 7
+.Ic \&ft
+request has an invalid argument.
+.El
+.Ss "Warnings related to plain text"
+.Bl -ohang
+.It Sy "blank line in fill mode, using .sp"
+.Pq mdoc
+The meaning of blank input lines is only well-defined in non-fill mode:
+In fill mode, line breaks of text input lines are not supposed to be
+significant.
+However, for compatibility with groff, blank lines in fill mode
+are replaced with
+.Ic \&sp
+requests.
+.It Sy "tab in filled text"
+.Pq mdoc , man
+The meaning of tab characters is only well-defined in non-fill mode:
+In fill mode, whitespace is not supposed to be significant
+on text input lines.
+As an implementation dependent choice, tab characters on text lines
+are passed through to the formatters in any case.
+Given that the text before the tab character will be filled,
+it is hard to predict which tab stop position the tab will advance to.
+.It Sy "whitespace at end of input line"
+.Pq mdoc , man , roff
+Whitespace at the end of input lines is almost never semantically
+significant \(em but in the odd case where it might be, it is
+extremely confusing when reviewing and maintaining documents.
+.It Sy "bad comment style"
+.Pq roff
+Comment lines start with a dot, a backslash, and a double-quote character.
+The
+.Nm
+utility treats the line as a comment line even without the backslash,
+but leaving out the backslash might not be portable.
+.It Sy "invalid escape sequence"
+.Pq roff
+An escape sequence has an invalid opening argument delimiter, lacks the
+closing argument delimiter, or the argument has too few characters.
+If the argument is incomplete,
+.Ic \e*
+and
+.Ic \en
+expand to an empty string,
+.Ic \eB
+to the digit
+.Sq 0 ,
+and
+.Ic \ew
+to the length of the incomplete argument.
+All other invalid escape sequences are ignored.
+.It Sy "undefined string, using \(dq\(dq"
+.Pq roff
+If a string is used without being defined before,
+its value is implicitly set to the empty string.
+However, defining strings explicitly before use
+keeps the code more readable.
+.El
+.Ss "Errors related to equations"
+.Bl -inset -compact
+.It "unexpected equation scope closure"
+.It "equation scope open on exit"
+.It "overlapping equation scopes"
+.It "unexpected end of equation"
+.It "equation syntax error"
+.El
+.Ss "Errors related to tables"
+.Bl -inset -compact
+.It "bad table syntax"
+.It "bad table option"
+.It "bad table layout"
+.It "no table layout cells specified"
+.It "no table data cells specified"
+.It "ignore data in cell"
+.It "data block still open"
+.It "ignoring extra data cells"
+.El
+.Ss "Errors related to roff, mdoc, and man code"
+.Bl -ohang
+.It Sy "input stack limit exceeded, infinite loop?"
+.Pq roff
+Explicit recursion limits are implemented for the following features,
+in order to prevent infinite loops:
+.Bl -dash -compact
+.It
+expansion of nested escape sequences
+including expansion of strings and number registers,
+.It
+expansion of nested user-defined macros,
+.It
+and
+.Ic \&so
+file inclusion.
+.El
+When a limit is hit, the output is incorrect, typically losing
+some content, but the parser can continue.
+.It Sy "skipping bad character"
+.Pq mdoc , man , roff
+The input file contains a byte that is not a printable
+.Xr ascii 7
+character.
+The message mentions the character number.
+The offending byte is replaced with a question mark
+.Pq Sq \&? .
+Consider editing the input file to replace the byte with an ASCII
+transliteration of the intended character.
+.It Sy "skipping unknown macro"
+.Pq mdoc , man , roff
+The first identifier on a request or macro line is neither recognized as a
+.Xr roff 7
+request, nor as a user-defined macro, nor, respectively, as an
+.Xr mdoc 7
+or
+.Xr man 7
+macro.
+It may be mistyped or unsupported.
+The request or macro is discarded including its arguments.
+.It Sy "skipping item outside list"
+.Pq mdoc
+An
+.Ic \&It
+macro occurs outside any
+.Ic \&Bl
+list.
+It is discarded including its arguments.
+.It Sy "skipping column outside column list"
+.Pq mdoc
+A
+.Ic \&Ta
+macro occurs outside any
+.Ic \&Bl Fl column
+block.
+It is discarded including its arguments.
+.It Sy "skipping end of block that is not open"
+.Pq mdoc , man , eqn , tbl , roff
+Various syntax elements can only be used to explicitly close blocks
+that have previously been opened.
+An
+.Xr mdoc 7
+block closing macro, a
+.Xr man 7
+.Ic \&RE
+or
+.Ic \&UE
+macro, or the end of an equation, table, or
+.Xr roff 7
+conditional request is encountered but no matching block is open.
+The offending request or macro is discarded.
+.It Sy "inserting missing end of block"
+.Pq mdoc , tbl
+Various
+.Xr mdoc 7
+macros as well as tables require explicit closing by dedicated macros.
+A block that doesn't support bad nesting
+ends before all of its children are properly closed.
+The open child nodes are closed implicitly.
+.It Sy "scope open on exit"
+.Pq mdoc , man , eqn , tbl , roff
+At the end of the document, an explicit
+.Xr mdoc 7
+block, a
+.Xr man 7
+next-line scope or
+.Ic \&RS
+or
+.Ic \&UR
+block, an equation, table, or
+.Xr roff 7
+conditional or ignore block is still open.
+The open block is closed implicitly.
+.It Sy "escaped character not allowed in a name"
+.Pq roff
+Macro, string and register identifiers consist of printable,
+non-whitespace ASCII characters.
+Escape sequences and characters and strings expressed in terms of them
+cannot form part of a name.
+The first argument of an
+.Ic \&am ,
+.Ic \&as ,
+.Ic \&de ,
+.Ic \&ds ,
+.Ic \&nr ,
+or
+.Ic \&rr
+request, or any argument of an
+.Ic \&rm
+request, or the name of a request or user defined macro being called,
+is terminated by an escape sequence.
+In the cases of
+.Ic \&as ,
+.Ic \&ds ,
+and
+.Ic \&nr ,
+the request has no effect at all.
+In the cases of
+.Ic \&am ,
+.Ic \&de ,
+.Ic \&rr ,
+and
+.Ic \&rm ,
+what was parsed up to this point is used as the arguments to the request,
+and the rest of the input line is discarded including the escape sequence.
+When parsing for a request or a user-defined macro name to be called,
+only the escape sequence is discarded.
+The characters preceding it are used as the request or macro name,
+the characters following it are used as the arguments to the request or macro.
+.It Sy "argument count wrong"
+.Pq mdoc , man , roff
+The indicated request or macro has too few or too many arguments.
+The syntax tree will contain the wrong number of arguments as given.
+Formatting behaviour depends on the specific request or macro in question.
+Note that the same message may also occur as a WARNING, see above.
+.It Sy "missing list type, using -item"
+.Pq mdoc
+A
+.Ic \&Bl
+macro fails to specify the list type.
+.It Sy "missing manual name, using \(dq\(dq"
+.Pq mdoc
+The first call to
+.Ic \&Nm
+lacks the required argument.
+.It Sy "uname(3) system call failed, using UNKNOWN"
+.Pq mdoc
+The
+.Ic \&Os
+macro is called without arguments, and the
+.Xr uname 3
+system call failed.
+As a workaround,
+.Nm
+can be compiled with
+.Sm off
+.Fl D Cm OSNAME=\(dq\e\(dq Ar string Cm \e\(dq\(dq .
+.Sm on
+.It Sy "unknown standard specifier"
+.Pq mdoc
+An
+.Ic \&St
+macro has an unknown argument and is discarded.
+.It Sy "skipping request without numeric argument"
+.Pq roff
+An
+.Ic \&it
+request has a non-numeric or negative argument or no argument at all.
+The invalid request is ignored.
+.It Sy "skipping all arguments"
+.Pq mdoc , man , eqn , roff
+An
+.Xr mdoc 7
+.Ic \&Bt ,
+.Ic \&Ed ,
+.Ic \&Ef ,
+.Ic \&Ek ,
+.Ic \&El ,
+.Ic \&Re ,
+or
+.Ic \&Ud
+macro, an
+.Ic \&It
+macro in a list that don't support item heads, a
+.Xr man 7
+.Ic \&LP ,
+.Ic \&P ,
+or
+.Ic \&PP
+macro, an
+.Xr eqn 7
+.Ic \&EN
+macro, or a
+.Xr roff 7
+.Sq \&..
+block closing request is invoked with at least one argument.
+All arguments are ignored.
+.It Sy "skipping excess arguments"
+.Pq mdoc , roff
+The
+.Ic \&Bf
+macro is invoked with more than one argument, or a request of the
+.Ic \&de
+family is invoked with more than two arguments.
+The excess arguments are ignored.
+.El
+.Ss FATAL errors
+.Bl -ohang
+.It Sy "input too large"
+.Pq mdoc , man
+Currently,
+.Nm
+cannot handle input files larger than its arbitrary size limit
+of 2^31 bytes (2 Gigabytes).
+Since useful manuals are always small, this is not a problem in practice.
+Parsing is aborted as soon as the condition is detected.
+.It Sy "NOT IMPLEMENTED: Bd -file"
+.Pq mdoc
+For security reasons, the
+.Ic \&Bd
+macro does not support the
+.Fl file
+argument.
+By requesting the inclusion of a sensitive file, a malicious document
+might otherwise trick a privileged user into inadvertently displaying
+the file on the screen, revealing the file content to bystanders.
+The parser exits immediately.
+.It Sy "NOT IMPLEMENTED: .so with absolute path or \(dq..\(dq"
+.Pq roff
+For security reasons,
+.Nm
+allows
+.Ic \&so
+file inclusion requests only with relative paths
+and only without ascending to any parent directory.
+By requesting the inclusion of a sensitive file, a malicious document
+might otherwise trick a privileged user into inadvertently displaying
+the file on the screen, revealing the file content to bystanders.
+The parser exits immediately.
+.It Sy ".so request failed"
+.Pq roff
+Servicing a
+.Ic \&so
+request requires reading an external file.
+While trying to do so, an
+.Xr open 2 ,
+.Xr stat 2 ,
+or
+.Xr read 2
+system call failed.
+The parser exits immediately.
+Before showing this message,
+.Nm
+always shows another message explaining why the system call failed.
+.El
.Sh COMPATIBILITY
This section summarises
.Nm
diff --git a/contrib/mdocml/mandoc.3 b/contrib/mdocml/mandoc.3
index fe6503d5477e..8f76ad21fff4 100644
--- a/contrib/mdocml/mandoc.3
+++ b/contrib/mdocml/mandoc.3
@@ -1,4 +1,4 @@
-.\" $Id: mandoc.3,v 1.22 2013/10/06 17:01:52 schwarze Exp $
+.\" $Id: mandoc.3,v 1.25 2014/08/05 05:48:56 schwarze Exp $
.\"
.\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons
.\" Copyright (c) 2010 Ingo Schwarze
@@ -15,21 +15,16 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
-.Dd $Mdocdate: October 6 2013 $
+.Dd $Mdocdate: August 5 2014 $
.Dt MANDOC 3
.Os
.Sh NAME
.Nm mandoc ,
-.Nm mandoc_escape ,
+.Nm man_deroff ,
.Nm man_meta ,
.Nm man_mparse ,
.Nm man_node ,
-.Nm mchars_alloc ,
-.Nm mchars_free ,
-.Nm mchars_num2char ,
-.Nm mchars_num2uc ,
-.Nm mchars_spec2cp ,
-.Nm mchars_spec2str ,
+.Nm mdoc_deroff ,
.Nm mdoc_meta ,
.Nm mdoc_node ,
.Nm mparse_alloc ,
@@ -45,68 +40,32 @@
.Sh LIBRARY
.Lb libmandoc
.Sh SYNOPSIS
-.In man.h
-.In mdoc.h
+.In sys/types.h
.In mandoc.h
-.Ft "enum mandoc_esc"
-.Fo mandoc_escape
-.Fa "const char const **end"
-.Fa "const char const **start"
-.Fa "int *sz"
-.Fc
-.Ft "const struct man_meta *"
-.Fo man_meta
-.Fa "const struct man *man"
-.Fc
-.Ft "const struct mparse *"
-.Fo man_mparse
-.Fa "const struct man *man"
-.Fc
-.Ft "const struct man_node *"
-.Fo man_node
-.Fa "const struct man *man"
-.Fc
-.Ft "struct mchars *"
-.Fn mchars_alloc "void"
-.Ft void
-.Fn mchars_free "struct mchars *p"
-.Ft char
-.Fn mchars_num2char "const char *cp" "size_t sz"
-.Ft int
-.Fn mchars_num2uc "const char *cp" "size_t sz"
-.Ft "const char *"
-.Fo mchars_spec2str
-.Fa "const struct mchars *p"
-.Fa "const char *cp"
-.Fa "size_t sz"
-.Fa "size_t *rsz"
-.Fc
-.Ft int
-.Fo mchars_spec2cp
-.Fa "const struct mchars *p"
-.Fa "const char *cp"
-.Fa "size_t sz"
-.Fc
-.Ft "const struct mdoc_meta *"
-.Fo mdoc_meta
-.Fa "const struct mdoc *mdoc"
-.Fc
-.Ft "const struct mdoc_node *"
-.Fo mdoc_node
-.Fa "const struct mdoc *mdoc"
-.Fc
-.Ft void
+.Fd "#define ASCII_NBRSP"
+.Fd "#define ASCII_HYPH"
+.Fd "#define ASCII_BREAK"
+.Ft struct mparse *
.Fo mparse_alloc
-.Fa "enum mparset type"
+.Fa "int options"
.Fa "enum mandoclevel wlevel"
-.Fa "mandocmsg msg"
-.Fa "void *msgarg"
+.Fa "mandocmsg mmsg"
+.Fa "char *defos"
+.Fc
+.Ft void
+.Fo (*mandocmsg)
+.Fa "enum mandocerr errtype"
+.Fa "enum mandoclevel level"
+.Fa "const char *file"
+.Fa "int line"
+.Fa "int col"
+.Fa "const char *msg"
.Fc
.Ft void
.Fo mparse_free
.Fa "struct mparse *parse"
.Fc
-.Ft void
+.Ft const char *
.Fo mparse_getkeep
.Fa "const struct mparse *parse"
.Fc
@@ -129,6 +88,7 @@
.Fa "struct mparse *parse"
.Fa "struct mdoc **mdoc"
.Fa "struct man **man"
+.Fa "char **sodest"
.Fc
.Ft "const char *"
.Fo mparse_strerror
@@ -138,11 +98,45 @@
.Fo mparse_strlevel
.Fa "enum mandoclevel"
.Fc
-.Vt extern const char * const * man_macronames;
+.In sys/types.h
+.In mandoc.h
+.In mdoc.h
+.Ft void
+.Fo mdoc_deroff
+.Fa "char **dest"
+.Fa "const struct mdoc_node *node"
+.Fc
+.Ft "const struct mdoc_meta *"
+.Fo mdoc_meta
+.Fa "const struct mdoc *mdoc"
+.Fc
+.Ft "const struct mdoc_node *"
+.Fo mdoc_node
+.Fa "const struct mdoc *mdoc"
+.Fc
.Vt extern const char * const * mdoc_argnames;
.Vt extern const char * const * mdoc_macronames;
-.Fd "#define ASCII_NBRSP"
-.Fd "#define ASCII_HYPH"
+.In sys/types.h
+.In mandoc.h
+.In man.h
+.Ft void
+.Fo man_deroff
+.Fa "char **dest"
+.Fa "const struct man_node *node"
+.Fc
+.Ft "const struct man_meta *"
+.Fo man_meta
+.Fa "const struct man *man"
+.Fc
+.Ft "const struct mparse *"
+.Fo man_mparse
+.Fa "const struct man *man"
+.Fc
+.Ft "const struct man_node *"
+.Fo man_node
+.Fa "const struct man *man"
+.Fc
+.Vt extern const char * const * man_macronames;
.Sh DESCRIPTION
The
.Nm mandoc
@@ -184,37 +178,22 @@ or invoke
.Fn mparse_reset
and parse new files.
.El
-.Pp
-The
-.Nm
-library also contains routines for translating character strings into glyphs
-.Pq see Fn mchars_alloc
-and parsing escape sequences from strings
-.Pq see Fn mandoc_escape .
.Sh REFERENCE
This section documents the functions, types, and variables available
via
-.In mandoc.h .
+.In mandoc.h ,
+with the exception of those documented in
+.Xr mandoc_escape 3
+and
+.Xr mchars_alloc 3 .
.Ss Types
.Bl -ohang
-.It Vt "enum mandoc_esc"
-An escape sequence classification.
.It Vt "enum mandocerr"
A fatal error, error, or warning message during parsing.
.It Vt "enum mandoclevel"
A classification of an
-.Vt "enum mandoclevel"
+.Vt "enum mandocerr"
as regards system operation.
-.It Vt "struct mchars"
-An opaque pointer to an object allowing for translation between
-character strings and glyphs.
-See
-.Fn mchars_alloc .
-.It Vt "enum mparset"
-The type of parser when reading input.
-This should usually be
-.Dv MPARSE_AUTO
-for auto-detection.
.It Vt "struct mparse"
An opaque pointer to a running parse sequence.
Created with
@@ -230,38 +209,20 @@ messages emitted by the parser.
.El
.Ss Functions
.Bl -ohang
-.It Fn mandoc_escape
-Scan an escape sequence, i.e., a character string beginning with
-.Sq \e .
-Pass a pointer to the character after the
-.Sq \e
-as
-.Va end ;
-it will be set to the supremum of the parsed escape sequence unless
-returning
-.Dv ESCAPE_ERROR ,
-in which case the string is bogus and should be
-thrown away.
-If not
-.Dv ESCAPE_ERROR
-or
-.Dv ESCAPE_IGNORE ,
-.Va start
-is set to the first relevant character of the substring (font, glyph,
-whatever) of length
-.Va sz .
-Both
-.Va start
-and
-.Va sz
-may be
-.Dv NULL .
-Declared in
-.In mandoc.h ,
-implemented in
-.Pa mandoc.c .
+.It Fn man_deroff
+Obtain a text-only representation of a
+.Vt struct man_node ,
+including text contained in its child nodes.
+To be used on children of the pointer returned from
+.Fn man_node .
+When it is no longer needed, the pointer returned from
+.Fn man_deroff
+can be passed to
+.Xr free 3 .
.It Fn man_meta
-Obtain the meta-data of a successful parse.
+Obtain the meta-data of a successful
+.Xr man 7
+parse.
This may only be used on a pointer returned by
.Fn mparse_result .
Declared in
@@ -275,67 +236,29 @@ Declared in
implemented in
.Pa man.c .
.It Fn man_node
-Obtain the root node of a successful parse.
+Obtain the root node of a successful
+.Xr man 7
+parse.
This may only be used on a pointer returned by
.Fn mparse_result .
Declared in
.In man.h ,
implemented in
.Pa man.c .
-.It Fn mchars_alloc
-Allocate an
-.Vt "struct mchars *"
-object for translating special characters into glyphs.
-See
-.Xr mandoc_char 7
-for an overview of special characters.
-The object must be freed with
-.Fn mchars_free .
-Declared in
-.In mandoc.h ,
-implemented in
-.Pa chars.c .
-.It Fn mchars_free
-Free an object created with
-.Fn mchars_alloc .
-Declared in
-.In mandoc.h ,
-implemented in
-.Pa chars.c .
-.It Fn mchars_num2char
-Convert a character index (e.g., the \eN\(aq\(aq escape) into a
-printable ASCII character.
-Returns \e0 (the nil character) if the input sequence is malformed.
-Declared in
-.In mandoc.h ,
-implemented in
-.Pa chars.c .
-.It Fn mchars_num2uc
-Convert a hexadecimal character index (e.g., the \e[uNNNN] escape) into
-a Unicode codepoint.
-Returns \e0 (the nil character) if the input sequence is malformed.
-Declared in
-.In mandoc.h ,
-implemented in
-.Pa chars.c .
-.It Fn mchars_spec2cp
-Convert a special character into a valid Unicode codepoint.
-Returns \-1 on failure or a non-zero Unicode codepoint on success.
-Declared in
-.In mandoc.h ,
-implemented in
-.Pa chars.c .
-.It Fn mchars_spec2str
-Convert a special character into an ASCII string.
-Returns
-.Dv NULL
-on failure.
-Declared in
-.In mandoc.h ,
-implemented in
-.Pa chars.c .
+.It Fn mdoc_deroff
+Obtain a text-only representation of a
+.Vt struct mdoc_node ,
+including text contained in its child nodes.
+To be used on children of the pointer returned from
+.Fn mdoc_node .
+When it is no longer needed, the pointer returned from
+.Fn mdoc_deroff
+can be passed to
+.Xr free 3 .
.It Fn mdoc_meta
-Obtain the meta-data of a successful parse.
+Obtain the meta-data of a successful
+.Xr mdoc
+parse.
This may only be used on a pointer returned by
.Fn mparse_result .
Declared in
@@ -343,7 +266,9 @@ Declared in
implemented in
.Pa mdoc.c .
.It Fn mdoc_node
-Obtain the root node of a successful parse.
+Obtain the root node of a successful
+.Xr mdoc
+parse.
This may only be used on a pointer returned by
.Fn mparse_result .
Declared in
@@ -352,6 +277,57 @@ implemented in
.Pa mdoc.c .
.It Fn mparse_alloc
Allocate a parser.
+The arguments have the following effect:
+.Bl -tag -offset 5n -width inttype
+.It Ar options
+When the
+.Dv MPARSE_MDOC
+or
+.Dv MPARSE_MAN
+bit is set, only that parser is used.
+Otherwise, the document type is automatically detected.
+.Pp
+When the
+.Dv MPARSE_SO
+bit is set,
+.Xr roff 7
+.Ic \&so
+file inclusion requests are always honoured.
+Otherwise, if the request is the only content in an input file,
+only the file name is remembered, to be returned in the
+.Fa sodest
+argument of
+.Fn mparse_result .
+.Pp
+When the
+.Dv MPARSE_QUICK
+bit is set, parsing is aborted after the NAME section.
+This is for example useful in
+.Xr makewhatis 8
+.Fl Q
+to quickly build minimal databases.
+.It Ar wlevel
+Can be set to
+.Dv MANDOCLEVEL_FATAL ,
+.Dv MANDOCLEVEL_ERROR ,
+or
+.Dv MANDOCLEVEL_WARNING .
+Messages below the selected level will be suppressed.
+.It Ar mmsg
+A callback function to handle errors and warnings.
+See
+.Pa main.c
+for an example.
+.It Ar defos
+A default string for the
+.Xr mdoc 7
+.Sq \&Os
+macro, overriding the
+.Dv OSNAME
+preprocessor definition and the results of
+.Xr uname 3 .
+.El
+.Pp
The same parser may be used for multiple files so long as
.Fn mparse_reset
is called between parses.
@@ -419,7 +395,7 @@ i.e., those where
.Fn mparse_readfd
returned less than MANDOCLEVEL_FATAL
.Pc
-should invoke this function, in which case one of the two pointers will
+should invoke this function, in which case one of the three pointers will
be filled in.
Declared in
.In mandoc.h ,
@@ -473,6 +449,8 @@ The following non-printing characters may be embedded in text strings:
A non-breaking space character.
.It Dv ASCII_HYPH
A soft hyphen.
+.It Dv ASCII_BREAK
+A breakable zero-width space.
.El
.Pp
Escape characters are also passed verbatim into text strings.
@@ -480,11 +458,9 @@ An escape character is a sequence of characters beginning with the
backslash
.Pq Sq \e .
To construct human-readable text, these should be intercepted with
-.Fn mandoc_escape
-and converted with one of
-.Fn mchars_num2char ,
-.Fn mchars_spec2str ,
-and so on.
+.Xr mandoc_escape 3
+and converted with one the functions described in
+.Xr mchars_alloc 3 .
.Ss Man Abstract Syntax Tree
This AST is governed by the ontological rules dictated in
.Xr man 7
@@ -529,7 +505,7 @@ where capitalised non-terminals represent nodes.
.El
.Pp
The only elements capable of nesting other elements are those with
-next-lint scope as documented in
+next-line scope as documented in
.Xr man 7 .
.Ss Mdoc Abstract Syntax Tree
This AST is governed by the ontological
@@ -665,10 +641,13 @@ front-ends to
.Xr mandoc 1
are unable to render them in any meaningful way.
Furthermore, behaviour when encountering badly-nested blocks is not
-consistent across troff implementations, especially when using multiple
+consistent across troff implementations, especially when using multiple
levels of badly-nested blocks.
.Sh SEE ALSO
.Xr mandoc 1 ,
+.Xr mandoc_escape 3 ,
+.Xr mandoc_malloc 3 ,
+.Xr mchars_alloc 3 ,
.Xr eqn 7 ,
.Xr man 7 ,
.Xr mandoc_char 7 ,
diff --git a/contrib/mdocml/mandoc.c b/contrib/mdocml/mandoc.c
index da738f20fa72..dd2227299931 100644
--- a/contrib/mdocml/mandoc.c
+++ b/contrib/mdocml/mandoc.c
@@ -1,7 +1,7 @@
-/* $Id: mandoc.c,v 1.74 2013/12/30 18:30:32 schwarze Exp $ */
+/* $Id: mandoc.c,v 1.83 2014/07/06 19:09:00 schwarze Exp $ */
/*
* Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons
- * Copyright (c) 2011, 2012, 2013 Ingo Schwarze
+ * Copyright (c) 2011, 2012, 2013, 2014 Ingo Schwarze
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -31,6 +31,7 @@
#include
#include "mandoc.h"
+#include "mandoc_aux.h"
#include "libmandoc.h"
#define DATESIZE 32
@@ -45,7 +46,7 @@ mandoc_escape(const char **end, const char **start, int *sz)
const char *local_start;
int local_sz;
char term;
- enum mandoc_esc gly;
+ enum mandoc_esc gly;
/*
* When the caller doesn't provide return storage,
@@ -74,11 +75,11 @@ mandoc_escape(const char **end, const char **start, int *sz)
* these, but each eventually returns a substring of the glyph
* name.
*/
- case ('('):
+ case '(':
gly = ESCAPE_SPECIAL;
*sz = 2;
break;
- case ('['):
+ case '[':
gly = ESCAPE_SPECIAL;
/*
* Unicode escapes are defined in groff as \[uXXXX] to
@@ -90,7 +91,7 @@ mandoc_escape(const char **end, const char **start, int *sz)
gly = ESCAPE_UNICODE;
term = ']';
break;
- case ('C'):
+ case 'C':
if ('\'' != **start)
return(ESCAPE_ERROR);
*start = ++*end;
@@ -104,50 +105,50 @@ mandoc_escape(const char **end, const char **start, int *sz)
/*
* Escapes taking no arguments at all.
*/
- case ('d'):
+ case 'd':
/* FALLTHROUGH */
- case ('u'):
+ case 'u':
return(ESCAPE_IGNORE);
/*
* The \z escape is supposed to output the following
- * character without advancing the cursor position.
+ * character without advancing the cursor position.
* Since we are mostly dealing with terminal mode,
* let us just skip the next character.
*/
- case ('z'):
+ case 'z':
return(ESCAPE_SKIPCHAR);
/*
* Handle all triggers matching \X(xy, \Xx, and \X[xxxx], where
* 'X' is the trigger. These have opaque sub-strings.
*/
- case ('F'):
+ case 'F':
/* FALLTHROUGH */
- case ('g'):
+ case 'g':
/* FALLTHROUGH */
- case ('k'):
+ case 'k':
/* FALLTHROUGH */
- case ('M'):
+ case 'M':
/* FALLTHROUGH */
- case ('m'):
+ case 'm':
/* FALLTHROUGH */
- case ('n'):
+ case 'n':
/* FALLTHROUGH */
- case ('V'):
+ case 'V':
/* FALLTHROUGH */
- case ('Y'):
+ case 'Y':
gly = ESCAPE_IGNORE;
/* FALLTHROUGH */
- case ('f'):
+ case 'f':
if (ESCAPE_ERROR == gly)
gly = ESCAPE_FONT;
switch (**start) {
- case ('('):
+ case '(':
*start = ++*end;
*sz = 2;
break;
- case ('['):
+ case '[':
*start = ++*end;
term = ']';
break;
@@ -160,60 +161,59 @@ mandoc_escape(const char **end, const char **start, int *sz)
/*
* These escapes are of the form \X'Y', where 'X' is the trigger
* and 'Y' is any string. These have opaque sub-strings.
+ * The \B and \w escapes are handled in roff.c, roff_res().
*/
- case ('A'):
+ case 'A':
/* FALLTHROUGH */
- case ('b'):
+ case 'b':
/* FALLTHROUGH */
- case ('B'):
+ case 'D':
/* FALLTHROUGH */
- case ('D'):
+ case 'o':
/* FALLTHROUGH */
- case ('o'):
+ case 'R':
/* FALLTHROUGH */
- case ('R'):
+ case 'X':
/* FALLTHROUGH */
- case ('w'):
- /* FALLTHROUGH */
- case ('X'):
- /* FALLTHROUGH */
- case ('Z'):
- if ('\'' != **start)
+ case 'Z':
+ if ('\0' == **start)
return(ESCAPE_ERROR);
gly = ESCAPE_IGNORE;
+ term = **start;
*start = ++*end;
- term = '\'';
break;
/*
* These escapes are of the form \X'N', where 'X' is the trigger
* and 'N' resolves to a numerical expression.
*/
- case ('h'):
+ case 'h':
/* FALLTHROUGH */
- case ('H'):
+ case 'H':
/* FALLTHROUGH */
- case ('L'):
+ case 'L':
/* FALLTHROUGH */
- case ('l'):
+ case 'l':
/* FALLTHROUGH */
- case ('S'):
+ case 'S':
/* FALLTHROUGH */
- case ('v'):
+ case 'v':
/* FALLTHROUGH */
- case ('x'):
- if ('\'' != **start)
+ case 'x':
+ if (strchr(" %&()*+-./0123456789:<=>", **start)) {
+ ++*end;
return(ESCAPE_ERROR);
+ }
gly = ESCAPE_IGNORE;
+ term = **start;
*start = ++*end;
- term = '\'';
break;
/*
* Special handling for the numbered character escape.
* XXX Do any other escapes need similar handling?
*/
- case ('N'):
+ case 'N':
if ('\0' == **start)
return(ESCAPE_ERROR);
(*end)++;
@@ -229,10 +229,10 @@ mandoc_escape(const char **end, const char **start, int *sz)
(*end)++;
return(ESCAPE_NUMBERED);
- /*
+ /*
* Sizes get a special category of their own.
*/
- case ('s'):
+ case 's':
gly = ESCAPE_IGNORE;
/* See +/- counts as a sign. */
@@ -240,15 +240,15 @@ mandoc_escape(const char **end, const char **start, int *sz)
(*end)++;
switch (**end) {
- case ('('):
+ case '(':
*start = ++*end;
*sz = 2;
break;
- case ('['):
+ case '[':
*start = ++*end;
term = ']';
break;
- case ('\''):
+ case '\'':
*start = ++*end;
term = '\'';
break;
@@ -280,9 +280,9 @@ mandoc_escape(const char **end, const char **start, int *sz)
if ('\0' != term) {
while (**end != term) {
switch (**end) {
- case ('\0'):
+ case '\0':
return(ESCAPE_ERROR);
- case ('\\'):
+ case '\\':
(*end)++;
if (ESCAPE_ERROR ==
mandoc_escape(end, NULL, NULL))
@@ -304,7 +304,7 @@ mandoc_escape(const char **end, const char **start, int *sz)
/* Run post-processors. */
switch (gly) {
- case (ESCAPE_FONT):
+ case ESCAPE_FONT:
if (2 == *sz) {
if ('C' == **start) {
/*
@@ -322,27 +322,27 @@ mandoc_escape(const char **end, const char **start, int *sz)
break;
switch (**start) {
- case ('3'):
+ case '3':
/* FALLTHROUGH */
- case ('B'):
+ case 'B':
gly = ESCAPE_FONTBOLD;
break;
- case ('2'):
+ case '2':
/* FALLTHROUGH */
- case ('I'):
+ case 'I':
gly = ESCAPE_FONTITALIC;
break;
- case ('P'):
+ case 'P':
gly = ESCAPE_FONTPREV;
break;
- case ('1'):
+ case '1':
/* FALLTHROUGH */
- case ('R'):
+ case 'R':
gly = ESCAPE_FONTROMAN;
break;
}
break;
- case (ESCAPE_SPECIAL):
+ case ESCAPE_SPECIAL:
if (1 == *sz && 'c' == **start)
gly = ESCAPE_NOSPACE;
break;
@@ -353,74 +353,6 @@ mandoc_escape(const char **end, const char **start, int *sz)
return(gly);
}
-void *
-mandoc_calloc(size_t num, size_t size)
-{
- void *ptr;
-
- ptr = calloc(num, size);
- if (NULL == ptr) {
- perror(NULL);
- exit((int)MANDOCLEVEL_SYSERR);
- }
-
- return(ptr);
-}
-
-
-void *
-mandoc_malloc(size_t size)
-{
- void *ptr;
-
- ptr = malloc(size);
- if (NULL == ptr) {
- perror(NULL);
- exit((int)MANDOCLEVEL_SYSERR);
- }
-
- return(ptr);
-}
-
-
-void *
-mandoc_realloc(void *ptr, size_t size)
-{
-
- ptr = realloc(ptr, size);
- if (NULL == ptr) {
- perror(NULL);
- exit((int)MANDOCLEVEL_SYSERR);
- }
-
- return(ptr);
-}
-
-char *
-mandoc_strndup(const char *ptr, size_t sz)
-{
- char *p;
-
- p = mandoc_malloc(sz + 1);
- memcpy(p, ptr, sz);
- p[(int)sz] = '\0';
- return(p);
-}
-
-char *
-mandoc_strdup(const char *ptr)
-{
- char *p;
-
- p = strdup(ptr);
- if (NULL == p) {
- perror(NULL);
- exit((int)MANDOCLEVEL_SYSERR);
- }
-
- return(p);
-}
-
/*
* Parse a quoted or unquoted roff-style request or macro argument.
* Return a pointer to the parsed argument, which is either the original
@@ -442,7 +374,7 @@ mandoc_getarg(struct mparse *parse, char **cpp, int ln, int *pos)
if ('"' == *start) {
quoted = 1;
start++;
- }
+ }
pairs = 0;
white = 0;
@@ -461,14 +393,14 @@ mandoc_getarg(struct mparse *parse, char **cpp, int ln, int *pos)
* backslashes and backslash-t to literal tabs.
*/
switch (cp[1]) {
- case ('t'):
+ case 't':
cp[0] = '\t';
/* FALLTHROUGH */
- case ('\\'):
+ case '\\':
pairs++;
cp++;
break;
- case (' '):
+ case ' ':
/* Skip escaped blanks. */
if (0 == quoted)
cp++;
@@ -497,7 +429,7 @@ mandoc_getarg(struct mparse *parse, char **cpp, int ln, int *pos)
/* Quoted argument without a closing quote. */
if (1 == quoted)
- mandoc_msg(MANDOCERR_BADQUOTE, parse, ln, *pos, NULL);
+ mandoc_msg(MANDOCERR_ARG_QUOTE, parse, ln, *pos, NULL);
/* NUL-terminate this argument and move to the next one. */
if (pairs)
@@ -511,7 +443,7 @@ mandoc_getarg(struct mparse *parse, char **cpp, int ln, int *pos)
*cpp = cp;
if ('\0' == *cp && (white || ' ' == cp[-1]))
- mandoc_msg(MANDOCERR_EOLNSPACE, parse, ln, *pos, NULL);
+ mandoc_msg(MANDOCERR_SPACE_EOL, parse, ln, *pos, NULL);
return(start);
}
@@ -579,14 +511,14 @@ mandoc_normdate(struct mparse *parse, char *in, int ln, int pos)
if (NULL == in || '\0' == *in ||
0 == strcmp(in, "$" "Mdocdate$")) {
- mandoc_msg(MANDOCERR_NODATE, parse, ln, pos, NULL);
+ mandoc_msg(MANDOCERR_DATE_MISSING, parse, ln, pos, NULL);
time(&t);
}
else if (a2time(&t, "%Y-%m-%d", in))
t = 0;
else if (!a2time(&t, "$" "Mdocdate: %b %d %Y $", in) &&
!a2time(&t, "%b %d, %Y", in)) {
- mandoc_msg(MANDOCERR_BADDATE, parse, ln, pos, NULL);
+ mandoc_msg(MANDOCERR_DATE_BAD, parse, ln, pos, in);
t = 0;
}
out = t ? time2a(t) : NULL;
@@ -594,10 +526,10 @@ mandoc_normdate(struct mparse *parse, char *in, int ln, int pos)
}
int
-mandoc_eos(const char *p, size_t sz, int enclosed)
+mandoc_eos(const char *p, size_t sz)
{
- const char *q;
- int found;
+ const char *q;
+ int enclosed, found;
if (0 == sz)
return(0);
@@ -608,24 +540,24 @@ mandoc_eos(const char *p, size_t sz, int enclosed)
* propagate outward.
*/
- found = 0;
+ enclosed = found = 0;
for (q = p + (int)sz - 1; q >= p; q--) {
switch (*q) {
- case ('\"'):
+ case '\"':
/* FALLTHROUGH */
- case ('\''):
+ case '\'':
/* FALLTHROUGH */
- case (']'):
+ case ']':
/* FALLTHROUGH */
- case (')'):
+ case ')':
if (0 == found)
enclosed = 1;
break;
- case ('.'):
+ case '.':
/* FALLTHROUGH */
- case ('!'):
+ case '!':
/* FALLTHROUGH */
- case ('?'):
+ case '?':
found = 1;
break;
default:
diff --git a/contrib/mdocml/mandoc.db.5 b/contrib/mdocml/mandoc.db.5
new file mode 100644
index 000000000000..8d5649578f54
--- /dev/null
+++ b/contrib/mdocml/mandoc.db.5
@@ -0,0 +1,144 @@
+.\" $Id: mandoc.db.5,v 1.1 2014/04/15 20:18:26 schwarze Exp $
+.\"
+.\" Copyright (c) 2014 Ingo Schwarze
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: April 15 2014 $
+.Dt MANDOC.DB 5
+.Os
+.Sh NAME
+.Nm mandoc.db
+.Nd manual page database
+.Sh DESCRIPTION
+The
+.Nm
+SQLite3 file format is used to store information about installed manual
+pages to facilitate semantic searching for manuals.
+Each manual page tree contains its own
+.Nm
+file; see
+.Sx FILES
+for examples.
+.Pp
+Such database files are generated by
+.Xr makewhatis 8
+and used by
+.Xr apropos 1
+and
+.Xr whatis 1 .
+.Pp
+One line in the following tables describes:
+.Bl -tag -width Ds
+.It Sy mpages
+One physical manual page file, no matter how many times and under which
+names it may appear in the file system.
+.It Sy mlinks
+One entry in the file system, no matter which content it points to.
+.It Sy names
+One manual page name, no matter whether it appears in a page header,
+in a NAME or SYNOPSIS section, or as a file name.
+.It Sy keys
+One chunk of text from some macro invocation.
+.El
+.Pp
+Each record in the latter three tables uses its
+.Va pageid
+column to point to a record in the
+.Sy mpages
+table.
+.Pp
+The other columns are as follows; unless stated otherwise, they are
+of type
+.Vt TEXT .
+.Bl -tag -width mpages.desc
+.It Sy mpages.desc
+The description line
+.Pq Sq \&Nd
+of the page.
+.It Sy mpages.form
+The
+.Vt INTEGER
+1 if the page is unformatted, i.e. in
+.Xr mdoc 7
+or
+.Xr man 7
+format, and 2 if it is formatted, i.e. a
+.Sq cat
+page.
+.It Sy mlinks.sec
+The manual section as found in the subdirectory name.
+.It Sy mlinks.arch
+The manual architecture as found in the subdirectory name, or
+.Qq any .
+.It Sy mlinks.name
+The manual name as found in the file name.
+.It Sy names.bits
+An
+.Vt INTEGER
+bit mask telling whether the name came from a header line, from the
+NAME or SYNOPSIS section, or from a file name.
+Bits are defined in
+.In mansearch.h .
+.It Sy names.name
+The name itself.
+.It Sy keys.bits
+An
+.Vt INTEGER
+bit mask telling which semantic contexts the key was found in;
+defined in
+.In mansearch.h ,
+documented in
+.Xr apropos 1 .
+.It Sy keys.key
+The string found in those contexts.
+.El
+.Sh FILES
+.Bl -tag -width /usr/share/mandoc.db -compact
+.It Pa /usr/share/mandoc.db
+The manual page database for the base system.
+.It Pa /usr/X11R6/mandoc.db
+The same for the
+.Xr X 7
+Window System.
+.It Pa /usr/local/mandoc.db
+The same for
+.Xr packages 7 .
+.El
+.Sh SEE ALSO
+.Xr apropos 1 ,
+.Xr man 1 ,
+.Xr sqlite3 1 ,
+.Xr whatis 1 ,
+.Xr mansearch 3 ,
+.Xr makewhatis 8
+.Sh HISTORY
+A manual page database
+.Pa /usr/lib/whatis
+first appeared in
+.Bx 2 .
+The present format first appeared in
+.Ox 5.6 .
+.Sh AUTHORS
+.An -nosplit
+The original version of
+.Xr makewhatis 8
+was written by
+.An Bill Joy
+in 1979.
+An SQLite3 version was first implemented by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
+in 2012.
+The present database format was designed by
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org
+in 2014.
diff --git a/contrib/mdocml/mandoc.h b/contrib/mdocml/mandoc.h
index 4c6a32f7a62d..fd91314d76e8 100644
--- a/contrib/mdocml/mandoc.h
+++ b/contrib/mdocml/mandoc.h
@@ -1,7 +1,7 @@
-/* $Id: mandoc.h,v 1.112 2013/12/30 18:30:32 schwarze Exp $ */
+/* $Id: mandoc.h,v 1.152 2014/08/06 15:09:05 schwarze Exp $ */
/*
* Copyright (c) 2010, 2011 Kristaps Dzonsons
- * Copyright (c) 2012, 2013 Ingo Schwarze
+ * Copyright (c) 2010-2014 Ingo Schwarze
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -20,6 +20,7 @@
#define ASCII_NBRSP 31 /* non-breaking space */
#define ASCII_HYPH 30 /* breakable hyphen */
+#define ASCII_BREAK 29 /* breakable zero-width space */
/*
* Status level. This refers to both internal status (i.e., whilst
@@ -48,66 +49,78 @@ enum mandocerr {
MANDOCERR_WARNING, /* ===== start of warnings ===== */
/* related to the prologue */
- MANDOCERR_NOTITLE, /* no title in document */
- MANDOCERR_UPPERCASE, /* document title should be all caps */
- MANDOCERR_BADMSEC, /* unknown manual section */
- MANDOCERR_BADVOLARCH, /* unknown manual volume or arch */
- MANDOCERR_NODATE, /* date missing, using today's date */
- MANDOCERR_BADDATE, /* cannot parse date, using it verbatim */
- MANDOCERR_PROLOGOOO, /* prologue macros out of order */
- MANDOCERR_PROLOGREP, /* duplicate prologue macro */
- MANDOCERR_BADPROLOG, /* macro not allowed in prologue */
- MANDOCERR_BADBODY, /* macro not allowed in body */
+ MANDOCERR_DT_NOTITLE, /* missing manual title, using UNTITLED: line */
+ MANDOCERR_TH_NOTITLE, /* missing manual title, using "": [macro] */
+ MANDOCERR_TITLE_CASE, /* lower case character in document title */
+ MANDOCERR_MSEC_MISSING, /* missing manual section, using "": macro */
+ MANDOCERR_MSEC_BAD, /* unknown manual section: Dt ... section */
+ MANDOCERR_ARCH_BAD, /* unknown manual volume or arch: Dt ... volume */
+ MANDOCERR_DATE_MISSING, /* missing date, using today's date */
+ MANDOCERR_DATE_BAD, /* cannot parse date, using it verbatim: date */
+ MANDOCERR_OS_MISSING, /* missing Os macro, using "" */
+ MANDOCERR_PROLOG_REP, /* duplicate prologue macro: macro */
+ MANDOCERR_PROLOG_LATE, /* late prologue macro: macro */
+ MANDOCERR_DT_LATE, /* skipping late title macro: Dt args */
+ MANDOCERR_PROLOG_ORDER, /* prologue macros out of order: macros */
/* related to document structure */
- MANDOCERR_SO, /* .so is fragile, better use ln(1) */
- MANDOCERR_NAMESECFIRST, /* NAME section must come first */
- MANDOCERR_BADNAMESEC, /* bad NAME section contents */
- MANDOCERR_SECOOO, /* sections out of conventional order */
- MANDOCERR_SECREP, /* duplicate section name */
- MANDOCERR_SECMSEC, /* section header suited to sections ... */
+ MANDOCERR_SO, /* .so is fragile, better use ln(1): so path */
+ MANDOCERR_DOC_EMPTY, /* no document body */
+ MANDOCERR_SEC_BEFORE, /* content before first section header: macro */
+ MANDOCERR_NAMESEC_FIRST, /* first section is not NAME: Sh title */
+ MANDOCERR_NAMESEC_BAD, /* bad NAME section contents: macro */
+ MANDOCERR_SEC_ORDER, /* sections out of conventional order: Sh title */
+ MANDOCERR_SEC_REP, /* duplicate section title: Sh title */
+ MANDOCERR_SEC_MSEC, /* unexpected section: Sh title for ... only */
/* related to macros and nesting */
- MANDOCERR_MACROOBS, /* skipping obsolete macro */
- MANDOCERR_IGNPAR, /* skipping paragraph macro */
- MANDOCERR_MOVEPAR, /* moving paragraph macro out of list */
- MANDOCERR_IGNNS, /* skipping no-space macro */
- MANDOCERR_SCOPENEST, /* blocks badly nested */
- MANDOCERR_CHILD, /* child violates parent syntax */
- MANDOCERR_NESTEDDISP, /* nested displays are not portable */
- MANDOCERR_SCOPEREP, /* already in literal mode */
- MANDOCERR_LINESCOPE, /* line scope broken */
+ MANDOCERR_MACRO_OBS, /* obsolete macro: macro */
+ MANDOCERR_PAR_SKIP, /* skipping paragraph macro: macro ... */
+ MANDOCERR_PAR_MOVE, /* moving paragraph macro out of list: macro */
+ MANDOCERR_NS_SKIP, /* skipping no-space macro */
+ MANDOCERR_BLK_NEST, /* blocks badly nested: macro ... */
+ MANDOCERR_BD_NEST, /* nested displays are not portable: macro ... */
+ MANDOCERR_BL_MOVE, /* moving content out of list: macro */
+ MANDOCERR_VT_CHILD, /* .Vt block has child macro: macro */
+ MANDOCERR_FI_SKIP, /* fill mode already enabled, skipping: fi */
+ MANDOCERR_NF_SKIP, /* fill mode already disabled, skipping: nf */
+ MANDOCERR_BLK_LINE, /* line scope broken: macro breaks macro */
- /* related to missing macro arguments */
- MANDOCERR_MACROEMPTY, /* skipping empty macro */
+ /* related to missing arguments */
+ MANDOCERR_REQ_EMPTY, /* skipping empty request: request */
+ MANDOCERR_COND_EMPTY, /* conditional request controls empty scope */
+ MANDOCERR_MACRO_EMPTY, /* skipping empty macro: macro */
+ MANDOCERR_ARG_EMPTY, /* empty argument, using 0n: macro arg */
MANDOCERR_ARGCWARN, /* argument count wrong */
- MANDOCERR_DISPTYPE, /* missing display type */
- MANDOCERR_LISTFIRST, /* list type must come first */
- MANDOCERR_NOWIDTHARG, /* tag lists require a width argument */
- MANDOCERR_FONTTYPE, /* missing font type */
- MANDOCERR_WNOSCOPE, /* skipping end of block that is not open */
+ MANDOCERR_BD_NOTYPE, /* missing display type, using -ragged: Bd */
+ MANDOCERR_BL_LATETYPE, /* list type is not the first argument: Bl arg */
+ MANDOCERR_BL_NOWIDTH, /* missing -width in -tag list, using 8n */
+ MANDOCERR_EX_NONAME, /* missing utility name, using "": Ex */
+ MANDOCERR_IT_NOHEAD, /* empty head in list item: Bl -type It */
+ MANDOCERR_IT_NOBODY, /* empty list item: Bl -type It */
+ MANDOCERR_BF_NOFONT, /* missing font type, using \fR: Bf */
+ MANDOCERR_BF_BADFONT, /* unknown font type, using \fR: Bf font */
+ MANDOCERR_ARG_STD, /* missing -std argument, adding it: macro */
- /* related to bad macro arguments */
- MANDOCERR_IGNARGV, /* skipping argument */
- MANDOCERR_ARGVREP, /* duplicate argument */
- MANDOCERR_DISPREP, /* duplicate display type */
- MANDOCERR_LISTREP, /* duplicate list type */
- MANDOCERR_BADATT, /* unknown AT&T UNIX version */
- MANDOCERR_BADBOOL, /* bad Boolean value */
- MANDOCERR_BADFONT, /* unknown font */
- MANDOCERR_BADSTANDARD, /* unknown standard specifier */
- MANDOCERR_BADWIDTH, /* bad width argument */
+ /* related to bad arguments */
+ MANDOCERR_ARG_QUOTE, /* unterminated quoted argument */
+ MANDOCERR_ARG_REP, /* duplicate argument: macro arg */
+ MANDOCERR_AN_REP, /* skipping duplicate argument: An -arg */
+ MANDOCERR_BD_REP, /* skipping duplicate display type: Bd -type */
+ MANDOCERR_BL_REP, /* skipping duplicate list type: Bl -type */
+ MANDOCERR_BL_SKIPW, /* skipping -width argument: Bl -type */
+ MANDOCERR_AT_BAD, /* unknown AT&T UNIX version: At version */
+ MANDOCERR_RS_BAD, /* invalid content in Rs block: macro */
+ MANDOCERR_SM_BAD, /* invalid Boolean argument: macro arg */
+ MANDOCERR_FT_BAD, /* unknown font, skipping request: ft font */
/* related to plain text */
- MANDOCERR_NOBLANKLN, /* blank line in non-literal context */
- MANDOCERR_BADTAB, /* tab in non-literal context */
- MANDOCERR_EOLNSPACE, /* end of line whitespace */
- MANDOCERR_BADCOMMENT, /* bad comment style */
- MANDOCERR_BADESCAPE, /* unknown escape sequence */
- MANDOCERR_BADQUOTE, /* unterminated quoted string */
-
- /* related to equations */
- MANDOCERR_EQNQUOTE, /* unexpected literal in equation */
+ MANDOCERR_FI_BLANK, /* blank line in fill mode, using .sp */
+ MANDOCERR_FI_TAB, /* tab in filled text */
+ MANDOCERR_SPACE_EOL, /* whitespace at end of input line */
+ MANDOCERR_COMMENT_BAD, /* bad comment style */
+ MANDOCERR_ESC_BAD, /* invalid escape sequence: esc */
+ MANDOCERR_STR_UNDEF, /* undefined string, using "": name */
MANDOCERR_ERROR, /* ===== start of errors ===== */
@@ -128,40 +141,40 @@ enum mandocerr {
MANDOCERR_TBLBLOCK, /* data block still open */
MANDOCERR_TBLEXTRADAT, /* ignoring extra data cells */
+ /* related to document structure and macros */
MANDOCERR_ROFFLOOP, /* input stack limit exceeded, infinite loop? */
- MANDOCERR_BADCHAR, /* skipping bad character */
- MANDOCERR_NAMESC, /* escaped character not allowed in a name */
- MANDOCERR_NONAME, /* manual name not yet set */
- MANDOCERR_NOTEXT, /* skipping text before the first section header */
- MANDOCERR_MACRO, /* skipping unknown macro */
- MANDOCERR_REQUEST, /* NOT IMPLEMENTED: skipping request */
+ MANDOCERR_BADCHAR, /* skipping bad character: number */
+ MANDOCERR_MACRO, /* skipping unknown macro: macro */
+ MANDOCERR_IT_STRAY, /* skipping item outside list: It ... */
+ MANDOCERR_TA_STRAY, /* skipping column outside column list: Ta */
+ MANDOCERR_BLK_NOTOPEN, /* skipping end of block that is not open */
+ MANDOCERR_BLK_BROKEN, /* inserting missing end of block: macro ... */
+ MANDOCERR_BLK_NOEND, /* appending missing end of block: macro */
+
+ /* related to request and macro arguments */
+ MANDOCERR_NAMESC, /* escaped character not allowed in a name: name */
MANDOCERR_ARGCOUNT, /* argument count wrong */
- MANDOCERR_STRAYTA, /* skipping column outside column list */
- MANDOCERR_NOSCOPE, /* skipping end of block that is not open */
- MANDOCERR_SCOPEBROKEN, /* missing end of block */
- MANDOCERR_SCOPEEXIT, /* scope open on exit */
- MANDOCERR_UNAME, /* uname(3) system call failed */
- /* FIXME: merge following with MANDOCERR_ARGCOUNT */
- MANDOCERR_NOARGS, /* macro requires line argument(s) */
- MANDOCERR_NOBODY, /* macro requires body argument(s) */
- MANDOCERR_NOARGV, /* macro requires argument(s) */
- MANDOCERR_NUMERIC, /* request requires a numeric argument */
- MANDOCERR_LISTTYPE, /* missing list type */
- MANDOCERR_ARGSLOST, /* line argument(s) will be lost */
- MANDOCERR_BODYLOST, /* body argument(s) will be lost */
+ MANDOCERR_BL_NOTYPE, /* missing list type, using -item: Bl */
+ MANDOCERR_NM_NONAME, /* missing manual name, using "": Nm */
+ MANDOCERR_OS_UNAME, /* uname(3) system call failed, using UNKNOWN */
+ MANDOCERR_ST_BAD, /* unknown standard specifier: St standard */
+ MANDOCERR_IT_NONUM, /* skipping request without numeric argument */
+ MANDOCERR_ARG_SKIP, /* skipping all arguments: macro args */
+ MANDOCERR_ARG_EXCESS, /* skipping excess arguments: macro ... args */
MANDOCERR_FATAL, /* ===== start of fatal errors ===== */
- MANDOCERR_NOTMANUAL, /* manual isn't really a manual */
- MANDOCERR_COLUMNS, /* column syntax is inconsistent */
- MANDOCERR_BADDISP, /* NOT IMPLEMENTED: .Bd -file */
- MANDOCERR_SYNTARGVCOUNT, /* argument count wrong, violates syntax */
- MANDOCERR_SYNTCHILD, /* child violates parent syntax */
- MANDOCERR_SYNTARGCOUNT, /* argument count wrong, violates syntax */
- MANDOCERR_SOPATH, /* NOT IMPLEMENTED: .so with absolute path or ".." */
- MANDOCERR_NODOCBODY, /* no document body */
- MANDOCERR_NODOCPROLOG, /* no document prologue */
- MANDOCERR_MEM, /* static buffer exhausted */
+ MANDOCERR_TOOLARGE, /* input too large */
+ MANDOCERR_BD_FILE, /* NOT IMPLEMENTED: Bd -file */
+ MANDOCERR_SO_PATH, /* NOT IMPLEMENTED: .so with absolute path or ".." */
+ MANDOCERR_SO_FAIL, /* .so request failed */
+
+ /* ===== system errors ===== */
+
+ MANDOCERR_SYSOPEN, /* cannot open file */
+ MANDOCERR_SYSSTAT, /* cannot stat file */
+ MANDOCERR_SYSREAD, /* cannot read file */
+
MANDOCERR_MAX
};
@@ -231,6 +244,7 @@ struct tbl_row {
struct tbl_row *next;
struct tbl_cell *first;
struct tbl_cell *last;
+ int vert; /* trailing vertical line */
};
enum tbl_datt {
@@ -353,7 +367,7 @@ struct eqn_box {
/*
* An equation consists of a tree of expressions starting at a given
- * line and position.
+ * line and position.
*/
struct eqn {
char *name; /* identifier (or NULL) */
@@ -363,15 +377,12 @@ struct eqn {
};
/*
- * The type of parse sequence. This value is usually passed via the
- * mandoc(1) command line of -man and -mdoc. It's almost exclusively
- * -mandoc but the others have been retained for compatibility.
+ * Parse options.
*/
-enum mparset {
- MPARSE_AUTO, /* magically determine the document type */
- MPARSE_MDOC, /* assume -mdoc */
- MPARSE_MAN /* assume -man */
-};
+#define MPARSE_MDOC 1 /* assume -mdoc */
+#define MPARSE_MAN 2 /* assume -man */
+#define MPARSE_SO 4 /* honour .so requests */
+#define MPARSE_QUICK 8 /* abort the parse early */
enum mandoc_esc {
ESCAPE_ERROR = 0, /* bail! unparsable escape */
@@ -399,30 +410,25 @@ struct man;
__BEGIN_DECLS
-void *mandoc_calloc(size_t, size_t);
enum mandoc_esc mandoc_escape(const char **, const char **, int *);
-void *mandoc_malloc(size_t);
-void *mandoc_realloc(void *, size_t);
-char *mandoc_strdup(const char *);
-char *mandoc_strndup(const char *, size_t);
struct mchars *mchars_alloc(void);
void mchars_free(struct mchars *);
-char mchars_num2char(const char *, size_t);
+char mchars_num2char(const char *, size_t);
int mchars_num2uc(const char *, size_t);
-int mchars_spec2cp(const struct mchars *,
+int mchars_spec2cp(const struct mchars *,
const char *, size_t);
-const char *mchars_spec2str(const struct mchars *,
+const char *mchars_spec2str(const struct mchars *,
const char *, size_t, size_t *);
-struct mparse *mparse_alloc(enum mparset, enum mandoclevel,
- mandocmsg, void *, char *);
+struct mparse *mparse_alloc(int, enum mandoclevel, mandocmsg,
+ const char *);
void mparse_free(struct mparse *);
void mparse_keep(struct mparse *);
enum mandoclevel mparse_readfd(struct mparse *, int, const char *);
enum mandoclevel mparse_readmem(struct mparse *, const void *, size_t,
const char *);
void mparse_reset(struct mparse *);
-void mparse_result(struct mparse *,
- struct mdoc **, struct man **);
+void mparse_result(struct mparse *,
+ struct mdoc **, struct man **, char **);
const char *mparse_getkeep(const struct mparse *);
const char *mparse_strerror(enum mandocerr);
const char *mparse_strlevel(enum mandoclevel);
diff --git a/contrib/mdocml/mandoc_aux.c b/contrib/mdocml/mandoc_aux.c
new file mode 100644
index 000000000000..b5376735ceea
--- /dev/null
+++ b/contrib/mdocml/mandoc_aux.c
@@ -0,0 +1,121 @@
+/* $Id: mandoc_aux.c,v 1.3 2014/07/09 08:20:34 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2011 Kristaps Dzonsons
+ * Copyright (c) 2014 Ingo Schwarze
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * 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
+
+#include
+
+#include
+#include
+#include
+#include
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+
+int
+mandoc_asprintf(char **dest, const char *fmt, ...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, fmt);
+ ret = vasprintf(dest, fmt, ap);
+ va_end(ap);
+
+ if (-1 == ret) {
+ perror(NULL);
+ exit((int)MANDOCLEVEL_SYSERR);
+ }
+ return(ret);
+}
+
+void *
+mandoc_calloc(size_t num, size_t size)
+{
+ void *ptr;
+
+ ptr = calloc(num, size);
+ if (NULL == ptr) {
+ perror(NULL);
+ exit((int)MANDOCLEVEL_SYSERR);
+ }
+ return(ptr);
+}
+
+void *
+mandoc_malloc(size_t size)
+{
+ void *ptr;
+
+ ptr = malloc(size);
+ if (NULL == ptr) {
+ perror(NULL);
+ exit((int)MANDOCLEVEL_SYSERR);
+ }
+ return(ptr);
+}
+
+void *
+mandoc_realloc(void *ptr, size_t size)
+{
+
+ ptr = realloc(ptr, size);
+ if (NULL == ptr) {
+ perror(NULL);
+ exit((int)MANDOCLEVEL_SYSERR);
+ }
+ return(ptr);
+}
+
+void *
+mandoc_reallocarray(void *ptr, size_t num, size_t size)
+{
+
+ ptr = reallocarray(ptr, num, size);
+ if (NULL == ptr) {
+ perror(NULL);
+ exit((int)MANDOCLEVEL_SYSERR);
+ }
+ return(ptr);
+}
+
+char *
+mandoc_strdup(const char *ptr)
+{
+ char *p;
+
+ p = strdup(ptr);
+ if (NULL == p) {
+ perror(NULL);
+ exit((int)MANDOCLEVEL_SYSERR);
+ }
+ return(p);
+}
+
+char *
+mandoc_strndup(const char *ptr, size_t sz)
+{
+ char *p;
+
+ p = mandoc_malloc(sz + 1);
+ memcpy(p, ptr, sz);
+ p[(int)sz] = '\0';
+ return(p);
+}
diff --git a/contrib/mdocml/mandoc_aux.h b/contrib/mdocml/mandoc_aux.h
new file mode 100644
index 000000000000..04f4baff6065
--- /dev/null
+++ b/contrib/mdocml/mandoc_aux.h
@@ -0,0 +1,33 @@
+/* $Id: mandoc_aux.h,v 1.2 2014/04/23 21:06:41 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2011 Kristaps Dzonsons
+ * Copyright (c) 2014 Ingo Schwarze
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef MANDOC_AUX_H
+#define MANDOC_AUX_H
+
+__BEGIN_DECLS
+
+int mandoc_asprintf(char **, const char *, ...);
+void *mandoc_calloc(size_t, size_t);
+void *mandoc_malloc(size_t);
+void *mandoc_realloc(void *, size_t);
+void *mandoc_reallocarray(void *, size_t, size_t);
+char *mandoc_strdup(const char *);
+char *mandoc_strndup(const char *, size_t);
+
+__END_DECLS
+
+#endif /*!MANDOC_AUX_H*/
diff --git a/contrib/mdocml/mandoc_escape.3 b/contrib/mdocml/mandoc_escape.3
new file mode 100644
index 000000000000..84243fdeb6eb
--- /dev/null
+++ b/contrib/mdocml/mandoc_escape.3
@@ -0,0 +1,362 @@
+.\" $Id: mandoc_escape.3,v 1.1 2014/08/05 05:48:56 schwarze Exp $
+.\"
+.\" Copyright (c) 2014 Ingo Schwarze
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: August 5 2014 $
+.Dt MANDOC_ESCAPE 3
+.Os
+.Sh NAME
+.Nm mandoc_escape
+.Nd parse roff escape sequences
+.Sh LIBRARY
+.Lb libmandoc
+.Sh SYNOPSIS
+.In sys/types.h
+.In mandoc.h
+.Ft "enum mandoc_esc"
+.Fo mandoc_escape
+.Fa "const char **end"
+.Fa "const char **start"
+.Fa "int *sz"
+.Fc
+.Sh DESCRIPTION
+This function scans a
+.Xr roff 7
+escape sequence.
+.Pp
+An escape sequence consists of
+.Bl -dash -compact -width 2n
+.It
+an initial backslash character
+.Pq Sq \e ,
+.It
+a single ASCII character called the escape sequence identifier,
+.It
+and, with only a few exceptions, an argument.
+.El
+.Pp
+Arguments can be given in the following forms; some escape sequence
+identifiers only accept some of these forms as specified below.
+The first three forms are called the standard forms.
+.Bl -tag -width 2n
+.It \&In brackets: Ic \&[ Ns Ar argument Ns Ic \&]
+The argument starts after the initial
+.Sq \&[ ,
+ends before the final
+.Sq \&] ,
+and the escape sequence ends with the final
+.Sq \&] .
+.It Two-character argument short form: Ic \&( Ns Ar ar
+This form can only be used for arguments
+consisting of exactly two characters.
+It has the same effect as
+.Ic \&[ Ns Ar ar Ns Ic \&] .
+.It One-character argument short form: Ar a
+This form can only be used for arguments
+consisting of exactly one character.
+It has the same effect as
+.Ic \&[ Ns Ar a Ns Ic \&] .
+.It Delimited form: Ar C Ns Ar argument Ns Ar C
+The argument starts after the initial delimiter character
+.Ar C ,
+ends before the next occurrence of the delimiter character
+.Ar C ,
+and the escape sequence ends with that second
+.Ar C .
+Some escape sequences allow arbitrary characters
+.Ar C
+as quoting characters, some restrict the range of characters
+that can be used as quoting characters.
+.El
+.Pp
+Upon function entry,
+.Fa end
+is expected to point to the escape sequence identifier.
+The values passed in as
+.Fa start
+and
+.Fa sz
+are ignored and overwritten.
+.Pp
+By design, this function cannot handle those
+.Xr roff 7
+escape sequences that require in-place expansion, in particular
+user-defined strings
+.Ic \e* ,
+number registers
+.Ic \en ,
+width measurements
+.Ic \ew ,
+and numerical expression control
+.Ic \eB .
+These are handled by
+.Fn roff_res ,
+a private preprocessor function called from
+.Fn roff_parseln ,
+see the file
+.Pa roff.c .
+.Pp
+The function
+.Fn mandoc_escape
+is used
+.Bl -dash -compact -width 2n
+.It
+recursively by itself, because some escape sequence arguments can
+in turn contain other escape sequences,
+.It
+for error detection internally by the
+.Xr roff 7
+parser part of the
+.Lb libmandoc ,
+see the file
+.Pa roff.c ,
+.It
+above all externally by the
+.Xr mandoc
+formatting modules, in particular
+.Fl Tascii
+and
+.Fl Thtml ,
+for formatting purposes, see the files
+.Pa term.c
+and
+.Pa html.c ,
+.It
+and rarely externally by high-level utilities using the mandoc library,
+for example
+.Xr makewhatis 8 ,
+to purge escape sequences from text.
+.El
+.Sh RETURN VALUES
+Upon function return, the pointer
+.Fa end
+is set to the character after the end of the escape sequence,
+such that the calling higher-level parser can easily continue.
+.Pp
+For escape sequences taking an argument, the pointer
+.Fa start
+is set to the beginning of the argument and
+.Fa sz
+is set to the length of the argument.
+For escape sequences not taking an argument,
+.Fa start
+is set to the character after the end of the sequence and
+.Fa sz
+is set to 0.
+Both
+.Fa start
+and
+.Fa sz
+may be
+.Dv NULL ;
+in that case, the argument and the length are not returned.
+.Pp
+For sequences taking an argument, the function
+.Fn mandoc_escape
+returns one of the following values:
+.Bl -tag -width 2n
+.It Dv ESCAPE_FONT
+The escape sequence
+.Ic \ef
+taking an argument in standard form:
+.Ic \ef[ , \ef( , \ef Ns Ar a .
+Two-character arguments starting with the character
+.Sq C
+are reduced to one-character arguments by skipping the
+.Sq C .
+More specific values are returned for the most commonly used arguments:
+.Bl -column "argument" "ESCAPE_FONTITALIC"
+.It argument Ta return value
+.It Cm R No or Cm 1 Ta Dv ESCAPE_FONTROMAN
+.It Cm I No or Cm 2 Ta Dv ESCAPE_FONTITALIC
+.It Cm B No or Cm 3 Ta Dv ESCAPE_FONTBOLD
+.It Cm P Ta Dv ESCAPE_FONTPREV
+.It Cm BI Ta Dv ESCAPE_FONTBI
+.El
+.It Dv ESCAPE_SPECIAL
+The escape sequence
+.Ic \eC
+taking an argument delimited with the single quote character
+and, as a special exception, the escape sequences
+.Em not
+having an identifier, that is, those where the argument, in standard
+form, directly follows the initial backslash:
+.Ic \eC' , \e[ , \e( , \e Ns Ar a .
+Note that the one-character argument short form can only be used for
+argument characters that do not clash with escape sequence identifiers.
+.Pp
+If the argument consists of more than one character
+and starts with the character
+.Sq u ,
+.Dv ESCAPE_UNICODE
+is returned as described below.
+If the argument is just the single character
+.Sq u ,
+.Dv ESCAPE_ERROR
+is returned.
+.Pp
+The
+.Dv ESCAPE_SPECIAL
+special character escape sequences can be rendered using the functions
+.Fn mchars_spec2cp
+and
+.Fn mchars_spec2str
+described in the
+.Xr mchars_alloc 3
+manual.
+.It Dv ESCAPE_UNICODE
+Escape sequences of the same format as described above under
+.Dv ESCAPE_SPECIAL ,
+but with an argument starting with the character
+.Sq u :
+.Ic \eC'u , \e[u .
+As a special exception,
+.Fa start
+is set to the character after the
+.Sq u ,
+and the
+.Fa sz
+return value does not include the
+.Sq u
+either.
+.Pp
+Such Unicode character escape sequences can be rendered using the function
+.Fn mchars_num2uc
+described in the
+.Xr mchars_alloc 3
+manual.
+.It Dv ESCAPE_NUMBERED
+The escape sequence
+.Ic \eN
+followed by a delimited argument.
+The delimiter character is arbitrary except that digits cannot be used.
+If a digit is encountered instead of the opening delimiter, that
+digit is considered to be the argument and the end of the sequence, and
+.Dv ESCAPE_IGNORE
+is returned.
+.Pp
+Such ASCII character escape sequences can be rendered using the function
+.Fn mchars_num2char
+described in the
+.Xr mchars_alloc 3
+manual.
+.It Dv ESCAPE_IGNORE
+.Bl -bullet -width 2n
+.It
+The escape sequence
+.Ic \es
+followed by an argument in standard form or by an argument delimited
+by the single quote character:
+.Ic \es' , \es[ , \es( , \es Ns Ar a .
+As a special exception, an optional
+.Sq +
+or
+.Sq \-
+character is allowed after the
+.Sq s
+for all forms.
+.It
+The escape sequences
+.Ic \eF ,
+.Ic \eg ,
+.Ic \ek ,
+.Ic \eM ,
+.Ic \em ,
+.Ic \en ,
+.Ic \eV ,
+and
+.Ic \eY
+followed by an argument in standard form.
+.It
+The escape sequences
+.Ic \eA ,
+.Ic \eb ,
+.Ic \eD ,
+.Ic \eo ,
+.Ic \eR ,
+.Ic \eX ,
+and
+.Ic \eZ
+followed by an argument delimited by an arbitrary character.
+.It
+The escape sequences
+.Ic \eH ,
+.Ic \eh ,
+.Ic \eL ,
+.Ic \el ,
+.Ic \eS ,
+.Ic \ev ,
+and
+.Ic \ex
+followed by an argument delimited by a character that cannot occur
+in numerical expressions.
+However, if any character that can occur in numerical expressions
+is found instead of a delimiter, the sequence is considered to end
+with that character, and
+.Dv ESCAPE_ERROR
+is returned.
+.El
+.It Dv ESCAPE_ERROR
+Escape sequences taking an argument but not matching any of the above patterns.
+In particular, that happens if the end of the logical input line
+is reached before the end of the argument.
+.El
+.Pp
+For sequences that do not take an argument, the function
+.Fn mandoc_escape
+returns one of the following values:
+.Bl -tag -width 2n
+.It Dv ESCAPE_SKIPCHAR
+The escape sequence
+.Qq \ez .
+.It Dv ESCAPE_NOSPACE
+The escape sequence
+.Qq \ec .
+.It Dv ESCAPE_IGNORE
+The escape sequences
+.Qq \ed
+and
+.Qq \eu .
+.El
+.Sh FILES
+This function is implemented in
+.Pa mandoc.c .
+.Sh SEE ALSO
+.Xr mchars_alloc 3 ,
+.Xr mandoc_char 7 ,
+.Xr roff 7
+.Sh HISTORY
+This function has been available since mandoc 1.11.2.
+.Sh AUTHORS
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org
+.Sh BUGS
+The function doesn't cleanly distinguish between sequences that are
+valid and supported, valid and ignored, valid and unsupported,
+syntactically invalid, or undefined.
+For sequences that are ignored or unsupported, it doesn't tell
+whether that deficiency is likely to cause major formatting problems
+and/or loss of document content.
+The function is already rather complicated and still parses some
+sequences incorrectly.
+.
+.ig
+For these sequences, the list given below specifies a starting string
+and either the length of the argument or an ending character.
+The argument starts after the starting string.
+In the former case, the sequence ends with the end of the argument.
+In the latter case, the argument ends before the ending character,
+and the sequence ends with the ending character.
+..
diff --git a/contrib/mdocml/mandoc_html.3 b/contrib/mdocml/mandoc_html.3
new file mode 100644
index 000000000000..994eb3a288e7
--- /dev/null
+++ b/contrib/mdocml/mandoc_html.3
@@ -0,0 +1,249 @@
+.\" $Id: mandoc_html.3,v 1.1 2014/07/23 18:13:09 schwarze Exp $
+.\"
+.\" Copyright (c) 2014 Ingo Schwarze
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: July 23 2014 $
+.Dt MANDOC_HTML 3
+.Os
+.Sh NAME
+.Nm mandoc_html
+.Nd internals of the mandoc HTML formatter
+.Sh SYNOPSIS
+.In "html.h"
+.Ft void
+.Fn print_gen_decls "struct html *h"
+.Ft void
+.Fn print_gen_head "struct html *h"
+.Ft struct tag *
+.Fo print_otag
+.Fa "struct html *h"
+.Fa "enum htmltag tag"
+.Fa "int sz"
+.Fa "const struct htmlpair *p"
+.Fc
+.Ft void
+.Fo print_tagq
+.Fa "struct html *h"
+.Fa "const struct tag *until"
+.Fc
+.Ft void
+.Fo print_stagq
+.Fa "struct html *h"
+.Fa "const struct tag *suntil"
+.Fc
+.Ft void
+.Fo print_text
+.Fa "struct html *h"
+.Fa "const char *word"
+.Fc
+.Sh DESCRIPTION
+The mandoc HTML formatter is not a formal library.
+However, as it is compiled into more than one program, in particular
+.Xr mandoc 1
+and
+.Xr man.cgi 8 ,
+and because it may be security-critical in some contexts,
+some documentation is useful to help to use it correctly and
+to prevent XSS vulnerabilities.
+.Pp
+The formatter produces HTML output on the standard output.
+Since proper escaping is usually required and best taken care of
+at one central place, the language-specific formatters
+.Po
+.Pa *_html.c ,
+see
+.Sx FILES
+.Pc
+are not supposed to print directly to
+.Dv stdout
+using functions like
+.Xr printf 3 ,
+.Xr putc 3 ,
+.Xr puts 3 ,
+or
+.Xr write 2 .
+Instead, they are expected to use the output functions declared in
+.Pa html.h
+and implemented as part of the main HTML formatting engine in
+.Pa html.c .
+.Ss Data structures
+These structures are declared in
+.Pa html.h .
+.Bl -tag -width Ds
+.It Vt struct html
+Internal state of the HTML formatter.
+.It Vt struct htmlpair
+Holds one HTML attribute.
+Members are
+.Fa "enum htmlattr key"
+and
+.Fa "const char *val" .
+Helper macros
+.Fn PAIR_*
+are provided to support initialization of such structures.
+.It Vt struct tag
+One entry for the LIFO stack of HTML elements.
+Members are
+.Fa "enum htmltag tag"
+and
+.Fa "struct tag *next" .
+.El
+.Ss Private interface functions
+The function
+.Fn print_gen_decls
+prints the opening
+.Ao Pf \&? Ic xml ? Ac
+and
+.Aq Pf \&! Ic DOCTYPE
+declarations required for the current document type.
+.Pp
+The function
+.Fn print_gen_head
+prints the opening
+.Aq Ic META
+and
+.Aq Ic LINK
+elements for the document
+.Aq Ic HEAD ,
+using the
+.Fa style
+member of
+.Fa h
+unless that is
+.Dv NULL .
+It uses
+.Fn print_otag
+which takes care of properly encoding attributes,
+which is relevant for the
+.Fa style
+link in particular.
+.Pp
+The function
+.Fn print_otag
+prints the start tag of an HTML element with the name
+.Fa tag ,
+including the
+.Fa sz
+attributes that can optionally be provided in the
+.Fa p
+array.
+It uses the private function
+.Fn print_attr
+which in turn uses the private function
+.Fn print_encode
+to take care of HTML encoding.
+If required by the element type, it remembers in
+.Fa h
+that the element is open.
+The function
+.Fn print_tagq
+is used to close out all open elements up to and including
+.Fa until ;
+.Fn print_stagq
+is a variant to close out all open elements up to but excluding
+.Fa suntil .
+.Pp
+The function
+.Fn print_text
+prints HTML element content.
+It uses the private function
+.Fn print_encode
+to take care of HTML encoding.
+If the document has requested a non-standard font, for example using a
+.Xr roff 7
+.Ic \ef
+font escape sequence,
+.Fn print_text
+wraps
+.Fa word
+in an HTML font selection element using the
+.Fn print_otag
+and
+.Fn print_tagq
+functions.
+.Pp
+The functions
+.Fn bufinit ,
+.Fn bufcat* ,
+and
+.Fn buffmt*
+do not directly produce output but buffer text in the
+.Fa buf
+member of
+.Fa h .
+They are not used internally by
+.Pa html.c
+but intended for use by the language-specific formatters
+to ease preparation of strings for the
+.Fa p
+argument of
+.Fn print_otag
+and for the
+.Fa word
+argument of
+.Fn print_text .
+Consequently, these functions do not do any HTML encoding.
+.Pp
+The functions
+.Fn html_strlen ,
+.Fn print_eqn ,
+.Fn print_tbl ,
+and
+.Fn print_tblclose
+are not yet documented.
+.Sh FILES
+.Bl -tag -width mandoc_aux.c -compact
+.It Pa main.h
+declarations of public functions for use by the main program,
+not yet documented
+.It Pa html.h
+declarations of data types and private functions
+for use by language-specific HTML formatters
+.It Pa html.c
+main HTML formatting engine and utility functions
+.It Pa mdoc_html.c
+.Xr mdoc 7
+HTML formatter
+.It Pa man_html.c
+.Xr man 7
+HTML formatter
+.It Pa tbl_html.c
+.Xr tbl 7
+HTML formatter
+.It Pa eqn_html.c
+.Xr eqn 7
+HTML formatter
+.It Pa out.h
+declarations of data types and private functions
+for shared use by all mandoc formatters,
+not yet documented
+.It Pa out.c
+private functions for shared use by all mandoc formatters
+.It Pa mandoc_aux.h
+declarations of common mandoc utility functions, see
+.Xr mandoc 3
+.It Pa mandoc_aux.c
+implementation of common mandoc utility functions
+.El
+.Sh SEE ALSO
+.Xr mandoc 1 ,
+.Xr mandoc 3 ,
+.Xr man.cgi 8
+.Sh AUTHORS
+.An -nosplit
+The mandoc HTML formatter was written by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv .
+This manual was written by
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org .
diff --git a/contrib/mdocml/mandoc_malloc.3 b/contrib/mdocml/mandoc_malloc.3
new file mode 100644
index 000000000000..c16798424af6
--- /dev/null
+++ b/contrib/mdocml/mandoc_malloc.3
@@ -0,0 +1,197 @@
+.\" $Id: mandoc_malloc.3,v 1.1 2014/08/05 05:48:56 schwarze Exp $
+.\"
+.\" Copyright (c) 2014 Ingo Schwarze
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: August 5 2014 $
+.Dt MANDOC_MALLOC 3
+.Os
+.Sh NAME
+.Nm mandoc_malloc ,
+.Nm mandoc_realloc ,
+.Nm mandoc_reallocarray ,
+.Nm mandoc_calloc ,
+.Nm mandoc_strdup ,
+.Nm mandoc_strndup ,
+.Nm mandoc_asprintf
+.Nd memory allocation function wrappers used in the mandoc library
+.Sh LIBRARY
+.Lb libmandoc
+.Sh SYNOPSIS
+.In sys/types.h
+.In mandoc_aux.h
+.Ft "void *"
+.Fo mandoc_malloc
+.Fa "size_t size"
+.Fc
+.Ft "void *"
+.Fo mandoc_realloc
+.Fa "void *ptr"
+.Fa "size_t size"
+.Fc
+.Ft "void *"
+.Fo mandoc_reallocarray
+.Fa "void *ptr"
+.Fa "size_t nmemb"
+.Fa "size_t size"
+.Fc
+.Ft "void *"
+.Fo mandoc_calloc
+.Fa "size_t nmemb"
+.Fa "size_t size"
+.Fc
+.Ft "char *"
+.Fo mandoc_strdup
+.Fa "const char *s"
+.Fc
+.Ft "char *"
+.Fo mandoc_strndup
+.Fa "const char *s"
+.Fa "size_t maxlen"
+.Fc
+.Ft int
+.Fo mandoc_asprintf
+.Fa "char **ret"
+.Fa "const char *format"
+.Fa "..."
+.Fc
+.Sh DESCRIPTION
+These functions call the
+.Lb libc
+functions of the same names, passing through their return values when
+successful.
+In case of failure, they do not return, but instead call
+.Xr perror 3
+and
+.Xr exit 3 .
+They can be used both internally by any code in the
+.Lb libmandoc
+and externally by programs using that library, for example
+.Xr mandoc 1 ,
+.Xr apropos 1 ,
+and
+.Xr makewhatis 8 .
+.Pp
+The function
+.Fn mandoc_malloc
+allocates one new object, leaving the memory uninitialized.
+The functions
+.Fn mandoc_realloc
+and
+.Fn mandoc_reallocarray
+change the size of an existing object or array, possibly moving it.
+When shrinking the size, existing data is truncated; when growing,
+the additional memory is not initialized.
+The function
+.Fn mandoc_calloc
+allocates a new array, initializing it to zero.
+.Pp
+The argument
+.Fa size
+is the size of each object.
+The argument
+.Fa nmemb
+is the new number of objects in the array.
+The argument
+.Fa ptr
+is a pointer to the existing object or array to be resized; if it is
+.Dv NULL ,
+a new object or array is allocated.
+.Pp
+The functions
+.Fn mandoc_strdup
+and
+.Fn mandoc_strndup
+copy a string into newly allocated memory.
+For
+.Fn mandoc_strdup ,
+the string pointed to by
+.Fa s
+needs to be NUL-terminated.
+For
+.Fn mandoc_strndup ,
+at most
+.Fa maxlen
+bytes are copied.
+The function
+.Fn mandoc_asprintf
+writes output formatted according to
+.Fa format
+into newly allocated memory and returns a pointer to the result in
+.Fa ret .
+For all three string functions, the result is always NUL-terminated.
+.Pp
+When the objects and strings are no longer needed,
+the pointers returned by these functions can be passed to
+.Xr free 3 .
+.Sh RETURN VALUES
+The function
+.Fn mandoc_asprintf
+always returns the number of characters written, excluding the
+final NUL byte.
+It never returns -1.
+.Pp
+The other functions always return a valid pointer; they never return
+.Dv NULL .
+.Sh FILES
+These functions are implemented in
+.Pa mandoc_aux.c .
+.Sh SEE ALSO
+.Xr asprintf 3 ,
+.Xr exit 3 ,
+.Xr malloc 3 ,
+.Xr perror 3 ,
+.Xr strdup 3
+.Sh STANDARDS
+The functions
+.Fn malloc ,
+.Fn realloc ,
+and
+.Fn calloc
+are required by
+.St -ansiC .
+The functions
+.Fn strdup
+and
+.Fn strndup
+are required by
+.St -p1003.1-2008 .
+The function
+.Fn asprintf
+is a widespread extension that first appeared in the GNU C library.
+.Pp
+The function
+.Fn reallocarray
+is an extension that first appeared in
+.Ox 5.6 .
+If it is not provided by the operating system, the mandoc build system
+uses a bundled portable implementation.
+.Sh HISTORY
+The functions
+.Fn mandoc_malloc ,
+.Fn mandoc_realloc ,
+.Fn mandoc_calloc ,
+and
+.Fn mandoc_strdup
+have been available since mandoc 1.9.12,
+.Fn mandoc_strndup
+since 1.11.5,
+and
+.Fn mandoc_asprintf
+and
+.Fn mandoc_reallocarray
+since 1.12.4 and 1.13.0.
+.Sh AUTHORS
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org
diff --git a/contrib/mdocml/mandocdb.c b/contrib/mdocml/mandocdb.c
new file mode 100644
index 000000000000..a604b468ff74
--- /dev/null
+++ b/contrib/mdocml/mandocdb.c
@@ -0,0 +1,2491 @@
+/* $Id: mandocdb.c,v 1.155 2014/08/06 15:09:05 schwarze Exp $ */
+/*
+ * Copyright (c) 2011, 2012 Kristaps Dzonsons
+ * Copyright (c) 2011, 2012, 2013, 2014 Ingo Schwarze
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * 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
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef HAVE_OHASH
+#include
+#else
+#include "compat_ohash.h"
+#endif
+#include
+
+#include "mdoc.h"
+#include "man.h"
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "manpath.h"
+#include "mansearch.h"
+
+extern int mansearch_keymax;
+extern const char *const mansearch_keynames[];
+
+#define SQL_EXEC(_v) \
+ if (SQLITE_OK != sqlite3_exec(db, (_v), NULL, NULL, NULL)) \
+ say("", "%s: %s", (_v), sqlite3_errmsg(db))
+#define SQL_BIND_TEXT(_s, _i, _v) \
+ if (SQLITE_OK != sqlite3_bind_text \
+ ((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \
+ say(mlink->file, "%s", sqlite3_errmsg(db))
+#define SQL_BIND_INT(_s, _i, _v) \
+ if (SQLITE_OK != sqlite3_bind_int \
+ ((_s), (_i)++, (_v))) \
+ say(mlink->file, "%s", sqlite3_errmsg(db))
+#define SQL_BIND_INT64(_s, _i, _v) \
+ if (SQLITE_OK != sqlite3_bind_int64 \
+ ((_s), (_i)++, (_v))) \
+ say(mlink->file, "%s", sqlite3_errmsg(db))
+#define SQL_STEP(_s) \
+ if (SQLITE_DONE != sqlite3_step((_s))) \
+ say(mlink->file, "%s", sqlite3_errmsg(db))
+
+enum op {
+ OP_DEFAULT = 0, /* new dbs from dir list or default config */
+ OP_CONFFILE, /* new databases from custom config file */
+ OP_UPDATE, /* delete/add entries in existing database */
+ OP_DELETE, /* delete entries from existing database */
+ OP_TEST /* change no databases, report potential problems */
+};
+
+enum form {
+ FORM_NONE, /* format is unknown */
+ FORM_SRC, /* format is -man or -mdoc */
+ FORM_CAT /* format is cat */
+};
+
+struct str {
+ char *rendered; /* key in UTF-8 or ASCII form */
+ const struct mpage *mpage; /* if set, the owning parse */
+ uint64_t mask; /* bitmask in sequence */
+ char key[]; /* may contain escape sequences */
+};
+
+struct inodev {
+ ino_t st_ino;
+ dev_t st_dev;
+};
+
+struct mpage {
+ struct inodev inodev; /* used for hashing routine */
+ int64_t pageid; /* pageid in mpages SQL table */
+ enum form form; /* format from file content */
+ char *sec; /* section from file content */
+ char *arch; /* architecture from file content */
+ char *title; /* title from file content */
+ char *desc; /* description from file content */
+ struct mlink *mlinks; /* singly linked list */
+};
+
+struct mlink {
+ char file[PATH_MAX]; /* filename rel. to manpath */
+ enum form dform; /* format from directory */
+ enum form fform; /* format from file name suffix */
+ char *dsec; /* section from directory */
+ char *arch; /* architecture from directory */
+ char *name; /* name from file name (not empty) */
+ char *fsec; /* section from file name suffix */
+ struct mlink *next; /* singly linked list */
+ struct mpage *mpage; /* parent */
+ int gzip; /* filename has a .gz suffix */
+};
+
+enum stmt {
+ STMT_DELETE_PAGE = 0, /* delete mpage */
+ STMT_INSERT_PAGE, /* insert mpage */
+ STMT_INSERT_LINK, /* insert mlink */
+ STMT_INSERT_NAME, /* insert name */
+ STMT_INSERT_KEY, /* insert parsed key */
+ STMT__MAX
+};
+
+typedef int (*mdoc_fp)(struct mpage *, const struct mdoc_node *);
+
+struct mdoc_handler {
+ mdoc_fp fp; /* optional handler */
+ uint64_t mask; /* set unless handler returns 0 */
+};
+
+static void dbclose(int);
+static void dbadd(struct mpage *, struct mchars *);
+static void dbadd_mlink(const struct mlink *mlink);
+static int dbopen(int);
+static void dbprune(void);
+static void filescan(const char *);
+static void *hash_alloc(size_t, void *);
+static void hash_free(void *, void *);
+static void *hash_calloc(size_t, size_t, void *);
+static void mlink_add(struct mlink *, const struct stat *);
+static void mlink_check(struct mpage *, struct mlink *);
+static void mlink_free(struct mlink *);
+static void mlinks_undupe(struct mpage *);
+static void mpages_free(void);
+static void mpages_merge(struct mchars *, struct mparse *);
+static void names_check(void);
+static void parse_cat(struct mpage *, int);
+static void parse_man(struct mpage *, const struct man_node *);
+static void parse_mdoc(struct mpage *, const struct mdoc_node *);
+static int parse_mdoc_body(struct mpage *, const struct mdoc_node *);
+static int parse_mdoc_head(struct mpage *, const struct mdoc_node *);
+static int parse_mdoc_Fd(struct mpage *, const struct mdoc_node *);
+static int parse_mdoc_Fn(struct mpage *, const struct mdoc_node *);
+static int parse_mdoc_Nd(struct mpage *, const struct mdoc_node *);
+static int parse_mdoc_Nm(struct mpage *, const struct mdoc_node *);
+static int parse_mdoc_Sh(struct mpage *, const struct mdoc_node *);
+static int parse_mdoc_Xr(struct mpage *, const struct mdoc_node *);
+static void putkey(const struct mpage *, char *, uint64_t);
+static void putkeys(const struct mpage *,
+ const char *, size_t, uint64_t);
+static void putmdockey(const struct mpage *,
+ const struct mdoc_node *, uint64_t);
+static void render_key(struct mchars *, struct str *);
+static void say(const char *, const char *, ...);
+static int set_basedir(const char *);
+static int treescan(void);
+static size_t utf8(unsigned int, char [7]);
+
+static char tempfilename[32];
+static char *progname;
+static int nodb; /* no database changes */
+static int mparse_options; /* abort the parse early */
+static int use_all; /* use all found files */
+static int debug; /* print what we're doing */
+static int warnings; /* warn about crap */
+static int write_utf8; /* write UTF-8 output; else ASCII */
+static int exitcode; /* to be returned by main */
+static enum op op; /* operational mode */
+static char basedir[PATH_MAX]; /* current base directory */
+static struct ohash mpages; /* table of distinct manual pages */
+static struct ohash mlinks; /* table of directory entries */
+static struct ohash names; /* table of all names */
+static struct ohash strings; /* table of all strings */
+static sqlite3 *db = NULL; /* current database */
+static sqlite3_stmt *stmts[STMT__MAX]; /* current statements */
+static uint64_t name_mask;
+
+static const struct mdoc_handler mdocs[MDOC_MAX] = {
+ { NULL, 0 }, /* Ap */
+ { NULL, 0 }, /* Dd */
+ { NULL, 0 }, /* Dt */
+ { NULL, 0 }, /* Os */
+ { parse_mdoc_Sh, TYPE_Sh }, /* Sh */
+ { parse_mdoc_head, TYPE_Ss }, /* Ss */
+ { NULL, 0 }, /* Pp */
+ { NULL, 0 }, /* D1 */
+ { NULL, 0 }, /* Dl */
+ { NULL, 0 }, /* Bd */
+ { NULL, 0 }, /* Ed */
+ { NULL, 0 }, /* Bl */
+ { NULL, 0 }, /* El */
+ { NULL, 0 }, /* It */
+ { NULL, 0 }, /* Ad */
+ { NULL, TYPE_An }, /* An */
+ { NULL, TYPE_Ar }, /* Ar */
+ { NULL, TYPE_Cd }, /* Cd */
+ { NULL, TYPE_Cm }, /* Cm */
+ { NULL, TYPE_Dv }, /* Dv */
+ { NULL, TYPE_Er }, /* Er */
+ { NULL, TYPE_Ev }, /* Ev */
+ { NULL, 0 }, /* Ex */
+ { NULL, TYPE_Fa }, /* Fa */
+ { parse_mdoc_Fd, 0 }, /* Fd */
+ { NULL, TYPE_Fl }, /* Fl */
+ { parse_mdoc_Fn, 0 }, /* Fn */
+ { NULL, TYPE_Ft }, /* Ft */
+ { NULL, TYPE_Ic }, /* Ic */
+ { NULL, TYPE_In }, /* In */
+ { NULL, TYPE_Li }, /* Li */
+ { parse_mdoc_Nd, 0 }, /* Nd */
+ { parse_mdoc_Nm, 0 }, /* Nm */
+ { NULL, 0 }, /* Op */
+ { NULL, 0 }, /* Ot */
+ { NULL, TYPE_Pa }, /* Pa */
+ { NULL, 0 }, /* Rv */
+ { NULL, TYPE_St }, /* St */
+ { NULL, TYPE_Va }, /* Va */
+ { parse_mdoc_body, TYPE_Va }, /* Vt */
+ { parse_mdoc_Xr, 0 }, /* Xr */
+ { NULL, 0 }, /* %A */
+ { NULL, 0 }, /* %B */
+ { NULL, 0 }, /* %D */
+ { NULL, 0 }, /* %I */
+ { NULL, 0 }, /* %J */
+ { NULL, 0 }, /* %N */
+ { NULL, 0 }, /* %O */
+ { NULL, 0 }, /* %P */
+ { NULL, 0 }, /* %R */
+ { NULL, 0 }, /* %T */
+ { NULL, 0 }, /* %V */
+ { NULL, 0 }, /* Ac */
+ { NULL, 0 }, /* Ao */
+ { NULL, 0 }, /* Aq */
+ { NULL, TYPE_At }, /* At */
+ { NULL, 0 }, /* Bc */
+ { NULL, 0 }, /* Bf */
+ { NULL, 0 }, /* Bo */
+ { NULL, 0 }, /* Bq */
+ { NULL, TYPE_Bsx }, /* Bsx */
+ { NULL, TYPE_Bx }, /* Bx */
+ { NULL, 0 }, /* Db */
+ { NULL, 0 }, /* Dc */
+ { NULL, 0 }, /* Do */
+ { NULL, 0 }, /* Dq */
+ { NULL, 0 }, /* Ec */
+ { NULL, 0 }, /* Ef */
+ { NULL, TYPE_Em }, /* Em */
+ { NULL, 0 }, /* Eo */
+ { NULL, TYPE_Fx }, /* Fx */
+ { NULL, TYPE_Ms }, /* Ms */
+ { NULL, 0 }, /* No */
+ { NULL, 0 }, /* Ns */
+ { NULL, TYPE_Nx }, /* Nx */
+ { NULL, TYPE_Ox }, /* Ox */
+ { NULL, 0 }, /* Pc */
+ { NULL, 0 }, /* Pf */
+ { NULL, 0 }, /* Po */
+ { NULL, 0 }, /* Pq */
+ { NULL, 0 }, /* Qc */
+ { NULL, 0 }, /* Ql */
+ { NULL, 0 }, /* Qo */
+ { NULL, 0 }, /* Qq */
+ { NULL, 0 }, /* Re */
+ { NULL, 0 }, /* Rs */
+ { NULL, 0 }, /* Sc */
+ { NULL, 0 }, /* So */
+ { NULL, 0 }, /* Sq */
+ { NULL, 0 }, /* Sm */
+ { NULL, 0 }, /* Sx */
+ { NULL, TYPE_Sy }, /* Sy */
+ { NULL, TYPE_Tn }, /* Tn */
+ { NULL, 0 }, /* Ux */
+ { NULL, 0 }, /* Xc */
+ { NULL, 0 }, /* Xo */
+ { parse_mdoc_head, 0 }, /* Fo */
+ { NULL, 0 }, /* Fc */
+ { NULL, 0 }, /* Oo */
+ { NULL, 0 }, /* Oc */
+ { NULL, 0 }, /* Bk */
+ { NULL, 0 }, /* Ek */
+ { NULL, 0 }, /* Bt */
+ { NULL, 0 }, /* Hf */
+ { NULL, 0 }, /* Fr */
+ { NULL, 0 }, /* Ud */
+ { NULL, TYPE_Lb }, /* Lb */
+ { NULL, 0 }, /* Lp */
+ { NULL, TYPE_Lk }, /* Lk */
+ { NULL, TYPE_Mt }, /* Mt */
+ { NULL, 0 }, /* Brq */
+ { NULL, 0 }, /* Bro */
+ { NULL, 0 }, /* Brc */
+ { NULL, 0 }, /* %C */
+ { NULL, 0 }, /* Es */
+ { NULL, 0 }, /* En */
+ { NULL, TYPE_Dx }, /* Dx */
+ { NULL, 0 }, /* %Q */
+ { NULL, 0 }, /* br */
+ { NULL, 0 }, /* sp */
+ { NULL, 0 }, /* %U */
+ { NULL, 0 }, /* Ta */
+};
+
+
+int
+main(int argc, char *argv[])
+{
+ int ch, i;
+ size_t j, sz;
+ const char *path_arg;
+ struct mchars *mc;
+ struct manpaths dirs;
+ struct mparse *mp;
+ struct ohash_info mpages_info, mlinks_info;
+
+ memset(stmts, 0, STMT__MAX * sizeof(sqlite3_stmt *));
+ memset(&dirs, 0, sizeof(struct manpaths));
+
+ mpages_info.alloc = mlinks_info.alloc = hash_alloc;
+ mpages_info.calloc = mlinks_info.calloc = hash_calloc;
+ mpages_info.free = mlinks_info.free = hash_free;
+
+ mpages_info.key_offset = offsetof(struct mpage, inodev);
+ mlinks_info.key_offset = offsetof(struct mlink, file);
+
+ progname = strrchr(argv[0], '/');
+ if (progname == NULL)
+ progname = argv[0];
+ else
+ ++progname;
+
+ /*
+ * We accept a few different invocations.
+ * The CHECKOP macro makes sure that invocation styles don't
+ * clobber each other.
+ */
+#define CHECKOP(_op, _ch) do \
+ if (OP_DEFAULT != (_op)) { \
+ fprintf(stderr, "%s: -%c: Conflicting option\n", \
+ progname, (_ch)); \
+ goto usage; \
+ } while (/*CONSTCOND*/0)
+
+ path_arg = NULL;
+ op = OP_DEFAULT;
+
+ while (-1 != (ch = getopt(argc, argv, "aC:Dd:npQT:tu:v")))
+ switch (ch) {
+ case 'a':
+ use_all = 1;
+ break;
+ case 'C':
+ CHECKOP(op, ch);
+ path_arg = optarg;
+ op = OP_CONFFILE;
+ break;
+ case 'D':
+ debug++;
+ break;
+ case 'd':
+ CHECKOP(op, ch);
+ path_arg = optarg;
+ op = OP_UPDATE;
+ break;
+ case 'n':
+ nodb = 1;
+ break;
+ case 'p':
+ warnings = 1;
+ break;
+ case 'Q':
+ mparse_options |= MPARSE_QUICK;
+ break;
+ case 'T':
+ if (strcmp(optarg, "utf8")) {
+ fprintf(stderr, "%s: -T%s: "
+ "Unsupported output format\n",
+ progname, optarg);
+ goto usage;
+ }
+ write_utf8 = 1;
+ break;
+ case 't':
+ CHECKOP(op, ch);
+ dup2(STDOUT_FILENO, STDERR_FILENO);
+ op = OP_TEST;
+ nodb = warnings = 1;
+ break;
+ case 'u':
+ CHECKOP(op, ch);
+ path_arg = optarg;
+ op = OP_DELETE;
+ break;
+ case 'v':
+ /* Compatibility with espie@'s makewhatis. */
+ break;
+ default:
+ goto usage;
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (OP_CONFFILE == op && argc > 0) {
+ fprintf(stderr, "%s: -C: Too many arguments\n",
+ progname);
+ goto usage;
+ }
+
+ exitcode = (int)MANDOCLEVEL_OK;
+ mp = mparse_alloc(mparse_options, MANDOCLEVEL_FATAL, NULL, NULL);
+ mc = mchars_alloc();
+
+ ohash_init(&mpages, 6, &mpages_info);
+ ohash_init(&mlinks, 6, &mlinks_info);
+
+ if (OP_UPDATE == op || OP_DELETE == op || OP_TEST == op) {
+
+ /*
+ * Most of these deal with a specific directory.
+ * Jump into that directory first.
+ */
+ if (OP_TEST != op && 0 == set_basedir(path_arg))
+ goto out;
+
+ if (dbopen(1)) {
+ /*
+ * The existing database is usable. Process
+ * all files specified on the command-line.
+ */
+ use_all = 1;
+ for (i = 0; i < argc; i++)
+ filescan(argv[i]);
+ if (OP_TEST != op)
+ dbprune();
+ } else {
+ /*
+ * Database missing or corrupt.
+ * Recreate from scratch.
+ */
+ exitcode = (int)MANDOCLEVEL_OK;
+ op = OP_DEFAULT;
+ if (0 == treescan())
+ goto out;
+ if (0 == dbopen(0))
+ goto out;
+ }
+ if (OP_DELETE != op)
+ mpages_merge(mc, mp);
+ dbclose(OP_DEFAULT == op ? 0 : 1);
+ } else {
+ /*
+ * If we have arguments, use them as our manpaths.
+ * If we don't, grok from manpath(1) or however else
+ * manpath_parse() wants to do it.
+ */
+ if (argc > 0) {
+ dirs.paths = mandoc_reallocarray(NULL,
+ argc, sizeof(char *));
+ dirs.sz = (size_t)argc;
+ for (i = 0; i < argc; i++)
+ dirs.paths[i] = mandoc_strdup(argv[i]);
+ } else
+ manpath_parse(&dirs, path_arg, NULL, NULL);
+
+ if (0 == dirs.sz) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say("", "Empty manpath");
+ }
+
+ /*
+ * First scan the tree rooted at a base directory, then
+ * build a new database and finally move it into place.
+ * Ignore zero-length directories and strip trailing
+ * slashes.
+ */
+ for (j = 0; j < dirs.sz; j++) {
+ sz = strlen(dirs.paths[j]);
+ if (sz && '/' == dirs.paths[j][sz - 1])
+ dirs.paths[j][--sz] = '\0';
+ if (0 == sz)
+ continue;
+
+ if (j) {
+ ohash_init(&mpages, 6, &mpages_info);
+ ohash_init(&mlinks, 6, &mlinks_info);
+ }
+
+ if (0 == set_basedir(dirs.paths[j]))
+ goto out;
+ if (0 == treescan())
+ goto out;
+ if (0 == dbopen(0))
+ goto out;
+
+ mpages_merge(mc, mp);
+ if (warnings && !nodb &&
+ ! (MPARSE_QUICK & mparse_options))
+ names_check();
+ dbclose(0);
+
+ if (j + 1 < dirs.sz) {
+ mpages_free();
+ ohash_delete(&mpages);
+ ohash_delete(&mlinks);
+ }
+ }
+ }
+out:
+ manpath_free(&dirs);
+ mchars_free(mc);
+ mparse_free(mp);
+ mpages_free();
+ ohash_delete(&mpages);
+ ohash_delete(&mlinks);
+ return(exitcode);
+usage:
+ fprintf(stderr, "usage: %s [-aDnpQ] [-C file] [-Tutf8]\n"
+ " %s [-aDnpQ] [-Tutf8] dir ...\n"
+ " %s [-DnpQ] [-Tutf8] -d dir [file ...]\n"
+ " %s [-Dnp] -u dir [file ...]\n"
+ " %s [-Q] -t file ...\n",
+ progname, progname, progname,
+ progname, progname);
+
+ return((int)MANDOCLEVEL_BADARG);
+}
+
+/*
+ * Scan a directory tree rooted at "basedir" for manpages.
+ * We use fts(), scanning directory parts along the way for clues to our
+ * section and architecture.
+ *
+ * If use_all has been specified, grok all files.
+ * If not, sanitise paths to the following:
+ *
+ * [./]man*[/]/.
+ * or
+ * [./]cat[/]/.0
+ *
+ * TODO: accomodate for multi-language directories.
+ */
+static int
+treescan(void)
+{
+ char buf[PATH_MAX];
+ FTS *f;
+ FTSENT *ff;
+ struct mlink *mlink;
+ int dform, gzip;
+ char *dsec, *arch, *fsec, *cp;
+ const char *path;
+ const char *argv[2];
+
+ argv[0] = ".";
+ argv[1] = (char *)NULL;
+
+ f = fts_open((char * const *)argv,
+ FTS_PHYSICAL | FTS_NOCHDIR, NULL);
+ if (NULL == f) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&fts_open");
+ return(0);
+ }
+
+ dsec = arch = NULL;
+ dform = FORM_NONE;
+
+ while (NULL != (ff = fts_read(f))) {
+ path = ff->fts_path + 2;
+ switch (ff->fts_info) {
+
+ /*
+ * Symbolic links require various sanity checks,
+ * then get handled just like regular files.
+ */
+ case FTS_SL:
+ if (NULL == realpath(path, buf)) {
+ if (warnings)
+ say(path, "&realpath");
+ continue;
+ }
+ if (strstr(buf, basedir) != buf) {
+ if (warnings) say("",
+ "%s: outside base directory", buf);
+ continue;
+ }
+ /* Use logical inode to avoid mpages dupe. */
+ if (-1 == stat(path, ff->fts_statp)) {
+ if (warnings)
+ say(path, "&stat");
+ continue;
+ }
+ /* FALLTHROUGH */
+
+ /*
+ * If we're a regular file, add an mlink by using the
+ * stored directory data and handling the filename.
+ */
+ case FTS_F:
+ if (0 == strcmp(path, MANDOC_DB))
+ continue;
+ if ( ! use_all && ff->fts_level < 2) {
+ if (warnings)
+ say(path, "Extraneous file");
+ continue;
+ }
+ gzip = 0;
+ fsec = NULL;
+ while (NULL == fsec) {
+ fsec = strrchr(ff->fts_name, '.');
+ if (NULL == fsec || strcmp(fsec+1, "gz"))
+ break;
+ gzip = 1;
+ *fsec = '\0';
+ fsec = NULL;
+ }
+ if (NULL == fsec) {
+ if ( ! use_all) {
+ if (warnings)
+ say(path,
+ "No filename suffix");
+ continue;
+ }
+ } else if (0 == strcmp(++fsec, "html")) {
+ if (warnings)
+ say(path, "Skip html");
+ continue;
+ } else if (0 == strcmp(fsec, "ps")) {
+ if (warnings)
+ say(path, "Skip ps");
+ continue;
+ } else if (0 == strcmp(fsec, "pdf")) {
+ if (warnings)
+ say(path, "Skip pdf");
+ continue;
+ } else if ( ! use_all &&
+ ((FORM_SRC == dform && strcmp(fsec, dsec)) ||
+ (FORM_CAT == dform && strcmp(fsec, "0")))) {
+ if (warnings)
+ say(path, "Wrong filename suffix");
+ continue;
+ } else
+ fsec[-1] = '\0';
+
+ mlink = mandoc_calloc(1, sizeof(struct mlink));
+ if (strlcpy(mlink->file, path,
+ sizeof(mlink->file)) >=
+ sizeof(mlink->file)) {
+ say(path, "Filename too long");
+ free(mlink);
+ continue;
+ }
+ mlink->dform = dform;
+ mlink->dsec = dsec;
+ mlink->arch = arch;
+ mlink->name = ff->fts_name;
+ mlink->fsec = fsec;
+ mlink->gzip = gzip;
+ mlink_add(mlink, ff->fts_statp);
+ continue;
+
+ case FTS_D:
+ /* FALLTHROUGH */
+ case FTS_DP:
+ break;
+
+ default:
+ if (warnings)
+ say(path, "Not a regular file");
+ continue;
+ }
+
+ switch (ff->fts_level) {
+ case 0:
+ /* Ignore the root directory. */
+ break;
+ case 1:
+ /*
+ * This might contain manX/ or catX/.
+ * Try to infer this from the name.
+ * If we're not in use_all, enforce it.
+ */
+ cp = ff->fts_name;
+ if (FTS_DP == ff->fts_info)
+ break;
+
+ if (0 == strncmp(cp, "man", 3)) {
+ dform = FORM_SRC;
+ dsec = cp + 3;
+ } else if (0 == strncmp(cp, "cat", 3)) {
+ dform = FORM_CAT;
+ dsec = cp + 3;
+ } else {
+ dform = FORM_NONE;
+ dsec = NULL;
+ }
+
+ if (NULL != dsec || use_all)
+ break;
+
+ if (warnings)
+ say(path, "Unknown directory part");
+ fts_set(f, ff, FTS_SKIP);
+ break;
+ case 2:
+ /*
+ * Possibly our architecture.
+ * If we're descending, keep tabs on it.
+ */
+ if (FTS_DP != ff->fts_info && NULL != dsec)
+ arch = ff->fts_name;
+ else
+ arch = NULL;
+ break;
+ default:
+ if (FTS_DP == ff->fts_info || use_all)
+ break;
+ if (warnings)
+ say(path, "Extraneous directory part");
+ fts_set(f, ff, FTS_SKIP);
+ break;
+ }
+ }
+
+ fts_close(f);
+ return(1);
+}
+
+/*
+ * Add a file to the mlinks table.
+ * Do not verify that it's a "valid" looking manpage (we'll do that
+ * later).
+ *
+ * Try to infer the manual section, architecture, and page name from the
+ * path, assuming it looks like
+ *
+ * [./]man*[/]/.
+ * or
+ * [./]cat[/]/.0
+ *
+ * See treescan() for the fts(3) version of this.
+ */
+static void
+filescan(const char *file)
+{
+ char buf[PATH_MAX];
+ struct stat st;
+ struct mlink *mlink;
+ char *p, *start;
+
+ assert(use_all);
+
+ if (0 == strncmp(file, "./", 2))
+ file += 2;
+
+ /*
+ * We have to do lstat(2) before realpath(3) loses
+ * the information whether this is a symbolic link.
+ * We need to know that because for symbolic links,
+ * we want to use the orginal file name, while for
+ * regular files, we want to use the real path.
+ */
+ if (-1 == lstat(file, &st)) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say(file, "&lstat");
+ return;
+ } else if (0 == ((S_IFREG | S_IFLNK) & st.st_mode)) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say(file, "Not a regular file");
+ return;
+ }
+
+ /*
+ * We have to resolve the file name to the real path
+ * in any case for the base directory check.
+ */
+ if (NULL == realpath(file, buf)) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say(file, "&realpath");
+ return;
+ }
+
+ if (OP_TEST == op)
+ start = buf;
+ else if (strstr(buf, basedir) == buf)
+ start = buf + strlen(basedir);
+ else {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say("", "%s: outside base directory", buf);
+ return;
+ }
+
+ /*
+ * Now we are sure the file is inside our tree.
+ * If it is a symbolic link, ignore the real path
+ * and use the original name.
+ * This implies passing stuff like "cat1/../man1/foo.1"
+ * on the command line won't work. So don't do that.
+ * Note the stat(2) can still fail if the link target
+ * doesn't exist.
+ */
+ if (S_IFLNK & st.st_mode) {
+ if (-1 == stat(buf, &st)) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say(file, "&stat");
+ return;
+ }
+ if (strlcpy(buf, file, sizeof(buf)) >= sizeof(buf)) {
+ say(file, "Filename too long");
+ return;
+ }
+ start = buf;
+ if (OP_TEST != op && strstr(buf, basedir) == buf)
+ start += strlen(basedir);
+ }
+
+ mlink = mandoc_calloc(1, sizeof(struct mlink));
+ if (strlcpy(mlink->file, start, sizeof(mlink->file)) >=
+ sizeof(mlink->file)) {
+ say(start, "Filename too long");
+ return;
+ }
+
+ /*
+ * First try to guess our directory structure.
+ * If we find a separator, try to look for man* or cat*.
+ * If we find one of these and what's underneath is a directory,
+ * assume it's an architecture.
+ */
+ if (NULL != (p = strchr(start, '/'))) {
+ *p++ = '\0';
+ if (0 == strncmp(start, "man", 3)) {
+ mlink->dform = FORM_SRC;
+ mlink->dsec = start + 3;
+ } else if (0 == strncmp(start, "cat", 3)) {
+ mlink->dform = FORM_CAT;
+ mlink->dsec = start + 3;
+ }
+
+ start = p;
+ if (NULL != mlink->dsec && NULL != (p = strchr(start, '/'))) {
+ *p++ = '\0';
+ mlink->arch = start;
+ start = p;
+ }
+ }
+
+ /*
+ * Now check the file suffix.
+ * Suffix of `.0' indicates a catpage, `.1-9' is a manpage.
+ */
+ p = strrchr(start, '\0');
+ while (p-- > start && '/' != *p && '.' != *p)
+ /* Loop. */ ;
+
+ if ('.' == *p) {
+ *p++ = '\0';
+ mlink->fsec = p;
+ }
+
+ /*
+ * Now try to parse the name.
+ * Use the filename portion of the path.
+ */
+ mlink->name = start;
+ if (NULL != (p = strrchr(start, '/'))) {
+ mlink->name = p + 1;
+ *p = '\0';
+ }
+ mlink_add(mlink, &st);
+}
+
+static void
+mlink_add(struct mlink *mlink, const struct stat *st)
+{
+ struct inodev inodev;
+ struct mpage *mpage;
+ unsigned int slot;
+
+ assert(NULL != mlink->file);
+
+ mlink->dsec = mandoc_strdup(mlink->dsec ? mlink->dsec : "");
+ mlink->arch = mandoc_strdup(mlink->arch ? mlink->arch : "");
+ mlink->name = mandoc_strdup(mlink->name ? mlink->name : "");
+ mlink->fsec = mandoc_strdup(mlink->fsec ? mlink->fsec : "");
+
+ if ('0' == *mlink->fsec) {
+ free(mlink->fsec);
+ mlink->fsec = mandoc_strdup(mlink->dsec);
+ mlink->fform = FORM_CAT;
+ } else if ('1' <= *mlink->fsec && '9' >= *mlink->fsec)
+ mlink->fform = FORM_SRC;
+ else
+ mlink->fform = FORM_NONE;
+
+ slot = ohash_qlookup(&mlinks, mlink->file);
+ assert(NULL == ohash_find(&mlinks, slot));
+ ohash_insert(&mlinks, slot, mlink);
+
+ inodev.st_ino = st->st_ino;
+ inodev.st_dev = st->st_dev;
+ slot = ohash_lookup_memory(&mpages, (char *)&inodev,
+ sizeof(struct inodev), inodev.st_ino);
+ mpage = ohash_find(&mpages, slot);
+ if (NULL == mpage) {
+ mpage = mandoc_calloc(1, sizeof(struct mpage));
+ mpage->inodev.st_ino = inodev.st_ino;
+ mpage->inodev.st_dev = inodev.st_dev;
+ ohash_insert(&mpages, slot, mpage);
+ } else
+ mlink->next = mpage->mlinks;
+ mpage->mlinks = mlink;
+ mlink->mpage = mpage;
+}
+
+static void
+mlink_free(struct mlink *mlink)
+{
+
+ free(mlink->dsec);
+ free(mlink->arch);
+ free(mlink->name);
+ free(mlink->fsec);
+ free(mlink);
+}
+
+static void
+mpages_free(void)
+{
+ struct mpage *mpage;
+ struct mlink *mlink;
+ unsigned int slot;
+
+ mpage = ohash_first(&mpages, &slot);
+ while (NULL != mpage) {
+ while (NULL != (mlink = mpage->mlinks)) {
+ mpage->mlinks = mlink->next;
+ mlink_free(mlink);
+ }
+ free(mpage->sec);
+ free(mpage->arch);
+ free(mpage->title);
+ free(mpage->desc);
+ free(mpage);
+ mpage = ohash_next(&mpages, &slot);
+ }
+}
+
+/*
+ * For each mlink to the mpage, check whether the path looks like
+ * it is formatted, and if it does, check whether a source manual
+ * exists by the same name, ignoring the suffix.
+ * If both conditions hold, drop the mlink.
+ */
+static void
+mlinks_undupe(struct mpage *mpage)
+{
+ char buf[PATH_MAX];
+ struct mlink **prev;
+ struct mlink *mlink;
+ char *bufp;
+
+ mpage->form = FORM_CAT;
+ prev = &mpage->mlinks;
+ while (NULL != (mlink = *prev)) {
+ if (FORM_CAT != mlink->dform) {
+ mpage->form = FORM_NONE;
+ goto nextlink;
+ }
+ (void)strlcpy(buf, mlink->file, sizeof(buf));
+ bufp = strstr(buf, "cat");
+ assert(NULL != bufp);
+ memcpy(bufp, "man", 3);
+ if (NULL != (bufp = strrchr(buf, '.')))
+ *++bufp = '\0';
+ (void)strlcat(buf, mlink->dsec, sizeof(buf));
+ if (NULL == ohash_find(&mlinks,
+ ohash_qlookup(&mlinks, buf)))
+ goto nextlink;
+ if (warnings)
+ say(mlink->file, "Man source exists: %s", buf);
+ if (use_all)
+ goto nextlink;
+ *prev = mlink->next;
+ mlink_free(mlink);
+ continue;
+nextlink:
+ prev = &(*prev)->next;
+ }
+}
+
+static void
+mlink_check(struct mpage *mpage, struct mlink *mlink)
+{
+ struct str *str;
+ unsigned int slot;
+
+ /*
+ * Check whether the manual section given in a file
+ * agrees with the directory where the file is located.
+ * Some manuals have suffixes like (3p) on their
+ * section number either inside the file or in the
+ * directory name, some are linked into more than one
+ * section, like encrypt(1) = makekey(8).
+ */
+
+ if (FORM_SRC == mpage->form &&
+ strcasecmp(mpage->sec, mlink->dsec))
+ say(mlink->file, "Section \"%s\" manual in %s directory",
+ mpage->sec, mlink->dsec);
+
+ /*
+ * Manual page directories exist for each kernel
+ * architecture as returned by machine(1).
+ * However, many manuals only depend on the
+ * application architecture as returned by arch(1).
+ * For example, some (2/ARM) manuals are shared
+ * across the "armish" and "zaurus" kernel
+ * architectures.
+ * A few manuals are even shared across completely
+ * different architectures, for example fdformat(1)
+ * on amd64, i386, sparc, and sparc64.
+ */
+
+ if (strcasecmp(mpage->arch, mlink->arch))
+ say(mlink->file, "Architecture \"%s\" manual in "
+ "\"%s\" directory", mpage->arch, mlink->arch);
+
+ /*
+ * XXX
+ * parse_cat() doesn't set NAME_TITLE yet.
+ */
+
+ if (FORM_CAT == mpage->form)
+ return;
+
+ /*
+ * Check whether this mlink
+ * appears as a name in the NAME section.
+ */
+
+ slot = ohash_qlookup(&names, mlink->name);
+ str = ohash_find(&names, slot);
+ assert(NULL != str);
+ if ( ! (NAME_TITLE & str->mask))
+ say(mlink->file, "Name missing in NAME section");
+}
+
+/*
+ * Run through the files in the global vector "mpages"
+ * and add them to the database specified in "basedir".
+ *
+ * This handles the parsing scheme itself, using the cues of directory
+ * and filename to determine whether the file is parsable or not.
+ */
+static void
+mpages_merge(struct mchars *mc, struct mparse *mp)
+{
+ char any[] = "any";
+ struct ohash_info str_info;
+ int fd[2];
+ struct mpage *mpage, *mpage_dest;
+ struct mlink *mlink, *mlink_dest;
+ struct mdoc *mdoc;
+ struct man *man;
+ char *sodest;
+ char *cp;
+ pid_t child_pid;
+ int status;
+ unsigned int pslot;
+ enum mandoclevel lvl;
+
+ str_info.alloc = hash_alloc;
+ str_info.calloc = hash_calloc;
+ str_info.free = hash_free;
+ str_info.key_offset = offsetof(struct str, key);
+
+ if (0 == nodb)
+ SQL_EXEC("BEGIN TRANSACTION");
+
+ mpage = ohash_first(&mpages, &pslot);
+ while (NULL != mpage) {
+ mlinks_undupe(mpage);
+ if (NULL == mpage->mlinks) {
+ mpage = ohash_next(&mpages, &pslot);
+ continue;
+ }
+
+ name_mask = NAME_MASK;
+ ohash_init(&names, 4, &str_info);
+ ohash_init(&strings, 6, &str_info);
+ mparse_reset(mp);
+ mdoc = NULL;
+ man = NULL;
+ sodest = NULL;
+ child_pid = 0;
+ fd[0] = -1;
+ fd[1] = -1;
+
+ if (mpage->mlinks->gzip) {
+ if (-1 == pipe(fd)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(mpage->mlinks->file, "&pipe gunzip");
+ goto nextpage;
+ }
+ switch (child_pid = fork()) {
+ case -1:
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(mpage->mlinks->file, "&fork gunzip");
+ child_pid = 0;
+ close(fd[1]);
+ close(fd[0]);
+ goto nextpage;
+ case 0:
+ close(fd[0]);
+ if (-1 == dup2(fd[1], STDOUT_FILENO)) {
+ say(mpage->mlinks->file,
+ "&dup gunzip");
+ exit(1);
+ }
+ execlp("gunzip", "gunzip", "-c",
+ mpage->mlinks->file, NULL);
+ say(mpage->mlinks->file, "&exec gunzip");
+ exit(1);
+ default:
+ close(fd[1]);
+ break;
+ }
+ }
+
+ /*
+ * Try interpreting the file as mdoc(7) or man(7)
+ * source code, unless it is already known to be
+ * formatted. Fall back to formatted mode.
+ */
+ if (FORM_CAT != mpage->mlinks->dform ||
+ FORM_CAT != mpage->mlinks->fform) {
+ lvl = mparse_readfd(mp, fd[0], mpage->mlinks->file);
+ if (lvl < MANDOCLEVEL_FATAL)
+ mparse_result(mp, &mdoc, &man, &sodest);
+ }
+
+ if (NULL != sodest) {
+ mlink_dest = ohash_find(&mlinks,
+ ohash_qlookup(&mlinks, sodest));
+ if (NULL != mlink_dest) {
+
+ /* The .so target exists. */
+
+ mpage_dest = mlink_dest->mpage;
+ mlink = mpage->mlinks;
+ while (1) {
+ mlink->mpage = mpage_dest;
+
+ /*
+ * If the target was already
+ * processed, add the links
+ * to the database now.
+ * Otherwise, this will
+ * happen when we come
+ * to the target.
+ */
+
+ if (mpage_dest->pageid)
+ dbadd_mlink(mlink);
+
+ if (NULL == mlink->next)
+ break;
+ mlink = mlink->next;
+ }
+
+ /* Move all links to the target. */
+
+ mlink->next = mlink_dest->next;
+ mlink_dest->next = mpage->mlinks;
+ mpage->mlinks = NULL;
+ }
+ goto nextpage;
+ } else if (NULL != mdoc) {
+ mpage->form = FORM_SRC;
+ mpage->sec = mdoc_meta(mdoc)->msec;
+ mpage->sec = mandoc_strdup(
+ NULL == mpage->sec ? "" : mpage->sec);
+ mpage->arch = mdoc_meta(mdoc)->arch;
+ mpage->arch = mandoc_strdup(
+ NULL == mpage->arch ? "" : mpage->arch);
+ mpage->title =
+ mandoc_strdup(mdoc_meta(mdoc)->title);
+ } else if (NULL != man) {
+ mpage->form = FORM_SRC;
+ mpage->sec =
+ mandoc_strdup(man_meta(man)->msec);
+ mpage->arch =
+ mandoc_strdup(mpage->mlinks->arch);
+ mpage->title =
+ mandoc_strdup(man_meta(man)->title);
+ } else {
+ mpage->form = FORM_CAT;
+ mpage->sec =
+ mandoc_strdup(mpage->mlinks->dsec);
+ mpage->arch =
+ mandoc_strdup(mpage->mlinks->arch);
+ mpage->title =
+ mandoc_strdup(mpage->mlinks->name);
+ }
+ putkey(mpage, mpage->sec, TYPE_sec);
+ putkey(mpage, '\0' == *mpage->arch ?
+ any : mpage->arch, TYPE_arch);
+
+ for (mlink = mpage->mlinks; mlink; mlink = mlink->next) {
+ if ('\0' != *mlink->dsec)
+ putkey(mpage, mlink->dsec, TYPE_sec);
+ if ('\0' != *mlink->fsec)
+ putkey(mpage, mlink->fsec, TYPE_sec);
+ putkey(mpage, '\0' == *mlink->arch ?
+ any : mlink->arch, TYPE_arch);
+ putkey(mpage, mlink->name, NAME_FILE);
+ }
+
+ assert(NULL == mpage->desc);
+ if (NULL != mdoc) {
+ if (NULL != (cp = mdoc_meta(mdoc)->name))
+ putkey(mpage, cp, NAME_HEAD);
+ parse_mdoc(mpage, mdoc_node(mdoc));
+ } else if (NULL != man)
+ parse_man(mpage, man_node(man));
+ else
+ parse_cat(mpage, fd[0]);
+ if (NULL == mpage->desc)
+ mpage->desc = mandoc_strdup(mpage->mlinks->name);
+
+ if (warnings && !use_all)
+ for (mlink = mpage->mlinks; mlink;
+ mlink = mlink->next)
+ mlink_check(mpage, mlink);
+
+ dbadd(mpage, mc);
+
+nextpage:
+ if (child_pid) {
+ if (-1 == waitpid(child_pid, &status, 0)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(mpage->mlinks->file, "&wait gunzip");
+ } else if (WIFSIGNALED(status)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(mpage->mlinks->file,
+ "gunzip died from signal %d",
+ WTERMSIG(status));
+ } else if (WEXITSTATUS(status)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(mpage->mlinks->file,
+ "gunzip failed with code %d",
+ WEXITSTATUS(status));
+ }
+ }
+ ohash_delete(&strings);
+ ohash_delete(&names);
+ mpage = ohash_next(&mpages, &pslot);
+ }
+
+ if (0 == nodb)
+ SQL_EXEC("END TRANSACTION");
+}
+
+static void
+names_check(void)
+{
+ sqlite3_stmt *stmt;
+ const char *name, *sec, *arch, *key;
+ int irc;
+
+ sqlite3_prepare_v2(db,
+ "SELECT name, sec, arch, key FROM ("
+ "SELECT name AS key, pageid FROM names "
+ "WHERE bits & ? AND NOT EXISTS ("
+ "SELECT pageid FROM mlinks "
+ "WHERE mlinks.pageid == names.pageid "
+ "AND mlinks.name == names.name"
+ ")"
+ ") JOIN ("
+ "SELECT sec, arch, name, pageid FROM mlinks "
+ "GROUP BY pageid"
+ ") USING (pageid);",
+ -1, &stmt, NULL);
+
+ if (SQLITE_OK != sqlite3_bind_int64(stmt, 1, NAME_TITLE))
+ say("", "%s", sqlite3_errmsg(db));
+
+ while (SQLITE_ROW == (irc = sqlite3_step(stmt))) {
+ name = (const char *)sqlite3_column_text(stmt, 0);
+ sec = (const char *)sqlite3_column_text(stmt, 1);
+ arch = (const char *)sqlite3_column_text(stmt, 2);
+ key = (const char *)sqlite3_column_text(stmt, 3);
+ say("", "%s(%s%s%s) lacks mlink \"%s\"", name, sec,
+ '\0' == *arch ? "" : "/",
+ '\0' == *arch ? "" : arch, key);
+ }
+ sqlite3_finalize(stmt);
+}
+
+static void
+parse_cat(struct mpage *mpage, int fd)
+{
+ FILE *stream;
+ char *line, *p, *title;
+ size_t len, plen, titlesz;
+
+ stream = (-1 == fd) ?
+ fopen(mpage->mlinks->file, "r") :
+ fdopen(fd, "r");
+ if (NULL == stream) {
+ if (warnings)
+ say(mpage->mlinks->file, "&fopen");
+ return;
+ }
+
+ /* Skip to first blank line. */
+
+ while (NULL != (line = fgetln(stream, &len)))
+ if ('\n' == *line)
+ break;
+
+ /*
+ * Assume the first line that is not indented
+ * is the first section header. Skip to it.
+ */
+
+ while (NULL != (line = fgetln(stream, &len)))
+ if ('\n' != *line && ' ' != *line)
+ break;
+
+ /*
+ * Read up until the next section into a buffer.
+ * Strip the leading and trailing newline from each read line,
+ * appending a trailing space.
+ * Ignore empty (whitespace-only) lines.
+ */
+
+ titlesz = 0;
+ title = NULL;
+
+ while (NULL != (line = fgetln(stream, &len))) {
+ if (' ' != *line || '\n' != line[len - 1])
+ break;
+ while (len > 0 && isspace((unsigned char)*line)) {
+ line++;
+ len--;
+ }
+ if (1 == len)
+ continue;
+ title = mandoc_realloc(title, titlesz + len);
+ memcpy(title + titlesz, line, len);
+ titlesz += len;
+ title[titlesz - 1] = ' ';
+ }
+
+ /*
+ * If no page content can be found, or the input line
+ * is already the next section header, or there is no
+ * trailing newline, reuse the page title as the page
+ * description.
+ */
+
+ if (NULL == title || '\0' == *title) {
+ if (warnings)
+ say(mpage->mlinks->file,
+ "Cannot find NAME section");
+ fclose(stream);
+ free(title);
+ return;
+ }
+
+ title = mandoc_realloc(title, titlesz + 1);
+ title[titlesz] = '\0';
+
+ /*
+ * Skip to the first dash.
+ * Use the remaining line as the description (no more than 70
+ * bytes).
+ */
+
+ if (NULL != (p = strstr(title, "- "))) {
+ for (p += 2; ' ' == *p || '\b' == *p; p++)
+ /* Skip to next word. */ ;
+ } else {
+ if (warnings)
+ say(mpage->mlinks->file,
+ "No dash in title line");
+ p = title;
+ }
+
+ plen = strlen(p);
+
+ /* Strip backspace-encoding from line. */
+
+ while (NULL != (line = memchr(p, '\b', plen))) {
+ len = line - p;
+ if (0 == len) {
+ memmove(line, line + 1, plen--);
+ continue;
+ }
+ memmove(line - 1, line + 1, plen - len);
+ plen -= 2;
+ }
+
+ mpage->desc = mandoc_strdup(p);
+ fclose(stream);
+ free(title);
+}
+
+/*
+ * Put a type/word pair into the word database for this particular file.
+ */
+static void
+putkey(const struct mpage *mpage, char *value, uint64_t type)
+{
+ char *cp;
+
+ assert(NULL != value);
+ if (TYPE_arch == type)
+ for (cp = value; *cp; cp++)
+ if (isupper((unsigned char)*cp))
+ *cp = _tolower((unsigned char)*cp);
+ putkeys(mpage, value, strlen(value), type);
+}
+
+/*
+ * Grok all nodes at or below a certain mdoc node into putkey().
+ */
+static void
+putmdockey(const struct mpage *mpage,
+ const struct mdoc_node *n, uint64_t m)
+{
+
+ for ( ; NULL != n; n = n->next) {
+ if (NULL != n->child)
+ putmdockey(mpage, n->child, m);
+ if (MDOC_TEXT == n->type)
+ putkey(mpage, n->string, m);
+ }
+}
+
+static void
+parse_man(struct mpage *mpage, const struct man_node *n)
+{
+ const struct man_node *head, *body;
+ char *start, *title;
+ char byte;
+ size_t sz;
+
+ if (NULL == n)
+ return;
+
+ /*
+ * We're only searching for one thing: the first text child in
+ * the BODY of a NAME section. Since we don't keep track of
+ * sections in -man, run some hoops to find out whether we're in
+ * the correct section or not.
+ */
+
+ if (MAN_BODY == n->type && MAN_SH == n->tok) {
+ body = n;
+ assert(body->parent);
+ if (NULL != (head = body->parent->head) &&
+ 1 == head->nchild &&
+ NULL != (head = (head->child)) &&
+ MAN_TEXT == head->type &&
+ 0 == strcmp(head->string, "NAME") &&
+ NULL != body->child) {
+
+ /*
+ * Suck the entire NAME section into memory.
+ * Yes, we might run away.
+ * But too many manuals have big, spread-out
+ * NAME sections over many lines.
+ */
+
+ title = NULL;
+ man_deroff(&title, body);
+ if (NULL == title)
+ return;
+
+ /*
+ * Go through a special heuristic dance here.
+ * Conventionally, one or more manual names are
+ * comma-specified prior to a whitespace, then a
+ * dash, then a description. Try to puzzle out
+ * the name parts here.
+ */
+
+ start = title;
+ for ( ;; ) {
+ sz = strcspn(start, " ,");
+ if ('\0' == start[sz])
+ break;
+
+ byte = start[sz];
+ start[sz] = '\0';
+
+ /*
+ * Assume a stray trailing comma in the
+ * name list if a name begins with a dash.
+ */
+
+ if ('-' == start[0] ||
+ ('\\' == start[0] && '-' == start[1]))
+ break;
+
+ putkey(mpage, start, NAME_TITLE);
+
+ if (' ' == byte) {
+ start += sz + 1;
+ break;
+ }
+
+ assert(',' == byte);
+ start += sz + 1;
+ while (' ' == *start)
+ start++;
+ }
+
+ if (start == title) {
+ putkey(mpage, start, NAME_TITLE);
+ free(title);
+ return;
+ }
+
+ while (isspace((unsigned char)*start))
+ start++;
+
+ if (0 == strncmp(start, "-", 1))
+ start += 1;
+ else if (0 == strncmp(start, "\\-\\-", 4))
+ start += 4;
+ else if (0 == strncmp(start, "\\-", 2))
+ start += 2;
+ else if (0 == strncmp(start, "\\(en", 4))
+ start += 4;
+ else if (0 == strncmp(start, "\\(em", 4))
+ start += 4;
+
+ while (' ' == *start)
+ start++;
+
+ mpage->desc = mandoc_strdup(start);
+ free(title);
+ return;
+ }
+ }
+
+ for (n = n->child; n; n = n->next) {
+ if (NULL != mpage->desc)
+ break;
+ parse_man(mpage, n);
+ }
+}
+
+static void
+parse_mdoc(struct mpage *mpage, const struct mdoc_node *n)
+{
+
+ assert(NULL != n);
+ for (n = n->child; NULL != n; n = n->next) {
+ switch (n->type) {
+ case MDOC_ELEM:
+ /* FALLTHROUGH */
+ case MDOC_BLOCK:
+ /* FALLTHROUGH */
+ case MDOC_HEAD:
+ /* FALLTHROUGH */
+ case MDOC_BODY:
+ /* FALLTHROUGH */
+ case MDOC_TAIL:
+ if (NULL != mdocs[n->tok].fp)
+ if (0 == (*mdocs[n->tok].fp)(mpage, n))
+ break;
+ if (mdocs[n->tok].mask)
+ putmdockey(mpage, n->child,
+ mdocs[n->tok].mask);
+ break;
+ default:
+ assert(MDOC_ROOT != n->type);
+ continue;
+ }
+ if (NULL != n->child)
+ parse_mdoc(mpage, n);
+ }
+}
+
+static int
+parse_mdoc_Fd(struct mpage *mpage, const struct mdoc_node *n)
+{
+ const char *start, *end;
+ size_t sz;
+
+ if (SEC_SYNOPSIS != n->sec ||
+ NULL == (n = n->child) ||
+ MDOC_TEXT != n->type)
+ return(0);
+
+ /*
+ * Only consider those `Fd' macro fields that begin with an
+ * "inclusion" token (versus, e.g., #define).
+ */
+
+ if (strcmp("#include", n->string))
+ return(0);
+
+ if (NULL == (n = n->next) || MDOC_TEXT != n->type)
+ return(0);
+
+ /*
+ * Strip away the enclosing angle brackets and make sure we're
+ * not zero-length.
+ */
+
+ start = n->string;
+ if ('<' == *start || '"' == *start)
+ start++;
+
+ if (0 == (sz = strlen(start)))
+ return(0);
+
+ end = &start[(int)sz - 1];
+ if ('>' == *end || '"' == *end)
+ end--;
+
+ if (end > start)
+ putkeys(mpage, start, end - start + 1, TYPE_In);
+ return(0);
+}
+
+static int
+parse_mdoc_Fn(struct mpage *mpage, const struct mdoc_node *n)
+{
+ char *cp;
+
+ if (NULL == (n = n->child) || MDOC_TEXT != n->type)
+ return(0);
+
+ /*
+ * Parse: .Fn "struct type *name" "char *arg".
+ * First strip away pointer symbol.
+ * Then store the function name, then type.
+ * Finally, store the arguments.
+ */
+
+ if (NULL == (cp = strrchr(n->string, ' ')))
+ cp = n->string;
+
+ while ('*' == *cp)
+ cp++;
+
+ putkey(mpage, cp, TYPE_Fn);
+
+ if (n->string < cp)
+ putkeys(mpage, n->string, cp - n->string, TYPE_Ft);
+
+ for (n = n->next; NULL != n; n = n->next)
+ if (MDOC_TEXT == n->type)
+ putkey(mpage, n->string, TYPE_Fa);
+
+ return(0);
+}
+
+static int
+parse_mdoc_Xr(struct mpage *mpage, const struct mdoc_node *n)
+{
+ char *cp;
+
+ if (NULL == (n = n->child))
+ return(0);
+
+ if (NULL == n->next) {
+ putkey(mpage, n->string, TYPE_Xr);
+ return(0);
+ }
+
+ mandoc_asprintf(&cp, "%s(%s)", n->string, n->next->string);
+ putkey(mpage, cp, TYPE_Xr);
+ free(cp);
+ return(0);
+}
+
+static int
+parse_mdoc_Nd(struct mpage *mpage, const struct mdoc_node *n)
+{
+
+ if (MDOC_BODY == n->type)
+ mdoc_deroff(&mpage->desc, n);
+ return(0);
+}
+
+static int
+parse_mdoc_Nm(struct mpage *mpage, const struct mdoc_node *n)
+{
+
+ if (SEC_NAME == n->sec)
+ putmdockey(mpage, n->child, NAME_TITLE);
+ else if (SEC_SYNOPSIS == n->sec && MDOC_HEAD == n->type)
+ putmdockey(mpage, n->child, NAME_SYN);
+ return(0);
+}
+
+static int
+parse_mdoc_Sh(struct mpage *mpage, const struct mdoc_node *n)
+{
+
+ return(SEC_CUSTOM == n->sec && MDOC_HEAD == n->type);
+}
+
+static int
+parse_mdoc_head(struct mpage *mpage, const struct mdoc_node *n)
+{
+
+ return(MDOC_HEAD == n->type);
+}
+
+static int
+parse_mdoc_body(struct mpage *mpage, const struct mdoc_node *n)
+{
+
+ return(MDOC_BODY == n->type);
+}
+
+/*
+ * Add a string to the hash table for the current manual.
+ * Each string has a bitmask telling which macros it belongs to.
+ * When we finish the manual, we'll dump the table.
+ */
+static void
+putkeys(const struct mpage *mpage,
+ const char *cp, size_t sz, uint64_t v)
+{
+ struct ohash *htab;
+ struct str *s;
+ const char *end;
+ unsigned int slot;
+ int i;
+
+ if (0 == sz)
+ return;
+
+ if (TYPE_Nm & v) {
+ htab = &names;
+ v &= name_mask;
+ name_mask &= ~NAME_FIRST;
+ if (debug > 1)
+ say(mpage->mlinks->file,
+ "Adding name %*s", sz, cp);
+ } else {
+ htab = &strings;
+ if (debug > 1)
+ for (i = 0; i < mansearch_keymax; i++)
+ if (1 << i & v)
+ say(mpage->mlinks->file,
+ "Adding key %s=%*s",
+ mansearch_keynames[i], sz, cp);
+ }
+
+ end = cp + sz;
+ slot = ohash_qlookupi(htab, cp, &end);
+ s = ohash_find(htab, slot);
+
+ if (NULL != s && mpage == s->mpage) {
+ s->mask |= v;
+ return;
+ } else if (NULL == s) {
+ s = mandoc_calloc(1, sizeof(struct str) + sz + 1);
+ memcpy(s->key, cp, sz);
+ ohash_insert(htab, slot, s);
+ }
+ s->mpage = mpage;
+ s->mask = v;
+}
+
+/*
+ * Take a Unicode codepoint and produce its UTF-8 encoding.
+ * This isn't the best way to do this, but it works.
+ * The magic numbers are from the UTF-8 packaging.
+ * They're not as scary as they seem: read the UTF-8 spec for details.
+ */
+static size_t
+utf8(unsigned int cp, char out[7])
+{
+ size_t rc;
+
+ rc = 0;
+ if (cp <= 0x0000007F) {
+ rc = 1;
+ out[0] = (char)cp;
+ } else if (cp <= 0x000007FF) {
+ rc = 2;
+ out[0] = (cp >> 6 & 31) | 192;
+ out[1] = (cp & 63) | 128;
+ } else if (cp <= 0x0000FFFF) {
+ rc = 3;
+ out[0] = (cp >> 12 & 15) | 224;
+ out[1] = (cp >> 6 & 63) | 128;
+ out[2] = (cp & 63) | 128;
+ } else if (cp <= 0x001FFFFF) {
+ rc = 4;
+ out[0] = (cp >> 18 & 7) | 240;
+ out[1] = (cp >> 12 & 63) | 128;
+ out[2] = (cp >> 6 & 63) | 128;
+ out[3] = (cp & 63) | 128;
+ } else if (cp <= 0x03FFFFFF) {
+ rc = 5;
+ out[0] = (cp >> 24 & 3) | 248;
+ out[1] = (cp >> 18 & 63) | 128;
+ out[2] = (cp >> 12 & 63) | 128;
+ out[3] = (cp >> 6 & 63) | 128;
+ out[4] = (cp & 63) | 128;
+ } else if (cp <= 0x7FFFFFFF) {
+ rc = 6;
+ out[0] = (cp >> 30 & 1) | 252;
+ out[1] = (cp >> 24 & 63) | 128;
+ out[2] = (cp >> 18 & 63) | 128;
+ out[3] = (cp >> 12 & 63) | 128;
+ out[4] = (cp >> 6 & 63) | 128;
+ out[5] = (cp & 63) | 128;
+ } else
+ return(0);
+
+ out[rc] = '\0';
+ return(rc);
+}
+
+/*
+ * Store the rendered version of a key, or alias the pointer
+ * if the key contains no escape sequences.
+ */
+static void
+render_key(struct mchars *mc, struct str *key)
+{
+ size_t sz, bsz, pos;
+ char utfbuf[7], res[6];
+ char *buf;
+ const char *seq, *cpp, *val;
+ int len, u;
+ enum mandoc_esc esc;
+
+ assert(NULL == key->rendered);
+
+ res[0] = '\\';
+ res[1] = '\t';
+ res[2] = ASCII_NBRSP;
+ res[3] = ASCII_HYPH;
+ res[4] = ASCII_BREAK;
+ res[5] = '\0';
+
+ val = key->key;
+ bsz = strlen(val);
+
+ /*
+ * Pre-check: if we have no stop-characters, then set the
+ * pointer as ourselvse and get out of here.
+ */
+ if (strcspn(val, res) == bsz) {
+ key->rendered = key->key;
+ return;
+ }
+
+ /* Pre-allocate by the length of the input */
+
+ buf = mandoc_malloc(++bsz);
+ pos = 0;
+
+ while ('\0' != *val) {
+ /*
+ * Halt on the first escape sequence.
+ * This also halts on the end of string, in which case
+ * we just copy, fallthrough, and exit the loop.
+ */
+ if ((sz = strcspn(val, res)) > 0) {
+ memcpy(&buf[pos], val, sz);
+ pos += sz;
+ val += sz;
+ }
+
+ switch (*val) {
+ case ASCII_HYPH:
+ buf[pos++] = '-';
+ val++;
+ continue;
+ case '\t':
+ /* FALLTHROUGH */
+ case ASCII_NBRSP:
+ buf[pos++] = ' ';
+ val++;
+ /* FALLTHROUGH */
+ case ASCII_BREAK:
+ continue;
+ default:
+ break;
+ }
+ if ('\\' != *val)
+ break;
+
+ /* Read past the slash. */
+
+ val++;
+
+ /*
+ * Parse the escape sequence and see if it's a
+ * predefined character or special character.
+ */
+
+ esc = mandoc_escape((const char **)&val,
+ &seq, &len);
+ if (ESCAPE_ERROR == esc)
+ break;
+ if (ESCAPE_SPECIAL != esc)
+ continue;
+
+ /*
+ * Render the special character
+ * as either UTF-8 or ASCII.
+ */
+
+ if (write_utf8) {
+ if (0 == (u = mchars_spec2cp(mc, seq, len)))
+ continue;
+ cpp = utfbuf;
+ if (0 == (sz = utf8(u, utfbuf)))
+ continue;
+ sz = strlen(cpp);
+ } else {
+ cpp = mchars_spec2str(mc, seq, len, &sz);
+ if (NULL == cpp)
+ continue;
+ if (ASCII_NBRSP == *cpp) {
+ cpp = " ";
+ sz = 1;
+ }
+ }
+
+ /* Copy the rendered glyph into the stream. */
+
+ bsz += sz;
+ buf = mandoc_realloc(buf, bsz);
+ memcpy(&buf[pos], cpp, sz);
+ pos += sz;
+ }
+
+ buf[pos] = '\0';
+ key->rendered = buf;
+}
+
+static void
+dbadd_mlink(const struct mlink *mlink)
+{
+ size_t i;
+
+ i = 1;
+ SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->dsec);
+ SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->arch);
+ SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->name);
+ SQL_BIND_INT64(stmts[STMT_INSERT_LINK], i, mlink->mpage->pageid);
+ SQL_STEP(stmts[STMT_INSERT_LINK]);
+ sqlite3_reset(stmts[STMT_INSERT_LINK]);
+}
+
+/*
+ * Flush the current page's terms (and their bits) into the database.
+ * Wrap the entire set of additions in a transaction to make sqlite be a
+ * little faster.
+ * Also, handle escape sequences at the last possible moment.
+ */
+static void
+dbadd(struct mpage *mpage, struct mchars *mc)
+{
+ struct mlink *mlink;
+ struct str *key;
+ size_t i;
+ unsigned int slot;
+
+ mlink = mpage->mlinks;
+
+ if (nodb) {
+ for (key = ohash_first(&names, &slot); NULL != key;
+ key = ohash_next(&names, &slot)) {
+ if (key->rendered != key->key)
+ free(key->rendered);
+ free(key);
+ }
+ for (key = ohash_first(&strings, &slot); NULL != key;
+ key = ohash_next(&strings, &slot)) {
+ if (key->rendered != key->key)
+ free(key->rendered);
+ free(key);
+ }
+ if (0 == debug)
+ return;
+ while (NULL != mlink) {
+ fputs(mlink->name, stdout);
+ if (NULL == mlink->next ||
+ strcmp(mlink->dsec, mlink->next->dsec) ||
+ strcmp(mlink->fsec, mlink->next->fsec) ||
+ strcmp(mlink->arch, mlink->next->arch)) {
+ putchar('(');
+ if ('\0' == *mlink->dsec)
+ fputs(mlink->fsec, stdout);
+ else
+ fputs(mlink->dsec, stdout);
+ if ('\0' != *mlink->arch)
+ printf("/%s", mlink->arch);
+ putchar(')');
+ }
+ mlink = mlink->next;
+ if (NULL != mlink)
+ fputs(", ", stdout);
+ }
+ printf(" - %s\n", mpage->desc);
+ return;
+ }
+
+ if (debug)
+ say(mlink->file, "Adding to database");
+
+ i = strlen(mpage->desc) + 1;
+ key = mandoc_calloc(1, sizeof(struct str) + i);
+ memcpy(key->key, mpage->desc, i);
+ render_key(mc, key);
+
+ i = 1;
+ SQL_BIND_TEXT(stmts[STMT_INSERT_PAGE], i, key->rendered);
+ SQL_BIND_INT(stmts[STMT_INSERT_PAGE], i, FORM_SRC == mpage->form);
+ SQL_STEP(stmts[STMT_INSERT_PAGE]);
+ mpage->pageid = sqlite3_last_insert_rowid(db);
+ sqlite3_reset(stmts[STMT_INSERT_PAGE]);
+
+ if (key->rendered != key->key)
+ free(key->rendered);
+ free(key);
+
+ while (NULL != mlink) {
+ dbadd_mlink(mlink);
+ mlink = mlink->next;
+ }
+ mlink = mpage->mlinks;
+
+ for (key = ohash_first(&names, &slot); NULL != key;
+ key = ohash_next(&names, &slot)) {
+ assert(key->mpage == mpage);
+ if (NULL == key->rendered)
+ render_key(mc, key);
+ i = 1;
+ SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, key->mask);
+ SQL_BIND_TEXT(stmts[STMT_INSERT_NAME], i, key->rendered);
+ SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, mpage->pageid);
+ SQL_STEP(stmts[STMT_INSERT_NAME]);
+ sqlite3_reset(stmts[STMT_INSERT_NAME]);
+ if (key->rendered != key->key)
+ free(key->rendered);
+ free(key);
+ }
+ for (key = ohash_first(&strings, &slot); NULL != key;
+ key = ohash_next(&strings, &slot)) {
+ assert(key->mpage == mpage);
+ if (NULL == key->rendered)
+ render_key(mc, key);
+ i = 1;
+ SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, key->mask);
+ SQL_BIND_TEXT(stmts[STMT_INSERT_KEY], i, key->rendered);
+ SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, mpage->pageid);
+ SQL_STEP(stmts[STMT_INSERT_KEY]);
+ sqlite3_reset(stmts[STMT_INSERT_KEY]);
+ if (key->rendered != key->key)
+ free(key->rendered);
+ free(key);
+ }
+}
+
+static void
+dbprune(void)
+{
+ struct mpage *mpage;
+ struct mlink *mlink;
+ size_t i;
+ unsigned int slot;
+
+ if (0 == nodb)
+ SQL_EXEC("BEGIN TRANSACTION");
+
+ for (mpage = ohash_first(&mpages, &slot); NULL != mpage;
+ mpage = ohash_next(&mpages, &slot)) {
+ mlink = mpage->mlinks;
+ if (debug)
+ say(mlink->file, "Deleting from database");
+ if (nodb)
+ continue;
+ for ( ; NULL != mlink; mlink = mlink->next) {
+ i = 1;
+ SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE],
+ i, mlink->dsec);
+ SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE],
+ i, mlink->arch);
+ SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE],
+ i, mlink->name);
+ SQL_STEP(stmts[STMT_DELETE_PAGE]);
+ sqlite3_reset(stmts[STMT_DELETE_PAGE]);
+ }
+ }
+
+ if (0 == nodb)
+ SQL_EXEC("END TRANSACTION");
+}
+
+/*
+ * Close an existing database and its prepared statements.
+ * If "real" is not set, rename the temporary file into the real one.
+ */
+static void
+dbclose(int real)
+{
+ size_t i;
+ int status;
+ pid_t child;
+
+ if (nodb)
+ return;
+
+ for (i = 0; i < STMT__MAX; i++) {
+ sqlite3_finalize(stmts[i]);
+ stmts[i] = NULL;
+ }
+
+ sqlite3_close(db);
+ db = NULL;
+
+ if (real)
+ return;
+
+ if ('\0' == *tempfilename) {
+ if (-1 == rename(MANDOC_DB "~", MANDOC_DB)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(MANDOC_DB, "&rename");
+ }
+ return;
+ }
+
+ switch (child = fork()) {
+ case -1:
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&fork cmp");
+ return;
+ case 0:
+ execlp("cmp", "cmp", "-s",
+ tempfilename, MANDOC_DB, NULL);
+ say("", "&exec cmp");
+ exit(0);
+ default:
+ break;
+ }
+ if (-1 == waitpid(child, &status, 0)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&wait cmp");
+ } else if (WIFSIGNALED(status)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "cmp died from signal %d", WTERMSIG(status));
+ } else if (WEXITSTATUS(status)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(MANDOC_DB,
+ "Data changed, but cannot replace database");
+ }
+
+ *strrchr(tempfilename, '/') = '\0';
+ switch (child = fork()) {
+ case -1:
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&fork rm");
+ return;
+ case 0:
+ execlp("rm", "rm", "-rf", tempfilename, NULL);
+ say("", "&exec rm");
+ exit((int)MANDOCLEVEL_SYSERR);
+ default:
+ break;
+ }
+ if (-1 == waitpid(child, &status, 0)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&wait rm");
+ } else if (WIFSIGNALED(status) || WEXITSTATUS(status)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "%s: Cannot remove temporary directory",
+ tempfilename);
+ }
+}
+
+/*
+ * This is straightforward stuff.
+ * Open a database connection to a "temporary" database, then open a set
+ * of prepared statements we'll use over and over again.
+ * If "real" is set, we use the existing database; if not, we truncate a
+ * temporary one.
+ * Must be matched by dbclose().
+ */
+static int
+dbopen(int real)
+{
+ const char *sql;
+ int rc, ofl;
+
+ if (nodb)
+ return(1);
+
+ *tempfilename = '\0';
+ ofl = SQLITE_OPEN_READWRITE;
+
+ if (real) {
+ rc = sqlite3_open_v2(MANDOC_DB, &db, ofl, NULL);
+ if (SQLITE_OK != rc) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ if (SQLITE_CANTOPEN != rc)
+ say(MANDOC_DB, "%s", sqlite3_errstr(rc));
+ return(0);
+ }
+ goto prepare_statements;
+ }
+
+ ofl |= SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE;
+
+ remove(MANDOC_DB "~");
+ rc = sqlite3_open_v2(MANDOC_DB "~", &db, ofl, NULL);
+ if (SQLITE_OK == rc)
+ goto create_tables;
+ if (MPARSE_QUICK & mparse_options) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(MANDOC_DB "~", "%s", sqlite3_errstr(rc));
+ return(0);
+ }
+
+ (void)strlcpy(tempfilename, "/tmp/mandocdb.XXXXXX",
+ sizeof(tempfilename));
+ if (NULL == mkdtemp(tempfilename)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&%s", tempfilename);
+ return(0);
+ }
+ (void)strlcat(tempfilename, "/" MANDOC_DB,
+ sizeof(tempfilename));
+ rc = sqlite3_open_v2(tempfilename, &db, ofl, NULL);
+ if (SQLITE_OK != rc) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "%s: %s", tempfilename, sqlite3_errstr(rc));
+ return(0);
+ }
+
+create_tables:
+ sql = "CREATE TABLE \"mpages\" (\n"
+ " \"desc\" TEXT NOT NULL,\n"
+ " \"form\" INTEGER NOT NULL,\n"
+ " \"pageid\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL\n"
+ ");\n"
+ "\n"
+ "CREATE TABLE \"mlinks\" (\n"
+ " \"sec\" TEXT NOT NULL,\n"
+ " \"arch\" TEXT NOT NULL,\n"
+ " \"name\" TEXT NOT NULL,\n"
+ " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) "
+ "ON DELETE CASCADE\n"
+ ");\n"
+ "CREATE INDEX mlinks_pageid_idx ON mlinks (pageid);\n"
+ "\n"
+ "CREATE TABLE \"names\" (\n"
+ " \"bits\" INTEGER NOT NULL,\n"
+ " \"name\" TEXT NOT NULL,\n"
+ " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) "
+ "ON DELETE CASCADE\n"
+ ");\n"
+ "\n"
+ "CREATE TABLE \"keys\" (\n"
+ " \"bits\" INTEGER NOT NULL,\n"
+ " \"key\" TEXT NOT NULL,\n"
+ " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) "
+ "ON DELETE CASCADE\n"
+ ");\n"
+ "CREATE INDEX keys_pageid_idx ON keys (pageid);\n";
+
+ if (SQLITE_OK != sqlite3_exec(db, sql, NULL, NULL, NULL)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(MANDOC_DB, "%s", sqlite3_errmsg(db));
+ sqlite3_close(db);
+ return(0);
+ }
+
+prepare_statements:
+ if (SQLITE_OK != sqlite3_exec(db,
+ "PRAGMA foreign_keys = ON", NULL, NULL, NULL)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(MANDOC_DB, "PRAGMA foreign_keys: %s",
+ sqlite3_errmsg(db));
+ sqlite3_close(db);
+ return(0);
+ }
+
+ sql = "DELETE FROM mpages WHERE pageid IN "
+ "(SELECT pageid FROM mlinks WHERE "
+ "sec=? AND arch=? AND name=?)";
+ sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_DELETE_PAGE], NULL);
+ sql = "INSERT INTO mpages "
+ "(desc,form) VALUES (?,?)";
+ sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_PAGE], NULL);
+ sql = "INSERT INTO mlinks "
+ "(sec,arch,name,pageid) VALUES (?,?,?,?)";
+ sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_LINK], NULL);
+ sql = "INSERT INTO names "
+ "(bits,name,pageid) VALUES (?,?,?)";
+ sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_NAME], NULL);
+ sql = "INSERT INTO keys "
+ "(bits,key,pageid) VALUES (?,?,?)";
+ sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_KEY], NULL);
+
+#ifndef __APPLE__
+ /*
+ * When opening a new database, we can turn off
+ * synchronous mode for much better performance.
+ */
+
+ if (real && SQLITE_OK != sqlite3_exec(db,
+ "PRAGMA synchronous = OFF", NULL, NULL, NULL)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(MANDOC_DB, "PRAGMA synchronous: %s",
+ sqlite3_errmsg(db));
+ sqlite3_close(db);
+ return(0);
+ }
+#endif
+
+ return(1);
+}
+
+static void *
+hash_calloc(size_t n, size_t sz, void *arg)
+{
+
+ return(mandoc_calloc(n, sz));
+}
+
+static void *
+hash_alloc(size_t sz, void *arg)
+{
+
+ return(mandoc_malloc(sz));
+}
+
+static void
+hash_free(void *p, void *arg)
+{
+
+ free(p);
+}
+
+static int
+set_basedir(const char *targetdir)
+{
+ static char startdir[PATH_MAX];
+ static int getcwd_status; /* 1 = ok, 2 = failure */
+ static int chdir_status; /* 1 = changed directory */
+ char *cp;
+
+ /*
+ * Remember the original working directory, if possible.
+ * This will be needed if the second or a later directory
+ * on the command line is given as a relative path.
+ * Do not error out if the current directory is not
+ * searchable: Maybe it won't be needed after all.
+ */
+ if (0 == getcwd_status) {
+ if (NULL == getcwd(startdir, sizeof(startdir))) {
+ getcwd_status = 2;
+ (void)strlcpy(startdir, strerror(errno),
+ sizeof(startdir));
+ } else
+ getcwd_status = 1;
+ }
+
+ /*
+ * We are leaving the old base directory.
+ * Do not use it any longer, not even for messages.
+ */
+ *basedir = '\0';
+
+ /*
+ * If and only if the directory was changed earlier and
+ * the next directory to process is given as a relative path,
+ * first go back, or bail out if that is impossible.
+ */
+ if (chdir_status && '/' != *targetdir) {
+ if (2 == getcwd_status) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "getcwd: %s", startdir);
+ return(0);
+ }
+ if (-1 == chdir(startdir)) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "&chdir %s", startdir);
+ return(0);
+ }
+ }
+
+ /*
+ * Always resolve basedir to the canonicalized absolute
+ * pathname and append a trailing slash, such that
+ * we can reliably check whether files are inside.
+ */
+ if (NULL == realpath(targetdir, basedir)) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say("", "&%s: realpath", targetdir);
+ return(0);
+ } else if (-1 == chdir(basedir)) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say("", "&chdir");
+ return(0);
+ }
+ chdir_status = 1;
+ cp = strchr(basedir, '\0');
+ if ('/' != cp[-1]) {
+ if (cp - basedir >= PATH_MAX - 1) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say("", "Filename too long");
+ return(0);
+ }
+ *cp++ = '/';
+ *cp = '\0';
+ }
+ return(1);
+}
+
+static void
+say(const char *file, const char *format, ...)
+{
+ va_list ap;
+ int use_errno;
+
+ if ('\0' != *basedir)
+ fprintf(stderr, "%s", basedir);
+ if ('\0' != *basedir && '\0' != *file)
+ fputc('/', stderr);
+ if ('\0' != *file)
+ fprintf(stderr, "%s", file);
+
+ use_errno = 1;
+ if (NULL != format) {
+ switch (*format) {
+ case '&':
+ format++;
+ break;
+ case '\0':
+ format = NULL;
+ break;
+ default:
+ use_errno = 0;
+ break;
+ }
+ }
+ if (NULL != format) {
+ if ('\0' != *basedir || '\0' != *file)
+ fputs(": ", stderr);
+ va_start(ap, format);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
+ }
+ if (use_errno) {
+ if ('\0' != *basedir || '\0' != *file || NULL != format)
+ fputs(": ", stderr);
+ perror(NULL);
+ } else
+ fputc('\n', stderr);
+}
diff --git a/contrib/mdocml/manpage.c b/contrib/mdocml/manpage.c
new file mode 100644
index 000000000000..00793ccce48d
--- /dev/null
+++ b/contrib/mdocml/manpage.c
@@ -0,0 +1,190 @@
+/* $Id: manpage.c,v 1.7 2014/01/06 03:02:46 schwarze Exp $ */
+/*
+ * Copyright (c) 2012 Kristaps Dzonsons
+ * Copyright (c) 2013 Ingo Schwarze
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * 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
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "manpath.h"
+#include "mansearch.h"
+
+static void show(const char *, const char *);
+
+int
+main(int argc, char *argv[])
+{
+ int ch, term;
+ size_t i, sz, len;
+ struct mansearch search;
+ struct manpage *res;
+ char *conf_file, *defpaths, *auxpaths, *cp;
+ char buf[PATH_MAX];
+ const char *cmd;
+ struct manpaths paths;
+ char *progname;
+ extern char *optarg;
+ extern int optind;
+
+ term = isatty(STDIN_FILENO) && isatty(STDOUT_FILENO);
+
+ progname = strrchr(argv[0], '/');
+ if (progname == NULL)
+ progname = argv[0];
+ else
+ ++progname;
+
+ auxpaths = defpaths = conf_file = NULL;
+ memset(&paths, 0, sizeof(struct manpaths));
+ memset(&search, 0, sizeof(struct mansearch));
+
+ while (-1 != (ch = getopt(argc, argv, "C:M:m:S:s:")))
+ switch (ch) {
+ case ('C'):
+ conf_file = optarg;
+ break;
+ case ('M'):
+ defpaths = optarg;
+ break;
+ case ('m'):
+ auxpaths = optarg;
+ break;
+ case ('S'):
+ search.arch = optarg;
+ break;
+ case ('s'):
+ search.sec = optarg;
+ break;
+ default:
+ goto usage;
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (0 == argc)
+ goto usage;
+
+ search.deftype = TYPE_Nm | TYPE_Nd;
+
+ manpath_parse(&paths, conf_file, defpaths, auxpaths);
+ ch = mansearch(&search, &paths, argc, argv, "Nd", &res, &sz);
+ manpath_free(&paths);
+
+ if (0 == ch)
+ goto usage;
+
+ if (0 == sz) {
+ free(res);
+ return(EXIT_FAILURE);
+ } else if (1 == sz && term) {
+ i = 1;
+ goto show;
+ } else if (NULL == res)
+ return(EXIT_FAILURE);
+
+ for (i = 0; i < sz; i++) {
+ printf("%6zu %s: %s\n",
+ i + 1, res[i].names, res[i].output);
+ free(res[i].names);
+ free(res[i].output);
+ }
+
+ if (0 == term) {
+ for (i = 0; i < sz; i++)
+ free(res[i].file);
+ free(res);
+ return(EXIT_SUCCESS);
+ }
+
+ i = 1;
+ printf("Enter a choice [1]: ");
+ fflush(stdout);
+
+ if (NULL != (cp = fgetln(stdin, &len)))
+ if ('\n' == cp[--len] && len > 0) {
+ cp[len] = '\0';
+ if ((i = atoi(cp)) < 1 || i > sz)
+ i = 0;
+ }
+
+ if (0 == i) {
+ for (i = 0; i < sz; i++)
+ free(res[i].file);
+ free(res);
+ return(EXIT_SUCCESS);
+ }
+show:
+ cmd = res[i - 1].form ? "mandoc" : "cat";
+ strlcpy(buf, res[i - 1].file, PATH_MAX);
+ for (i = 0; i < sz; i++)
+ free(res[i].file);
+ free(res);
+
+ show(cmd, buf);
+ /* NOTREACHED */
+usage:
+ fprintf(stderr, "usage: %s [-C conf] "
+ "[-M paths] "
+ "[-m paths] "
+ "[-S arch] "
+ "[-s section] "
+ "expr ...\n",
+ progname);
+ return(EXIT_FAILURE);
+}
+
+static void
+show(const char *cmd, const char *file)
+{
+ int fds[2];
+ pid_t pid;
+
+ if (-1 == pipe(fds)) {
+ perror(NULL);
+ exit(EXIT_FAILURE);
+ }
+
+ if (-1 == (pid = fork())) {
+ perror(NULL);
+ exit(EXIT_FAILURE);
+ } else if (pid > 0) {
+ dup2(fds[0], STDIN_FILENO);
+ close(fds[1]);
+ cmd = NULL != getenv("MANPAGER") ?
+ getenv("MANPAGER") :
+ (NULL != getenv("PAGER") ?
+ getenv("PAGER") : "more");
+ execlp(cmd, cmd, (char *)NULL);
+ perror(cmd);
+ exit(EXIT_FAILURE);
+ }
+
+ dup2(fds[1], STDOUT_FILENO);
+ close(fds[0]);
+ execlp(cmd, cmd, file, (char *)NULL);
+ perror(cmd);
+ exit(EXIT_FAILURE);
+}
diff --git a/contrib/mdocml/manpath.c b/contrib/mdocml/manpath.c
new file mode 100644
index 000000000000..61b2c7e05077
--- /dev/null
+++ b/contrib/mdocml/manpath.c
@@ -0,0 +1,222 @@
+/* $Id: manpath.c,v 1.15 2014/04/23 21:06:41 schwarze Exp $ */
+/*
+ * Copyright (c) 2011 Ingo Schwarze
+ * Copyright (c) 2011 Kristaps Dzonsons
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * 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
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "mandoc_aux.h"
+#include "manpath.h"
+
+#define MAN_CONF_FILE "/etc/man.conf"
+#define MAN_CONF_KEY "_whatdb"
+
+static void manpath_add(struct manpaths *, const char *);
+static void manpath_parseline(struct manpaths *, char *);
+
+void
+manpath_parse(struct manpaths *dirs, const char *file,
+ char *defp, char *auxp)
+{
+#ifdef USE_MANPATH
+ char cmd[(PATH_MAX * 3) + 20];
+ FILE *stream;
+ char *buf;
+ size_t sz, bsz;
+
+ strlcpy(cmd, "manpath", sizeof(cmd));
+ if (file) {
+ strlcat(cmd, " -C ", sizeof(cmd));
+ strlcat(cmd, file, sizeof(cmd));
+ }
+ if (auxp) {
+ strlcat(cmd, " -m ", sizeof(cmd));
+ strlcat(cmd, auxp, sizeof(cmd));
+ }
+ if (defp) {
+ strlcat(cmd, " -M ", sizeof(cmd));
+ strlcat(cmd, defp, sizeof(cmd));
+ }
+
+ /* Open manpath(1). Ignore errors. */
+
+ stream = popen(cmd, "r");
+ if (NULL == stream)
+ return;
+
+ buf = NULL;
+ bsz = 0;
+
+ /* Read in as much output as we can. */
+
+ do {
+ buf = mandoc_realloc(buf, bsz + 1024);
+ sz = fread(buf + bsz, 1, 1024, stream);
+ bsz += sz;
+ } while (sz > 0);
+
+ if ( ! ferror(stream) && feof(stream) &&
+ bsz && '\n' == buf[bsz - 1]) {
+ buf[bsz - 1] = '\0';
+ manpath_parseline(dirs, buf);
+ }
+
+ free(buf);
+ pclose(stream);
+#else
+ char *insert;
+
+ /* Always prepend -m. */
+ manpath_parseline(dirs, auxp);
+
+ /* If -M is given, it overrides everything else. */
+ if (NULL != defp) {
+ manpath_parseline(dirs, defp);
+ return;
+ }
+
+ /* MANPATH and man.conf(5) cooperate. */
+ defp = getenv("MANPATH");
+ if (NULL == file)
+ file = MAN_CONF_FILE;
+
+ /* No MANPATH; use man.conf(5) only. */
+ if (NULL == defp || '\0' == defp[0]) {
+ manpath_manconf(dirs, file);
+ return;
+ }
+
+ /* Prepend man.conf(5) to MANPATH. */
+ if (':' == defp[0]) {
+ manpath_manconf(dirs, file);
+ manpath_parseline(dirs, defp);
+ return;
+ }
+
+ /* Append man.conf(5) to MANPATH. */
+ if (':' == defp[strlen(defp) - 1]) {
+ manpath_parseline(dirs, defp);
+ manpath_manconf(dirs, file);
+ return;
+ }
+
+ /* Insert man.conf(5) into MANPATH. */
+ insert = strstr(defp, "::");
+ if (NULL != insert) {
+ *insert++ = '\0';
+ manpath_parseline(dirs, defp);
+ manpath_manconf(dirs, file);
+ manpath_parseline(dirs, insert + 1);
+ return;
+ }
+
+ /* MANPATH overrides man.conf(5) completely. */
+ manpath_parseline(dirs, defp);
+#endif
+}
+
+/*
+ * Parse a FULL pathname from a colon-separated list of arrays.
+ */
+static void
+manpath_parseline(struct manpaths *dirs, char *path)
+{
+ char *dir;
+
+ if (NULL == path)
+ return;
+
+ for (dir = strtok(path, ":"); dir; dir = strtok(NULL, ":"))
+ manpath_add(dirs, dir);
+}
+
+/*
+ * Add a directory to the array, ignoring bad directories.
+ * Grow the array one-by-one for simplicity's sake.
+ */
+static void
+manpath_add(struct manpaths *dirs, const char *dir)
+{
+ char buf[PATH_MAX];
+ char *cp;
+ size_t i;
+
+ if (NULL == (cp = realpath(dir, buf)))
+ return;
+
+ for (i = 0; i < dirs->sz; i++)
+ if (0 == strcmp(dirs->paths[i], dir))
+ return;
+
+ dirs->paths = mandoc_reallocarray(dirs->paths,
+ dirs->sz + 1, sizeof(char *));
+
+ dirs->paths[dirs->sz++] = mandoc_strdup(cp);
+}
+
+void
+manpath_free(struct manpaths *p)
+{
+ size_t i;
+
+ for (i = 0; i < p->sz; i++)
+ free(p->paths[i]);
+
+ free(p->paths);
+}
+
+void
+manpath_manconf(struct manpaths *dirs, const char *file)
+{
+ FILE *stream;
+ char *p, *q;
+ size_t len, keysz;
+
+ keysz = strlen(MAN_CONF_KEY);
+ assert(keysz > 0);
+
+ if (NULL == (stream = fopen(file, "r")))
+ return;
+
+ while (NULL != (p = fgetln(stream, &len))) {
+ if (0 == len || '\n' != p[--len])
+ break;
+ p[len] = '\0';
+ while (isspace((unsigned char)*p))
+ p++;
+ if (strncmp(MAN_CONF_KEY, p, keysz))
+ continue;
+ p += keysz;
+ while (isspace((unsigned char)*p))
+ p++;
+ if ('\0' == *p)
+ continue;
+ if (NULL == (q = strrchr(p, '/')))
+ continue;
+ *q = '\0';
+ manpath_add(dirs, p);
+ }
+
+ fclose(stream);
+}
diff --git a/contrib/mdocml/manpath.h b/contrib/mdocml/manpath.h
new file mode 100644
index 000000000000..2fe1b36e15f2
--- /dev/null
+++ b/contrib/mdocml/manpath.h
@@ -0,0 +1,38 @@
+/* $Id: manpath.h,v 1.6 2012/06/08 10:32:40 kristaps Exp $ */
+/*
+ * Copyright (c) 2011 Ingo Schwarze
+ * Copyright (c) 2011 Kristaps Dzonsons
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef MANPATH_H
+#define MANPATH_H
+
+/*
+ * Unsorted list of unique, absolute paths to be searched for manual
+ * databases.
+ */
+struct manpaths {
+ size_t sz;
+ char **paths;
+};
+
+__BEGIN_DECLS
+
+void manpath_manconf(struct manpaths *, const char *);
+void manpath_parse(struct manpaths *, const char *, char *, char *);
+void manpath_free(struct manpaths *);
+
+__END_DECLS
+
+#endif /*!MANPATH_H*/
diff --git a/contrib/mdocml/mansearch.3 b/contrib/mdocml/mansearch.3
new file mode 100644
index 000000000000..28590e52845c
--- /dev/null
+++ b/contrib/mdocml/mansearch.3
@@ -0,0 +1,228 @@
+.\" $Id: mansearch.3,v 1.2 2014/08/05 15:29:30 schwarze Exp $
+.\"
+.\" Copyright (c) 2014 Ingo Schwarze
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: August 5 2014 $
+.Dt MANSEARCH 3
+.Os
+.Sh NAME
+.Nm mansearch ,
+.Nm mansearch_setup
+.Nd search manual page databases
+.Sh SYNOPSIS
+.In stdint.h
+.In manpath.h
+.In mansearch.h
+.Ft int
+.Fo mansearch_setup
+.Fa "int start"
+.Fc
+.Ft int
+.Fo mansearch
+.Fa "const struct mansearch *search"
+.Fa "const struct manpaths *paths"
+.Fa "int argc"
+.Fa "char *argv[]"
+.Fa "const char *outkey"
+.Fa "struct manpage **res"
+.Fa "size_t *sz"
+.Fc
+.Sh DESCRIPTION
+The
+.Fn mansearch
+function returns information about manuals matching a search query from a
+.Xr mandoc.db 5
+SQLite3 database.
+.Pp
+The query arguments are as follows:
+.Bl -tag -width Ds
+.It Fa "const struct mansearch *search"
+Search options, defined in
+.In mansearch.h .
+.It Fa "const struct manpaths *paths"
+Directories to be searched, defined in
+.In manpath.h .
+.It Fa "int argc" , "char *argv[]"
+Search criteria, usually taken from the command line.
+.El
+.Pp
+The
+.Fa "const char *outkey"
+selects which data to return in the
+.Va output
+field of the
+.Fa res
+structures.
+It takes any of the macro keys defined in
+.Pa mansearch_const.c
+and described in
+.Xr apropos 1 .
+.Pp
+The output arguments are as follows:
+.Bl -tag -width Ds
+.It Fa "struct manpage **res"
+Returns a pointer to an array of result structures defined in
+.In mansearch.h .
+The user is expected to call
+.Xr free 3
+on the
+.Va file ,
+.Va names ,
+and
+.Va output
+fields of all structures, as well as the
+.Fa res
+array itself.
+.It Fa "size_t *sz"
+Returns the number of result structures contained in
+.Fa res .
+.El
+.Pp
+To speed up searches, the
+.Fn mansearch_setup
+function can optionally be called with a
+.Fa start
+argument of 1 before
+.Fn mansearch
+to set up an SQLite3 pagecache.
+If it was called, it has to be called again with a
+.Fa start
+argument of 0 after the last call to
+.Fn mansearch
+to release the memory used for the pagecache.
+.Sh IMPLEMENTATION NOTES
+For each manual page tree, the search is done in two steps.
+In the first step, a list of pages matching the search criteria is built.
+In the second step, the requested information about these pages is
+retrieved from the database and assembled into the
+.Fa res
+array.
+.Pp
+All function mentioned here are defined in the file
+.Pa mansearch.c .
+No functions except
+.Fn mansearch
+and
+.Fn sql_statement
+build any SQL code, and no functions except
+.Fn mansearch ,
+.Fn buildnames ,
+and
+.Fn buildoutput
+execute it.
+.Ss Finding matches
+The query is built using the following grammar:
+.Bd -literal -offset indent
+ ::= "SELECT * FROM mpages WHERE"
+ ::= "(" ")" |
+ "OR" |
+ "AND" |
+ "desc" "?" |
+ "id IN (SELECT pageid FROM" ")"
+ ::= "names WHERE name" "?" |
+ "keys WHERE key" "? AND bits & ?"
+ ::= "MATCH" | "REGEXP"
+.Ed
+.Pp
+The MATCH and REGEXP operators are implemented by the functions
+.Fn sql_match
+and
+.Fn sql_regexp ,
+respectively.
+This is required because SQLite3 natively neither supports
+case-insensitive substring matching nor regular expression matching,
+but only string identity, shell globbing, and the weird home-brewed
+LIKE operator.
+.Pp
+Command line parsing is done by the function
+.Fn exprcomp
+building a singly linked list of
+.Vt expr
+structures, using the helper functions
+.Fn exprterm
+and
+.Fn exprspec .
+The resulting SQL statement is assembled by the function
+.Fn sql_statement
+and evaluated in the main loop of the
+.Fn mansearch
+function.
+.Ss Assembling the results
+The names, sections, and architectures of the manuals found
+are assembled into the
+.Va names
+field of the result structure by the function
+.Fn buildnames ,
+using the following query:
+.Pp
+.Dl "SELECT * FROM mlinks WHERE pageid=? ORDER BY sec, arch, name"
+.Pp
+If the
+.Fa outkey
+differs from
+.Qq Nd ,
+the requested output data is assembled into the
+.Va output
+field of the result structure by the function
+.Fn buildoutput ,
+using the following query:
+.Pp
+.Dl "SELECT * FROM keys WHERE pageid=? AND bits & ?"
+.Sh FILES
+.Bl -tag -width mandoc.db -compact
+.It Pa mandoc.db
+The manual page database.
+.El
+.Sh EXAMPLES
+The simplest invocation
+.Pp
+.Dl apropos keyword
+.Pp
+results in the following SQL query:
+.Bd -literal
+SELECT * FROM mpages WHERE (
+ id IN (SELECT pageid FROM names WHERE name MATCH 'keyword') OR
+ desc MATCH 'keyword'
+);
+.Ed
+.Pp
+A more complicated request like
+.Pp
+.Dl apropos -s 2 Nm,Xr=getuid
+.Pp
+results in:
+.Bd -literal
+SELECT * FROM mpages WHERE (
+ id IN (SELECT pageid FROM names WHERE name MATCH 'getuid') OR
+ id IN (SELECT pageid FROM keys WHERE key MATCH 'getuid' AND bits & 4)
+) AND id IN (SELECT pageid FROM keys WHERE key REGEXP '^2$' AND bits & 2);
+.Ed
+.Sh SEE ALSO
+.Xr apropos 1 ,
+.Xr mandoc.db 5 ,
+.Xr makewhatis 8
+.Sh HISTORY
+The
+.Fn mansearch
+subsystem first appeared in
+.Ox 5.6 .
+.Sh AUTHORS
+.An -nosplit
+A module to search manual page databases was first written by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
+in 2011, at first using the Berkeley DB;
+he rewrote it for SQLite3 in 2012.
+The current version received major changes from
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org .
diff --git a/contrib/mdocml/mansearch.c b/contrib/mdocml/mansearch.c
new file mode 100644
index 000000000000..e1226b1ce173
--- /dev/null
+++ b/contrib/mdocml/mansearch.c
@@ -0,0 +1,861 @@
+/* $Id: mansearch.c,v 1.42 2014/08/09 14:24:53 schwarze Exp $ */
+/*
+ * Copyright (c) 2012 Kristaps Dzonsons
+ * Copyright (c) 2013, 2014 Ingo Schwarze
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * 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
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include