Remove the dependency on SQLite without loss of functionality.
authorschwarze <schwarze@openbsd.org>
Mon, 1 Aug 2016 10:32:39 +0000 (10:32 +0000)
committerschwarze <schwarze@openbsd.org>
Mon, 1 Aug 2016 10:32:39 +0000 (10:32 +0000)
Drop the obsolete names_check() now that we deleted MLINKS.
Run "doas makewhatis" after compiling and installing this.

Earlier version tested by jmc@ and jturner@;
"commit it all" deraadt@   "commit and dodge" krw@

17 files changed:
usr.bin/mandoc/Makefile
usr.bin/mandoc/dba.c [new file with mode: 0644]
usr.bin/mandoc/dba.h [new file with mode: 0644]
usr.bin/mandoc/dba_array.c [new file with mode: 0644]
usr.bin/mandoc/dba_array.h [new file with mode: 0644]
usr.bin/mandoc/dba_read.c [new file with mode: 0644]
usr.bin/mandoc/dba_write.c [new file with mode: 0644]
usr.bin/mandoc/dba_write.h [new file with mode: 0644]
usr.bin/mandoc/dbm.c [new file with mode: 0644]
usr.bin/mandoc/dbm.h [new file with mode: 0644]
usr.bin/mandoc/dbm_map.c [new file with mode: 0644]
usr.bin/mandoc/dbm_map.h [new file with mode: 0644]
usr.bin/mandoc/main.c
usr.bin/mandoc/mandocdb.c
usr.bin/mandoc/mansearch.c
usr.bin/mandoc/mansearch.h
usr.bin/mandoc/mansearch_const.c [deleted file]

index fe1a7cc..234c754 100644 (file)
@@ -1,10 +1,10 @@
-#      $OpenBSD: Makefile,v 1.102 2016/07/10 10:03:15 schwarze Exp $
+#      $OpenBSD: Makefile,v 1.103 2016/08/01 10:32:39 schwarze Exp $
 
 .include <bsd.own.mk>
 
 CFLAGS  += -W -Wall -Wstrict-prototypes -Wno-unused-parameter
 DPADD += ${LIBUTIL}
-LDADD  += -lsqlite3 -lutil -lz
+LDADD  += -lutil -lz
 
 SRCS=  mandoc.c mandoc_aux.c mandoc_ohash.c preconv.c read.c \
        roff.c tbl.c tbl_opts.c tbl_layout.c tbl_data.c eqn.c
@@ -15,7 +15,8 @@ SRCS+=        main.c mdoc_term.c tag.c chars.c term.c tree.c man_term.c eqn_term.c
 SRCS+= mdoc_man.c
 SRCS+= html.c mdoc_html.c man_html.c out.c eqn_html.c
 SRCS+= term_ps.c term_ascii.c tbl_term.c tbl_html.c
-SRCS+= manpath.c mandocdb.c mansearch_const.c mansearch.c
+SRCS+= dbm_map.c dbm.c dba_write.c dba_array.c dba.c dba_read.c
+SRCS+= manpath.c mandocdb.c mansearch.c
 
 PROG=  mandoc
 
@@ -49,7 +50,7 @@ LIBMANDOC_OBJS = ${LIBMDOC_OBJS} ${LIBMAN_OBJS} ${LIBROFF_OBJS} \
                chars.o msec.o preconv.o read.o
 HTML_OBJS =    html.o mdoc_html.o man_html.o tbl_html.o eqn_html.o out.o
 CGI_OBJS =     ${LIBMANDOC_OBJS} ${HTML_OBJS} \
-               mansearch.o mansearch_const.o cgi.o
+               dbm_map.o dbm.o mansearch.o cgi.o
 
 cgi.o: cgi.h main.h manconf.h mandoc.h mandoc_aux.h mansearch.h \
        man.h mdoc.h roff.h
diff --git a/usr.bin/mandoc/dba.c b/usr.bin/mandoc/dba.c
new file mode 100644 (file)
index 0000000..b43cb82
--- /dev/null
@@ -0,0 +1,433 @@
+/*     $OpenBSD: dba.c,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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.
+ *
+ * Allocation-based version of the mandoc database, for read-write access.
+ * The interface is defined in "dba.h".
+ */
+#include <sys/types.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc_aux.h"
+#include "mansearch.h"
+#include "dba_write.h"
+#include "dba_array.h"
+#include "dba.h"
+
+static void    *prepend(const char *, char);
+static void     dba_pages_write(struct dba_array *);
+static int      compare_names(const void *, const void *);
+static int      compare_strings(const void *, const void *);
+static void     dba_macros_write(struct dba_array *);
+static void     dba_macro_write(struct dba_array *);
+
+
+/*** top-level functions **********************************************/
+
+struct dba *
+dba_new(int32_t npages)
+{
+       struct dba      *dba;
+       int32_t          im;
+
+       dba = mandoc_malloc(sizeof(*dba));
+       dba->pages = dba_array_new(npages, DBA_GROW);
+       dba->macros = dba_array_new(MACRO_MAX, 0);
+       for (im = 0; im < MACRO_MAX; im++)
+               dba_array_set(dba->macros, im, dba_array_new(128, DBA_GROW));
+       return dba;
+}
+
+void
+dba_free(struct dba *dba)
+{
+       struct dba_array        *page, *macro, *entry;
+
+       dba_array_FOREACH(dba->macros, macro) {
+               dba_array_undel(macro);
+               dba_array_FOREACH(macro, entry) {
+                       free(dba_array_get(entry, 0));
+                       dba_array_free(dba_array_get(entry, 1));
+                       dba_array_free(entry);
+               }
+               dba_array_free(macro);
+       }
+       dba_array_free(dba->macros);
+
+       dba_array_undel(dba->pages);
+       dba_array_FOREACH(dba->pages, page) {
+               dba_array_free(dba_array_get(page, DBP_NAME));
+               dba_array_free(dba_array_get(page, DBP_SECT));
+               dba_array_free(dba_array_get(page, DBP_ARCH));
+               free(dba_array_get(page, DBP_DESC));
+               dba_array_free(dba_array_get(page, DBP_FILE));
+               dba_array_free(page);
+       }
+       dba_array_free(dba->pages);
+
+       free(dba);
+}
+
+/*
+ * Write the complete mandoc database to disk; the format is:
+ * - One integer each for magic and version.
+ * - One pointer each to the macros table and to the final magic.
+ * - The pages table.
+ * - The macros table.
+ * - And at the very end, the magic integer again.
+ */
+int
+dba_write(const char *fname, struct dba *dba)
+{
+       int      save_errno;
+       int32_t  pos_end, pos_macros, pos_macros_ptr;
+
+       if (dba_open(fname) == -1)
+               return -1;
+       dba_int_write(MANDOCDB_MAGIC);
+       dba_int_write(MANDOCDB_VERSION);
+       pos_macros_ptr = dba_skip(1, 2);
+       dba_pages_write(dba->pages);
+       pos_macros = dba_tell();
+       dba_macros_write(dba->macros);
+       pos_end = dba_tell();
+       dba_int_write(MANDOCDB_MAGIC);
+       dba_seek(pos_macros_ptr);
+       dba_int_write(pos_macros);
+       dba_int_write(pos_end);
+       if (dba_close() == -1) {
+               save_errno = errno;
+               unlink(fname);
+               errno = save_errno;
+               return -1;
+       }
+       return 0;
+}
+
+
+/*** functions for handling pages *************************************/
+
+/*
+ * Create a new page and append it to the pages table.
+ */
+struct dba_array *
+dba_page_new(struct dba_array *pages, const char *name, const char *sect,
+    const char *arch, const char *desc, const char *file, enum form form)
+{
+       struct dba_array *page, *entry;
+
+       page = dba_array_new(DBP_MAX, 0);
+       entry = dba_array_new(1, DBA_STR | DBA_GROW);
+       dba_array_add(entry, prepend(name, NAME_FILE & NAME_MASK));
+       dba_array_add(page, entry);
+       entry = dba_array_new(1, DBA_STR | DBA_GROW);
+       dba_array_add(entry, (void *)sect);
+       dba_array_add(page, entry);
+       if (arch != NULL && *arch != '\0') {
+               entry = dba_array_new(1, DBA_STR | DBA_GROW);
+               dba_array_add(entry, (void *)arch);
+       } else
+               entry = NULL;
+       dba_array_add(page, entry);
+       dba_array_add(page, mandoc_strdup(desc));
+       entry = dba_array_new(1, DBA_STR | DBA_GROW);
+       dba_array_add(entry, prepend(file, form));
+       dba_array_add(page, entry);
+       dba_array_add(pages, page);
+       return page;
+}
+
+/*
+ * Add a section, architecture, or file name to an existing page.
+ * Passing the NULL pointer for the architecture makes the page MI.
+ * In that case, any earlier or later architectures are ignored.
+ */
+void
+dba_page_add(struct dba_array *page, int32_t ie, const char *str)
+{
+       struct dba_array        *entries;
+       char                    *entry;
+
+       entries = dba_array_get(page, ie);
+       if (ie == DBP_ARCH) {
+               if (entries == NULL)
+                       return;
+               if (str == NULL) {
+                       dba_array_free(entries);
+                       dba_array_set(page, DBP_ARCH, NULL);
+                       return;
+               }
+       }
+       if (*str == '\0')
+               return;
+       dba_array_FOREACH(entries, entry) {
+               if (ie == DBP_FILE && *entry < ' ')
+                       entry++;
+               if (strcmp(entry, str) == 0)
+                       return;
+       }
+       dba_array_add(entries, (void *)str);
+}
+
+/*
+ * Add an additional name to an existing page.
+ */
+void
+dba_page_alias(struct dba_array *page, const char *name, uint64_t mask)
+{
+       struct dba_array        *entries;
+       char                    *entry;
+       char                     maskbyte;
+
+       if (*name == '\0')
+               return;
+       maskbyte = mask & NAME_MASK;
+       entries = dba_array_get(page, DBP_NAME);
+       dba_array_FOREACH(entries, entry) {
+               if (strcmp(entry + 1, name) == 0) {
+                       *entry |= maskbyte;
+                       return;
+               }
+       }
+       dba_array_add(entries, prepend(name, maskbyte));
+}
+
+/*
+ * Return a pointer to a temporary copy of instr with inbyte prepended.
+ */
+static void *
+prepend(const char *instr, char inbyte)
+{
+       static char     *outstr = NULL;
+       static size_t    outlen = 0;
+       size_t           newlen;
+
+       newlen = strlen(instr) + 1;
+       if (newlen > outlen) {
+               outstr = mandoc_realloc(outstr, newlen + 1);
+               outlen = newlen;
+       }
+       *outstr = inbyte;
+       memcpy(outstr + 1, instr, newlen);
+       return outstr;
+}
+
+/*
+ * Write the pages table to disk; the format is:
+ * - One integer containing the number of pages.
+ * - For each page, five pointers to the names, sections,
+ *   architectures, description, and file names of the page.
+ *   MI pages write 0 instead of the architecture pointer.
+ * - One list each for names, sections, architectures, descriptions and
+ *   file names.  The description for each page ends with a NUL byte.
+ *   For all the other lists, each string ends with a NUL byte,
+ *   and the last string for a page ends with two NUL bytes.
+ * - To assure alignment of following integers,
+ *   the end is padded with NUL bytes up to a multiple of four bytes.
+ */
+static void
+dba_pages_write(struct dba_array *pages)
+{
+       struct dba_array        *page, *entry;
+       int32_t                  pos_pages, pos_end;
+
+       pos_pages = dba_array_writelen(pages, 5);
+       dba_array_FOREACH(pages, page) {
+               dba_array_setpos(page, DBP_NAME, dba_tell());
+               entry = dba_array_get(page, DBP_NAME);
+               dba_array_sort(entry, compare_names);
+               dba_array_writelst(entry);
+       }
+       dba_array_FOREACH(pages, page) {
+               dba_array_setpos(page, DBP_SECT, dba_tell());
+               entry = dba_array_get(page, DBP_SECT);
+               dba_array_sort(entry, compare_strings);
+               dba_array_writelst(entry);
+       }
+       dba_array_FOREACH(pages, page) {
+               if ((entry = dba_array_get(page, DBP_ARCH)) != NULL) {
+                       dba_array_setpos(page, DBP_ARCH, dba_tell());
+                       dba_array_sort(entry, compare_strings);
+                       dba_array_writelst(entry);
+               } else
+                       dba_array_setpos(page, DBP_ARCH, 0);
+       }
+       dba_array_FOREACH(pages, page) {
+               dba_array_setpos(page, DBP_DESC, dba_tell());
+               dba_str_write(dba_array_get(page, DBP_DESC));
+       }
+       dba_array_FOREACH(pages, page) {
+               dba_array_setpos(page, DBP_FILE, dba_tell());
+               dba_array_writelst(dba_array_get(page, DBP_FILE));
+       }
+       pos_end = dba_align();
+       dba_seek(pos_pages);
+       dba_array_FOREACH(pages, page)
+               dba_array_writepos(page);
+       dba_seek(pos_end);
+}
+
+static int
+compare_names(const void *vp1, const void *vp2)
+{
+       const char      *cp1, *cp2;
+       int              diff;
+
+       cp1 = *(char **)vp1;
+       cp2 = *(char **)vp2;
+       return (diff = *cp2 - *cp1) ? diff :
+           strcasecmp(cp1 + 1, cp2 + 1);
+}
+
+static int
+compare_strings(const void *vp1, const void *vp2)
+{
+       const char      *cp1, *cp2;
+
+       cp1 = *(char **)vp1;
+       cp2 = *(char **)vp2;
+       return strcmp(cp1, cp2);
+}
+
+/*** functions for handling macros ************************************/
+
+/*
+ * Create a new macro entry and append it to one of the macro tables.
+ */
+void
+dba_macro_new(struct dba *dba, int32_t im, const char *value,
+    const int32_t *pp)
+{
+       struct dba_array        *entry, *pages;
+       const int32_t           *ip;
+       int32_t                  np;
+
+       np = 0;
+       for (ip = pp; *ip; ip++)
+               np++;
+       pages = dba_array_new(np, DBA_GROW);
+       for (ip = pp; *ip; ip++)
+               dba_array_add(pages, dba_array_get(dba->pages,
+                   be32toh(*ip) / 5 / sizeof(*ip) - 1));
+
+       entry = dba_array_new(2, 0);
+       dba_array_add(entry, mandoc_strdup(value));
+       dba_array_add(entry, pages);
+
+       dba_array_add(dba_array_get(dba->macros, im), entry);
+}
+
+/*
+ * Look up a macro entry by value and add a reference to a new page to it.
+ * If the value does not yet exist, create a new macro entry
+ * and add it to the macro table in question.
+ */
+void
+dba_macro_add(struct dba_array *macros, int32_t im, const char *value,
+    struct dba_array *page)
+{
+       struct dba_array        *macro, *entry, *pages;
+
+       if (*value == '\0')
+               return;
+       macro = dba_array_get(macros, im);
+       dba_array_FOREACH(macro, entry)
+               if (strcmp(value, dba_array_get(entry, 0)) == 0)
+                       break;
+       if (entry == NULL) {
+               entry = dba_array_new(2, 0);
+               dba_array_add(entry, mandoc_strdup(value));
+               pages = dba_array_new(1, DBA_GROW);
+               dba_array_add(entry, pages);
+               dba_array_add(macro, entry);
+       } else
+               pages = dba_array_get(entry, 1);
+       dba_array_add(pages, page);
+}
+
+/*
+ * Write the macros table to disk; the format is:
+ * - The number of macro tables (actually, MACRO_MAX).
+ * - That number of pointers to the individual macro tables.
+ * - The individual macro tables.
+ */
+static void
+dba_macros_write(struct dba_array *macros)
+{
+       struct dba_array        *macro;
+       int32_t                  im, pos_macros, pos_end;
+
+       pos_macros = dba_array_writelen(macros, 1);
+       im = 0;
+       dba_array_FOREACH(macros, macro) {
+               dba_array_setpos(macros, im++, dba_tell());
+               dba_macro_write(macro);
+       }
+       pos_end = dba_tell();
+       dba_seek(pos_macros);
+       dba_array_writepos(macros);
+       dba_seek(pos_end);
+}
+
+/*
+ * Write one individual macro table to disk; the format is:
+ * - The number of entries in the table.
+ * - For each entry, two pointers, the first one to the value
+ *   and the second one to the list of pages.
+ * - A list of values, each ending in a NUL byte.
+ * - To assure alignment of following integers,
+ *   padding with NUL bytes up to a multiple of four bytes.
+ * - A list of pointers to pages, each list ending in a 0 integer.
+ */
+static void
+dba_macro_write(struct dba_array *macro)
+{
+       struct dba_array        *entry, *pages, *page;
+       int                      empty;
+       int32_t                  addr, pos_macro, pos_end;
+
+       dba_array_FOREACH(macro, entry) {
+               pages = dba_array_get(entry, 1);
+               empty = 1;
+               dba_array_FOREACH(pages, page)
+                       if (dba_array_getpos(page))
+                               empty = 0;
+               if (empty)
+                       dba_array_del(macro);
+       }
+       pos_macro = dba_array_writelen(macro, 2);
+       dba_array_FOREACH(macro, entry) {
+               dba_array_setpos(entry, 0, dba_tell());
+               dba_str_write(dba_array_get(entry, 0));
+       }
+       dba_align();
+       dba_array_FOREACH(macro, entry) {
+               dba_array_setpos(entry, 1, dba_tell());
+               pages = dba_array_get(entry, 1);
+               dba_array_FOREACH(pages, page)
+                       if ((addr = dba_array_getpos(page)))
+                               dba_int_write(addr);
+               dba_int_write(0);
+       }
+       pos_end = dba_tell();
+       dba_seek(pos_macro);
+       dba_array_FOREACH(macro, entry)
+               dba_array_writepos(entry);
+       dba_seek(pos_end);
+}
diff --git a/usr.bin/mandoc/dba.h b/usr.bin/mandoc/dba.h
new file mode 100644 (file)
index 0000000..fa613f1
--- /dev/null
@@ -0,0 +1,51 @@
+/*     $OpenBSD: dba.h,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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.
+ *
+ * Public interface of the allocation-based version
+ * of the mandoc database, for read-write access.
+ * To be used by dba.c, dba_read.c, and makewhatis(8).
+ */
+
+#define        DBP_NAME        0
+#define        DBP_SECT        1
+#define        DBP_ARCH        2
+#define        DBP_DESC        3
+#define        DBP_FILE        4
+#define        DBP_MAX         5
+
+struct dba_array;
+
+struct dba {
+       struct dba_array        *pages;
+       struct dba_array        *macros;
+};
+
+
+struct dba     *dba_new(int32_t);
+void            dba_free(struct dba *);
+struct dba     *dba_read(const char *);
+int             dba_write(const char *, struct dba *);
+
+struct dba_array *dba_page_new(struct dba_array *, const char *,
+                       const char *, const char *, const char *,
+                       const char *, enum form);
+void            dba_page_add(struct dba_array *, int32_t, const char *);
+void            dba_page_alias(struct dba_array *, const char *, uint64_t);
+
+void            dba_macro_new(struct dba *, int32_t,
+                       const char *, const int32_t *);
+void            dba_macro_add(struct dba_array *, int32_t,
+                       const char *, struct dba_array *);
diff --git a/usr.bin/mandoc/dba_array.c b/usr.bin/mandoc/dba_array.c
new file mode 100644 (file)
index 0000000..dd08a32
--- /dev/null
@@ -0,0 +1,188 @@
+/*     $OpenBSD: dba_array.c,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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.
+ *
+ * Allocation-based arrays for the mandoc database, for read-write access.
+ * The interface is defined in "dba_array.h".
+ */
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc_aux.h"
+#include "dba_write.h"
+#include "dba_array.h"
+
+struct dba_array {
+       void    **ep;   /* Array of entries. */
+       int32_t  *em;   /* Array of map positions. */
+       int       flags;
+       int32_t   ea;   /* Entries allocated. */
+       int32_t   eu;   /* Entries used (including deleted). */
+       int32_t   ed;   /* Entries deleted. */
+       int32_t   ec;   /* Currently active entry. */
+       int32_t   pos;  /* Map position of this array. */
+};
+
+
+struct dba_array *
+dba_array_new(int32_t ea, int flags)
+{
+       struct dba_array        *array;
+
+       assert(ea > 0);
+       array = mandoc_malloc(sizeof(*array));
+       array->ep = mandoc_reallocarray(NULL, ea, sizeof(*array->ep));
+       array->em = mandoc_reallocarray(NULL, ea, sizeof(*array->em));
+       array->ea = ea;
+       array->eu = 0;
+       array->ed = 0;
+       array->ec = 0;
+       array->flags = flags;
+       array->pos = 0;
+       return array;
+}
+
+void
+dba_array_free(struct dba_array *array)
+{
+       int32_t  ie;
+
+       if (array == NULL)
+               return;
+       if (array->flags & DBA_STR)
+               for (ie = 0; ie < array->eu; ie++)
+                       free(array->ep[ie]);
+       free(array->ep);
+       free(array->em);
+       free(array);
+}
+
+void
+dba_array_set(struct dba_array *array, int32_t ie, void *entry)
+{
+       assert(ie >= 0);
+       assert(ie < array->ea);
+       assert(ie <= array->eu);
+       if (ie == array->eu)
+               array->eu++;
+       if (array->flags & DBA_STR)
+               entry = mandoc_strdup(entry);
+       array->ep[ie] = entry;
+       array->em[ie] = 0;
+}
+
+void
+dba_array_add(struct dba_array *array, void *entry)
+{
+       if (array->eu == array->ea) {
+               assert(array->flags & DBA_GROW);
+               array->ep = mandoc_reallocarray(array->ep,
+                   2, sizeof(*array->ep) * array->ea);
+               array->em = mandoc_reallocarray(array->em,
+                   2, sizeof(*array->em) * array->ea);
+               array->ea *= 2;
+       }
+       dba_array_set(array, array->eu, entry);
+}
+
+void *
+dba_array_get(struct dba_array *array, int32_t ie)
+{
+       if (ie < 0 || ie >= array->eu || array->em[ie] == -1)
+               return NULL;
+       return array->ep[ie];
+}
+
+void
+dba_array_start(struct dba_array *array)
+{
+       array->ec = array->eu;
+}
+
+void *
+dba_array_next(struct dba_array *array)
+{
+       if (array->ec < array->eu)
+               array->ec++;
+       else
+               array->ec = 0;
+       while (array->ec < array->eu && array->em[array->ec] == -1)
+               array->ec++;
+       return array->ec < array->eu ? array->ep[array->ec] : NULL;
+}
+
+void
+dba_array_del(struct dba_array *array)
+{
+       if (array->ec < array->eu && array->em[array->ec] != -1) {
+               array->em[array->ec] = -1;
+               array->ed++;
+       }
+}
+
+void
+dba_array_undel(struct dba_array *array)
+{
+       memset(array->em, 0, sizeof(*array->em) * array->eu);
+}
+
+void
+dba_array_setpos(struct dba_array *array, int32_t ie, int32_t pos)
+{
+       array->em[ie] = pos;
+}
+
+int32_t
+dba_array_getpos(struct dba_array *array)
+{
+       return array->pos;
+}
+
+void
+dba_array_sort(struct dba_array *array, dba_compare_func func)
+{
+       assert(array->ed == 0);
+       qsort(array->ep, array->eu, sizeof(*array->ep), func);
+}
+
+int32_t
+dba_array_writelen(struct dba_array *array, int32_t nmemb)
+{
+       dba_int_write(array->eu - array->ed);
+       return dba_skip(nmemb, array->eu - array->ed);
+}
+
+void
+dba_array_writepos(struct dba_array *array)
+{
+       int32_t  ie;
+
+       array->pos = dba_tell();
+       for (ie = 0; ie < array->eu; ie++)
+               if (array->em[ie] != -1)
+                       dba_int_write(array->em[ie]);
+}
+
+void
+dba_array_writelst(struct dba_array *array)
+{
+       const char      *str;
+
+       dba_array_FOREACH(array, str)
+               dba_str_write(str);
+       dba_char_write('\0');
+}
diff --git a/usr.bin/mandoc/dba_array.h b/usr.bin/mandoc/dba_array.h
new file mode 100644 (file)
index 0000000..167f68f
--- /dev/null
@@ -0,0 +1,47 @@
+/*     $OpenBSD: dba_array.h,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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.
+ *
+ * Public interface for allocation-based arrays
+ * for the mandoc database, for read-write access.
+ * To be used by dba*.c and by makewhatis(8).
+ */
+
+struct dba_array;
+
+#define        DBA_STR         0x01    /* Map contains strings, not pointers. */
+#define        DBA_GROW        0x02    /* Allow the array to grow. */
+
+#define        dba_array_FOREACH(a, e) \
+       dba_array_start(a); \
+       while (((e) = dba_array_next(a)) != NULL)
+
+typedef int dba_compare_func(const void *, const void *);
+
+struct dba_array *dba_array_new(int32_t, int);
+void            dba_array_free(struct dba_array *);
+void            dba_array_set(struct dba_array *, int32_t, void *);
+void            dba_array_add(struct dba_array *, void *);
+void           *dba_array_get(struct dba_array *, int32_t);
+void            dba_array_start(struct dba_array *);
+void           *dba_array_next(struct dba_array *);
+void            dba_array_del(struct dba_array *);
+void            dba_array_undel(struct dba_array *);
+void            dba_array_setpos(struct dba_array *, int32_t, int32_t);
+int32_t                 dba_array_getpos(struct dba_array *);
+void            dba_array_sort(struct dba_array *, dba_compare_func);
+int32_t                 dba_array_writelen(struct dba_array *, int32_t);
+void            dba_array_writepos(struct dba_array *);
+void            dba_array_writelst(struct dba_array *);
diff --git a/usr.bin/mandoc/dba_read.c b/usr.bin/mandoc/dba_read.c
new file mode 100644 (file)
index 0000000..c7128de
--- /dev/null
@@ -0,0 +1,74 @@
+/*     $OpenBSD: dba_read.c,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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.
+ *
+ * Function to read the mandoc database from disk into RAM,
+ * such that data can be added or removed.
+ * The interface is defined in "dba.h".
+ * This file is seperate from dba.c because this also uses "dbm.h".
+ */
+#include <regex.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "mandoc_aux.h"
+#include "mansearch.h"
+#include "dba_array.h"
+#include "dba.h"
+#include "dbm.h"
+
+
+struct dba *
+dba_read(const char *fname)
+{
+       struct dba              *dba;
+       struct dba_array        *page;
+       struct dbm_page         *pdata;
+       struct dbm_macro        *mdata;
+       const char              *cp;
+       int32_t                  im, ip, iv, npages;
+
+       if (dbm_open(fname) == -1)
+               return NULL;
+       npages = dbm_page_count();
+       dba = dba_new(npages);
+       for (ip = 0; ip < npages; ip++) {
+               pdata = dbm_page_get(ip);
+               page = dba_page_new(dba->pages, pdata->name, pdata->sect,
+                   pdata->arch, pdata->desc, pdata->file + 1, *pdata->file);
+               cp = pdata->name;
+               while (*(cp = strchr(cp, '\0') + 1) != '\0')
+                       dba_page_add(page, DBP_NAME, cp);
+               cp = pdata->sect;
+               while (*(cp = strchr(cp, '\0') + 1) != '\0')
+                       dba_page_add(page, DBP_SECT, cp);
+               if ((cp = pdata->arch) != NULL)
+                       while (*(cp = strchr(cp, '\0') + 1) != '\0')
+                               dba_page_add(page, DBP_ARCH, cp);
+               cp = pdata->file;
+               while (*(cp = strchr(cp, '\0') + 1) != '\0')
+                       dba_page_add(page, DBP_FILE, cp);
+       }
+       for (im = 0; im < MACRO_MAX; im++) {
+               for (iv = 0; iv < dbm_macro_count(im); iv++) {
+                       mdata = dbm_macro_get(im, iv);
+                       dba_macro_new(dba, im, mdata->value, mdata->pp);
+               }
+       }
+       dbm_close();
+       return dba;
+}
diff --git a/usr.bin/mandoc/dba_write.c b/usr.bin/mandoc/dba_write.c
new file mode 100644 (file)
index 0000000..ef15dbe
--- /dev/null
@@ -0,0 +1,117 @@
+/*     $OpenBSD: dba_write.c,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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.
+ *
+ * Low-level functions for serializing allocation-based data to disk.
+ * The interface is defined in "dba_write.h".
+ */
+#include <assert.h>
+#include <endian.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "dba_write.h"
+
+static FILE    *ofp;
+
+
+int
+dba_open(const char *fname)
+{
+       ofp = fopen(fname, "w");
+       return ofp == NULL ? -1 : 0;
+}
+
+int
+dba_close(void)
+{
+       return fclose(ofp) == EOF ? -1 : 0;
+}
+
+int32_t
+dba_tell(void)
+{
+       long             pos;
+
+       if ((pos = ftell(ofp)) == -1)
+               err(1, "ftell");
+       if (pos >= INT32_MAX) {
+               errno = EOVERFLOW;
+               err(1, "ftell = %ld", pos);
+       }
+       return pos;
+}
+
+void
+dba_seek(int32_t pos)
+{
+       if (fseek(ofp, pos, SEEK_SET) == -1)
+               err(1, "fseek(%d)", pos);
+}
+
+int32_t
+dba_align(void)
+{
+       int32_t          pos;
+
+       pos = dba_tell();
+       while (pos & 3) {
+               dba_char_write('\0');
+               pos++;
+       }
+       return pos;
+}
+
+int32_t
+dba_skip(int32_t nmemb, int32_t sz)
+{
+       const int32_t    out[5] = {0, 0, 0, 0, 0};
+       int32_t          i, pos;
+
+       assert(sz >= 0);
+       assert(nmemb > 0);
+       assert(nmemb <= 5);
+       pos = dba_tell();
+       for (i = 0; i < sz; i++)
+               if (nmemb - fwrite(&out, sizeof(out[0]), nmemb, ofp))
+                       err(1, "fwrite");
+       return pos;
+}
+
+void
+dba_char_write(int c)
+{
+       if (putc(c, ofp) == EOF)
+               err(1, "fputc");
+}
+
+void
+dba_str_write(const char *str)
+{
+       if (fputs(str, ofp) == EOF)
+               err(1, "fputs");
+       dba_char_write('\0');
+}
+
+void
+dba_int_write(int32_t i)
+{
+       i = htobe32(i);
+       if (fwrite(&i, sizeof(i), 1, ofp) != 1)
+               err(1, "fwrite");
+}
diff --git a/usr.bin/mandoc/dba_write.h b/usr.bin/mandoc/dba_write.h
new file mode 100644 (file)
index 0000000..bbbaa5e
--- /dev/null
@@ -0,0 +1,30 @@
+/*     $OpenBSD: dba_write.h,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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.
+ *
+ * Internal interface to low-level functions
+ * for serializing allocation-based data to disk.
+ * For use by dba_array.c and dba.c only.
+ */
+
+int     dba_open(const char *);
+int     dba_close(void);
+int32_t         dba_tell(void);
+void    dba_seek(int32_t);
+int32_t         dba_align(void);
+int32_t         dba_skip(int32_t, int32_t);
+void    dba_char_write(int);
+void    dba_str_write(const char *);
+void    dba_int_write(int32_t);
diff --git a/usr.bin/mandoc/dbm.c b/usr.bin/mandoc/dbm.c
new file mode 100644 (file)
index 0000000..3334a2c
--- /dev/null
@@ -0,0 +1,452 @@
+/*     $OpenBSD: dbm.c,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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.
+ *
+ * Map-based version of the mandoc database, for read-only access.
+ * The interface is defined in "dbm.h".
+ */
+#include <assert.h>
+#include <endian.h>
+#include <err.h>
+#include <errno.h>
+#include <regex.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mansearch.h"
+#include "dbm_map.h"
+#include "dbm.h"
+
+struct macro {
+       int32_t value;
+       int32_t pages;
+};
+
+struct page {
+       int32_t name;
+       int32_t sect;
+       int32_t arch;
+       int32_t desc;
+       int32_t file;
+};
+
+enum iter {
+       ITER_NONE = 0,
+       ITER_NAME,
+       ITER_SECT,
+       ITER_ARCH,
+       ITER_DESC,
+       ITER_MACRO
+};
+
+static struct macro    *macros[MACRO_MAX];
+static int32_t          nvals[MACRO_MAX];
+static struct page     *pages;
+static int32_t          npages;
+static enum iter        iteration;
+
+static struct dbm_res   page_bytitle(enum iter, const struct dbm_match *);
+static struct dbm_res   page_byarch(const struct dbm_match *);
+static struct dbm_res   page_bymacro(int32_t, const struct dbm_match *);
+static char            *macro_bypage(int32_t, int32_t);
+
+
+/*** top level functions **********************************************/
+
+/*
+ * Open a disk-based mandoc database for read-only access.
+ * Map the pages and macros[] arrays.
+ * Return 0 on success.  Return -1 and set errno on failure.
+ */
+int
+dbm_open(const char *fname)
+{
+       const int32_t   *mp, *ep;
+       int32_t          im;
+
+       if (dbm_map(fname) == -1)
+               return -1;
+
+       if ((npages = be32toh(*dbm_getint(4))) < 0) {
+               warnx("dbm_open(%s): Invalid number of pages: %d",
+                   fname, npages);
+               goto fail;
+       }
+       pages = (struct page *)dbm_getint(5);
+
+       if ((mp = dbm_get(*dbm_getint(2))) == NULL) {
+               warnx("dbm_open(%s): Invalid offset of macros array", fname);
+               goto fail;
+       }
+       if (be32toh(*mp) != MACRO_MAX) {
+               warnx("dbm_open(%s): Invalid number of macros: %d",
+                   fname, be32toh(*mp));
+               goto fail;
+       }
+       for (im = 0; im < MACRO_MAX; im++) {
+               if ((ep = dbm_get(*++mp)) == NULL) {
+                       warnx("dbm_open(%s): Invalid offset of macro %d",
+                           fname, im);
+                       goto fail;
+               }
+               nvals[im] = be32toh(*ep);
+               macros[im] = (struct macro *)++ep;
+       }
+       return 0;
+
+fail:
+       dbm_unmap();
+       errno = EFTYPE;
+       return -1;
+}
+
+void
+dbm_close(void)
+{
+       dbm_unmap();
+}
+
+
+/*** functions for handling pages *************************************/
+
+int32_t
+dbm_page_count(void)
+{
+       return npages;
+}
+
+/*
+ * Give the caller pointers to the data for one manual page.
+ */
+struct dbm_page *
+dbm_page_get(int32_t ip)
+{
+       static struct dbm_page   res;
+
+       assert(ip >= 0);
+       assert(ip < npages);
+       res.name = dbm_get(pages[ip].name);
+       res.sect = dbm_get(pages[ip].sect);
+       res.arch = pages[ip].arch ? dbm_get(pages[ip].arch) : NULL;
+       res.desc = dbm_get(pages[ip].desc);
+       res.file = dbm_get(pages[ip].file);
+       res.addr = dbm_addr(pages + ip);
+       return &res;
+}
+
+/*
+ * Functions to start filtered iterations over manual pages.
+ */
+void
+dbm_page_byname(const struct dbm_match *match)
+{
+       assert(match != NULL);
+       page_bytitle(ITER_NAME, match);
+}
+
+void
+dbm_page_bysect(const struct dbm_match *match)
+{
+       assert(match != NULL);
+       page_bytitle(ITER_SECT, match);
+}
+
+void
+dbm_page_byarch(const struct dbm_match *match)
+{
+       assert(match != NULL);
+       page_byarch(match);
+}
+
+void
+dbm_page_bydesc(const struct dbm_match *match)
+{
+       assert(match != NULL);
+       page_bytitle(ITER_DESC, match);
+}
+
+void
+dbm_page_bymacro(int32_t im, const struct dbm_match *match)
+{
+       assert(im >= 0);
+       assert(im < MACRO_MAX);
+       assert(match != NULL);
+       page_bymacro(im, match);
+}
+
+/*
+ * Return the number of the next manual page in the current iteration.
+ */
+struct dbm_res
+dbm_page_next(void)
+{
+       struct dbm_res                   res = {-1, 0};
+
+       switch(iteration) {
+       case ITER_NONE:
+               return res;
+       case ITER_ARCH:
+               return page_byarch(NULL);
+       case ITER_MACRO:
+               return page_bymacro(0, NULL);
+       default:
+               return page_bytitle(iteration, NULL);
+       }
+}
+
+/*
+ * Functions implementing the iteration over manual pages.
+ */
+static struct dbm_res
+page_bytitle(enum iter arg_iter, const struct dbm_match *arg_match)
+{
+       static const struct dbm_match   *match;
+       static const char               *cp;    
+       static int32_t                   ip;
+       struct dbm_res                   res = {-1, 0};
+
+       assert(arg_iter == ITER_NAME || arg_iter == ITER_DESC ||
+           arg_iter == ITER_SECT);
+
+       /* Initialize for a new iteration. */
+
+       if (arg_match != NULL) {
+               iteration = arg_iter;
+               match = arg_match;
+               switch (iteration) {
+               case ITER_NAME:
+                       cp = dbm_get(pages[0].name);
+                       break;
+               case ITER_SECT:
+                       cp = dbm_get(pages[0].sect);
+                       break;
+               case ITER_DESC:
+                       cp = dbm_get(pages[0].desc);
+                       break;
+               default:
+                       abort();
+               }
+               ip = 0;
+               return res;
+       }
+
+       /* Search for a name. */
+
+       while (ip < npages) {
+               if (iteration == ITER_NAME)
+                       cp++;
+               if (dbm_match(match, cp))
+                       break;
+               cp = strchr(cp, '\0') + 1;
+               if (iteration == ITER_DESC)
+                       ip++;
+               else if (*cp == '\0') {
+                       cp++;
+                       ip++;
+               }
+       }
+
+       /* Reached the end without a match. */
+
+       if (ip == npages) {
+               iteration = ITER_NONE;
+               match = NULL;
+               cp = NULL;
+               return res;
+       }
+
+       /* Found a match; save the quality for later retrieval. */
+
+       res.page = ip;
+       res.bits = iteration == ITER_NAME ? cp[-1] : 0;
+
+       /* Skip the remaining names of this page. */
+
+       if (++ip < npages) {
+               do {
+                       cp++;
+               } while (cp[-1] != '\0' ||
+                   (iteration != ITER_DESC && cp[-2] != '\0'));
+       }
+       return res;
+}
+
+static struct dbm_res
+page_byarch(const struct dbm_match *arg_match)
+{
+       static const struct dbm_match   *match;
+       struct dbm_res                   res = {-1, 0};
+       static int32_t                   ip;
+       const char                      *cp;    
+
+       /* Initialize for a new iteration. */
+
+       if (arg_match != NULL) {
+               iteration = ITER_ARCH;
+               match = arg_match;
+               ip = 0;
+               return res;
+       }
+
+       /* Search for an architecture. */
+
+       for ( ; ip < npages; ip++)
+               if (pages[ip].arch)
+                       for (cp = dbm_get(pages[ip].arch);
+                           *cp != '\0';
+                           cp = strchr(cp, '\0') + 1)
+                               if (dbm_match(match, cp)) {
+                                       res.page = ip++;
+                                       return res;
+                               }
+
+       /* Reached the end without a match. */
+
+       iteration = ITER_NONE;
+       match = NULL;
+       return res;
+}
+
+static struct dbm_res
+page_bymacro(int32_t im, const struct dbm_match *match)
+{
+       static const int32_t    *pp;
+       struct dbm_res           res = {-1, 0};
+       const char              *cp;
+       int32_t                  iv;
+
+       assert(im >= 0);
+       assert(im < MACRO_MAX);
+
+       /* Initialize for a new iteration. */
+
+       if (match != NULL) {
+               iteration = ITER_MACRO;
+               cp = nvals[im] ? dbm_get(macros[im]->value) : NULL;
+               for (iv = 0; iv < nvals[im]; iv++) {
+                       if (dbm_match(match, cp))
+                               break;
+                       cp = strchr(cp, '\0') + 1;
+               }
+               pp = iv == nvals[im] ? NULL : dbm_get(macros[im][iv].pages);
+               return res;
+       }
+       if (iteration != ITER_MACRO)
+               return res;
+
+       /* No more matches. */
+
+       if (pp == NULL || *pp == 0) {
+               iteration = ITER_NONE;
+               pp = NULL;
+               return res;
+       }
+
+       /* Found a match. */
+
+       res.page = (struct page *)dbm_get(*pp++) - pages;
+       return res;
+}
+
+
+/*** functions for handling macros ************************************/
+
+int32_t
+dbm_macro_count(int32_t im)
+{
+       assert(im >= 0);
+       assert(im < MACRO_MAX);
+       return nvals[im];
+}
+
+struct dbm_macro *
+dbm_macro_get(int32_t im, int32_t iv)
+{
+       static struct dbm_macro macro;
+
+       assert(im >= 0);
+       assert(im < MACRO_MAX);
+       assert(iv >= 0);
+       assert(iv < nvals[im]);
+       macro.value = dbm_get(macros[im][iv].value);
+       macro.pp = dbm_get(macros[im][iv].pages);
+       return &macro;
+}
+
+/*
+ * Filtered iteration over macro entries.
+ */
+void
+dbm_macro_bypage(int32_t im, int32_t ip)
+{
+       assert(im >= 0);
+       assert(im < MACRO_MAX);
+       assert(ip != 0);
+       macro_bypage(im, ip);
+}
+
+char *
+dbm_macro_next(void)
+{
+       return macro_bypage(MACRO_MAX, 0);
+}
+
+static char *
+macro_bypage(int32_t arg_im, int32_t arg_ip)
+{
+       static const int32_t    *pp;
+       static int32_t           im, ip, iv;
+
+       /* Initialize for a new iteration. */
+
+       if (arg_im < MACRO_MAX && arg_ip != 0) {
+               im = arg_im;
+               ip = arg_ip;
+               pp = dbm_get(macros[im]->pages);
+               iv = 0;
+               return NULL;
+       }
+       if (im >= MACRO_MAX)
+               return NULL;
+
+       /* Search for the next value. */
+
+       while (iv < nvals[im]) {
+               if (*pp == ip)
+                       break;
+               if (*pp == 0)
+                       iv++;
+               pp++;
+       }
+
+       /* Reached the end without a match. */
+
+       if (iv == nvals[im]) {
+               im = MACRO_MAX;
+               ip = 0;
+               pp = NULL;
+               return NULL;
+       }
+
+       /* Found a match; skip the remaining pages of this entry. */
+
+       if (++iv < nvals[im])
+               while (*pp++ != 0)
+                       continue;
+
+       return dbm_get(macros[im][iv - 1].value);
+}
diff --git a/usr.bin/mandoc/dbm.h b/usr.bin/mandoc/dbm.h
new file mode 100644 (file)
index 0000000..0f12ee1
--- /dev/null
@@ -0,0 +1,68 @@
+/*     $OpenBSD: dbm.h,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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.
+ *
+ * Public interface for the map-based version
+ * of the mandoc database, for read-only access.
+ * To be used by dbm*.c, dba_read.c, and man(1) and apropos(1).
+ */
+
+enum dbm_mtype {
+       DBM_EXACT = 0,
+       DBM_SUB,
+       DBM_REGEX
+};
+
+struct dbm_match {
+       regex_t         *re;
+       const char      *str;
+       enum dbm_mtype   type;
+};
+
+struct dbm_res {
+       int32_t          page;
+       int32_t          bits;
+};
+
+struct dbm_page {
+       const char      *name;
+       const char      *sect;
+       const char      *arch;
+       const char      *desc;
+       const char      *file;
+       int32_t          addr;
+};
+
+struct dbm_macro {
+       const char      *value;
+       const int32_t   *pp;
+};
+
+int             dbm_open(const char *);
+void            dbm_close(void);
+
+int32_t                 dbm_page_count(void);
+struct dbm_page        *dbm_page_get(int32_t);
+void            dbm_page_byname(const struct dbm_match *);
+void            dbm_page_bysect(const struct dbm_match *);
+void            dbm_page_byarch(const struct dbm_match *);
+void            dbm_page_bydesc(const struct dbm_match *);
+void            dbm_page_bymacro(int32_t, const struct dbm_match *);
+struct dbm_res  dbm_page_next(void);
+
+int32_t                 dbm_macro_count(int32_t);
+struct dbm_macro *dbm_macro_get(int32_t, int32_t);
+void            dbm_macro_bypage(int32_t, int32_t);
+char           *dbm_macro_next(void);
diff --git a/usr.bin/mandoc/dbm_map.c b/usr.bin/mandoc/dbm_map.c
new file mode 100644 (file)
index 0000000..4c81197
--- /dev/null
@@ -0,0 +1,174 @@
+/*     $OpenBSD: dbm_map.c,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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.
+ *
+ * Low-level routines for the map-based version
+ * of the mandoc database, for read-only access.
+ * The interface is defined in "dbm_map.h".
+ */
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <endian.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <regex.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mansearch.h"
+#include "dbm_map.h"
+#include "dbm.h"
+
+static struct stat      st;
+static char            *dbm_base;
+static int              ifd;
+static int32_t          max_offset;
+
+/*
+ * Open a disk-based database for read-only access.
+ * Validate the file format as far as it is not mandoc-specific.
+ * Return 0 on success.  Return -1 and set errno on failure.
+ */
+int
+dbm_map(const char *fname)
+{
+       int              save_errno;
+       const int32_t   *magic;
+
+       if ((ifd = open(fname, O_RDONLY)) == -1)
+               return -1;
+       if (fstat(ifd, &st) == -1)
+               goto fail;
+       if (st.st_size < 5) {
+               warnx("dbm_map(%s): File too short", fname);
+               errno = EFTYPE;
+               goto fail;
+       }
+       if (st.st_size > INT32_MAX) {
+               errno = EFBIG;
+               goto fail;
+       }
+       if ((dbm_base = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED,
+           ifd, 0)) == MAP_FAILED)
+               goto fail;
+       magic = dbm_getint(0);
+       if (be32toh(*magic) != MANDOCDB_MAGIC) {
+               warnx("dbm_map(%s): Bad initial magic %x (expected %x)",
+                   fname, be32toh(*magic), MANDOCDB_MAGIC);
+               errno = EFTYPE;
+               goto fail;
+       }
+       magic = dbm_getint(1);
+       if (be32toh(*magic) != MANDOCDB_VERSION) {
+               warnx("dbm_map(%s): Bad version number %d (expected %d)",
+                   fname, be32toh(*magic), MANDOCDB_VERSION);
+               errno = EFTYPE;
+               goto fail;
+       }
+       max_offset = be32toh(*dbm_getint(3)) + sizeof(int32_t);
+       if (st.st_size != max_offset) {
+               warnx("dbm_map(%s): Inconsistent file size %llu (expected %d)",
+                   fname, st.st_size, max_offset);
+               errno = EFTYPE;
+               goto fail;
+       }
+       if ((magic = dbm_get(*dbm_getint(3))) == NULL) {
+               errno = EFTYPE;
+               goto fail;
+       }
+       if (be32toh(*magic) != MANDOCDB_MAGIC) {
+               warnx("dbm_map(%s): Bad final magic %x (expected %x)",
+                   fname, be32toh(*magic), MANDOCDB_MAGIC);
+               errno = EFTYPE;
+               goto fail;
+       }
+       return 0;
+
+fail:
+       save_errno = errno;
+       close(ifd);
+       errno = save_errno;
+       return -1;
+}
+
+void
+dbm_unmap(void)
+{
+       if (munmap(dbm_base, st.st_size) == -1)
+               warn("dbm_unmap: munmap");
+       if (close(ifd) == -1)
+               warn("dbm_unmap: close");
+       dbm_base = (char *)-1;
+}
+
+/*
+ * Take a raw integer as it was read from the database.
+ * Interpret it as an offset into the database file
+ * and return a pointer to that place in the file.
+ */
+void *
+dbm_get(int32_t offset)
+{
+       offset = be32toh(offset);
+       if (offset < 0 || offset >= max_offset) {
+               warnx("dbm_get: Database corrupt: offset %d > %d",
+                   offset, max_offset);
+               return NULL;
+       }
+       return dbm_base + offset;
+}
+
+/*
+ * Assume the database starts with some integers.
+ * Assume they are numbered starting from 0, increasing.
+ * Get a pointer to one with the number "offset".
+ */
+int32_t *
+dbm_getint(int32_t offset)
+{
+       return (int32_t *)dbm_base + offset;
+}
+
+/*
+ * The reverse of dbm_get().
+ * Take pointer into the database file
+ * and convert it to the raw integer
+ * that would be used to refer to that place in the file.
+ */
+int32_t
+dbm_addr(const void *p)
+{
+       return htobe32((char *)p - dbm_base);
+}
+
+int
+dbm_match(const struct dbm_match *match, const char *str)
+{
+       switch (match->type) {
+       case DBM_EXACT:
+               return strcmp(str, match->str) == 0;
+       case DBM_SUB:
+               return strcasestr(str, match->str) != NULL;
+       case DBM_REGEX:
+               return regexec(match->re, str, 0, NULL, 0) == 0;
+       default:
+               abort();
+       }
+}
diff --git a/usr.bin/mandoc/dbm_map.h b/usr.bin/mandoc/dbm_map.h
new file mode 100644 (file)
index 0000000..6a4db57
--- /dev/null
@@ -0,0 +1,29 @@
+/*     $OpenBSD: dbm_map.h,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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.
+ *
+ * Private interface for low-level routines for the map-based version 
+ * of the mandoc database, for read-only access.
+ * To be used by dbm*.c only.
+ */
+
+struct dbm_match;
+
+int             dbm_map(const char *);
+void            dbm_unmap(void);
+void           *dbm_get(int32_t);
+int32_t                *dbm_getint(int32_t);
+int32_t                 dbm_addr(const void *);
+int             dbm_match(const struct dbm_match *, const char *);
index 2bc4902..45f5c52 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: main.c,v 1.176 2016/07/15 19:31:53 schwarze Exp $ */
+/*     $OpenBSD: main.c,v 1.177 2016/08/01 10:32:39 schwarze Exp $ */
 /*
  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2010-2012, 2014-2016 Ingo Schwarze <schwarze@openbsd.org>
@@ -317,9 +317,6 @@ main(int argc, char *argv[])
        /* man(1), whatis(1), apropos(1) */
 
        if (search.argmode != ARG_FILE) {
-               if (argc == 0)
-                       usage(search.argmode);
-
                if (search.argmode == ARG_NAME &&
                    outmode == OUTMODE_ONE)
                        search.firstmatch = 1;
@@ -327,7 +324,6 @@ main(int argc, char *argv[])
                /* Access the mandoc database. */
 
                manconf_parse(&conf, conf_file, defpaths, auxpaths);
-               mansearch_setup(1);
                if ( ! mansearch(&search, &conf.manpath,
                    argc, argv, &res, &sz))
                        usage(search.argmode);
@@ -431,7 +427,7 @@ main(int argc, char *argv[])
 
                        if (resp == NULL)
                                parse(&curp, fd, *argv);
-                       else if (resp->form & FORM_SRC) {
+                       else if (resp->form == FORM_SRC) {
                                /* For .so only; ignore failure. */
                                chdir(conf.manpath.paths[resp->ipath]);
                                parse(&curp, fd, resp->file);
@@ -480,7 +476,6 @@ out:
        if (search.argmode != ARG_FILE) {
                manconf_free(&conf);
                mansearch_free(res, sz);
-               mansearch_setup(0);
        }
 
        free(defos);
@@ -584,7 +579,8 @@ fs_lookup(const struct manpaths *paths, size_t ipath,
        glob_t           globinfo;
        struct manpage  *page;
        char            *file;
-       int              form, globres;
+       int              globres;
+       enum form        form;
 
        form = FORM_SRC;
        mandoc_asprintf(&file, "%s/man%s/%s.%s",
index d5e9a10..b208055 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: mandocdb.c,v 1.172 2016/07/19 13:30:16 schwarze Exp $ */
+/*     $OpenBSD: mandocdb.c,v 1.173 2016/08/01 10:32:39 schwarze Exp $ */
 /*
  * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2011-2016 Ingo Schwarze <schwarze@openbsd.org>
@@ -26,6 +26,7 @@
 #include <fcntl.h>
 #include <fts.h>
 #include <limits.h>
+#include <stdarg.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <stdint.h>
@@ -33,8 +34,6 @@
 #include <string.h>
 #include <unistd.h>
 
-#include <sqlite3.h>
-
 #include "mandoc_aux.h"
 #include "mandoc_ohash.h"
 #include "mandoc.h"
 #include "man.h"
 #include "manconf.h"
 #include "mansearch.h"
+#include "dba_array.h"
+#include "dba.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 */
@@ -87,14 +68,14 @@ struct      inodev {
 
 struct mpage {
        struct inodev    inodev;  /* used for hashing routine */
-       int64_t          pageid;  /* pageid in mpages SQL table */
+       struct dba_array *dba;
        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 */
-       int              form;    /* format from file content */
        int              name_head_done;
+       enum form        form;    /* format from file content */
 };
 
 struct mlink {
@@ -105,19 +86,9 @@ struct      mlink {
        char            *fsec;    /* section from file name suffix */
        struct mlink    *next;    /* singly linked list */
        struct mpage    *mpage;   /* parent */
-       int              dform;   /* format from directory */
-       int              fform;   /* format from file name suffix */
        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_SELECT_NAME,       /* retrieve existing name flags */
-       STMT_INSERT_KEY,        /* insert parsed key */
-       STMT__MAX
+       enum form        dform;   /* format from directory */
+       enum form        fform;   /* format from file name suffix */
 };
 
 typedef        int (*mdoc_fp)(struct mpage *, const struct roff_meta *,
@@ -131,20 +102,18 @@ struct    mdoc_handler {
 
 int             mandocdb(int, char *[]);
 
-static void     dbclose(int);
-static void     dbadd(struct mpage *);
+static void     dbadd(struct dba *, struct mpage *);
 static void     dbadd_mlink(const struct mlink *mlink);
-static void     dbadd_mlink_name(const struct mlink *mlink);
-static int      dbopen(int);
-static void     dbprune(void);
+static void     dbprune(struct dba *);
+static void     dbwrite(struct dba *);
 static void     filescan(const char *);
 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 *);
+int             mpages_compare(const void *, const void *);
 static void     mpages_free(void);
-static void     mpages_merge(struct mparse *);
-static void     names_check(void);
+static void     mpages_merge(struct dba *, struct mparse *);
 static void     parse_cat(struct mpage *, int);
 static void     parse_man(struct mpage *, const struct roff_meta *,
                        const struct roff_node *);
@@ -180,7 +149,6 @@ static      int      set_basedir(const char *, int);
 static int      treescan(void);
 static size_t   utf8(unsigned int, char [7]);
 
-static char             tempfilename[32];
 static int              nodb; /* no database changes */
 static int              mparse_options; /* abort the parse early */
 static int              use_all; /* use all found files */
@@ -194,8 +162,6 @@ 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] = {
@@ -330,6 +296,7 @@ mandocdb(int argc, char *argv[])
 {
        struct manconf    conf;
        struct mparse    *mp;
+       struct dba       *dba;
        const char       *path_arg, *progname;
        size_t            j, sz;
        int               ch, i;
@@ -340,7 +307,6 @@ mandocdb(int argc, char *argv[])
        }
 
        memset(&conf, 0, sizeof(conf));
-       memset(stmts, 0, STMT__MAX * sizeof(sqlite3_stmt *));
 
        /*
         * We accept a few different invocations.
@@ -439,7 +405,7 @@ mandocdb(int argc, char *argv[])
                if (OP_TEST != op && 0 == set_basedir(path_arg, 1))
                        goto out;
 
-               if (dbopen(1)) {
+               if ((dba = dba_read(MANDOC_DB)) != NULL) {
                        /*
                         * The existing database is usable.  Process
                         * all files specified on the command-line.
@@ -455,7 +421,7 @@ mandocdb(int argc, char *argv[])
                        for (i = 0; i < argc; i++)
                                filescan(argv[i]);
                        if (OP_TEST != op)
-                               dbprune();
+                               dbprune(dba);
                } else {
                        /*
                         * Database missing or corrupt.
@@ -465,12 +431,13 @@ mandocdb(int argc, char *argv[])
                        op = OP_DEFAULT;
                        if (0 == treescan())
                                goto out;
-                       if (0 == dbopen(0))
-                               goto out;
+                       dba = dba_new(128);
                }
                if (OP_DELETE != op)
-                       mpages_merge(mp);
-               dbclose(OP_DEFAULT == op ? 0 : 1);
+                       mpages_merge(dba, mp);
+               if (nodb == 0)
+                       dbwrite(dba);
+               dba_free(dba);
        } else {
                /*
                 * If we have arguments, use them as our manpaths.
@@ -515,14 +482,11 @@ mandocdb(int argc, char *argv[])
                                continue;
                        if (0 == treescan())
                                continue;
-                       if (0 == dbopen(0))
-                               continue;
-
-                       mpages_merge(mp);
-                       if (warnings && !nodb &&
-                           ! (MPARSE_QUICK & mparse_options))
-                               names_check();
-                       dbclose(0);
+                       dba = dba_new(128);
+                       mpages_merge(dba, mp);
+                       if (nodb == 0)
+                               dbwrite(dba);
+                       dba_free(dba);
 
                        if (j + 1 < conf.manpath.sz) {
                                mpages_free();
@@ -572,7 +536,8 @@ treescan(void)
        FTS             *f;
        FTSENT          *ff;
        struct mlink    *mlink;
-       int              dform, gzip;
+       int              gzip;
+       enum form        dform;
        char            *dsec, *arch, *fsec, *cp;
        const char      *path;
        const char      *argv[2];
@@ -938,6 +903,7 @@ mlink_add(struct mlink *mlink, const struct stat *st)
                mpage = mandoc_calloc(1, sizeof(struct mpage));
                mpage->inodev.st_ino = inodev.st_ino;
                mpage->inodev.st_dev = inodev.st_dev;
+               mpage->form = FORM_NONE;
                ohash_insert(&mpages, slot, mpage);
        } else
                mlink->next = mpage->mlinks;
@@ -1086,28 +1052,32 @@ mlink_check(struct mpage *mpage, struct mlink *mlink)
  * and filename to determine whether the file is parsable or not.
  */
 static void
-mpages_merge(struct mparse *mp)
+mpages_merge(struct dba *dba, struct mparse *mp)
 {
-       char                     any[] = "any";
-       struct mpage            *mpage, *mpage_dest;
+       struct mpage            **mplist, *mpage, *mpage_dest;
        struct mlink            *mlink, *mlink_dest;
        struct roff_man         *man;
        char                    *sodest;
        char                    *cp;
        int                      fd;
-       unsigned int             pslot;
-
-       if ( ! nodb)
-               SQL_EXEC("BEGIN TRANSACTION");
+       unsigned int             ip, npages, pslot;
 
+       npages = ohash_entries(&mpages);
+       mplist = mandoc_reallocarray(NULL, npages, sizeof(*mplist));
+       ip = 0;
        mpage = ohash_first(&mpages, &pslot);
        while (mpage != NULL) {
                mlinks_undupe(mpage);
-               if ((mlink = mpage->mlinks) == NULL) {
-                       mpage = ohash_next(&mpages, &pslot);
-                       continue;
-               }
+               if (mpage->mlinks != NULL)
+                       mplist[ip++] = mpage;
+               mpage = ohash_next(&mpages, &pslot);
+       }
+       npages = ip;
+       qsort(mplist, npages, sizeof(*mplist), mpages_compare);
 
+       for (ip = 0; ip < npages; ip++) {
+               mpage = mplist[ip];
+               mlink = mpage->mlinks;
                name_mask = NAME_MASK;
                mandoc_ohash_init(&names, 4, offsetof(struct str, key));
                mandoc_ohash_init(&strings, 6, offsetof(struct str, key));
@@ -1156,8 +1126,8 @@ mpages_merge(struct mparse *mp)
                                         * to the target.
                                         */
 
-                                       if (mpage_dest->pageid)
-                                               dbadd_mlink_name(mlink);
+                                       if (mpage_dest->dba != NULL)
+                                               dbadd_mlink(mlink);
 
                                        if (mlink->next == NULL)
                                                break;
@@ -1193,19 +1163,6 @@ mpages_merge(struct mparse *mp)
                        mpage->arch = mandoc_strdup(mlink->arch);
                        mpage->title = mandoc_strdup(mlink->name);
                }
-               putkey(mpage, mpage->sec, TYPE_sec);
-               if (*mpage->arch != '\0')
-                       putkey(mpage, mpage->arch, TYPE_arch);
-
-               for ( ; mlink != NULL; 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(mpage->desc == NULL);
                if (man != NULL && man->macroset == MACROSET_MDOC)
@@ -1222,52 +1179,24 @@ mpages_merge(struct mparse *mp)
                             mlink = mlink->next)
                                mlink_check(mpage, mlink);
 
-               dbadd(mpage);
+               dbadd(dba, mpage);
                mlink = mpage->mlinks;
 
 nextpage:
                ohash_delete(&strings);
                ohash_delete(&names);
-               mpage = ohash_next(&mpages, &pslot);
        }
-
-       if (0 == nodb)
-               SQL_EXEC("END TRANSACTION");
+       free(mplist);
 }
 
-static void
-names_check(void)
+int
+mpages_compare(const void *vp1, const void *vp2)
 {
-       sqlite3_stmt    *stmt;
-       const char      *name, *sec, *arch, *key;
-
-       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 (sqlite3_bind_int64(stmt, 1, NAME_TITLE) != SQLITE_OK)
-               say("", "%s", sqlite3_errmsg(db));
-
-       while (sqlite3_step(stmt) == SQLITE_ROW) {
-               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);
+       const struct mpage      *mp1, *mp2;
+
+       mp1 = *(const struct mpage **)vp1;
+       mp2 = *(const struct mpage **)vp2;
+       return strcmp(mp1->mlinks->file, mp2->mlinks->file);
 }
 
 static void
@@ -1392,13 +1321,6 @@ parse_cat(struct mpage *mpage, int fd)
 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);
 }
 
@@ -1791,7 +1713,7 @@ putkeys(const struct mpage *mpage, char *cp, size_t sz, uint64_t v)
        } else {
                htab = &strings;
                if (debug > 1)
-                   for (i = 0; i < mansearch_keymax; i++)
+                   for (i = 0; i < KEY_MAX; i++)
                        if ((uint64_t)1 << i & v)
                            say(mpage->mlinks->file,
                                "Adding key %s=%*s",
@@ -1997,53 +1919,24 @@ render_string(char **public, size_t *psz)
 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]);
-}
-
-static void
-dbadd_mlink_name(const struct mlink *mlink)
-{
-       uint64_t         bits;
-       size_t           i;
-
-       dbadd_mlink(mlink);
-
-       i = 1;
-       SQL_BIND_INT64(stmts[STMT_SELECT_NAME], i, mlink->mpage->pageid);
-       bits = NAME_FILE & NAME_MASK;
-       if (sqlite3_step(stmts[STMT_SELECT_NAME]) == SQLITE_ROW) {
-               bits |= sqlite3_column_int64(stmts[STMT_SELECT_NAME], 0);
-               sqlite3_reset(stmts[STMT_SELECT_NAME]);
-       }
-
-       i = 1;
-       SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, bits);
-       SQL_BIND_TEXT(stmts[STMT_INSERT_NAME], i, mlink->name);
-       SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, mlink->mpage->pageid);
-       SQL_STEP(stmts[STMT_INSERT_NAME]);
-       sqlite3_reset(stmts[STMT_INSERT_NAME]);
+       dba_page_alias(mlink->mpage->dba, mlink->name, NAME_FILE);
+       dba_page_add(mlink->mpage->dba, DBP_SECT, mlink->dsec);
+       dba_page_add(mlink->mpage->dba, DBP_SECT, mlink->fsec);
+       dba_page_add(mlink->mpage->dba, DBP_ARCH, mlink->arch);
+       dba_page_add(mlink->mpage->dba, DBP_FILE, mlink->file);
 }
 
 /*
  * 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)
+dbadd(struct dba *dba, struct mpage *mpage)
 {
        struct mlink    *mlink;
        struct str      *key;
        char            *cp;
+       uint64_t         mask;
        size_t           i;
        unsigned int     slot;
        int              mustfree;
@@ -2088,111 +1981,90 @@ dbadd(struct mpage *mpage)
        cp = mpage->desc;
        i = strlen(cp);
        mustfree = render_string(&cp, &i);
-       i = 1;
-       SQL_BIND_TEXT(stmts[STMT_INSERT_PAGE], i, cp);
-       SQL_BIND_INT(stmts[STMT_INSERT_PAGE], i, mpage->form);
-       SQL_STEP(stmts[STMT_INSERT_PAGE]);
-       mpage->pageid = sqlite3_last_insert_rowid(db);
-       sqlite3_reset(stmts[STMT_INSERT_PAGE]);
+       mpage->dba = dba_page_new(dba->pages, mlink->name, mpage->sec,
+           *mpage->arch == '\0' ? mlink->arch : mpage->arch,
+           cp, mlink->file, mpage->form);
        if (mustfree)
                free(cp);
 
-       while (NULL != mlink) {
+       while (mlink != NULL) {
                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);
-               i = 1;
-               SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, key->mask);
-               SQL_BIND_TEXT(stmts[STMT_INSERT_NAME], i, key->key);
-               SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, mpage->pageid);
-               SQL_STEP(stmts[STMT_INSERT_NAME]);
-               sqlite3_reset(stmts[STMT_INSERT_NAME]);
+               dba_page_alias(mpage->dba, key->key, key->mask);
                free(key);
        }
        for (key = ohash_first(&strings, &slot); NULL != key;
             key = ohash_next(&strings, &slot)) {
                assert(key->mpage == mpage);
-               i = 1;
-               SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, key->mask);
-               SQL_BIND_TEXT(stmts[STMT_INSERT_KEY], i, key->key);
-               SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, mpage->pageid);
-               SQL_STEP(stmts[STMT_INSERT_KEY]);
-               sqlite3_reset(stmts[STMT_INSERT_KEY]);
+               i = 0;
+               for (mask = TYPE_Xr; mask <= TYPE_Lb; mask *= 2) {
+                       if (key->mask & mask)
+                               dba_macro_add(dba->macros, i,
+                                   key->key, mpage->dba);
+                       i++;
+               }
                free(key);
        }
 }
 
 static void
-dbprune(void)
+dbprune(struct dba *dba)
 {
-       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]);
+       struct dba_array        *page, *files;
+       char                    *file;
+
+       dba_array_FOREACH(dba->pages, page) {
+               files = dba_array_get(page, DBP_FILE);
+               dba_array_FOREACH(files, file) {
+                       if (*file < ' ')
+                               file++;
+                       if (ohash_find(&mlinks, ohash_qlookup(&mlinks,
+                           file)) != NULL) {
+                               if (debug)
+                                       say(file, "Deleting from database");
+                               dba_array_del(dba->pages);
+                               break;
+                       }
                }
        }
-
-       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.
+ * Write the database from memory to disk.
  */
 static void
-dbclose(int real)
+dbwrite(struct dba *dba)
 {
-       size_t           i;
+       char             tfn[32];
        int              status;
        pid_t            child;
 
-       if (nodb)
+       if (dba_write(MANDOC_DB "~", dba) != -1) {
+               if (rename(MANDOC_DB "~", MANDOC_DB) == -1) {
+                       exitcode = (int)MANDOCLEVEL_SYSERR;
+                       say(MANDOC_DB, "&rename");
+                       unlink(MANDOC_DB "~");
+               }
                return;
-
-       for (i = 0; i < STMT__MAX; i++) {
-               sqlite3_finalize(stmts[i]);
-               stmts[i] = NULL;
        }
 
-       sqlite3_close(db);
-       db = NULL;
-
-       if (real)
+       (void)strlcpy(tfn, "/tmp/mandocdb.XXXXXXXX", sizeof(tfn));
+       if (mkdtemp(tfn) == NULL) {
+               exitcode = (int)MANDOCLEVEL_SYSERR;
+               say("", "&%s", tfn);
                return;
+       }
 
-       if ('\0' == *tempfilename) {
-               if (-1 == rename(MANDOC_DB "~", MANDOC_DB)) {
-                       exitcode = (int)MANDOCLEVEL_SYSERR;
-                       say(MANDOC_DB, "&rename");
-               }
-               return;
+       (void)strlcat(tfn, "/" MANDOC_DB, sizeof(tfn));
+       if (dba_write(tfn, dba) == -1) {
+               exitcode = (int)MANDOCLEVEL_SYSERR;
+               say(tfn, "&dba_write");
+               goto out;
        }
 
        switch (child = fork()) {
@@ -2201,14 +2073,13 @@ dbclose(int real)
                say("", "&fork cmp");
                return;
        case 0:
-               execlp("cmp", "cmp", "-s",
-                   tempfilename, MANDOC_DB, (char *)NULL);
+               execlp("cmp", "cmp", "-s", tfn, MANDOC_DB, (char *)NULL);
                say("", "&exec cmp");
                exit(0);
        default:
                break;
        }
-       if (-1 == waitpid(child, &status, 0)) {
+       if (waitpid(child, &status, 0) == -1) {
                exitcode = (int)MANDOCLEVEL_SYSERR;
                say("", "&wait cmp");
        } else if (WIFSIGNALED(status)) {
@@ -2220,171 +2091,27 @@ dbclose(int real)
                    "Data changed, but cannot replace database");
        }
 
-       *strrchr(tempfilename, '/') = '\0';
+out:
+       *strrchr(tfn, '/') = '\0';
        switch (child = fork()) {
        case -1:
                exitcode = (int)MANDOCLEVEL_SYSERR;
                say("", "&fork rm");
                return;
        case 0:
-               execlp("rm", "rm", "-rf", tempfilename, (char *)NULL);
+               execlp("rm", "rm", "-rf", tfn, (char *)NULL);
                say("", "&exec rm");
                exit((int)MANDOCLEVEL_SYSERR);
        default:
                break;
        }
-       if (-1 == waitpid(child, &status, 0)) {
+       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",
-                   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;
+               say("", "%s: Cannot remove temporary directory", tfn);
        }
-
-       (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"
-             " UNIQUE (\"name\", \"pageid\") ON CONFLICT REPLACE\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 = "SELECT bits FROM names where pageid = ?";
-       sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_SELECT_NAME], 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);
-
-       /*
-        * 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;
-       }
-
-       return 1;
 }
 
 static int
index e40fd1c..6b02ff5 100644 (file)
@@ -1,7 +1,7 @@
-/*     $OpenBSD: mansearch.c,v 1.50 2016/07/09 15:23:36 schwarze Exp $ */
+/*     $OpenBSD: mansearch.c,v 1.51 2016/08/01 10:32:39 schwarze Exp $ */
 /*
  * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
- * Copyright (c) 2013, 2014, 2015 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2013, 2014, 2015, 2016 Ingo Schwarze <schwarze@openbsd.org>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
 #include <string.h>
 #include <unistd.h>
 
-#include <sqlite3.h>
-
 #include "mandoc.h"
 #include "mandoc_aux.h"
 #include "mandoc_ohash.h"
 #include "manconf.h"
 #include "mansearch.h"
-
-extern int mansearch_keymax;
-extern const char *const mansearch_keynames[];
-
-#define        SQL_BIND_TEXT(_db, _s, _i, _v) \
-       do { if (SQLITE_OK != sqlite3_bind_text \
-               ((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \
-               errx((int)MANDOCLEVEL_SYSERR, "%s", sqlite3_errmsg((_db))); \
-       } while (0)
-#define        SQL_BIND_INT64(_db, _s, _i, _v) \
-       do { if (SQLITE_OK != sqlite3_bind_int64 \
-               ((_s), (_i)++, (_v))) \
-               errx((int)MANDOCLEVEL_SYSERR, "%s", sqlite3_errmsg((_db))); \
-       } while (0)
-#define        SQL_BIND_BLOB(_db, _s, _i, _v) \
-       do { if (SQLITE_OK != sqlite3_bind_blob \
-               ((_s), (_i)++, (&_v), sizeof(_v), SQLITE_STATIC)) \
-               errx((int)MANDOCLEVEL_SYSERR, "%s", sqlite3_errmsg((_db))); \
-       } while (0)
+#include "dbm.h"
 
 struct expr {
-       regex_t          regexp;  /* compiled regexp, if applicable */
-       const char      *substr;  /* to search for, if applicable */
-       struct expr     *next;    /* next in sequence */
-       uint64_t         bits;    /* type-mask */
-       int              equal;   /* equality, not subsring match */
-       int              open;    /* opening parentheses before */
-       int              and;     /* logical AND before */
-       int              close;   /* closing parentheses after */
+       /* Used for terms: */
+       struct dbm_match match;   /* Match type and expression. */
+       uint64_t         bits;    /* Type mask. */
+       /* Used for OR and AND groups: */
+       struct expr     *next;    /* Next child in the parent group. */
+       struct expr     *child;   /* First child in this group. */
+       enum { EXPR_TERM, EXPR_OR, EXPR_AND } type;
 };
 
-struct match {
-       uint64_t         pageid; /* identifier in database */
-       uint64_t         bits; /* name type mask */
-       char            *desc; /* manual page description */
-       int              form; /* bit field: formatted, zipped? */
+const char *const mansearch_keynames[KEY_MAX] = {
+       "arch", "sec",  "Xr",   "Ar",   "Fa",   "Fl",   "Dv",   "Fn",
+       "Ic",   "Pa",   "Cm",   "Li",   "Em",   "Cd",   "Va",   "Ft",
+       "Tn",   "Er",   "Ev",   "Sy",   "Sh",   "In",   "Ss",   "Ox",
+       "An",   "Mt",   "St",   "Bx",   "At",   "Nx",   "Fx",   "Lk",
+       "Ms",   "Bsx",  "Dx",   "Rs",   "Vt",   "Lb",   "Nm",   "Nd"
 };
 
-static void             buildnames(const struct mansearch *,
-                               struct manpage *, sqlite3 *,
-                               sqlite3_stmt *, uint64_t,
-                               const char *, int form);
-static char            *buildoutput(sqlite3 *, sqlite3_stmt *,
-                                uint64_t, uint64_t);
+
+static struct ohash    *manmerge(struct expr *, struct ohash *);
+static struct ohash    *manmerge_term(struct expr *, struct ohash *);
+static struct ohash    *manmerge_or(struct expr *, struct ohash *);
+static struct ohash    *manmerge_and(struct expr *, struct ohash *);
+static char            *buildnames(const struct dbm_page *);
+static char            *buildoutput(size_t, int32_t);
+static size_t           lstlen(const char *);
+static void             lstcat(char *, size_t *, const char *);
+static int              lstmatch(const char *, const char *);
 static struct expr     *exprcomp(const struct mansearch *,
-                               int, char *[]);
+                               int, char *[], int *);
+static struct expr     *expr_and(const struct mansearch *,
+                               int, char *[], int *);
+static struct expr     *exprterm(const struct mansearch *,
+                               int, char *[], int *);
 static void             exprfree(struct expr *);
-static struct expr     *exprterm(const struct mansearch *, char *, int);
 static int              manpage_compare(const void *, const void *);
-static void             sql_append(char **sql, size_t *sz,
-                               const char *newstr, int count);
-static void             sql_match(sqlite3_context *context,
-                               int argc, sqlite3_value **argv);
-static void             sql_regexp(sqlite3_context *context,
-                               int argc, sqlite3_value **argv);
-static char            *sql_statement(const struct expr *);
 
 
-int
-mansearch_setup(int start)
-{
-       static void     *pagecache;
-       int              c;
-
-#define        PC_PAGESIZE     1280
-#define        PC_NUMPAGES     256
-
-       if (start) {
-               if (NULL != pagecache) {
-                       warnx("pagecache already enabled");
-                       return (int)MANDOCLEVEL_BADARG;
-               }
-
-               pagecache = mmap(NULL, PC_PAGESIZE * PC_NUMPAGES,
-                   PROT_READ | PROT_WRITE,
-                   MAP_SHARED | MAP_ANON, -1, 0);
-
-               if (MAP_FAILED == pagecache) {
-                       warn("mmap");
-                       pagecache = NULL;
-                       return (int)MANDOCLEVEL_SYSERR;
-               }
-
-               c = sqlite3_config(SQLITE_CONFIG_PAGECACHE,
-                   pagecache, PC_PAGESIZE, PC_NUMPAGES);
-
-               if (SQLITE_OK == c)
-                       return (int)MANDOCLEVEL_OK;
-
-               warnx("pagecache: %s", sqlite3_errstr(c));
-
-       } else if (NULL == pagecache) {
-               warnx("pagecache missing");
-               return (int)MANDOCLEVEL_BADARG;
-       }
-
-       if (-1 == munmap(pagecache, PC_PAGESIZE * PC_NUMPAGES)) {
-               warn("munmap");
-               pagecache = NULL;
-               return (int)MANDOCLEVEL_SYSERR;
-       }
-
-       pagecache = NULL;
-       return (int)MANDOCLEVEL_OK;
-}
-
 int
 mansearch(const struct mansearch *search,
                const struct manpaths *paths,
                int argc, char *argv[],
                struct manpage **res, size_t *sz)
 {
-       int64_t          pageid;
-       uint64_t         outbit, iterbit;
        char             buf[PATH_MAX];
-       char            *sql;
+       struct dbm_res  *rp;
+       struct expr     *e;
+       struct dbm_page *page;
        struct manpage  *mpage;
-       struct expr     *e, *ep;
-       sqlite3         *db;
-       sqlite3_stmt    *s, *s2;
-       struct match    *mp;
-       struct ohash     htab;
-       unsigned int     idx;
-       size_t           i, j, cur, maxres;
-       int              c, chdir_status, getcwd_status, indexbit;
-
-       if (argc == 0 || (e = exprcomp(search, argc, argv)) == NULL) {
+       struct ohash    *htab;
+       size_t           cur, i, maxres, outkey;
+       unsigned int     slot;
+       int              argi, chdir_status, getcwd_status, im;
+
+       argi = 0;
+       if ((e = exprcomp(search, argc, argv, &argi)) == NULL) {
                *sz = 0;
                return 0;
        }
@@ -174,19 +103,14 @@ mansearch(const struct mansearch *search,
        cur = maxres = 0;
        *res = NULL;
 
-       if (NULL != search->outkey) {
-               outbit = TYPE_Nd;
-               for (indexbit = 0, iterbit = 1;
-                    indexbit < mansearch_keymax;
-                    indexbit++, iterbit <<= 1) {
+       outkey = KEY_Nd;
+       if (search->outkey != NULL)
+               for (im = 0; im < KEY_MAX; im++)
                        if (0 == strcasecmp(search->outkey,
-                           mansearch_keynames[indexbit])) {
-                               outbit = iterbit;
+                           mansearch_keynames[im])) {
+                               outkey = im;
                                break;
                        }
-               }
-       } else
-               outbit = 0;
 
        /*
         * Remember the original working directory, if possible.
@@ -202,8 +126,6 @@ mansearch(const struct mansearch *search,
        } else
                getcwd_status = 1;
 
-       sql = sql_statement(e);
-
        /*
         * Loop over the directories (containing databases) for us to
         * search.
@@ -229,123 +151,48 @@ mansearch(const struct mansearch *search,
                }
                chdir_status = 1;
 
-               c = sqlite3_open_v2(MANDOC_DB, &db,
-                   SQLITE_OPEN_READONLY, NULL);
-
-               if (SQLITE_OK != c) {
+               if (dbm_open(MANDOC_DB) == -1) {
                        warn("%s/%s", paths->paths[i], MANDOC_DB);
-                       sqlite3_close(db);
                        continue;
                }
 
-               /*
-                * Define the SQL functions for substring
-                * and regular expression matching.
-                */
-
-               c = sqlite3_create_function(db, "match", 2,
-                   SQLITE_UTF8 | SQLITE_DETERMINISTIC,
-                   NULL, sql_match, NULL, NULL);
-               assert(SQLITE_OK == c);
-               c = sqlite3_create_function(db, "regexp", 2,
-                   SQLITE_UTF8 | SQLITE_DETERMINISTIC,
-                   NULL, sql_regexp, NULL, NULL);
-               assert(SQLITE_OK == c);
-
-               j = 1;
-               c = sqlite3_prepare_v2(db, sql, -1, &s, NULL);
-               if (SQLITE_OK != c)
-                       errx((int)MANDOCLEVEL_SYSERR,
-                           "%s", sqlite3_errmsg(db));
-
-               for (ep = e; NULL != ep; ep = ep->next) {
-                       if (NULL == ep->substr) {
-                               SQL_BIND_BLOB(db, s, j, ep->regexp);
-                       } else
-                               SQL_BIND_TEXT(db, s, j, ep->substr);
-                       if (0 == ((TYPE_Nd | TYPE_Nm) & ep->bits))
-                               SQL_BIND_INT64(db, s, j, ep->bits);
+               if ((htab = manmerge(e, NULL)) == NULL) {
+                       dbm_close();
+                       continue;
                }
 
-               mandoc_ohash_init(&htab, 4, offsetof(struct match, pageid));
-
-               /*
-                * Hash each entry on its [unique] document identifier.
-                * This is a uint64_t.
-                * Instead of using a hash function, simply convert the
-                * uint64_t to a uint32_t, the hash value's type.
-                * This gives good performance and preserves the
-                * distribution of buckets in the table.
-                */
-               while (SQLITE_ROW == (c = sqlite3_step(s))) {
-                       pageid = sqlite3_column_int64(s, 2);
-                       idx = ohash_lookup_memory(&htab,
-                           (char *)&pageid, sizeof(uint64_t),
-                           (uint32_t)pageid);
+               for (rp = ohash_first(htab, &slot); rp != NULL;
+                   rp = ohash_next(htab, &slot)) {
+                       page = dbm_page_get(rp->page);
 
-                       if (NULL != ohash_find(&htab, idx))
+                       if (lstmatch(search->sec, page->sect) == 0 ||
+                           lstmatch(search->arch, page->arch) == 0)
                                continue;
 
-                       mp = mandoc_calloc(1, sizeof(struct match));
-                       mp->pageid = pageid;
-                       mp->form = sqlite3_column_int(s, 1);
-                       mp->bits = sqlite3_column_int64(s, 3);
-                       if (TYPE_Nd == outbit)
-                               mp->desc = mandoc_strdup((const char *)
-                                   sqlite3_column_text(s, 0));
-                       ohash_insert(&htab, idx, mp);
-               }
-
-               if (SQLITE_DONE != c)
-                       warnx("%s", sqlite3_errmsg(db));
-
-               sqlite3_finalize(s);
-
-               c = sqlite3_prepare_v2(db,
-                   "SELECT sec, arch, name, pageid FROM mlinks "
-                   "WHERE pageid=? ORDER BY sec, arch, name",
-                   -1, &s, NULL);
-               if (SQLITE_OK != c)
-                       errx((int)MANDOCLEVEL_SYSERR,
-                           "%s", sqlite3_errmsg(db));
-
-               c = sqlite3_prepare_v2(db,
-                   "SELECT bits, key, pageid FROM keys "
-                   "WHERE pageid=? AND bits & ?",
-                   -1, &s2, NULL);
-               if (SQLITE_OK != c)
-                       errx((int)MANDOCLEVEL_SYSERR,
-                           "%s", sqlite3_errmsg(db));
-
-               for (mp = ohash_first(&htab, &idx);
-                               NULL != mp;
-                               mp = ohash_next(&htab, &idx)) {
                        if (cur + 1 > maxres) {
                                maxres += 1024;
                                *res = mandoc_reallocarray(*res,
-                                   maxres, sizeof(struct manpage));
+                                   maxres, sizeof(**res));
                        }
                        mpage = *res + cur;
+                       mandoc_asprintf(&mpage->file, "%s/%s",
+                           paths->paths[i], page->file + 1);
+                       mpage->names = buildnames(page);
+                       mpage->output = (int)outkey == KEY_Nd ?
+                           mandoc_strdup(page->desc) :
+                           buildoutput(outkey, page->addr);
                        mpage->ipath = i;
-                       mpage->bits = mp->bits;
-                       mpage->sec = 10;
-                       mpage->form = mp->form;
-                       buildnames(search, mpage, db, s, mp->pageid,
-                           paths->paths[i], mp->form);
-                       if (mpage->names != NULL) {
-                               mpage->output = TYPE_Nd & outbit ?
-                                   mp->desc : outbit ?
-                                   buildoutput(db, s2, mp->pageid, outbit) :
-                                   NULL;
-                               cur++;
-                       }
-                       free(mp);
+                       mpage->bits = rp->bits;
+                       mpage->sec = *page->sect - '0';
+                       if (mpage->sec < 0 || mpage->sec > 9)
+                               mpage->sec = 10;
+                       mpage->form = *page->file;
+                       free(rp);
+                       cur++;
                }
-
-               sqlite3_finalize(s);
-               sqlite3_finalize(s2);
-               sqlite3_close(db);
-               ohash_delete(&htab);
+               ohash_delete(htab);
+               free(htab);
+               dbm_close();
 
                /*
                 * In man(1) mode, prefer matches in earlier trees
@@ -359,11 +206,169 @@ mansearch(const struct mansearch *search,
        if (chdir_status && getcwd_status && chdir(buf) == -1)
                warn("%s", buf);
        exprfree(e);
-       free(sql);
        *sz = cur;
        return 1;
 }
 
+/*
+ * Merge the results for the expression tree rooted at e
+ * into the the result list htab.
+ */
+static struct ohash *
+manmerge(struct expr *e, struct ohash *htab)
+{
+       switch (e->type) {
+       case EXPR_TERM:
+               return manmerge_term(e, htab);
+       case EXPR_OR:
+               return manmerge_or(e->child, htab);
+       case EXPR_AND:
+               return manmerge_and(e->child, htab);
+       default:
+               abort();
+       }
+}
+
+static struct ohash *
+manmerge_term(struct expr *e, struct ohash *htab)
+{
+       struct dbm_res   res, *rp;
+       uint64_t         ib;
+       unsigned int     slot;
+       int              im;
+
+       if (htab == NULL) {
+               htab = mandoc_malloc(sizeof(*htab));
+               mandoc_ohash_init(htab, 4, offsetof(struct dbm_res, page));
+       }
+
+       for (im = 0, ib = 1; im < KEY_MAX; im++, ib <<= 1) {
+               if ((e->bits & ib) == 0)
+                       continue;
+
+               switch (ib) {
+               case TYPE_arch:
+                       dbm_page_byarch(&e->match);
+                       break;
+               case TYPE_sec:
+                       dbm_page_bysect(&e->match);
+                       break;
+               case TYPE_Nm:
+                       dbm_page_byname(&e->match);
+                       break;
+               case TYPE_Nd:
+                       dbm_page_bydesc(&e->match);
+                       break;
+               default:
+                       dbm_page_bymacro(im - 2, &e->match);
+                       break;
+               }
+
+               /*
+                * When hashing for deduplication, use the unique
+                * page ID itself instead of a hash function;
+                * that is quite efficient.
+                */
+
+               for (;;) {
+                       res = dbm_page_next();
+                       if (res.page == -1)
+                               break;
+                       slot = ohash_lookup_memory(htab,
+                           (char *)&res, sizeof(res.page), res.page);
+                       if ((rp = ohash_find(htab, slot)) != NULL) {
+                               rp->bits |= res.bits;
+                               continue;
+                       }
+                       rp = mandoc_malloc(sizeof(*rp));
+                       *rp = res;
+                       ohash_insert(htab, slot, rp);
+               }
+       }
+       return htab;
+}
+
+static struct ohash *
+manmerge_or(struct expr *e, struct ohash *htab)
+{
+       while (e != NULL) {
+               htab = manmerge(e, htab);
+               e = e->next;
+       }
+       return htab;
+}
+
+static struct ohash *
+manmerge_and(struct expr *e, struct ohash *htab)
+{
+       struct ohash    *hand, *h1, *h2;
+       struct dbm_res  *res;
+       unsigned int     slot1, slot2;
+
+       /* Evaluate the first term of the AND clause. */
+
+       hand = manmerge(e, NULL);
+
+       while ((e = e->next) != NULL) {
+
+               /* Evaluate the next term and prepare for ANDing. */
+
+               h2 = manmerge(e, NULL);
+               if (ohash_entries(h2) < ohash_entries(hand)) {
+                       h1 = h2;
+                       h2 = hand;
+               } else
+                       h1 = hand;
+               hand = mandoc_malloc(sizeof(*hand));
+               mandoc_ohash_init(hand, 4, offsetof(struct dbm_res, page));
+
+               /* Keep all pages that are in both result sets. */
+
+               for (res = ohash_first(h1, &slot1); res != NULL;
+                   res = ohash_next(h1, &slot1)) {
+                       if (ohash_find(h2, ohash_lookup_memory(h2,
+                           (char *)res, sizeof(res->page),
+                           res->page)) == NULL)
+                               free(res);
+                       else
+                               ohash_insert(hand, ohash_lookup_memory(hand,
+                                   (char *)res, sizeof(res->page),
+                                   res->page), res);
+               }
+
+               /* Discard the merged results. */
+
+               for (res = ohash_first(h2, &slot2); res != NULL;
+                   res = ohash_next(h2, &slot2))
+                       free(res);
+               ohash_delete(h2);
+               free(h2);
+               ohash_delete(h1);
+               free(h1);
+       }
+
+       /* Merge the result of the AND into htab. */
+
+       if (htab == NULL)
+               return hand;
+
+       for (res = ohash_first(hand, &slot1); res != NULL;
+           res = ohash_next(hand, &slot1)) {
+               slot2 = ohash_lookup_memory(htab,
+                   (char *)res, sizeof(res->page), res->page);
+               if (ohash_find(htab, slot2) == NULL)
+                       ohash_insert(htab, slot2, res);
+               else
+                       free(res);
+       }
+
+       /* Discard the merged result. */
+
+       ohash_delete(hand);
+       free(hand);
+       return htab;
+}
+
 void
 mansearch_free(struct manpage *res, size_t sz)
 {
@@ -390,260 +395,114 @@ manpage_compare(const void *vp1, const void *vp2)
            strcasecmp(mp1->names, mp2->names);
 }
 
-static void
-buildnames(const struct mansearch *search, struct manpage *mpage,
-               sqlite3 *db, sqlite3_stmt *s,
-               uint64_t pageid, const char *path, int form)
-{
-       glob_t           globinfo;
-       char            *firstname, *newnames, *prevsec, *prevarch;
-       const char      *oldnames, *sep1, *name, *sec, *sep2, *arch, *fsec;
-       size_t           i;
-       int              c, globres;
-
-       mpage->file = NULL;
-       mpage->names = NULL;
-       firstname = prevsec = prevarch = NULL;
-       i = 1;
-       SQL_BIND_INT64(db, s, i, pageid);
-       while (SQLITE_ROW == (c = sqlite3_step(s))) {
-
-               /* Decide whether we already have some names. */
-
-               if (NULL == mpage->names) {
-                       oldnames = "";
-                       sep1 = "";
-               } else {
-                       oldnames = mpage->names;
-                       sep1 = ", ";
-               }
-
-               /* Fetch the next name, rejecting sec/arch mismatches. */
-
-               sec = (const char *)sqlite3_column_text(s, 0);
-               if (search->sec != NULL && strcasecmp(sec, search->sec))
-                       continue;
-               arch = (const char *)sqlite3_column_text(s, 1);
-               if (search->arch != NULL && *arch != '\0' &&
-                   strcasecmp(arch, search->arch))
-                       continue;
-               name = (const char *)sqlite3_column_text(s, 2);
-
-               /* Remember the first section found. */
-
-               if (9 < mpage->sec && '1' <= *sec && '9' >= *sec)
-                       mpage->sec = (*sec - '1') + 1;
-
-               /* If the section changed, append the old one. */
-
-               if (NULL != prevsec &&
-                   (strcmp(sec, prevsec) ||
-                    strcmp(arch, prevarch))) {
-                       sep2 = '\0' == *prevarch ? "" : "/";
-                       mandoc_asprintf(&newnames, "%s(%s%s%s)",
-                           oldnames, prevsec, sep2, prevarch);
-                       free(mpage->names);
-                       oldnames = mpage->names = newnames;
-                       free(prevsec);
-                       free(prevarch);
-                       prevsec = prevarch = NULL;
-               }
-
-               /* Save the new section, to append it later. */
-
-               if (NULL == prevsec) {
-                       prevsec = mandoc_strdup(sec);
-                       prevarch = mandoc_strdup(arch);
-               }
-
-               /* Append the new name. */
-
-               mandoc_asprintf(&newnames, "%s%s%s",
-                   oldnames, sep1, name);
-               free(mpage->names);
-               mpage->names = newnames;
-
-               /* Also save the first file name encountered. */
-
-               if (mpage->file != NULL)
-                       continue;
-
-               if (form & FORM_SRC) {
-                       sep1 = "man";
-                       fsec = sec;
-               } else {
-                       sep1 = "cat";
-                       fsec = "0";
-               }
-               sep2 = *arch == '\0' ? "" : "/";
-               mandoc_asprintf(&mpage->file, "%s/%s%s%s%s/%s.%s",
-                   path, sep1, sec, sep2, arch, name, fsec);
-               if (access(mpage->file, R_OK) != -1)
-                       continue;
-
-               /* Handle unusual file name extensions. */
-
-               if (firstname == NULL)
-                       firstname = mpage->file;
-               else
-                       free(mpage->file);
-               mandoc_asprintf(&mpage->file, "%s/%s%s%s%s/%s.*",
-                   path, sep1, sec, sep2, arch, name);
-               globres = glob(mpage->file, 0, NULL, &globinfo);
-               free(mpage->file);
-               mpage->file = globres ? NULL :
-                   mandoc_strdup(*globinfo.gl_pathv);
-               globfree(&globinfo);
-       }
-       if (c != SQLITE_DONE)
-               warnx("%s", sqlite3_errmsg(db));
-       sqlite3_reset(s);
-
-       /* If none of the files is usable, use the first name. */
-
-       if (mpage->file == NULL)
-               mpage->file = firstname;
-       else if (mpage->file != firstname)
-               free(firstname);
-
-       /* Append one final section to the names. */
-
-       if (prevsec != NULL) {
-               sep2 = *prevarch == '\0' ? "" : "/";
-               mandoc_asprintf(&newnames, "%s(%s%s%s)",
-                   mpage->names, prevsec, sep2, prevarch);
-               free(mpage->names);
-               mpage->names = newnames;
-               free(prevsec);
-               free(prevarch);
-       }
-}
-
 static char *
-buildoutput(sqlite3 *db, sqlite3_stmt *s, uint64_t pageid, uint64_t outbit)
+buildnames(const struct dbm_page *page)
 {
-       char            *output, *newoutput;
-       const char      *oldoutput, *sep1, *data;
-       size_t           i;
-       int              c;
-
-       output = NULL;
-       i = 1;
-       SQL_BIND_INT64(db, s, i, pageid);
-       SQL_BIND_INT64(db, s, i, outbit);
-       while (SQLITE_ROW == (c = sqlite3_step(s))) {
-               if (NULL == output) {
-                       oldoutput = "";
-                       sep1 = "";
-               } else {
-                       oldoutput = output;
-                       sep1 = " # ";
-               }
-               data = (const char *)sqlite3_column_text(s, 1);
-               mandoc_asprintf(&newoutput, "%s%s%s",
-                   oldoutput, sep1, data);
-               free(output);
-               output = newoutput;
+       char    *buf;
+       size_t   i, sz;
+
+       sz = lstlen(page->name) + 1 + lstlen(page->sect) +
+           (page->arch == NULL ? 0 : 1 + lstlen(page->arch)) + 2;
+       buf = mandoc_malloc(sz);
+       i = 0;
+       lstcat(buf, &i, page->name);
+       buf[i++] = '(';
+       lstcat(buf, &i, page->sect);
+       if (page->arch != NULL) {
+               buf[i++] = '/';
+               lstcat(buf, &i, page->arch);
        }
-       if (SQLITE_DONE != c)
-               warnx("%s", sqlite3_errmsg(db));
-       sqlite3_reset(s);
-       return output;
+       buf[i++] = ')';
+       buf[i++] = '\0';
+       assert(i == sz);
+       return buf;
 }
 
 /*
- * Implement substring match as an application-defined SQL function.
- * Using the SQL LIKE or GLOB operators instead would be a bad idea
- * because that would require escaping metacharacters in the string
- * being searched for.
+ * Count the buffer space needed to print the NUL-terminated
+ * list of NUL-terminated strings, when printing two separator
+ * characters between strings.
  */
-static void
-sql_match(sqlite3_context *context, int argc, sqlite3_value **argv)
+static size_t
+lstlen(const char *cp)
 {
+       size_t   sz;
 
-       assert(2 == argc);
-       sqlite3_result_int(context, NULL != strcasestr(
-           (const char *)sqlite3_value_text(argv[1]),
-           (const char *)sqlite3_value_text(argv[0])));
+       for (sz = 0;; sz++) {
+               if (cp[0] == '\0') {
+                       if (cp[1] == '\0')
+                               break;
+                       sz++;
+               } else if (cp[0] < ' ')
+                       sz--;
+               cp++;
+       }
+       return sz;
 }
 
 /*
- * Implement regular expression match
- * as an application-defined SQL function.
+ * Print the NUL-terminated list of NUL-terminated strings
+ * into the buffer, seperating strings with a comma and a blank.
  */
 static void
-sql_regexp(sqlite3_context *context, int argc, sqlite3_value **argv)
+lstcat(char *buf, size_t *i, const char *cp)
 {
-
-       assert(2 == argc);
-       sqlite3_result_int(context, !regexec(
-           (regex_t *)sqlite3_value_blob(argv[0]),
-           (const char *)sqlite3_value_text(argv[1]),
-           0, NULL, 0));
+       for (;;) {
+               if (cp[0] == '\0') {
+                       if (cp[1] == '\0')
+                               break;
+                       buf[(*i)++] = ',';
+                       buf[(*i)++] = ' ';
+               } else if (cp[0] >= ' ')
+                       buf[(*i)++] = cp[0];
+               cp++;
+       }
 }
 
-static void
-sql_append(char **sql, size_t *sz, const char *newstr, int count)
+/*
+ * Return 1 if the string *want occurs in any of the strings
+ * in the NUL-terminated string list *have, or 0 otherwise.
+ * If either argument is NULL or empty, assume no filtering
+ * is desired and return 1.
+ */
+static int
+lstmatch(const char *want, const char *have)
 {
-       size_t           newsz;
-
-       newsz = 1 < count ? (size_t)count : strlen(newstr);
-       *sql = mandoc_realloc(*sql, *sz + newsz + 1);
-       if (1 < count)
-               memset(*sql + *sz, *newstr, (size_t)count);
-       else
-               memcpy(*sql + *sz, newstr, newsz);
-       *sz += newsz;
-       (*sql)[*sz] = '\0';
+        if (want == NULL || have == NULL || *have == '\0')
+                return 1;
+        while (*have != '\0') {
+                if (strcasestr(have, want) != NULL)
+                        return 1;
+                have = strchr(have, '\0') + 1;
+        }
+        return 0;
 }
 
 /*
- * Prepare the search SQL statement.
+ * Build a list of values taken by the macro im
+ * in the manual page with big-endian address addr.
  */
 static char *
-sql_statement(const struct expr *e)
+buildoutput(size_t im, int32_t addr)
 {
-       char            *sql;
-       size_t           sz;
-       int              needop;
-
-       sql = mandoc_strdup(e->equal ?
-           "SELECT desc, form, pageid, bits "
-               "FROM mpages NATURAL JOIN names WHERE " :
-           "SELECT desc, form, pageid, 0 FROM mpages WHERE ");
-       sz = strlen(sql);
-
-       for (needop = 0; NULL != e; e = e->next) {
-               if (e->and)
-                       sql_append(&sql, &sz, " AND ", 1);
-               else if (needop)
-                       sql_append(&sql, &sz, " OR ", 1);
-               if (e->open)
-                       sql_append(&sql, &sz, "(", e->open);
-               sql_append(&sql, &sz,
-                   TYPE_Nd & e->bits
-                   ? (NULL == e->substr
-                       ? "desc REGEXP ?"
-                       : "desc MATCH ?")
-                   : TYPE_Nm == e->bits
-                   ? (NULL == e->substr
-                       ? "pageid IN (SELECT pageid FROM names "
-                         "WHERE name REGEXP ?)"
-                       : e->equal
-                       ? "name = ? "
-                       : "pageid IN (SELECT pageid FROM names "
-                         "WHERE name MATCH ?)")
-                   : (NULL == e->substr
-                       ? "pageid IN (SELECT pageid FROM keys "
-                         "WHERE key REGEXP ? AND bits & ?)"
-                       : "pageid IN (SELECT pageid FROM keys "
-                         "WHERE key MATCH ? AND bits & ?)"), 1);
-               if (e->close)
-                       sql_append(&sql, &sz, ")", e->close);
-               needop = 1;
-       }
+       const char      *oldoutput, *sep;
+       char            *output, *newoutput, *value;
 
-       return sql;
+       output = NULL;
+       dbm_macro_bypage(im - 2, addr);
+       while ((value = dbm_macro_next()) != NULL) {
+               if (output == NULL) {
+                       oldoutput = "";
+                       sep = "";
+               } else {
+                       oldoutput = output;
+                       sep = " # ";
+               }
+               mandoc_asprintf(&newoutput, "%s%s%s", oldoutput, sep, value);
+               free(output);
+               output = newoutput;
+       }
+       return output;
 }
 
 /*
@@ -652,188 +511,231 @@ sql_statement(const struct expr *e)
  * "(", "foo=bar", etc.).
  */
 static struct expr *
-exprcomp(const struct mansearch *search, int argc, char *argv[])
+exprcomp(const struct mansearch *search, int argc, char *argv[], int *argi)
 {
-       uint64_t         mask;
-       int              i, toopen, logic, igncase, toclose;
-       struct expr     *first, *prev, *cur, *next;
-
-       first = cur = NULL;
-       logic = igncase = toopen = toclose = 0;
-
-       for (i = 0; i < argc; i++) {
-               if (0 == strcmp("(", argv[i])) {
-                       if (igncase)
-                               goto fail;
-                       toopen++;
-                       toclose++;
-                       continue;
-               } else if (0 == strcmp(")", argv[i])) {
-                       if (toopen || logic || igncase || NULL == cur)
-                               goto fail;
-                       cur->close++;
-                       if (0 > --toclose)
-                               goto fail;
-                       continue;
-               } else if (0 == strcmp("-a", argv[i])) {
-                       if (toopen || logic || igncase || NULL == cur)
-                               goto fail;
-                       logic = 1;
+       struct expr     *parent, *child;
+       int              needterm, nested;
+
+       if ((nested = *argi) == argc)
+               return NULL;
+       needterm = 1;
+       parent = child = NULL;
+       while (*argi < argc) {
+               if (strcmp(")", argv[*argi]) == 0) {
+                       if (needterm)
+                               warnx("missing term "
+                                   "before closing parenthesis");
+                       needterm = 0;
+                       if (nested)
+                               break;
+                       warnx("ignoring unmatched right parenthesis");
+                       ++*argi;
                        continue;
-               } else if (0 == strcmp("-o", argv[i])) {
-                       if (toopen || logic || igncase || NULL == cur)
-                               goto fail;
-                       logic = 2;
+               }
+               if (strcmp("-o", argv[*argi]) == 0) {
+                       if (needterm) {
+                               if (*argi > 0)
+                                       warnx("ignoring -o after %s",
+                                           argv[*argi - 1]);
+                               else
+                                       warnx("ignoring initial -o");
+                       }
+                       needterm = 1;
+                       ++*argi;
                        continue;
-               } else if (0 == strcmp("-i", argv[i])) {
-                       if (igncase)
-                               goto fail;
-                       igncase = 1;
+               }
+               needterm = 0;
+               if (child == NULL) {
+                       child = expr_and(search, argc, argv, argi);
                        continue;
                }
-               next = exprterm(search, argv[i], !igncase);
-               if (NULL == next)
-                       goto fail;
-               if (NULL == first)
-                       first = next;
-               else
-                       cur->next = next;
-               prev = cur = next;
-
-               /*
-                * Searching for descriptions must be split out
-                * because they are stored in the mpages table,
-                * not in the keys table.
-                */
+               if (parent == NULL) {
+                       parent = mandoc_calloc(1, sizeof(*parent));
+                       parent->type = EXPR_OR;
+                       parent->next = NULL;
+                       parent->child = child;
+               }
+               child->next = expr_and(search, argc, argv, argi);
+               child = child->next;
+       }
+       if (needterm && *argi)
+               warnx("ignoring trailing %s", argv[*argi - 1]);
+       return parent == NULL ? child : parent;
+}
 
-               for (mask = TYPE_Nm; mask <= TYPE_Nd; mask <<= 1) {
-                       if (mask & cur->bits && ~mask & cur->bits) {
-                               next = mandoc_calloc(1,
-                                   sizeof(struct expr));
-                               memcpy(next, cur, sizeof(struct expr));
-                               prev->open = 1;
-                               cur->bits = mask;
-                               cur->next = next;
-                               cur = next;
-                               cur->bits &= ~mask;
+static struct expr *
+expr_and(const struct mansearch *search, int argc, char *argv[], int *argi)
+{
+       struct expr     *parent, *child;
+       int              needterm;
+
+       needterm = 1;
+       parent = child = NULL;
+       while (*argi < argc) {
+               if (strcmp(")", argv[*argi]) == 0) {
+                       if (needterm)
+                               warnx("missing term "
+                                   "before closing parenthesis");
+                       needterm = 0;
+                       break;
+               }
+               if (strcmp("-o", argv[*argi]) == 0)
+                       break;
+               if (strcmp("-a", argv[*argi]) == 0) {
+                       if (needterm) {
+                               if (*argi > 0)
+                                       warnx("ignoring -a after %s",
+                                           argv[*argi - 1]);
+                               else
+                                       warnx("ignoring initial -a");
                        }
+                       needterm = 1;
+                       ++*argi;
+                       continue;
+               }
+               if (needterm == 0)
+                       break;
+               if (child == NULL) {
+                       child = exprterm(search, argc, argv, argi);
+                       if (child != NULL)
+                               needterm = 0;
+                       continue;
+               }
+               needterm = 0;
+               if (parent == NULL) {
+                       parent = mandoc_calloc(1, sizeof(*parent));
+                       parent->type = EXPR_AND;
+                       parent->next = NULL;
+                       parent->child = child;
+               }
+               child->next = exprterm(search, argc, argv, argi);
+               if (child->next != NULL) {
+                       child = child->next;
+                       needterm = 0;
                }
-               prev->and = (1 == logic);
-               prev->open += toopen;
-               if (cur != prev)
-                       cur->close = 1;
-
-               toopen = logic = igncase = 0;
        }
-       if ( ! (toopen || logic || igncase || toclose))
-               return first;
-
-fail:
-       if (NULL != first)
-               exprfree(first);
-       return NULL;
+       if (needterm && *argi)
+               warnx("ignoring trailing %s", argv[*argi - 1]);
+       return parent == NULL ? child : parent;
 }
 
 static struct expr *
-exprterm(const struct mansearch *search, char *buf, int cs)
+exprterm(const struct mansearch *search, int argc, char *argv[], int *argi)
 {
        char             errbuf[BUFSIZ];
        struct expr     *e;
        char            *key, *val;
        uint64_t         iterbit;
-       int              i, irc;
-
-       if ('\0' == *buf)
-               return NULL;
+       int              cs, i, irc;
+
+       if (strcmp("(", argv[*argi]) == 0) {
+               ++*argi;
+               e = exprcomp(search, argc, argv, argi);
+               if (*argi < argc) {
+                       assert(strcmp(")", argv[*argi]) == 0);
+                       ++*argi;
+               } else
+                       warnx("unclosed parenthesis");
+               return e;
+       }
 
-       e = mandoc_calloc(1, sizeof(struct expr));
+       e = mandoc_calloc(1, sizeof(*e));
+       e->type = EXPR_TERM;
+       e->bits = 0;
+       e->next = NULL;
+       e->child = NULL;
 
        if (search->argmode == ARG_NAME) {
                e->bits = TYPE_Nm;
-               e->substr = buf;
-               e->equal = 1;
+               e->match.type = DBM_EXACT;
+               e->match.str = argv[(*argi)++];
                return e;
        }
 
        /*
         * Separate macro keys from search string.
-        * If needed, request regular expression handling
-        * by setting e->substr to NULL.
+        * If needed, request regular expression handling.
         */
 
        if (search->argmode == ARG_WORD) {
                e->bits = TYPE_Nm;
-               e->substr = NULL;
-               mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", buf);
+               e->match.type = DBM_REGEX;
+               mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", argv[*argi]);
                cs = 0;
-       } else if ((val = strpbrk(buf, "=~")) == NULL) {
+       } else if ((val = strpbrk(argv[*argi], "=~")) == NULL) {
                e->bits = TYPE_Nm | TYPE_Nd;
-               e->substr = buf;
+               e->match.type = DBM_SUB;
+               e->match.str = argv[*argi];
        } else {
-               if (val == buf)
+               if (val == argv[*argi])
                        e->bits = TYPE_Nm | TYPE_Nd;
-               if ('=' == *val)
-                       e->substr = val + 1;
+               if (*val == '=') {
+                       e->match.type = DBM_SUB;
+                       e->match.str = val + 1;
+               } else
+                       e->match.type = DBM_REGEX;
                *val++ = '\0';
-               if (NULL != strstr(buf, "arch"))
+               if (strstr(argv[*argi], "arch") != NULL)
                        cs = 0;
        }
 
        /* Compile regular expressions. */
 
-       if (NULL == e->substr) {
-               irc = regcomp(&e->regexp, val,
+       if (e->match.type == DBM_REGEX) {
+               e->match.re = mandoc_malloc(sizeof(*e->match.re));
+               irc = regcomp(e->match.re, val,
                    REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE));
+               if (irc) {
+                       regerror(irc, e->match.re, errbuf, sizeof(errbuf));
+                       warnx("regcomp /%s/: %s", val, errbuf);
+               }
                if (search->argmode == ARG_WORD)
                        free(val);
                if (irc) {
-                       regerror(irc, &e->regexp, errbuf, sizeof(errbuf));
-                       warnx("regcomp: %s", errbuf);
+                       free(e->match.re);
                        free(e);
+                       ++*argi;
                        return NULL;
                }
        }
 
-       if (e->bits)
+       if (e->bits) {
+               ++*argi;
                return e;
+       }
 
        /*
         * Parse out all possible fields.
         * If the field doesn't resolve, bail.
         */
 
-       while (NULL != (key = strsep(&buf, ","))) {
+       while (NULL != (key = strsep(&argv[*argi], ","))) {
                if ('\0' == *key)
                        continue;
-               for (i = 0, iterbit = 1;
-                    i < mansearch_keymax;
-                    i++, iterbit <<= 1) {
-                       if (0 == strcasecmp(key,
-                           mansearch_keynames[i])) {
+               for (i = 0, iterbit = 1; i < KEY_MAX; i++, iterbit <<= 1) {
+                       if (0 == strcasecmp(key, mansearch_keynames[i])) {
                                e->bits |= iterbit;
                                break;
                        }
                }
-               if (i == mansearch_keymax) {
-                       if (strcasecmp(key, "any")) {
-                               free(e);
-                               return NULL;
-                       }
+               if (i == KEY_MAX) {
+                       if (strcasecmp(key, "any"))
+                               warnx("treating unknown key "
+                                   "\"%s\" as \"any\"", key);
                        e->bits |= ~0ULL;
                }
        }
 
+       ++*argi;
        return e;
 }
 
 static void
-exprfree(struct expr *p)
+exprfree(struct expr *e)
 {
-       struct expr     *pp;
-
-       while (NULL != p) {
-               pp = p->next;
-               free(p);
-               p = pp;
-       }
+       if (e->next != NULL)
+               exprfree(e->next);
+       if (e->child != NULL)
+               exprfree(e->child);
+       free(e);
 }
index 43d0fe6..c278052 100644 (file)
@@ -1,7 +1,7 @@
-/*     $OpenBSD: mansearch.h,v 1.20 2015/11/07 13:57:55 schwarze Exp $ */
+/*     $OpenBSD: mansearch.h,v 1.21 2016/08/01 10:32:39 schwarze Exp $ */
 /*
  * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
- * Copyright (c) 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2013, 2014, 2016 Ingo Schwarze <schwarze@openbsd.org>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
  */
 
 #define        MANDOC_DB        "mandoc.db"
+#define        MANDOCDB_MAGIC   0x3a7d0cdb
+#define        MANDOCDB_VERSION 1
+
+#define        MACRO_MAX        36
+#define        KEY_Nd           39
+#define        KEY_MAX          40
 
 #define        TYPE_arch        0x0000000000000001ULL
 #define        TYPE_sec         0x0000000000000002ULL
 #define        NAME_FILE        0x0000004000000010ULL
 #define        NAME_MASK        0x000000000000001fULL
 
-#define        FORM_CAT         0  /* manual page is preformatted */
-#define        FORM_SRC         1  /* format is mdoc(7) or man(7) */
-#define        FORM_NONE        4  /* format is unknown */
+enum   form {
+       FORM_SRC = 1,   /* Format is mdoc(7) or man(7). */
+       FORM_CAT,       /* Manual page is preformatted. */
+       FORM_NONE       /* Format is unknown. */
+};
 
 enum   argmode {
        ARG_FILE = 0,
@@ -84,7 +92,7 @@ struct        manpage {
        size_t           ipath; /* number of the manpath */
        uint64_t         bits; /* name type mask */
        int              sec; /* section number, 10 means invalid */
-       int              form; /* 0 == catpage */
+       enum form        form;
 };
 
 struct mansearch {
@@ -98,7 +106,6 @@ struct       mansearch {
 
 struct manpaths;
 
-int    mansearch_setup(int);
 int    mansearch(const struct mansearch *cfg, /* options */
                const struct manpaths *paths, /* manpaths */
                int argc, /* size of argv */
diff --git a/usr.bin/mandoc/mansearch_const.c b/usr.bin/mandoc/mansearch_const.c
deleted file mode 100644 (file)
index 80bd759..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/*     $OpenBSD: mansearch_const.c,v 1.6 2014/12/01 08:05:02 schwarze Exp $ */
-/*
- * Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
- *
- * 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 <sys/types.h>
-
-#include <stdint.h>
-
-#include "mansearch.h"
-
-const int mansearch_keymax = 40;
-
-const char *const mansearch_keynames[40] = {
-       "arch", "sec",  "Xr",   "Ar",   "Fa",   "Fl",   "Dv",   "Fn",
-       "Ic",   "Pa",   "Cm",   "Li",   "Em",   "Cd",   "Va",   "Ft",
-       "Tn",   "Er",   "Ev",   "Sy",   "Sh",   "In",   "Ss",   "Ox",
-       "An",   "Mt",   "St",   "Bx",   "At",   "Nx",   "Fx",   "Lk",
-       "Ms",   "Bsx",  "Dx",   "Rs",   "Vt",   "Lb",   "Nm",   "Nd"
-};