diff options
Diffstat (limited to 'libdb/db_lookup.c')
-rw-r--r-- | libdb/db_lookup.c | 513 |
1 files changed, 513 insertions, 0 deletions
diff --git a/libdb/db_lookup.c b/libdb/db_lookup.c new file mode 100644 index 0000000..b43269a --- /dev/null +++ b/libdb/db_lookup.c @@ -0,0 +1,513 @@ +/* + * 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 <assert.h> +#include <stdbool.h> +#include <stdio.h> +#include <errno.h> +#include <ctype.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#include "attribute.h" +#include "error.h" +#include "fnmatch.h" +#include "gl_array_list.h" +#include "gl_xlist.h" +#include "regex.h" +#include "xalloc.h" +#include "xvasprintf.h" + +#include "gettext.h" +#define _(String) gettext (String) + +#include "manconfig.h" + +#include "debug.h" +#include "fatal.h" +#include "filenames.h" +#include "glcontainers.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 (const char *filename) +{ + error (0, errno, _("can't lock index cache %s"), filename); +} +#endif /* NDBM || BTREE */ + +/* issue fatal message, then exit */ +_Noreturn void gripe_corrupt_data (MYDBM_FILE dbf) +{ + fatal (0, _("index cache %s corrupt"), dbf->name); +} + +/* deal with situation where we cannot replace a key */ +_Noreturn void gripe_replace_key (MYDBM_FILE dbf, const char *data) +{ + error (0, 0, _("cannot replace key %s"), data); + gripe_corrupt_data (dbf); +} + +static char *copy_if_set (const char *str) +{ + if (STREQ (str, "-")) + return NULL; + else + return xstrdup (str); +} + +const char * ATTRIBUTE_CONST 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; + char *value; + + value = xasprintf ("%s\t%s", page, ext); + assert (value); + memset (&key, 0, sizeof key); + MYDBM_SET (key, value); + return key; +} + +/* 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) +{ + char *low, *p; + + p = low = xmalloc (strlen (name) + 1); + while (*name) + *p++ = CTYPE (tolower, *name++); + *p = *name; + return low; +} + +/* return char ptr array to the data's fields */ +static char **split_data (MYDBM_FILE dbf, 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 (dbf); + } + } + + /* 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 (dbf); + } + + return start; +} + +/* Parse the db-returned data and put it into a mandata format */ +struct mandata *split_content (MYDBM_FILE dbf, char *cont_ptr) +{ + struct mandata *info; + char *start[FIELDS]; + char **data; + + data = split_data (dbf, cont_ptr, start); + + info = XZALLOC (struct mandata); + info->name = copy_if_set (*(data++)); + info->ext = xstrdup (*(data++)); + info->sec = xstrdup (*(data++)); + info->mtime.tv_sec = (time_t) atol (*(data++)); + info->mtime.tv_nsec = atol (*(data++)); + info->id = **(data++); /* single char id */ + info->pointer = xstrdup (*(data++)); + info->filter = xstrdup (*(data++)); + info->comp = xstrdup (*(data++)); + info->whatis = xstrdup (*(data)); + return info; +} + +bool ATTRIBUTE_PURE name_ext_equals (const void *elt1, const void *elt2) +{ + const struct name_ext *ref1 = elt1, *ref2 = elt2; + return STREQ (ref1->name, ref2->name) && STREQ (ref1->ext, ref2->ext); +} + +int ATTRIBUTE_PURE name_ext_compare (const void *elt1, const void *elt2) +{ + const struct name_ext *ref1 = elt1, *ref2 = elt2; + int name_cmp = strcmp (ref1->name, ref2->name); + if (name_cmp) + return name_cmp; + return strcmp (ref1->ext, ref2->ext); +} + +/* Extract all of the names/extensions associated with this key. Each case + * variant of a name will be returned separately. + * + * This returns a newly-allocated list of struct name_ext, which the caller + * is expected to free. + */ +gl_list_t list_extensions (char *data) +{ + gl_list_t list = gl_list_create_empty (GL_ARRAY_LIST, name_ext_equals, + NULL, plain_free, true); + char *name; + + while ((name = strsep (&data, "\t")) != NULL) { + char *ext; + struct name_ext *name_ext; + + ext = strsep (&data, "\t"); + if (!ext) + break; + + name_ext = XMALLOC (struct name_ext); + /* Don't copy these; they will point into the given string. */ + name_ext->name = name; + name_ext->ext = ext; + gl_sortedlist_add (list, name_ext_compare, name_ext); + } + + debug ("found %zu names/extensions\n", gl_list_size (list)); + return list; +} + +/* 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, zero-length list will be returned. + 2) One data item exists. Item is returned as first in list of structures. + 3) Many items exist. They are all returned, in a multiple structure list. + */ +static gl_list_t dblookup (MYDBM_FILE dbf, const char *page, + const char *section, int flags) +{ + gl_list_t infos; + struct mandata *info = NULL; + datum key, cont; + + infos = gl_list_create_empty (GL_ARRAY_LIST, NULL, NULL, + (gl_listelement_dispose_fn) + free_mandata_struct, + true); + + 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 */ + ; + else if (*MYDBM_DPTR (cont) != '\t') { /* Just one entry */ + bool matches = false; + + info = split_content (dbf, MYDBM_DPTR (cont)); + if (!info->name) + info->name = xstrdup (page); + if (!(flags & MATCH_CASE) || STREQ (info->name, page)) { + if (section == NULL) + matches = true; + else if (flags & EXACT) { + if (STREQ (section, info->ext)) + matches = true; + } else { + if (STRNEQ (section, info->ext, + strlen (section))) + matches = true; + } + } + if (matches) + gl_list_add_last (infos, info); + else + free_mandata_struct (info); + } else { /* Multiple entries */ + gl_list_t refs; + struct name_ext *ref; + + /* Extract all of the case-variant-names/extensions + * associated with this key. + */ + + refs = list_extensions (MYDBM_DPTR (cont) + 1); + + /* Make the multi keys and look them up */ + + GL_LIST_FOREACH (refs, ref) { + 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 (ref->name, page)) + continue; + + if (section != NULL) { + if (flags & EXACT) { + if (!STREQ (section, ref->ext)) + continue; + } else { + if (!STRNEQ (section, ref->ext, + strlen (section))) + continue; + } + } + + /* So the key is suitable ... */ + key = make_multi_key (ref->name, ref->ext); + 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 (dbf); + } + MYDBM_FREE_DPTR (key); + + /* Allocate info struct and add it to the list. */ + info = split_content (dbf, MYDBM_DPTR (multi_cont)); + if (!info->name) + info->name = xstrdup (ref->name); + gl_list_add_last (infos, info); + } + + gl_list_free (refs); + } + MYDBM_FREE_DPTR (cont); + + return infos; +} + +gl_list_t dblookup_all (MYDBM_FILE dbf, const char *page, + const char *section, bool 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, bool match_case) +{ + gl_list_t infos = dblookup (dbf, page, section, + EXACT | (match_case ? MATCH_CASE : 0)); + struct mandata *info = NULL; + + if (gl_list_size (infos)) { + /* Return the first item and free the rest of the list. */ + info = (struct mandata *) gl_list_get_at (infos, 0); + gl_list_set_at (infos, 0, NULL); /* steal memory */ + } + gl_list_free (infos); + return info; +} + +gl_list_t dblookup_pattern (MYDBM_FILE dbf, const char *pattern, + const char *section, bool match_case, + bool pattern_regex, bool try_descriptions) +{ + gl_list_t infos; + datum key, cont; + regex_t preg; + + infos = gl_list_create_empty (GL_ARRAY_LIST, NULL, NULL, + (gl_listelement_dispose_fn) + free_mandata_struct, + true); + + 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 = man_btree_nextkeydata (dbf, &key, &cont); + while (!end) { +#endif /* !BTREE */ + struct mandata *info = NULL; + char *tab; + bool got_match; + + if (!MYDBM_DPTR (cont)) + { + debug ("key was %s\n", MYDBM_DPTR (key)); + fatal (0, + _("Database %s corrupted; rebuild with " + "mandb --create"), + dbf->name); + } + + if (*MYDBM_DPTR (key) == '$') + goto nextpage; + +#pragma GCC diagnostic push +#if GNUC_PREREQ(10,0) +# pragma GCC diagnostic ignored "-Wanalyzer-use-after-free" +#endif + if (*MYDBM_DPTR (cont) == '\t') + goto nextpage; +#pragma GCC diagnostic pop + + /* a real page */ + + info = split_content (dbf, MYDBM_DPTR (cont)); + + /* 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; + + gl_list_add_last (infos, info); + info = NULL; /* avoid freeing later */ + +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 = man_btree_nextkeydata (dbf, &key, &cont); +#endif /* !BTREE */ + free_mandata_struct (info); + } + + if (pattern_regex) + regfree (&preg); + + return infos; +} |