]> git.cameronkatri.com Git - mandoc.git/blobdiff - mandocdb.c
fix the section number in the <title> element for preformatted pages;
[mandoc.git] / mandocdb.c
index af97019f1456a69d7f910803c41886704e17650f..0ef661b2d57c79894c22eaa890a529c8ab680c29 100644 (file)
@@ -1,7 +1,7 @@
-/*     $Id: mandocdb.c,v 1.243 2017/02/17 14:31:52 schwarze Exp $ */
+/* $Id: mandocdb.c,v 1.268 2021/08/07 13:02:10 schwarze Exp $ */
 /*
+ * Copyright (c) 2011-2020 Ingo Schwarze <schwarze@openbsd.org>
  * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
- * Copyright (c) 2011-2017 Ingo Schwarze <schwarze@openbsd.org>
  * Copyright (c) 2016 Ed Maste <emaste@freebsd.org>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * 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.
+ *
+ * Implementation of the makewhatis(8) program.
  */
 #include "config.h"
 
 #include <sys/types.h>
+#include <sys/mman.h>
 #include <sys/stat.h>
-#include <sys/wait.h>
 
 #include <assert.h>
 #include <ctype.h>
@@ -52,6 +54,7 @@
 #include "roff.h"
 #include "mdoc.h"
 #include "man.h"
+#include "mandoc_parse.h"
 #include "manconf.h"
 #include "mansearch.h"
 #include "dba_array.h"
@@ -117,7 +120,7 @@ struct      mdoc_handler {
 int             mandocdb(int, char *[]);
 
 static void     dbadd(struct dba *, struct mpage *);
-static void     dbadd_mlink(const struct mlink *mlink);
+static void     dbadd_mlink(const struct mlink *);
 static void     dbprune(struct dba *);
 static void     dbwrite(struct dba *);
 static void     filescan(const char *);
@@ -139,6 +142,8 @@ static      void     parse_mdoc(struct mpage *, const struct roff_meta *,
                        const struct roff_node *);
 static int      parse_mdoc_head(struct mpage *, const struct roff_meta *,
                        const struct roff_node *);
+static int      parse_mdoc_Fa(struct mpage *, const struct roff_meta *,
+                       const struct roff_node *);
 static int      parse_mdoc_Fd(struct mpage *, const struct roff_meta *,
                        const struct roff_node *);
 static void     parse_mdoc_fname(struct mpage *, const struct roff_node *);
@@ -160,6 +165,9 @@ static      void     putkey(const struct mpage *, char *, uint64_t);
 static void     putkeys(const struct mpage *, char *, size_t, uint64_t);
 static void     putmdockey(const struct mpage *,
                        const struct roff_node *, uint64_t, int);
+#ifdef READ_ALLOWED_PATH
+static int      read_allowed(const char *);
+#endif
 static int      render_string(char **, size_t *);
 static void     say(const char *, const char *, ...)
                        __attribute__((__format__ (__printf__, 2, 3)));
@@ -176,6 +184,7 @@ 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 size_t           basedir_len; /* strlen(basedir) */
 static struct mpage    *mpage_head; /* list of distinct manual pages */
 static struct ohash     mpages; /* table of distinct manual pages */
 static struct ohash     mlinks; /* table of directory entries */
@@ -183,8 +192,7 @@ static      struct ohash     names; /* table of all names */
 static struct ohash     strings; /* table of all strings */
 static uint64_t         name_mask;
 
-static const struct mdoc_handler mdocs[MDOC_MAX] = {
-       { NULL, 0, 0 },  /* Ap */
+static const struct mdoc_handler mdoc_handlers[MDOC_MAX - MDOC_Dd] = {
        { NULL, 0, NODE_NOPRT },  /* Dd */
        { NULL, 0, NODE_NOPRT },  /* Dt */
        { NULL, 0, NODE_NOPRT },  /* Os */
@@ -200,6 +208,7 @@ static      const struct mdoc_handler mdocs[MDOC_MAX] = {
        { NULL, 0, 0 },  /* It */
        { NULL, 0, 0 },  /* Ad */
        { NULL, TYPE_An, 0 },  /* An */
+       { NULL, 0, 0 },  /* Ap */
        { NULL, TYPE_Ar, 0 },  /* Ar */
        { NULL, TYPE_Cd, 0 },  /* Cd */
        { NULL, TYPE_Cm, 0 },  /* Cm */
@@ -207,11 +216,11 @@ static    const struct mdoc_handler mdocs[MDOC_MAX] = {
        { NULL, TYPE_Er, 0 },  /* Er */
        { NULL, TYPE_Ev, 0 },  /* Ev */
        { NULL, 0, 0 },  /* Ex */
-       { NULL, TYPE_Fa, 0 },  /* Fa */
+       { parse_mdoc_Fa, 0, 0 },  /* Fa */
        { parse_mdoc_Fd, 0, 0 },  /* Fd */
        { NULL, TYPE_Fl, 0 },  /* Fl */
        { parse_mdoc_Fn, 0, 0 },  /* Fn */
-       { NULL, TYPE_Ft, 0 },  /* Ft */
+       { NULL, TYPE_Ft | TYPE_Vt, 0 },  /* Ft */
        { NULL, TYPE_Ic, 0 },  /* Ic */
        { NULL, TYPE_In, 0 },  /* In */
        { NULL, TYPE_Li, 0 },  /* Li */
@@ -302,11 +311,8 @@ static     const struct mdoc_handler mdocs[MDOC_MAX] = {
        { NULL, 0, 0 },  /* En */
        { NULL, TYPE_Dx, NODE_NOSRC },  /* Dx */
        { NULL, 0, 0 },  /* %Q */
-       { NULL, 0, 0 },  /* br */
-       { NULL, 0, 0 },  /* sp */
        { NULL, 0, 0 },  /* %U */
        { NULL, 0, 0 },  /* Ta */
-       { NULL, 0, 0 },  /* ll */
 };
 
 
@@ -321,7 +327,7 @@ mandocdb(int argc, char *argv[])
        int               ch, i;
 
 #if HAVE_PLEDGE
-       if (pledge("stdio rpath wpath cpath fattr flock proc exec", NULL) == -1) {
+       if (pledge("stdio rpath wpath cpath", NULL) == -1) {
                warn("pledge");
                return (int)MANDOCLEVEL_SYSERR;
        }
@@ -342,15 +348,16 @@ mandocdb(int argc, char *argv[])
         * clobber each other.
         */
 #define        CHECKOP(_op, _ch) do \
-       if (OP_DEFAULT != (_op)) { \
+       if ((_op) != OP_DEFAULT) { \
                warnx("-%c: Conflicting option", (_ch)); \
                goto usage; \
        } while (/*CONSTCOND*/0)
 
+       mparse_options = MPARSE_VALIDATE;
        path_arg = NULL;
        op = OP_DEFAULT;
 
-       while (-1 != (ch = getopt(argc, argv, "aC:Dd:npQT:tu:v")))
+       while ((ch = getopt(argc, argv, "aC:Dd:npQT:tu:v")) != -1)
                switch (ch) {
                case 'a':
                        use_all = 1;
@@ -378,7 +385,7 @@ mandocdb(int argc, char *argv[])
                        mparse_options |= MPARSE_QUICK;
                        break;
                case 'T':
-                       if (strcmp(optarg, "utf8")) {
+                       if (strcmp(optarg, "utf8") != 0) {
                                warnx("-T%s: Unsupported output format",
                                    optarg);
                                goto usage;
@@ -415,24 +422,24 @@ mandocdb(int argc, char *argv[])
        }
 #endif
 
-       if (OP_CONFFILE == op && argc > 0) {
+       if (op == OP_CONFFILE && argc > 0) {
                warnx("-C: Too many arguments");
                goto usage;
        }
 
        exitcode = (int)MANDOCLEVEL_OK;
        mchars_alloc();
-       mp = mparse_alloc(mparse_options, MANDOCLEVEL_BADARG, NULL, NULL);
+       mp = mparse_alloc(mparse_options, MANDOC_OS_OTHER, NULL);
        mandoc_ohash_init(&mpages, 6, offsetof(struct mpage, inodev));
        mandoc_ohash_init(&mlinks, 6, offsetof(struct mlink, file));
 
-       if (OP_UPDATE == op || OP_DELETE == op || OP_TEST == op) {
+       if (op == OP_UPDATE || op == OP_DELETE || op == OP_TEST) {
 
                /*
                 * Most of these deal with a specific directory.
                 * Jump into that directory first.
                 */
-               if (OP_TEST != op && 0 == set_basedir(path_arg, 1))
+               if (op != OP_TEST && set_basedir(path_arg, 1) == 0)
                        goto out;
 
                dba = nodb ? dba_new(128) : dba_read(MANDOC_DB);
@@ -441,15 +448,6 @@ mandocdb(int argc, char *argv[])
                         * The existing database is usable.  Process
                         * all files specified on the command-line.
                         */
-#if HAVE_PLEDGE
-                       if (!nodb) {
-                               if (pledge("stdio rpath wpath cpath fattr flock", NULL) == -1) {
-                                       warn("pledge");
-                                       exitcode = (int)MANDOCLEVEL_SYSERR;
-                                       goto out;
-                               }
-                       }
-#endif
                        use_all = 1;
                        for (i = 0; i < argc; i++)
                                filescan(argv[i]);
@@ -462,11 +460,11 @@ mandocdb(int argc, char *argv[])
                                    " from scratch", strerror(errno));
                        exitcode = (int)MANDOCLEVEL_OK;
                        op = OP_DEFAULT;
-                       if (0 == treescan())
+                       if (treescan() == 0)
                                goto out;
                        dba = dba_new(128);
                }
-               if (OP_DELETE != op)
+               if (op != OP_DELETE)
                        mpages_merge(dba, mp);
                if (nodb == 0)
                        dbwrite(dba);
@@ -500,7 +498,7 @@ mandocdb(int argc, char *argv[])
                        sz = strlen(conf.manpath.paths[j]);
                        if (sz && conf.manpath.paths[j][sz - 1] == '/')
                                conf.manpath.paths[j][--sz] = '\0';
-                       if (0 == sz)
+                       if (sz == 0)
                                continue;
 
                        if (j) {
@@ -510,9 +508,9 @@ mandocdb(int argc, char *argv[])
                                    offsetof(struct mlink, file));
                        }
 
-                       if ( ! set_basedir(conf.manpath.paths[j], argc > 0))
+                       if (set_basedir(conf.manpath.paths[j], argc > 0) == 0)
                                continue;
-                       if (0 == treescan())
+                       if (treescan() == 0)
                                continue;
                        dba = dba_new(128);
                        mpages_merge(dba, mp);
@@ -589,7 +587,7 @@ treescan(void)
        const char      *argv[2];
 
        argv[0] = ".";
-       argv[1] = (char *)NULL;
+       argv[1] = NULL;
 
        f = fts_open((char * const *)argv, FTS_PHYSICAL | FTS_NOCHDIR,
            fts_compare);
@@ -616,9 +614,9 @@ treescan(void)
                                        say(path, "&realpath");
                                continue;
                        }
-                       if (strstr(buf, basedir) != buf
-#ifdef HOMEBREWDIR
-                           && strstr(buf, HOMEBREWDIR) != buf
+                       if (strncmp(buf, basedir, basedir_len) != 0
+#ifdef READ_ALLOWED_PATH
+                           && !read_allowed(buf)
 #endif
                        ) {
                                if (warnings) say("",
@@ -785,17 +783,17 @@ treescan(void)
  * See treescan() for the fts(3) version of this.
  */
 static void
-filescan(const char *file)
+filescan(const char *infile)
 {
-       char             buf[PATH_MAX];
        struct stat      st;
        struct mlink    *mlink;
-       char            *p, *start;
+       char            *linkfile, *p, *realdir, *start, *usefile;
+       size_t           realdir_len;
 
        assert(use_all);
 
-       if (0 == strncmp(file, "./", 2))
-               file += 2;
+       if (strncmp(infile, "./", 2) == 0)
+               infile += 2;
 
        /*
         * We have to do lstat(2) before realpath(3) loses
@@ -804,13 +802,13 @@ filescan(const char *file)
         * we want to use the orginal file name, while for
         * regular files, we want to use the real path.
         */
-       if (-1 == lstat(file, &st)) {
+       if (lstat(infile, &st) == -1) {
                exitcode = (int)MANDOCLEVEL_BADARG;
-               say(file, "&lstat");
+               say(infile, "&lstat");
                return;
-       } else if (0 == ((S_IFREG | S_IFLNK) & st.st_mode)) {
+       } else if (S_ISREG(st.st_mode) == 0 && S_ISLNK(st.st_mode) == 0) {
                exitcode = (int)MANDOCLEVEL_BADARG;
-               say(file, "Not a regular file");
+               say(infile, "Not a regular file");
                return;
        }
 
@@ -818,23 +816,24 @@ filescan(const char *file)
         * We have to resolve the file name to the real path
         * in any case for the base directory check.
         */
-       if (NULL == realpath(file, buf)) {
+       if ((usefile = realpath(infile, NULL)) == NULL) {
                exitcode = (int)MANDOCLEVEL_BADARG;
-               say(file, "&realpath");
+               say(infile, "&realpath");
                return;
        }
 
-       if (OP_TEST == op)
-               start = buf;
-       else if (strstr(buf, basedir) == buf)
-               start = buf + strlen(basedir);
-#ifdef HOMEBREWDIR
-       else if (strstr(buf, HOMEBREWDIR) == buf)
-               start = buf;
+       if (op == OP_TEST)
+               start = usefile;
+       else if (strncmp(usefile, basedir, basedir_len) == 0)
+               start = usefile + basedir_len;
+#ifdef READ_ALLOWED_PATH
+       else if (read_allowed(usefile))
+               start = usefile;
 #endif
        else {
                exitcode = (int)MANDOCLEVEL_BADARG;
-               say("", "%s: outside base directory", buf);
+               say("", "%s: outside base directory", infile);
+               free(usefile);
                return;
        }
 
@@ -842,25 +841,72 @@ filescan(const char *file)
         * 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)) {
+       do {
+               if (S_ISLNK(st.st_mode) == 0)
+                       break;
+
+               /*
+                * Some implementations of realpath(3) may succeed
+                * even if the target of the link does not exist,
+                * so check again for extra safety.
+                */
+               if (stat(usefile, &st) == -1) {
                        exitcode = (int)MANDOCLEVEL_BADARG;
-                       say(file, "&stat");
+                       say(infile, "&stat");
+                       free(usefile);
                        return;
                }
-               if (strlcpy(buf, file, sizeof(buf)) >= sizeof(buf)) {
-                       say(file, "Filename too long");
-                       return;
+               linkfile = mandoc_strdup(infile);
+               if (op == OP_TEST) {
+                       free(usefile);
+                       start = usefile = linkfile;
+                       break;
                }
-               start = buf;
-               if (OP_TEST != op && strstr(buf, basedir) == buf)
-                       start += strlen(basedir);
-       }
+               if (strncmp(infile, basedir, basedir_len) == 0) {
+                       free(usefile);
+                       usefile = linkfile;
+                       start = usefile + basedir_len;
+                       break;
+               }
+
+               /*
+                * This symbolic link points into the basedir
+                * from the outside.  Let's see whether any of
+                * the parent directories resolve to the basedir.
+                */
+               p = strchr(linkfile, '\0');
+               do {
+                       while (*--p != '/')
+                               continue;
+                       *p = '\0';
+                       if ((realdir = realpath(linkfile, NULL)) == NULL) {
+                               exitcode = (int)MANDOCLEVEL_BADARG;
+                               say(infile, "&realpath");
+                               free(linkfile);
+                               free(usefile);
+                               return;
+                       }
+                       realdir_len = strlen(realdir) + 1;
+                       free(realdir);
+                       *p = '/';
+               } while (realdir_len > basedir_len);
+
+               /*
+                * If one of the directories resolves to the basedir,
+                * use the rest of the original name.
+                * Otherwise, the best we can do
+                * is to use the filename pointed to.
+                */
+               if (realdir_len == basedir_len) {
+                       free(usefile);
+                       usefile = linkfile;
+                       start = p + 1;
+               } else {
+                       free(linkfile);
+                       start = usefile + basedir_len;
+               }
+       } while (/* CONSTCOND */ 0);
 
        mlink = mandoc_calloc(1, sizeof(struct mlink));
        mlink->dform = FORM_NONE;
@@ -868,6 +914,7 @@ filescan(const char *file)
            sizeof(mlink->file)) {
                say(start, "Filename too long");
                free(mlink);
+               free(usefile);
                return;
        }
 
@@ -876,13 +923,13 @@ filescan(const char *file)
         * but outside our tree, guess the base directory.
         */
 
-       if (op == OP_TEST || (start == buf && *start == '/')) {
-               if (strncmp(buf, "man/", 4) == 0)
-                       start = buf + 4;
-               else if ((start = strstr(buf, "/man/")) != NULL)
+       if (op == OP_TEST || (start == usefile && *start == '/')) {
+               if (strncmp(usefile, "man/", 4) == 0)
+                       start = usefile + 4;
+               else if ((start = strstr(usefile, "/man/")) != NULL)
                        start += 5;
                else
-                       start = buf;
+                       start = usefile;
        }
 
        /*
@@ -891,18 +938,18 @@ filescan(const char *file)
         * If we find one of these and what's underneath is a directory,
         * assume it's an architecture.
         */
-       if (NULL != (p = strchr(start, '/'))) {
+       if ((p = strchr(start, '/')) != NULL) {
                *p++ = '\0';
-               if (0 == strncmp(start, "man", 3)) {
+               if (strncmp(start, "man", 3) == 0) {
                        mlink->dform = FORM_SRC;
                        mlink->dsec = start + 3;
-               } else if (0 == strncmp(start, "cat", 3)) {
+               } else if (strncmp(start, "cat", 3) == 0) {
                        mlink->dform = FORM_CAT;
                        mlink->dsec = start + 3;
                }
 
                start = p;
-               if (NULL != mlink->dsec && NULL != (p = strchr(start, '/'))) {
+               if (mlink->dsec != NULL && (p = strchr(start, '/')) != NULL) {
                        *p++ = '\0';
                        mlink->arch = start;
                        start = p;
@@ -914,10 +961,10 @@ filescan(const char *file)
         * Suffix of `.0' indicates a catpage, `.1-9' is a manpage.
         */
        p = strrchr(start, '\0');
-       while (p-- > start && '/' != *p && '.' != *p)
-               /* Loop. */ ;
+       while (p-- > start && *p != '/' && *p != '.')
+               continue;
 
-       if ('.' == *p) {
+       if (*p == '.') {
                *p++ = '\0';
                mlink->fsec = p;
        }
@@ -927,11 +974,12 @@ filescan(const char *file)
         * Use the filename portion of the path.
         */
        mlink->name = start;
-       if (NULL != (p = strrchr(start, '/'))) {
+       if ((p = strrchr(start, '/')) != NULL) {
                mlink->name = p + 1;
                *p = '\0';
        }
        mlink_add(mlink, &st);
+       free(usefile);
 }
 
 static void
@@ -1124,8 +1172,7 @@ mpages_merge(struct dba *dba, struct mparse *mp)
 {
        struct mpage            *mpage, *mpage_dest;
        struct mlink            *mlink, *mlink_dest;
-       struct roff_man         *man;
-       char                    *sodest;
+       struct roff_meta        *meta;
        char                    *cp;
        int                      fd;
 
@@ -1138,8 +1185,7 @@ mpages_merge(struct dba *dba, struct mparse *mp)
                mandoc_ohash_init(&names, 4, offsetof(struct str, key));
                mandoc_ohash_init(&strings, 6, offsetof(struct str, key));
                mparse_reset(mp);
-               man = NULL;
-               sodest = NULL;
+               meta = NULL;
 
                if ((fd = mparse_open(mp, mlink->file)) == -1) {
                        say(mlink->file, "&open");
@@ -1154,14 +1200,14 @@ mpages_merge(struct dba *dba, struct mparse *mp)
                        mparse_readfd(mp, fd, mlink->file);
                        close(fd);
                        fd = -1;
-                       mparse_result(mp, &man, &sodest);
+                       meta = mparse_result(mp);
                }
 
-               if (sodest != NULL) {
+               if (meta != NULL && meta->sodest != NULL) {
                        mlink_dest = ohash_find(&mlinks,
-                           ohash_qlookup(&mlinks, sodest));
+                           ohash_qlookup(&mlinks, meta->sodest));
                        if (mlink_dest == NULL) {
-                               mandoc_asprintf(&cp, "%s.gz", sodest);
+                               mandoc_asprintf(&cp, "%s.gz", meta->sodest);
                                mlink_dest = ohash_find(&mlinks,
                                    ohash_qlookup(&mlinks, cp));
                                free(cp);
@@ -1196,41 +1242,43 @@ mpages_merge(struct dba *dba, struct mparse *mp)
                                mlink->next = mlink_dest->next;
                                mlink_dest->next = mpage->mlinks;
                                mpage->mlinks = NULL;
+                               goto nextpage;
                        }
-                       goto nextpage;
-               } else if (man != NULL && man->macroset == MACROSET_MDOC) {
-                       mdoc_validate(man);
+                       meta->macroset = MACROSET_NONE;
+               }
+               if (meta != NULL && meta->macroset == MACROSET_MDOC) {
                        mpage->form = FORM_SRC;
-                       mpage->sec = man->meta.msec;
+                       mpage->sec = meta->msec;
                        mpage->sec = mandoc_strdup(
                            mpage->sec == NULL ? "" : mpage->sec);
-                       mpage->arch = man->meta.arch;
+                       mpage->arch = meta->arch;
                        mpage->arch = mandoc_strdup(
                            mpage->arch == NULL ? "" : mpage->arch);
-                       mpage->title = mandoc_strdup(man->meta.title);
-               } else if (man != NULL && man->macroset == MACROSET_MAN) {
-                       man_validate(man);
-                       if (*man->meta.msec != '\0' ||
-                           *man->meta.msec != '\0') {
+                       mpage->title = mandoc_strdup(meta->title);
+               } else if (meta != NULL && meta->macroset == MACROSET_MAN) {
+                       if (*meta->msec != '\0' || *meta->title != '\0') {
                                mpage->form = FORM_SRC;
-                               mpage->sec = mandoc_strdup(man->meta.msec);
+                               mpage->sec = mandoc_strdup(meta->msec);
                                mpage->arch = mandoc_strdup(mlink->arch);
-                               mpage->title = mandoc_strdup(man->meta.title);
+                               mpage->title = mandoc_strdup(meta->title);
                        } else
-                               man = NULL;
+                               meta = NULL;
                }
 
                assert(mpage->desc == NULL);
-               if (man == NULL) {
-                       mpage->form = FORM_CAT;
+               if (meta == NULL || meta->sodest != NULL) {
                        mpage->sec = mandoc_strdup(mlink->dsec);
                        mpage->arch = mandoc_strdup(mlink->arch);
                        mpage->title = mandoc_strdup(mlink->name);
-                       parse_cat(mpage, fd);
-               } else if (man->macroset == MACROSET_MDOC)
-                       parse_mdoc(mpage, &man->meta, man->first);
+                       if (meta == NULL) {
+                               mpage->form = FORM_CAT;
+                               parse_cat(mpage, fd);
+                       } else
+                               mpage->form = FORM_SRC;
+               } else if (meta->macroset == MACROSET_MDOC)
+                       parse_mdoc(mpage, meta, meta->first);
                else
-                       parse_man(mpage, &man->meta, man->first);
+                       parse_man(mpage, meta, meta->first);
                if (mpage->desc == NULL) {
                        mpage->desc = mandoc_strdup(mlink->name);
                        if (warnings)
@@ -1383,7 +1431,12 @@ parse_cat(struct mpage *mpage, int fd)
                plen -= 2;
        }
 
-       mpage->desc = mandoc_strdup(p);
+       /*
+        * Cut off excessive one-line descriptions.
+        * Bad pages are not worth better heuristics.
+        */
+
+       mpage->desc = mandoc_strndup(p, 150);
        fclose(stream);
        free(title);
 }
@@ -1527,7 +1580,12 @@ parse_man(struct mpage *mpage, const struct roff_meta *meta,
                        while (' ' == *start)
                                start++;
 
-                       mpage->desc = mandoc_strdup(start);
+                       /*
+                        * Cut off excessive one-line descriptions.
+                        * Bad pages are not worth better heuristics.
+                        */
+
+                       mpage->desc = mandoc_strndup(start, 150);
                        free(title);
                        return;
                }
@@ -1544,26 +1602,30 @@ static void
 parse_mdoc(struct mpage *mpage, const struct roff_meta *meta,
        const struct roff_node *n)
 {
+       const struct mdoc_handler *handler;
 
-       assert(NULL != n);
-       for (n = n->child; NULL != n; n = n->next) {
-               if (n->flags & mdocs[n->tok].taboo)
+       for (n = n->child; n != NULL; n = n->next) {
+               if (n->tok == TOKEN_NONE || n->tok < ROFF_MAX)
+                       continue;
+               assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
+               handler = mdoc_handlers + (n->tok - MDOC_Dd);
+               if (n->flags & handler->taboo)
                        continue;
+
                switch (n->type) {
                case ROFFT_ELEM:
                case ROFFT_BLOCK:
                case ROFFT_HEAD:
                case ROFFT_BODY:
                case ROFFT_TAIL:
-                       if (NULL != mdocs[n->tok].fp)
-                              if (0 == (*mdocs[n->tok].fp)(mpage, meta, n))
-                                      break;
-                       if (mdocs[n->tok].mask)
+                       if (handler->fp != NULL &&
+                           (*handler->fp)(mpage, meta, n) == 0)
+                               break;
+                       if (handler->mask)
                                putmdockey(mpage, n->child,
-                                   mdocs[n->tok].mask, mdocs[n->tok].taboo);
+                                   handler->mask, handler->taboo);
                        break;
                default:
-                       assert(n->type != ROFFT_ROOT);
                        continue;
                }
                if (NULL != n->child)
@@ -1571,6 +1633,20 @@ parse_mdoc(struct mpage *mpage, const struct roff_meta *meta,
        }
 }
 
+static int
+parse_mdoc_Fa(struct mpage *mpage, const struct roff_meta *meta,
+       const struct roff_node *n)
+{
+       uint64_t mask;
+
+       mask = TYPE_Fa;
+       if (n->sec == SEC_SYNOPSIS)
+               mask |= TYPE_Vt;
+
+       putmdockey(mpage, n->child, mask, 0);
+       return 0;
+}
+
 static int
 parse_mdoc_Fd(struct mpage *mpage, const struct roff_meta *meta,
        const struct roff_node *n)
@@ -1640,15 +1716,20 @@ static int
 parse_mdoc_Fn(struct mpage *mpage, const struct roff_meta *meta,
        const struct roff_node *n)
 {
+       uint64_t mask;
 
        if (n->child == NULL)
                return 0;
 
        parse_mdoc_fname(mpage, n->child);
 
-       for (n = n->child->next; n != NULL; n = n->next)
-               if (n->type == ROFFT_TEXT)
-                       putkey(mpage, n->string, TYPE_Fa);
+       n = n->child->next;
+       if (n != NULL && n->type == ROFFT_TEXT) {
+               mask = TYPE_Fa;
+               if (n->sec == SEC_SYNOPSIS)
+                       mask |= TYPE_Vt;
+               putmdockey(mpage, n, mask, 0);
+       }
 
        return 0;
 }
@@ -2119,9 +2200,27 @@ dbprune(struct dba *dba)
 static void
 dbwrite(struct dba *dba)
 {
-       char             tfn[32];
-       int              status;
-       pid_t            child;
+       struct stat      sb1, sb2;
+       char             tfn[33], *cp1, *cp2;
+       off_t            i;
+       int              fd1, fd2;
+
+       /*
+        * Do not write empty databases, and delete existing ones
+        * when makewhatis -u causes them to become empty.
+        */
+
+       dba_array_start(dba->pages);
+       if (dba_array_next(dba->pages) == NULL) {
+               if (unlink(MANDOC_DB) == -1 && errno != ENOENT)
+                       say(MANDOC_DB, "&unlink");
+               return;
+       }
+
+       /*
+        * Build the database in a temporary file,
+        * then atomically move it into place.
+        */
 
        if (dba_write(MANDOC_DB "~", dba) != -1) {
                if (rename(MANDOC_DB "~", MANDOC_DB) == -1) {
@@ -2132,65 +2231,73 @@ dbwrite(struct dba *dba)
                return;
        }
 
+       /*
+        * We lack write permission and cannot replace the database
+        * file, but let's at least check whether the data changed.
+        */
+
        (void)strlcpy(tfn, "/tmp/mandocdb.XXXXXXXX", sizeof(tfn));
        if (mkdtemp(tfn) == NULL) {
                exitcode = (int)MANDOCLEVEL_SYSERR;
                say("", "&%s", tfn);
                return;
        }
-
+       cp1 = cp2 = MAP_FAILED;
+       fd1 = fd2 = -1;
        (void)strlcat(tfn, "/" MANDOC_DB, sizeof(tfn));
        if (dba_write(tfn, dba) == -1) {
-               exitcode = (int)MANDOCLEVEL_SYSERR;
                say(tfn, "&dba_write");
-               goto out;
+               goto err;
        }
-
-       switch (child = fork()) {
-       case -1:
-               exitcode = (int)MANDOCLEVEL_SYSERR;
-               say("", "&fork cmp");
-               return;
-       case 0:
-               execlp("cmp", "cmp", "-s", tfn, MANDOC_DB, (char *)NULL);
-               say("", "&exec cmp");
-               exit(0);
-       default:
-               break;
+       if ((fd1 = open(MANDOC_DB, O_RDONLY, 0)) == -1) {
+               say(MANDOC_DB, "&open");
+               goto err;
        }
-       if (waitpid(child, &status, 0) == -1) {
-               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");
+       if ((fd2 = open(tfn, O_RDONLY, 0)) == -1) {
+               say(tfn, "&open");
+               goto err;
+       }
+       if (fstat(fd1, &sb1) == -1) {
+               say(MANDOC_DB, "&fstat");
+               goto err;
+       }
+       if (fstat(fd2, &sb2) == -1) {
+               say(tfn, "&fstat");
+               goto err;
+       }
+       if (sb1.st_size != sb2.st_size)
+               goto err;
+       if ((cp1 = mmap(NULL, sb1.st_size, PROT_READ, MAP_PRIVATE,
+           fd1, 0)) == MAP_FAILED) {
+               say(MANDOC_DB, "&mmap");
+               goto err;
        }
+       if ((cp2 = mmap(NULL, sb2.st_size, PROT_READ, MAP_PRIVATE,
+           fd2, 0)) == MAP_FAILED) {
+               say(tfn, "&mmap");
+               goto err;
+       }
+       for (i = 0; i < sb1.st_size; i++)
+               if (cp1[i] != cp2[i])
+                       goto err;
+       goto out;
+
+err:
+       exitcode = (int)MANDOCLEVEL_SYSERR;
+       say(MANDOC_DB, "Data changed, but cannot replace database");
 
 out:
+       if (cp1 != MAP_FAILED)
+               munmap(cp1, sb1.st_size);
+       if (cp2 != MAP_FAILED)
+               munmap(cp2, sb2.st_size);
+       if (fd1 != -1)
+               close(fd1);
+       if (fd2 != -1)
+               close(fd2);
+       unlink(tfn);
        *strrchr(tfn, '/') = '\0';
-       switch (child = fork()) {
-       case -1:
-               exitcode = (int)MANDOCLEVEL_SYSERR;
-               say("", "&fork rm");
-               return;
-       case 0:
-               execlp("rm", "rm", "-rf", tfn, (char *)NULL);
-               say("", "&exec rm");
-               exit((int)MANDOCLEVEL_SYSERR);
-       default:
-               break;
-       }
-       if (waitpid(child, &status, 0) == -1) {
-               exitcode = (int)MANDOCLEVEL_SYSERR;
-               say("", "&wait rm");
-       } else if (WIFSIGNALED(status) || WEXITSTATUS(status)) {
-               exitcode = (int)MANDOCLEVEL_SYSERR;
-               say("", "%s: Cannot remove temporary directory", tfn);
-       }
+       rmdir(tfn);
 }
 
 static int
@@ -2199,7 +2306,6 @@ set_basedir(const char *targetdir, int report_baddir)
        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.
@@ -2208,8 +2314,8 @@ set_basedir(const char *targetdir, int report_baddir)
         * 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))) {
+       if (getcwd_status == 0) {
+               if (getcwd(startdir, sizeof(startdir)) == NULL) {
                        getcwd_status = 2;
                        (void)strlcpy(startdir, strerror(errno),
                            sizeof(startdir));
@@ -2222,19 +2328,20 @@ set_basedir(const char *targetdir, int report_baddir)
         * Do not use it any longer, not even for messages.
         */
        *basedir = '\0';
+       basedir_len = 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) {
+       if (chdir_status && *targetdir != '/') {
+               if (getcwd_status == 2) {
                        exitcode = (int)MANDOCLEVEL_SYSERR;
                        say("", "getcwd: %s", startdir);
                        return 0;
                }
-               if (-1 == chdir(startdir)) {
+               if (chdir(startdir) == -1) {
                        exitcode = (int)MANDOCLEVEL_SYSERR;
                        say("", "&chdir %s", startdir);
                        return 0;
@@ -2246,48 +2353,71 @@ set_basedir(const char *targetdir, int report_baddir)
         * pathname and append a trailing slash, such that
         * we can reliably check whether files are inside.
         */
-       if (NULL == realpath(targetdir, basedir)) {
+       if (realpath(targetdir, basedir) == NULL) {
                if (report_baddir || errno != ENOENT) {
                        exitcode = (int)MANDOCLEVEL_BADARG;
                        say("", "&%s: realpath", targetdir);
                }
+               *basedir = '\0';
                return 0;
-       } else if (-1 == chdir(basedir)) {
+       } else if (chdir(basedir) == -1) {
                if (report_baddir || errno != ENOENT) {
                        exitcode = (int)MANDOCLEVEL_BADARG;
                        say("", "&chdir");
                }
+               *basedir = '\0';
                return 0;
        }
        chdir_status = 1;
-       cp = strchr(basedir, '\0');
-       if ('/' != cp[-1]) {
-               if (cp - basedir >= PATH_MAX - 1) {
+       basedir_len = strlen(basedir);
+       if (basedir[basedir_len - 1] != '/') {
+               if (basedir_len >= PATH_MAX - 1) {
                        exitcode = (int)MANDOCLEVEL_SYSERR;
                        say("", "Filename too long");
+                       *basedir = '\0';
+                       basedir_len = 0;
                        return 0;
                }
-               *cp++ = '/';
-               *cp = '\0';
+               basedir[basedir_len++] = '/';
+               basedir[basedir_len] = '\0';
        }
        return 1;
 }
 
+#ifdef READ_ALLOWED_PATH
+static int
+read_allowed(const char *candidate)
+{
+       const char      *cp;
+       size_t           len;
+
+       for (cp = READ_ALLOWED_PATH;; cp += len) {
+               while (*cp == ':')
+                       cp++;
+               if (*cp == '\0')
+                       return 0;
+               len = strcspn(cp, ":");
+               if (strncmp(candidate, cp, len) == 0)
+                       return 1;
+       }
+}
+#endif
+
 static void
 say(const char *file, const char *format, ...)
 {
        va_list          ap;
        int              use_errno;
 
-       if ('\0' != *basedir)
+       if (*basedir != '\0')
                fprintf(stderr, "%s", basedir);
-       if ('\0' != *basedir && '\0' != *file)
+       if (*basedir != '\0' && *file != '\0')
                fputc('/', stderr);
-       if ('\0' != *file)
+       if (*file != '\0')
                fprintf(stderr, "%s", file);
 
        use_errno = 1;
-       if (NULL != format) {
+       if (format != NULL) {
                switch (*format) {
                case '&':
                        format++;
@@ -2300,15 +2430,15 @@ say(const char *file, const char *format, ...)
                        break;
                }
        }
-       if (NULL != format) {
-               if ('\0' != *basedir || '\0' != *file)
+       if (format != NULL) {
+               if (*basedir != '\0' || *file != '\0')
                        fputs(": ", stderr);
                va_start(ap, format);
                vfprintf(stderr, format, ap);
                va_end(ap);
        }
        if (use_errno) {
-               if ('\0' != *basedir || '\0' != *file || NULL != format)
+               if (*basedir != '\0' || *file != '\0' || format != NULL)
                        fputs(": ", stderr);
                perror(NULL);
        } else