summaryrefslogtreecommitdiffstats
path: root/libdb/db_lookup.c
diff options
context:
space:
mode:
Diffstat (limited to 'libdb/db_lookup.c')
-rw-r--r--libdb/db_lookup.c493
1 files changed, 493 insertions, 0 deletions
diff --git a/libdb/db_lookup.c b/libdb/db_lookup.c
new file mode 100644
index 0000000..140a505
--- /dev/null
+++ b/libdb/db_lookup.c
@@ -0,0 +1,493 @@
+/*
+ * db_lookup.c: low level database interface routines for man.
+ *
+ * Copyright (C) 1994, 1995 Graeme W. Wilford. (Wilf.)
+ * Copyright (C) 2001, 2002, 2003, 2006, 2007, 2008, 2009, 2012
+ * Colin Watson.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Mon Aug 8 20:35:30 BST 1994 Wilf. (G.Wilford@ee.surrey.ac.uk)
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "fnmatch.h"
+#include "regex.h"
+#include "xvasprintf.h"
+
+#include "gettext.h"
+#define _(String) gettext (String)
+
+#include "manconfig.h"
+
+#include "error.h"
+#include "lower.h"
+#include "wordfnmatch.h"
+#include "xregcomp.h"
+
+#include "mydbm.h"
+#include "db_storage.h"
+
+/* If using ndbm or BTREE, copy the static storage before doing anything
+ * interesting with it. If using gdbm, firstkey and nextkey need to copy the
+ * storage because our ordered wrappers keep an effectively static copy.
+ */
+datum copy_datum (datum dat)
+{
+ if (MYDBM_DPTR (dat)) {
+ MYDBM_SET_DPTR (dat, memcpy (xmalloc (MYDBM_DSIZE (dat) + 1),
+ MYDBM_DPTR (dat),
+ MYDBM_DSIZE (dat)));
+ MYDBM_DPTR (dat)[MYDBM_DSIZE (dat)] = '\0';
+ }
+ return dat;
+}
+
+/* gdbm does locking itself. */
+#if defined(NDBM) || defined(BTREE)
+void gripe_lock (char *filename)
+{
+ error (0, errno, _("can't lock index cache %s"), filename);
+}
+#endif /* NDBM || BTREE */
+
+/* issue fatal message, then exit */
+void gripe_corrupt_data (void)
+{
+ error (FATAL, 0, _("index cache %s corrupt"), database);
+}
+
+/* deal with situation where we cannot replace a key */
+void gripe_replace_key (const char *data)
+{
+ error (0, 0, _("cannot replace key %s"), data);
+ gripe_corrupt_data ();
+}
+
+static char *copy_if_set (const char *str)
+{
+ if (STREQ (str, "-"))
+ return NULL;
+ else
+ return xstrdup (str);
+}
+
+const char *dash_if_unset (const char *str)
+{
+ if (str)
+ return str;
+ else
+ return "-";
+}
+
+/* Just print out what would be stored in the db */
+void dbprintf (const struct mandata *info)
+{
+ debug ("name: %s\n"
+ "sec. ext: %s\n"
+ "section: %s\n"
+ "comp. ext: %s\n"
+ "id: %c\n"
+ "mtime: %ld.%09ld\n"
+ "pointer: %s\n"
+ "filter: %s\n"
+ "whatis: %s\n\n",
+ dash_if_unset (info->name),
+ info->ext, info->sec, info->comp,
+ info->id, (long) info->mtime.tv_sec, (long) info->mtime.tv_nsec,
+ info->pointer, info->filter, info->whatis);
+}
+
+/* Form a multi-style key from page and extension info. The page should
+ * *not* be name_to_key()'d - that should only happen to the parent.
+ */
+datum make_multi_key (const char *page, const char *ext)
+{
+ datum key;
+
+ memset (&key, 0, sizeof key);
+ MYDBM_SET (key, xasprintf ("%s\t%s", page, ext));
+ return key;
+}
+
+/* Free allocated elements of a mandata structure, but not the structure
+ * itself.
+ */
+void free_mandata_elements (struct mandata *pinfo)
+{
+ if (pinfo->addr)
+ /* TODO: this memory appears to be properly owned by the
+ * caller; why do we free it here?
+ */
+ free (pinfo->addr); /* free the 'content' */
+ free (pinfo->name); /* free the real name */
+}
+
+/* Go through the linked list of structures, free()ing the 'content' and the
+ * structs themselves.
+ */
+void free_mandata_struct (struct mandata *pinfo)
+{
+ while (pinfo) {
+ struct mandata *next;
+
+ next = pinfo->next;
+ free_mandata_elements (pinfo);
+ free (pinfo); /* free the structure */
+ pinfo = next;
+ }
+}
+
+/* Get the key that should be used for a given name. The caller is
+ * responsible for freeing the return value.
+ */
+char *name_to_key (const char *name)
+{
+ return lower (name);
+}
+
+/* return char ptr array to the data's fields */
+static char **split_data (char *content, char *start[])
+{
+ int count;
+
+ /* initialise pointers to first N-1 fields */
+ for (count = 0; count < FIELDS - 1 ; count++) {
+ start[count] = strsep (&content, "\t");
+ if (!start[count]) {
+ error (0, 0,
+ ngettext ("only %d field in content",
+ "only %d fields in content", count),
+ count);
+ gripe_corrupt_data ();
+ }
+ }
+
+ /* initialise pointer to Nth field (whatis) */
+ start[FIELDS - 1] = content;
+ if (!start[FIELDS - 1]) {
+ error (0, 0,
+ ngettext ("only %d field in content",
+ "only %d fields in content", FIELDS - 1),
+ FIELDS - 1);
+ gripe_corrupt_data ();
+ }
+
+ return start;
+}
+
+/* Parse the db-returned data and put it into a mandata format */
+void split_content (char *cont_ptr, struct mandata *pinfo)
+{
+ char *start[FIELDS];
+ char **data;
+
+ data = split_data (cont_ptr, start);
+
+ pinfo->name = copy_if_set (*(data++));
+ pinfo->ext = *(data++);
+ pinfo->sec = *(data++);
+ pinfo->mtime.tv_sec = (time_t) atol (*(data++));
+ pinfo->mtime.tv_nsec = atol (*(data++));
+ pinfo->id = **(data++); /* single char id */
+ pinfo->pointer = *(data++);
+ pinfo->filter = *(data++);
+ pinfo->comp = *(data++);
+ pinfo->whatis = *(data);
+
+ pinfo->addr = cont_ptr;
+ pinfo->next = (struct mandata *) NULL;
+}
+
+/* Extract all of the names/extensions associated with this key. Each case
+ * variant of a name will be returned separately.
+ *
+ * names and ext should be pointers to valid memory which will be filled in
+ * with the address of the allocated arrays of names and extensions. The
+ * caller is expected to free these arrays.
+ */
+int list_extensions (char *data, char ***names, char ***ext)
+{
+ int count = 0;
+ int bound = 4; /* most multi keys will have fewer than this */
+
+ *names = xnmalloc (bound, sizeof **names);
+ *ext = xnmalloc (bound, sizeof **ext);
+ while (((*names)[count] = strsep (&data, "\t")) != NULL) {
+ (*ext)[count] = strsep (&data, "\t");
+ if ((*ext)[count])
+ ++count;
+ else
+ break;
+
+ if (count >= bound) {
+ bound *= 2;
+ *names = xnrealloc (*names, bound, sizeof **names);
+ *ext = xnrealloc (*ext, bound, sizeof **ext);
+ }
+ }
+
+ debug ("found %d names/extensions\n", count);
+ return count;
+}
+
+/* These should be bitwise-ored together. */
+#define ALL 0
+#define EXACT 1
+#define MATCH_CASE 2
+
+/*
+ There are three possibilities on lookup:
+
+ 1) No data exists, lookup will fail, returned structure will be NULL.
+ 2) One data item exists. Item is returned as first in set of structures.
+ 3) Many items exist. They are all returned, in a multiple structure set.
+ */
+static struct mandata *dblookup (MYDBM_FILE dbf, const char *page,
+ const char *section, int flags)
+{
+ struct mandata *info = NULL;
+ datum key, cont;
+
+ memset (&key, 0, sizeof key);
+ memset (&cont, 0, sizeof cont);
+
+ MYDBM_SET (key, name_to_key (page));
+ cont = MYDBM_FETCH (dbf, key);
+ MYDBM_FREE_DPTR (key);
+
+ if (MYDBM_DPTR (cont) == NULL) { /* No entries at all */
+ return info; /* indicate no entries */
+ } else if (*MYDBM_DPTR (cont) != '\t') { /* Just one entry */
+ info = infoalloc ();
+ split_content (MYDBM_DPTR (cont), info);
+ if (!info->name)
+ info->name = xstrdup (page);
+ if (!(flags & MATCH_CASE) || STREQ (info->name, page)) {
+ if (section == NULL)
+ return info;
+ if (flags & EXACT) {
+ if (STREQ (section, info->ext))
+ return info;
+ } else {
+ if (STRNEQ (section, info->ext,
+ strlen (section)))
+ return info;
+ }
+ }
+ free_mandata_struct (info);
+ return NULL;
+ } else { /* multiple entries */
+ char **names, **ext;
+ struct mandata *ret = NULL;
+ int refs, i;
+
+ /* Extract all of the case-variant-names/extensions
+ * associated with this key.
+ */
+
+ refs = list_extensions (MYDBM_DPTR (cont) + 1, &names, &ext);
+
+ /* Make the multi keys and look them up */
+
+ for (i = 0; i < refs; ++i) {
+ datum multi_cont;
+
+ memset (&multi_cont, 0, sizeof multi_cont);
+
+ /* Decide whether this part of a multi key is
+ * suitable.
+ */
+
+ if ((flags & MATCH_CASE) && !STREQ (names[i], page))
+ continue;
+
+ if (section != NULL) {
+ if (flags & EXACT) {
+ if (!STREQ (section, ext[i]))
+ continue;
+ } else {
+ if (!STRNEQ (section, ext[i],
+ strlen (section)))
+ continue;
+ }
+ }
+
+ /* So the key is suitable ... */
+ key = make_multi_key (names[i], ext[i]);
+ debug ("multi key lookup (%s)\n", MYDBM_DPTR (key));
+ multi_cont = MYDBM_FETCH (dbf, key);
+ if (MYDBM_DPTR (multi_cont) == NULL) {
+ error (0, 0, _("bad fetch on multi key %s"),
+ MYDBM_DPTR (key));
+ gripe_corrupt_data ();
+ }
+ MYDBM_FREE_DPTR (key);
+
+ /* allocate info struct, fill it in and
+ point info to the next in the list */
+ if (!ret)
+ ret = info = infoalloc ();
+ else
+ info = info->next = infoalloc ();
+ split_content (MYDBM_DPTR (multi_cont), info);
+ if (!info->name)
+ info->name = xstrdup (names[i]);
+ }
+
+ free (names);
+ free (ext);
+ MYDBM_FREE_DPTR (cont);
+ return ret;
+ }
+}
+
+struct mandata *dblookup_all (MYDBM_FILE dbf, const char *page,
+ const char *section, int match_case)
+{
+ return dblookup (dbf, page, section,
+ ALL | (match_case ? MATCH_CASE : 0));
+}
+
+struct mandata *dblookup_exact (MYDBM_FILE dbf, const char *page,
+ const char *section, int match_case)
+{
+ return dblookup (dbf, page, section,
+ EXACT | (match_case ? MATCH_CASE : 0));
+}
+
+struct mandata *dblookup_pattern (MYDBM_FILE dbf, const char *pattern,
+ const char *section, int match_case,
+ int pattern_regex, int try_descriptions)
+{
+ struct mandata *ret = NULL, *tail = NULL;
+ datum key, cont;
+ regex_t preg;
+
+ if (pattern_regex)
+ xregcomp (&preg, pattern,
+ REG_EXTENDED | REG_NOSUB |
+ (match_case ? 0 : REG_ICASE));
+
+#ifndef BTREE
+ datum nextkey;
+
+ key = MYDBM_FIRSTKEY (dbf);
+ while (MYDBM_DPTR (key)) {
+ cont = MYDBM_FETCH (dbf, key);
+#else /* BTREE */
+ int end;
+
+ end = btree_nextkeydata (dbf, &key, &cont);
+ while (!end) {
+#endif /* !BTREE */
+ struct mandata info;
+ char *tab;
+ int got_match;
+
+ memset (&info, 0, sizeof (info));
+
+ if (!MYDBM_DPTR (cont))
+ {
+ debug ("key was %s\n", MYDBM_DPTR (key));
+ error (FATAL, 0,
+ _("Database %s corrupted; rebuild with "
+ "mandb --create"),
+ database);
+ }
+
+ if (*MYDBM_DPTR (key) == '$')
+ goto nextpage;
+
+ if (*MYDBM_DPTR (cont) == '\t')
+ goto nextpage;
+
+ /* a real page */
+
+ split_content (MYDBM_DPTR (cont), &info);
+
+ /* If there's a section given, does it match either the
+ * section or extension of this page?
+ */
+ if (section &&
+ (!STREQ (section, info.sec) && !STREQ (section, info.ext)))
+ goto nextpage;
+
+ tab = strrchr (MYDBM_DPTR (key), '\t');
+ if (tab)
+ *tab = '\0';
+
+ if (!info.name)
+ info.name = xstrdup (MYDBM_DPTR (key));
+
+ if (pattern_regex)
+ got_match = (regexec (&preg, info.name,
+ 0, NULL, 0) == 0);
+ else
+ got_match = fnmatch (pattern, info.name,
+ match_case ? 0
+ : FNM_CASEFOLD) == 0;
+ if (try_descriptions && !got_match && info.whatis) {
+ if (pattern_regex)
+ got_match = (regexec (&preg, info.whatis,
+ 0, NULL, 0) == 0);
+ else
+ got_match = word_fnmatch (pattern,
+ info.whatis);
+ }
+ if (!got_match)
+ goto nextpage_tab;
+
+ if (!ret)
+ ret = tail = infoalloc ();
+ else
+ tail = tail->next = infoalloc ();
+ memcpy (tail, &info, sizeof (info));
+ info.name = NULL; /* steal memory */
+ MYDBM_SET_DPTR (cont, NULL); /* == info.addr */
+
+nextpage_tab:
+ if (tab)
+ *tab = '\t';
+nextpage:
+#ifndef BTREE
+ nextkey = MYDBM_NEXTKEY (dbf, key);
+ MYDBM_FREE_DPTR (cont);
+ MYDBM_FREE_DPTR (key);
+ key = nextkey;
+#else /* BTREE */
+ MYDBM_FREE_DPTR (cont);
+ MYDBM_FREE_DPTR (key);
+ end = btree_nextkeydata (dbf, &key, &cont);
+#endif /* !BTREE */
+ info.addr = NULL;
+ free_mandata_elements (&info);
+ }
+
+ if (pattern_regex)
+ regfree (&preg);
+
+ return ret;
+}