From ff2dbb0f77feb7ad872603e25f443eae80e0c642 Mon Sep 17 00:00:00 2001 From: schwarze Date: Mon, 1 Aug 2016 10:32:39 +0000 Subject: [PATCH] Remove the dependency on SQLite without loss of functionality. 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@ --- usr.bin/mandoc/Makefile | 9 +- usr.bin/mandoc/dba.c | 433 +++++++ usr.bin/mandoc/dba.h | 51 + usr.bin/mandoc/dba_array.c | 188 +++ usr.bin/mandoc/dba_array.h | 47 + usr.bin/mandoc/dba_read.c | 74 ++ usr.bin/mandoc/dba_write.c | 117 ++ usr.bin/mandoc/dba_write.h | 30 + usr.bin/mandoc/dbm.c | 452 +++++++ usr.bin/mandoc/dbm.h | 68 ++ usr.bin/mandoc/dbm_map.c | 174 +++ .../mandoc/{mansearch_const.c => dbm_map.h} | 28 +- usr.bin/mandoc/main.c | 12 +- usr.bin/mandoc/mandocdb.c | 507 ++------ usr.bin/mandoc/mansearch.c | 1038 ++++++++--------- usr.bin/mandoc/mansearch.h | 21 +- 16 files changed, 2257 insertions(+), 992 deletions(-) create mode 100644 usr.bin/mandoc/dba.c create mode 100644 usr.bin/mandoc/dba.h create mode 100644 usr.bin/mandoc/dba_array.c create mode 100644 usr.bin/mandoc/dba_array.h create mode 100644 usr.bin/mandoc/dba_read.c create mode 100644 usr.bin/mandoc/dba_write.c create mode 100644 usr.bin/mandoc/dba_write.h create mode 100644 usr.bin/mandoc/dbm.c create mode 100644 usr.bin/mandoc/dbm.h create mode 100644 usr.bin/mandoc/dbm_map.c rename usr.bin/mandoc/{mansearch_const.c => dbm_map.h} (58%) diff --git a/usr.bin/mandoc/Makefile b/usr.bin/mandoc/Makefile index fe1a7cc15ac..234c75400eb 100644 --- a/usr.bin/mandoc/Makefile +++ b/usr.bin/mandoc/Makefile @@ -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 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 index 00000000000..b43cb82bf23 --- /dev/null +++ b/usr.bin/mandoc/dba.c @@ -0,0 +1,433 @@ +/* $OpenBSD: dba.c,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */ +/* + * Copyright (c) 2016 Ingo Schwarze + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Allocation-based version of the mandoc database, for read-write access. + * The interface is defined in "dba.h". + */ +#include +#include +#include +#include +#include +#include + +#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 index 00000000000..fa613f146bd --- /dev/null +++ b/usr.bin/mandoc/dba.h @@ -0,0 +1,51 @@ +/* $OpenBSD: dba.h,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */ +/* + * Copyright (c) 2016 Ingo Schwarze + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * 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 index 00000000000..dd08a3281f7 --- /dev/null +++ b/usr.bin/mandoc/dba_array.c @@ -0,0 +1,188 @@ +/* $OpenBSD: dba_array.c,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */ +/* + * Copyright (c) 2016 Ingo Schwarze + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Allocation-based arrays for the mandoc database, for read-write access. + * The interface is defined in "dba_array.h". + */ +#include +#include +#include +#include + +#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 index 00000000000..167f68f9b96 --- /dev/null +++ b/usr.bin/mandoc/dba_array.h @@ -0,0 +1,47 @@ +/* $OpenBSD: dba_array.h,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */ +/* + * Copyright (c) 2016 Ingo Schwarze + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * 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 index 00000000000..c7128de2849 --- /dev/null +++ b/usr.bin/mandoc/dba_read.c @@ -0,0 +1,74 @@ +/* $OpenBSD: dba_read.c,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */ +/* + * Copyright (c) 2016 Ingo Schwarze + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * 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 +#include +#include +#include +#include + +#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 index 00000000000..ef15dbedf7c --- /dev/null +++ b/usr.bin/mandoc/dba_write.c @@ -0,0 +1,117 @@ +/* $OpenBSD: dba_write.c,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */ +/* + * Copyright (c) 2016 Ingo Schwarze + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Low-level functions for serializing allocation-based data to disk. + * The interface is defined in "dba_write.h". + */ +#include +#include +#include +#include +#include +#include +#include + +#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 index 00000000000..bbbaa5ea787 --- /dev/null +++ b/usr.bin/mandoc/dba_write.h @@ -0,0 +1,30 @@ +/* $OpenBSD: dba_write.h,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */ +/* + * Copyright (c) 2016 Ingo Schwarze + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * 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 index 00000000000..3334a2ce366 --- /dev/null +++ b/usr.bin/mandoc/dbm.c @@ -0,0 +1,452 @@ +/* $OpenBSD: dbm.c,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */ +/* + * Copyright (c) 2016 Ingo Schwarze + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Map-based version of the mandoc database, for read-only access. + * The interface is defined in "dbm.h". + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 ¯o; +} + +/* + * 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 index 00000000000..0f12ee1e2b5 --- /dev/null +++ b/usr.bin/mandoc/dbm.h @@ -0,0 +1,68 @@ +/* $OpenBSD: dbm.h,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */ +/* + * Copyright (c) 2016 Ingo Schwarze + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * 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 index 00000000000..4c81197a91a --- /dev/null +++ b/usr.bin/mandoc/dbm_map.c @@ -0,0 +1,174 @@ +/* $OpenBSD: dbm_map.c,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */ +/* + * Copyright (c) 2016 Ingo Schwarze + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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/mansearch_const.c b/usr.bin/mandoc/dbm_map.h similarity index 58% rename from usr.bin/mandoc/mansearch_const.c rename to usr.bin/mandoc/dbm_map.h index 80bd759d6f2..6a4db57b749 100644 --- a/usr.bin/mandoc/mansearch_const.c +++ b/usr.bin/mandoc/dbm_map.h @@ -1,6 +1,6 @@ -/* $OpenBSD: mansearch_const.c,v 1.6 2014/12/01 08:05:02 schwarze Exp $ */ +/* $OpenBSD: dbm_map.h,v 1.1 2016/08/01 10:32:39 schwarze Exp $ */ /* - * Copyright (c) 2014 Ingo Schwarze + * Copyright (c) 2016 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -13,19 +13,17 @@ * 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. */ -#include - -#include - -#include "mansearch.h" -const int mansearch_keymax = 40; +struct dbm_match; -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" -}; +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 *); diff --git a/usr.bin/mandoc/main.c b/usr.bin/mandoc/main.c index 2bc490207e0..45f5c52d9f7 100644 --- a/usr.bin/mandoc/main.c +++ b/usr.bin/mandoc/main.c @@ -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 * Copyright (c) 2010-2012, 2014-2016 Ingo Schwarze @@ -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", diff --git a/usr.bin/mandoc/mandocdb.c b/usr.bin/mandoc/mandocdb.c index d5e9a10fc74..b208055ff74 100644 --- a/usr.bin/mandoc/mandocdb.c +++ b/usr.bin/mandoc/mandocdb.c @@ -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 * Copyright (c) 2011-2016 Ingo Schwarze @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -33,8 +34,6 @@ #include #include -#include - #include "mandoc_aux.h" #include "mandoc_ohash.h" #include "mandoc.h" @@ -43,29 +42,11 @@ #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 diff --git a/usr.bin/mandoc/mansearch.c b/usr.bin/mandoc/mansearch.c index e40fd1c0fab..6b02ff5b6d6 100644 --- a/usr.bin/mandoc/mansearch.c +++ b/usr.bin/mandoc/mansearch.c @@ -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 - * Copyright (c) 2013, 2014, 2015 Ingo Schwarze + * Copyright (c) 2013, 2014, 2015, 2016 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -33,140 +33,69 @@ #include #include -#include - #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); } diff --git a/usr.bin/mandoc/mansearch.h b/usr.bin/mandoc/mansearch.h index 43d0fe6c058..c2780528a2f 100644 --- a/usr.bin/mandoc/mansearch.h +++ b/usr.bin/mandoc/mansearch.h @@ -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 - * Copyright (c) 2013, 2014 Ingo Schwarze + * Copyright (c) 2013, 2014, 2016 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,6 +17,12 @@ */ #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 @@ -66,9 +72,11 @@ #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 */ -- 2.20.1