summaryrefslogtreecommitdiff
path: root/Build/source/texk/kpathsea/win32hash.c
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/texk/kpathsea/win32hash.c')
-rw-r--r--Build/source/texk/kpathsea/win32hash.c760
1 files changed, 760 insertions, 0 deletions
diff --git a/Build/source/texk/kpathsea/win32hash.c b/Build/source/texk/kpathsea/win32hash.c
new file mode 100644
index 00000000000..1c1252b3797
--- /dev/null
+++ b/Build/source/texk/kpathsea/win32hash.c
@@ -0,0 +1,760 @@
+/* hash.c: hash table operations.
+
+Copyright (C) 1994, 95, 96, 97, 98, 99, 2000 Karl Berry & Olaf Weber.
+
+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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+#include <kpathsea/config.h>
+#include <kpathsea/c-ctype.h>
+
+#include <kpathsea/hash.h>
+#include <kpathsea/str-list.h>
+
+#ifdef HAVE_GETURL
+#include <kpathsea/remote.h>
+#endif
+
+/* The hash function. We go for simplicity here. */
+
+/* All our hash tables are related to filenames. */
+#ifdef MONOCASE_FILENAMES
+#if defined(WIN32) && !defined(__i386_pc_gnu__)
+/* This is way faster under Win32. */
+#define TRANSFORM(x) ((unsigned)CharLower((LPTSTR)(BYTE)(x)))
+#else
+#define TRANSFORM(x) (tolower(x))
+#endif
+#else
+#define TRANSFORM(x) (x)
+#endif
+
+#define MAX_HASH_TABLES 64
+
+#define HASH_NULL(ptr) (ptr == hash_memory)
+#define HASH_PTR(ptr) ((char *)hash_memory + (unsigned long)(ptr))
+#define HASH_REL(rel) ((unsigned long)((char *)(rel) - (char *)hash_memory))
+#define HASH_BUCKETS(table, n) (*((hash_element_type **)((char *)hash_memory + (unsigned long)table->buckets + (n) * sizeof(hash_element_type *))))
+/* #define HASH_BUCKETS(table, n) *((hash_element_type **)(HASH_PTR((unsigned long) ((hash_element_type **)HASH_PTR(table->buckets))[n]))) */
+HANDLE hash_handle = 0;
+void *hash_memory = NULL;
+static unsigned long hash_memory_allocated = 0;
+unsigned long hash_memory_size = 8L*1024L*1024L;
+
+typedef struct _hash_memory_description {
+ unsigned long sig;
+ unsigned long memory_size;
+ unsigned long first_free_address;
+ unsigned long hash_offsets[MAX_HASH_TABLES];
+ unsigned long db_dir_list_len;
+ unsigned long db_dir_list;
+} hash_memory_description;
+
+hash_memory_description *hash_bloc;
+
+/* We need to protect initialisation and insert / lookup */
+static HANDLE hash_mutex;
+static boolean hash_init_done = false;
+
+extern str_list_type db_dir_list;
+
+void hash_restore_str_list(unsigned long ptr, unsigned len, str_list_type *l);
+void hash_save_str_list(str_list_type l);
+
+#if 0
+extern unsigned hash P2H(hash_table_type *table, const_string key);
+#ifdef NORMALIZED
+extern unsigned hash_normalized P2H(hash_table_type *table, const_string key);
+#endif
+#else
+static unsigned
+hash P2C(hash_table_type *, table, const_string, key)
+{
+ unsigned n = 0;
+
+ // fprintf(stderr, "Hashing %s to ", key);
+
+ /* Our keys aren't often anagrams of each other, so no point in
+ weighting the characters. */
+ while (*key != 0)
+ n = (n + n + TRANSFORM (*key++)) % table->size;
+
+ // fprintf(stderr, "%d\n", n);
+
+ return n;
+}
+
+#ifdef NORMALIZED
+/* Identical has function as above, but does not normalize keys. */
+static unsigned
+hash_normalized P2C(hash_table_type *, table, const_string, key)
+{
+ unsigned n = 0;
+
+ // fprintf(stderr, "Hashing (normalized) %s to ", key);
+
+ /* Our keys aren't often anagrams of each other, so no point in
+ weighting the characters. */
+ while (*key != 0)
+ n = (n + n + (*key++)) % table->size;
+
+ // fprintf(stderr, "%d\n", n);
+
+ return n;
+}
+#endif
+#endif
+
+/*
+ This function will be registered by atexit() to be called upon
+ program termination. It will free resources allocated for
+ hash-tables. It has to be registered only once, when the very first
+ hash-table is allocated.
+*/
+void CDECL
+hash_cleanup (void)
+{
+#ifdef HAVE_GETURL
+ cleanup_remote_files();
+#endif
+
+ if (hash_memory != NULL) {
+ if (UnmapViewOfFile(hash_memory) == 0) {
+ /* Report an error */
+ WARNING2("Can't UnMapViewOfFile(%x) (Error %d)!\n", hash_memory, GetLastError());
+ }
+ hash_memory = NULL;
+ }
+
+ if (hash_handle) {
+ if (CloseHandle(hash_handle) == 0) {
+ /* Report an error */
+ WARNING2("Can't CloseHandle(%x) (Error %d)!\n", hash_handle, GetLastError());
+ }
+ hash_handle = NULL;
+ }
+
+ if (hash_mutex) {
+ if (CloseHandle(hash_mutex) == 0) {
+ /* Report an error */
+ WARNING2("Can't CloseHandle(%x) (Error %d)!\n", hash_mutex, GetLastError());
+ }
+ hash_mutex = NULL;
+ }
+
+#ifdef KPSE_DEBUG
+ if (KPSE_DEBUG_P (KPSE_DEBUG_SHM))
+ DEBUGF1("Cleaned up shared hash tables, %ld memory was allocated.\n", hash_memory_allocated);
+#endif
+}
+
+KPSEDLL boolean hash_init_needed = false;
+
+boolean
+hash_lock(boolean force)
+{
+ int dwWaitResult;
+
+ if (!force && !hash_init_done)
+ return true;
+
+ /* Request ownership of mutex. */
+ dwWaitResult = WaitForSingleObject(hash_mutex, 25000L);
+
+ switch (dwWaitResult) {
+ /* The thread got mutex ownership. */
+ case WAIT_OBJECT_0:
+ return true;
+
+ /* Cannot get mutex ownership due to time-out. */
+ case WAIT_TIMEOUT:
+ WARNING("Timeout waiting to lock shared memory access!\n");
+ return false;
+
+ /* Got ownership of the abandoned mutex object. */
+ case WAIT_ABANDONED:
+ WARNING("Waiting to lock shared memory access abandoned!\n");
+ return false;
+ }
+
+ return false;
+}
+
+void
+hash_unlock(void)
+{
+ if (! hash_init_done)
+ return;
+
+ if (ReleaseMutex(hash_mutex) == 0) {
+ FATAL2("%s: Can't ReleaseMutex() access to shared memory databases (Error %d)!\n", kpse_program_name, GetLastError());
+ }
+}
+
+static void
+hash_start_init()
+{
+#ifdef KPSE_DEBUG
+ if (KPSE_DEBUG_P (KPSE_DEBUG_SHM))
+ DEBUGF1("%s: Start of shared hash tables initialisation.\n", kpse_program_name);
+#endif
+ hash_lock(true);
+}
+
+void
+hash_end_init()
+{
+ hash_init_done = true;
+ hash_unlock();
+
+#ifdef KPSE_DEBUG
+ if (KPSE_DEBUG_P (KPSE_DEBUG_SHM))
+ DEBUGF1("%s: End of shared hash tables initialisation.\n", kpse_program_name);
+#endif
+}
+
+void
+hash_init (void)
+{
+ SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
+
+ hash_init_done = false;
+ if ((hash_mutex = CreateMutex(NULL, FALSE, "kpse_initialization")) == 0) {
+ FATAL1("Can't CreateMutex() for shared memory locking (Error %d)!\n", GetLastError());
+ }
+
+ if ((hash_handle = OpenFileMapping(FILE_MAP_ALL_ACCESS, TRUE, HASH_MAPPING_NAME)) == 0) {
+
+ hash_start_init();
+
+ /* First call, need to allocate memory. */
+ if ((hash_handle = CreateFileMapping(INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE,
+ 0, hash_memory_size, HASH_MAPPING_NAME)) == 0) {
+ /* Report an error. */
+ FATAL2("Can't CreateFileMapping() of size %ul (Error %d)!\n", hash_memory_size, GetLastError());
+ };
+ atexit(hash_cleanup);
+ hash_init_needed = true;
+ }
+
+ if ((hash_memory = MapViewOfFile(hash_handle, FILE_MAP_ALL_ACCESS, 0, 0, 0)) == NULL) {
+ /* Report an error. */
+ FATAL2("Can't MapViewOfFile(%x) (Error %d)!\n", hash_handle, GetLastError());
+ }
+
+ hash_bloc = (hash_memory_description *)hash_memory;
+ if (hash_init_needed) {
+ /* Is it more efficient to zero the whole block first ? */
+ ZeroMemory(hash_memory, sizeof(hash_bloc));
+ hash_bloc->sig = sizeof(hash_memory_description);
+ hash_bloc->memory_size = hash_memory_size;
+ hash_bloc->first_free_address = sizeof(hash_memory_description);
+ }
+ else {
+ extern boolean first_search;
+
+ hash_start_init();
+
+#ifdef KPSE_DEBUG
+ if (KPSE_DEBUG_P (KPSE_DEBUG_SHM))
+ DEBUGF1("Reusing shared hash tables at %x.\n", hash_memory);
+#endif
+
+ assert(hash_bloc->sig == sizeof(hash_memory_description));
+ /*
+ We could be a little bit cleaner here, just
+ relying on the hashtable_instances enumeration.
+ */
+ cnf_hash = (hash_table_type *)hash_exists_p(hashtable_cnf);
+ db = (hash_table_type *)hash_exists_p(hashtable_db);
+ alias_db = (hash_table_type *)hash_exists_p(hashtable_alias);
+ map = (hash_table_type *)hash_exists_p(hashtable_map);
+ link_table = (hash_table_type *)hash_exists_p(hashtable_links);
+ remote_db = (hash_table_type *)hash_exists_p(hashtable_remote);
+
+ db_dir_list = str_list_init();
+ /* Awful kludge ! */
+ first_search = false;
+ hash_restore_str_list(hash_bloc->db_dir_list, hash_bloc->db_dir_list_len, &db_dir_list);
+
+ }
+}
+
+void
+hash_reinit()
+{
+ if (!hash_init_needed) {
+ fprintf(stderr, "%s: your `ls-R' database files have been modified\n", kpse_program_name);
+ fprintf(stderr, "while kpathsea Dll was loaded by other programs.\n");
+ fprintf(stderr, "It is recommended that you terminate all of them.\n");
+ fprintf(stderr, "in order to reinitialize the shared databases.\n");
+ }
+}
+
+boolean
+hash_extend_memory ()
+{
+ /* We need to allocate a new memory bloc twice the size
+ of the previous one, copy everything and free the previous block.
+ */
+ SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
+ HANDLE new_hash_handle = 0;
+ void *new_hash_memory = NULL;
+ unsigned long new_hash_memory_size = 2 * hash_memory_size;
+
+ assert(hash_memory != NULL);
+
+ /* First, create some private unnamed bloc */
+ if ((new_hash_handle = CreateFileMapping(INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE,
+ 0, new_hash_memory_size, NULL)) == 0) {
+ /* Report an error. */
+ FATAL2("Can't CreateFileMapping() of size %ul (Error %d)!\n", new_hash_memory_size, GetLastError());
+ };
+
+ if ((new_hash_memory = MapViewOfFile(new_hash_handle, FILE_MAP_ALL_ACCESS, 0, 0, 0)) == NULL) {
+ /* Report an error. */
+ FATAL2("Can't MapViewOfFile(%x) (Error %d)!\n", new_hash_handle, GetLastError());
+ }
+ /* Copy the data */
+ CopyMemory(new_hash_memory, hash_memory, hash_memory_size);
+ /* Cleanup handles */
+ hash_cleanup();
+ /* make the values point to the new bloc */
+ hash_memory_size = new_hash_memory_size;
+ hash_memory = new_hash_memory;
+ hash_handle = new_hash_handle;
+
+ /* Now, create the actual named, shareable bloc */
+ if ((new_hash_handle = CreateFileMapping(INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE,
+ 0, hash_memory_size, HASH_MAPPING_NAME)) == 0) {
+ /* Report an error. */
+ FATAL2("Can't CreateFileMapping() of size %ul (Error %d)!\n", new_hash_memory_size, GetLastError());
+ };
+
+ if ((new_hash_memory = MapViewOfFile(new_hash_handle, FILE_MAP_ALL_ACCESS, 0, 0, 0)) == NULL) {
+ /* Report an error. */
+ FATAL2("Can't MapViewOfFile(%x) (Error %d)!\n", new_hash_handle, GetLastError());
+ }
+ /* Copy the data */
+ CopyMemory(new_hash_memory, hash_memory, hash_memory_size);
+ /* Cleanup the handles */
+ hash_cleanup();
+
+ hash_memory = new_hash_memory;
+ hash_handle = new_hash_handle;
+ hash_bloc = (hash_memory_description *)hash_memory;
+
+ return true;
+}
+
+unsigned long
+hash_malloc P1C(unsigned, size)
+{
+ unsigned long ret = hash_bloc->first_free_address;
+ hash_bloc->first_free_address += size;
+
+ if ((unsigned long)hash_bloc->first_free_address > (unsigned long)hash_bloc->memory_size) {
+ if (! hash_extend_memory()) {
+ fprintf(stderr, "Memory exhausted: base = %x, first_free_address = %x, size = %x\n",
+ hash_memory, hash_bloc->first_free_address, hash_bloc->memory_size);
+ }
+ }
+ assert((unsigned long)hash_bloc->first_free_address < (unsigned long)hash_bloc->memory_size);
+
+ hash_memory_allocated += size;
+
+ return ret;
+}
+
+unsigned long
+hash_strdup P1C(const_string, p)
+{
+ unsigned long q = hash_malloc(strlen(p)+1);
+ memcpy(HASH_PTR(q), p, strlen(p)+1);
+ return q;
+}
+
+void
+hash_free P1C(void *, address)
+{
+ /* Don't want to play with this for the moment.
+ Hash-Tables just grow up, no need to remove
+ anything from them. The `hash_remove' function
+ is only called from contrib/variables.c .
+ */
+}
+
+void
+hash_save_str_list(str_list_type l)
+{
+ unsigned int i;
+ unsigned long *q;
+ assert(hash_bloc != NULL);
+ hash_bloc->db_dir_list_len = STR_LIST_LENGTH(l);
+
+ if (hash_bloc->db_dir_list_len == 0) return;
+
+ hash_bloc->db_dir_list = hash_malloc(hash_bloc->db_dir_list_len * sizeof(unsigned long));
+ for (i = 0, q = (unsigned long *)HASH_PTR(hash_bloc->db_dir_list);
+ i < hash_bloc->db_dir_list_len;
+ i++, q++) {
+ *q = hash_strdup(STR_LIST_ELT(l, i));
+ }
+}
+
+void
+hash_restore_str_list(unsigned long ptr, unsigned len, str_list_type *l)
+{
+ int i;
+ unsigned long *q;
+ for (i = 0, q = (unsigned long *)HASH_PTR(ptr);
+ i < len;
+ i++, q++) {
+ str_list_add (l, HASH_PTR(*q));
+ }
+#if 0
+ fprintf(stderr, "Restored db_dir_list :\n");
+ for (i = 0; i < STR_LIST_LENGTH(*l); i++) {
+ fprintf(stderr, "\t%s\n", STR_LIST_ELT(*l, i));
+ }
+#endif
+}
+
+hash_table_type *
+hash_exists_p P1C(hashtable_instances, hash_number)
+{
+ unsigned long rel;
+
+ assert(hash_memory != NULL);
+ assert(hash_bloc != NULL);
+
+#if 0
+ fprintf(stderr, "Looking up for table %d ... ", hash_number);
+#endif
+ if (hash_bloc) {
+ if (rel = hash_bloc->hash_offsets[hash_number]) {
+#if 0
+ fprintf(stderr, "found (%x) for %d\n", (char *)hash_memory + rel, hash_number);
+#endif
+ return (hash_table_type *)((char *)hash_memory + rel);
+ }
+ }
+#if 0
+ fprintf(stderr, "found (null) for %d\n", hash_number);
+#endif
+ return NULL;
+}
+
+hash_table_type *
+hash_create P2C(unsigned, size, hashtable_instances, hash_number)
+{
+ /* hash_table_type ret; changed into "static ..." to work around gcc
+ optimizer bug for Alpha. */
+ static hash_table_type *ret;
+ unsigned b;
+
+ if (hash_memory == NULL) {
+ hash_init();
+ }
+
+ if (ret = hash_exists_p(hash_number)) {
+ assert(size == ret->size);
+ return ret;
+ }
+
+ hash_lock(false);
+
+#ifdef KPSE_DEBUG
+ if (KPSE_DEBUG_P (KPSE_DEBUG_SHM))
+ DEBUGF2("Hash: creating shared hash table %u of size %u\n", hash_number, size);
+#endif
+
+ hash_bloc->hash_offsets[hash_number] = hash_malloc(sizeof(hash_table_type));
+ ret = (hash_table_type *)HASH_PTR(hash_bloc->hash_offsets[hash_number]);
+
+ ret->buckets = (hash_element_type **)hash_malloc(size * sizeof(hash_element_type *));
+ ret->size = size;
+
+ /* calloc's zeroes aren't necessarily NULL, so be safe. */
+ for (b = 0; b <ret->size; b++)
+ HASH_BUCKETS(ret, b) = NULL;
+
+ hash_unlock();
+
+ return ret;
+}
+
+#if 0
+/* Not really usable. */
+hash_table_type *
+hash_reset P1C(hashtable_instances, hash_number)
+{
+ /* hash_table_type ret; changed into "static ..." to work around gcc
+ optimizer bug for Alpha. */
+ static hash_table_type *ret;
+ unsigned b;
+
+ if (hash_memory == NULL
+ || (ret = hash_exists_p(hash_number)) == NULL) {
+ return NULL;
+ }
+
+ hash_lock(false);
+
+#ifdef KPSE_DEBUG
+ if (KPSE_DEBUG_P (KPSE_DEBUG_SHM))
+ DEBUGF1("Hash: resetting shared hash table %u\n", hash_number);
+#endif
+
+ /* calloc's zeroes aren't necessarily NULL, so be safe. */
+ for (b = 0; b <ret->size; b++)
+ HASH_BUCKETS(ret, b) = NULL;
+
+ hash_unlock();
+
+ return ret;
+}
+
+#endif
+/* WARNING: the comment below is frightening !
+ However, it does not seem to be the case that the strings
+ are changed after being inserted in the map.
+ IHMO, this should be strictly forbidden.
+*/
+/* Whether or not KEY is already in MAP, insert it and VALUE. Do not
+ duplicate the strings, in case they're being purposefully shared. */
+
+void
+hash_insert P3C(hash_table_type *, table, const_string, key,
+ const_string, value)
+{
+
+ unsigned n = hash (table, key);
+ unsigned long new_elt;
+
+ hash_lock(false);
+
+ new_elt = hash_malloc(sizeof(hash_element_type));
+
+ ((hash_element_type *)(HASH_PTR(new_elt)))->key = (const_string)hash_strdup(key);
+ ((hash_element_type *)(HASH_PTR(new_elt)))->value = (const_string)hash_strdup(value);
+ ((hash_element_type *)(HASH_PTR(new_elt)))->next = NULL;
+
+ /* Insert the new element at the end of the list. */
+ if (! HASH_BUCKETS(table, n))
+ /* first element in bucket is a special case. */
+ HASH_BUCKETS(table, n) = (hash_element_type *)new_elt;
+ else
+ {
+ hash_element_type *loc = (hash_element_type *)HASH_PTR(HASH_BUCKETS(table, n));
+ while (loc->next) /* Find the last element. */
+ loc = (hash_element_type *)HASH_PTR(loc->next);
+ loc->next = (hash_element_type *)new_elt; /* Insert the new one after. */
+ }
+
+ hash_unlock();
+}
+
+#ifdef NORMALIZED
+/* Same as above, for normalized keys. */
+void
+hash_insert_normalized P3C(hash_table_type *, table,
+ const_string, key,
+ const_string, value)
+{
+ unsigned n = hash_normalized (table, key);
+ unsigned long new_elt;
+
+ hash_lock(false);
+
+ new_elt = hash_malloc(sizeof(hash_element_type));
+
+ ((hash_element_type *)(HASH_PTR(new_elt)))->key = (const_string)hash_strdup(key);
+ ((hash_element_type *)(HASH_PTR(new_elt)))->value = (const_string)hash_strdup(value);
+ ((hash_element_type *)(HASH_PTR(new_elt)))->next = NULL;
+
+ /* Insert the new element at the end of the list. */
+ if (! HASH_BUCKETS(table, n))
+ /* first element in bucket is a special case. */
+ HASH_BUCKETS(table, n) = (hash_element_type *)new_elt;
+ else
+ {
+ hash_element_type *loc = (hash_element_type *)HASH_PTR(HASH_BUCKETS(table, n));
+ while (loc->next) /* Find the last element. */
+ loc = (hash_element_type *)HASH_PTR(loc->next);
+ loc->next = (hash_element_type *)new_elt; /* Insert the new one after. */
+ }
+
+ hash_unlock();
+}
+#endif /* WIN32*/
+
+/* Remove a (KEY, VALUE) pair. */
+
+void
+hash_remove P3C(hash_table_type *, table, const_string, key,
+ const_string, value)
+{
+ hash_element_type *p;
+ hash_element_type *q;
+ unsigned n = hash (table, key);
+
+ hash_lock(false);
+
+ /* Find pair. */
+ for (q = NULL, p = (hash_element_type *)HASH_PTR(HASH_BUCKETS(table, n)); ! HASH_NULL(p); q = p, p = (hash_element_type *)HASH_PTR(p->next))
+ if (FILESTRCASEEQ (key, HASH_PTR(p->key)) && STREQ (value, HASH_PTR(p->value)))
+ break;
+ if (p) {
+ /* We found something, remove it from the chain. */
+ if (q) q->next = p->next; else HASH_BUCKETS(table, n) = p->next;
+ /* We cannot dispose of the contents. */
+ hash_free ((void *)HASH_REL(p));
+ }
+
+ hash_unlock();
+}
+
+/* Look up STR in MAP. Return a (dynamically-allocated) list of the
+ corresponding strings or NULL if no match. */
+
+#ifdef KPSE_DEBUG
+/* Print the hash values as integers if this is nonzero. */
+boolean kpse_debug_hash_lookup_int = false;
+#endif
+
+string *
+hash_lookup P2C(hash_table_type *, table, const_string, key)
+{
+ hash_element_type *p;
+ str_list_type ret;
+ unsigned n = hash (table, key);
+
+ ret = str_list_init ();
+
+ hash_lock(false);
+
+ /* Look at everything in this bucket. */
+ for (p = (hash_element_type *)HASH_PTR(HASH_BUCKETS(table, n)); ! HASH_NULL(p); p = (hash_element_type *)HASH_PTR(p->next))
+ if (FILESTRCASEEQ (key, HASH_PTR(p->key)))
+ /* Cast because the general str_list_type shouldn't force const data. */
+ str_list_add (&ret, (string) xstrdup((string)HASH_PTR(p->value)));
+
+ /* If we found anything, mark end of list with null. */
+ if (STR_LIST (ret))
+ str_list_add (&ret, NULL);
+
+#ifdef KPSE_DEBUG
+ if (KPSE_DEBUG_P (KPSE_DEBUG_HASH))
+ {
+ DEBUGF1 ("hash_shared_lookup(%s) =>", key);
+ if (!STR_LIST (ret))
+ fputs (" (nil)\n", stderr);
+ else
+ {
+ string *r;
+ for (r = STR_LIST (ret); *r; r++)
+ {
+ putc (' ', stderr);
+ if (kpse_debug_hash_lookup_int)
+ fprintf (stderr, "%ld", (long) *r);
+ else
+ fputs (*r, stderr);
+ }
+ putc ('\n', stderr);
+ }
+ fflush (stderr);
+ }
+#endif
+
+ hash_unlock();
+
+ return STR_LIST (ret);
+}
+
+/* We only print nonempty buckets, to decrease output volume. */
+
+void
+hash_print P2C(hash_table_type *, table, boolean, summary_only)
+{
+ unsigned b;
+ unsigned total_elements = 0, total_buckets = 0;
+
+ if (table == NULL) return;
+
+ hash_lock(false);
+
+ for (b = 0; b < table->size; b++) {
+ hash_element_type *bucket = (hash_element_type *)HASH_PTR(HASH_BUCKETS(table, b));
+
+ if (! HASH_NULL(bucket)) {
+ unsigned len = 1;
+ hash_element_type *tb;
+
+ total_buckets++;
+ if (!summary_only) fprintf (stderr, "%4d ", b);
+
+ for (tb = (hash_element_type *)HASH_PTR(bucket->next); ! HASH_NULL(tb); tb = (hash_element_type *)HASH_PTR(tb->next))
+ len++;
+ if (!summary_only) fprintf (stderr, ":%-5d", len);
+ total_elements += len;
+
+ if (!summary_only) {
+ for (tb = bucket; ! HASH_NULL(tb); tb = (hash_element_type *)HASH_PTR(tb->next))
+ fprintf (stderr, " %s=>%s", HASH_PTR(tb->key), HASH_PTR(tb->value));
+ putc ('\n', stderr);
+ }
+ }
+ }
+
+ hash_unlock();
+
+ fprintf (stderr,
+ "%u buckets, %u nonempty (%u%%); %u entries, average chain %.1f.\n",
+ table->size,
+ total_buckets,
+ 100 * total_buckets / table->size,
+ total_elements,
+ total_buckets ? total_elements / (double) total_buckets : 0.0);
+}
+
+/* Simple iterator over hash-table elements */
+void
+hash_iter P2C(hash_table_type *, table, hash_iter_fn, fn)
+{
+ unsigned b;
+
+ if (! table) return;
+
+ hash_lock(false);
+
+ for (b = 0; b < table->size; b++) {
+ hash_element_type *bucket = (hash_element_type *)HASH_PTR(HASH_BUCKETS(table, b));
+ if (! HASH_NULL(bucket)) {
+ unsigned len = 1;
+ hash_element_type *tb;
+ for (tb = bucket; ! HASH_NULL(tb); tb = (hash_element_type *)HASH_PTR(tb->next)) {
+#ifdef KPSE_DEBUG
+ if (KPSE_DEBUG_P (KPSE_DEBUG_HASH))
+ {
+ DEBUGF2("applying (shared) fn to %s%s\n", HASH_PTR(tb->value), HASH_PTR(tb->key));
+ }
+#endif
+ (*fn)(HASH_PTR(tb->key), HASH_PTR(tb->value));
+ }
+ }
+ }
+
+ hash_unlock();
+}