xref: /openbsd/gnu/usr.bin/cvs/src/hash.c (revision 3cab2bb3)
1 /*
2  * Copyright (c) 1992, Brian Berliner and Jeff Polk
3  *
4  * You may distribute under the terms of the GNU General Public License as
5  * specified in the README file that comes with the CVS source distribution.
6  *
7  * Polk's hash list manager.  So cool.
8  */
9 
10 #include "cvs.h"
11 #include <assert.h>
12 
13 /* Global caches.  The idea is that we maintain a linked list of "free"d
14    nodes or lists, and get new items from there.  It has been suggested
15    to use an obstack instead, but off the top of my head, I'm not sure
16    that would gain enough to be worth worrying about.  */
17 static List *listcache = NULL;
18 static Node *nodecache = NULL;
19 
20 static void freenode_mem PROTO((Node * p));
21 
22 /* hash function */
23 static int
24 hashp (key)
25     const char *key;
26 {
27     unsigned int h = 0;
28     unsigned int g;
29 
30     assert(key != NULL);
31 
32     while (*key != 0)
33     {
34 	unsigned int c = *key++;
35 	/* The FOLD_FN_CHAR is so that findnode_fn works.  */
36 	h = (h << 4) + FOLD_FN_CHAR (c);
37 	if ((g = h & 0xf0000000) != 0)
38 	    h = (h ^ (g >> 24)) ^ g;
39     }
40 
41     return (h % HASHSIZE);
42 }
43 
44 /*
45  * create a new list (or get an old one from the cache)
46  */
47 List *
48 getlist ()
49 {
50     int i;
51     List *list;
52     Node *node;
53 
54     if (listcache != NULL)
55     {
56 	/* get a list from the cache and clear it */
57 	list = listcache;
58 	listcache = listcache->next;
59 	list->next = (List *) NULL;
60 	for (i = 0; i < HASHSIZE; i++)
61 	    list->hasharray[i] = (Node *) NULL;
62     }
63     else
64     {
65 	/* make a new list from scratch */
66 	list = (List *) xmalloc (sizeof (List));
67 	memset ((char *) list, 0, sizeof (List));
68 	node = getnode ();
69 	list->list = node;
70 	node->type = HEADER;
71 	node->next = node->prev = node;
72     }
73     return (list);
74 }
75 
76 /*
77  * free up a list
78  */
79 void
80 dellist (listp)
81     List **listp;
82 {
83     int i;
84     Node *p;
85 
86     if (*listp == (List *) NULL)
87 	return;
88 
89     p = (*listp)->list;
90 
91     /* free each node in the list (except header) */
92     while (p->next != p)
93 	delnode (p->next);
94 
95     /* free any list-private data, without freeing the actual header */
96     freenode_mem (p);
97 
98     /* free up the header nodes for hash lists (if any) */
99     for (i = 0; i < HASHSIZE; i++)
100     {
101 	if ((p = (*listp)->hasharray[i]) != (Node *) NULL)
102 	{
103 	    /* put the nodes into the cache */
104 #ifndef NOCACHE
105 	    p->type = NT_UNKNOWN;
106 	    p->next = nodecache;
107 	    nodecache = p;
108 #else
109 	    /* If NOCACHE is defined we turn off the cache.  This can make
110 	       it easier to tools to determine where items were allocated
111 	       and freed, for tracking down memory leaks and the like.  */
112 	    free (p);
113 #endif
114 	}
115     }
116 
117     /* put it on the cache */
118 #ifndef NOCACHE
119     (*listp)->next = listcache;
120     listcache = *listp;
121 #else
122     free ((*listp)->list);
123     free (*listp);
124 #endif
125     *listp = (List *) NULL;
126 }
127 
128 /*
129  * get a new list node
130  */
131 Node *
132 getnode ()
133 {
134     Node *p;
135 
136     if (nodecache != (Node *) NULL)
137     {
138 	/* get one from the cache */
139 	p = nodecache;
140 	nodecache = p->next;
141     }
142     else
143     {
144 	/* make a new one */
145 	p = (Node *) xmalloc (sizeof (Node));
146     }
147 
148     /* always make it clean */
149     memset ((char *) p, 0, sizeof (Node));
150     p->type = NT_UNKNOWN;
151 
152     return (p);
153 }
154 
155 /*
156  * remove a node from it's list (maybe hash list too) and free it
157  */
158 void
159 delnode (p)
160     Node *p;
161 {
162     if (p == (Node *) NULL)
163 	return;
164 
165     /* take it out of the list */
166     p->next->prev = p->prev;
167     p->prev->next = p->next;
168 
169     /* if it was hashed, remove it from there too */
170     if (p->hashnext != (Node *) NULL)
171     {
172 	p->hashnext->hashprev = p->hashprev;
173 	p->hashprev->hashnext = p->hashnext;
174     }
175 
176     /* free up the storage */
177     freenode (p);
178 }
179 
180 /*
181  * free up the storage associated with a node
182  */
183 static void
184 freenode_mem (p)
185     Node *p;
186 {
187     if (p->delproc != (void (*) ()) NULL)
188 	p->delproc (p);			/* call the specified delproc */
189     else
190     {
191 	if (p->data != NULL)		/* otherwise free() it if necessary */
192 	    free (p->data);
193     }
194     if (p->key != NULL)			/* free the key if necessary */
195 	free (p->key);
196 
197     /* to be safe, re-initialize these */
198     p->key = p->data = (char *) NULL;
199     p->delproc = (void (*) ()) NULL;
200 }
201 
202 /*
203  * free up the storage associated with a node and recycle it
204  */
205 void
206 freenode (p)
207     Node *p;
208 {
209     /* first free the memory */
210     freenode_mem (p);
211 
212     /* then put it in the cache */
213 #ifndef NOCACHE
214     p->type = NT_UNKNOWN;
215     p->next = nodecache;
216     nodecache = p;
217 #else
218     free (p);
219 #endif
220 }
221 
222 /*
223  * Link item P into list LIST before item MARKER.  If P->KEY is non-NULL and
224  * that key is already in the hash table, return -1 without modifying any
225  * parameter.
226  *
227  * return 0 on success
228  */
229 int
230 insert_before (list, marker, p)
231     List *list;
232     Node *marker;
233     Node *p;
234 {
235     if (p->key != NULL)			/* hash it too? */
236     {
237 	int hashval;
238 	Node *q;
239 
240 	hashval = hashp (p->key);
241 	if (list->hasharray[hashval] == NULL)	/* make a header for list? */
242 	{
243 	    q = getnode ();
244 	    q->type = HEADER;
245 	    list->hasharray[hashval] = q->hashnext = q->hashprev = q;
246 	}
247 
248 	/* put it into the hash list if it's not already there */
249 	for (q = list->hasharray[hashval]->hashnext;
250 	     q != list->hasharray[hashval]; q = q->hashnext)
251 	{
252 	    if (strcmp (p->key, q->key) == 0)
253 		return (-1);
254 	}
255 	q = list->hasharray[hashval];
256 	p->hashprev = q->hashprev;
257 	p->hashnext = q;
258 	p->hashprev->hashnext = p;
259 	q->hashprev = p;
260     }
261 
262     p->next = marker;
263     p->prev = marker->prev;
264     marker->prev->next = p;
265     marker->prev = p;
266 
267     return (0);
268 }
269 
270 /*
271  * insert item p at end of list "list" (maybe hash it too) if hashing and it
272  * already exists, return -1 and don't actually put it in the list
273  *
274  * return 0 on success
275  */
276 int
277 addnode (list, p)
278     List *list;
279     Node *p;
280 {
281   return insert_before (list, list->list, p);
282 }
283 
284 /*
285  * Like addnode, but insert p at the front of `list'.  This bogosity is
286  * necessary to preserve last-to-first output order for some RCS functions.
287  */
288 int
289 addnode_at_front (list, p)
290     List *list;
291     Node *p;
292 {
293   return insert_before (list, list->list->next, p);
294 }
295 
296 /* Look up an entry in hash list table and return a pointer to the
297    node.  Return NULL if not found.  Abort with a fatal error for
298    errors.  */
299 Node *
300 findnode (list, key)
301     List *list;
302     const char *key;
303 {
304     Node *head, *p;
305 
306     /* This probably should be "assert (list != NULL)" (or if not we
307        should document the current behavior), but only if we check all
308        the callers to see if any are relying on this behavior.  */
309     if (list == (List *) NULL)
310 	return ((Node *) NULL);
311 
312     assert (key != NULL);
313 
314     head = list->hasharray[hashp (key)];
315     if (head == (Node *) NULL)
316 	/* Not found.  */
317 	return ((Node *) NULL);
318 
319     for (p = head->hashnext; p != head; p = p->hashnext)
320 	if (strcmp (p->key, key) == 0)
321 	    return (p);
322     return ((Node *) NULL);
323 }
324 
325 /*
326  * Like findnode, but for a filename.
327  */
328 Node *
329 findnode_fn (list, key)
330     List *list;
331     const char *key;
332 {
333     Node *head, *p;
334 
335     /* This probably should be "assert (list != NULL)" (or if not we
336        should document the current behavior), but only if we check all
337        the callers to see if any are relying on this behavior.  */
338     if (list == (List *) NULL)
339 	return ((Node *) NULL);
340 
341     assert (key != NULL);
342 
343     head = list->hasharray[hashp (key)];
344     if (head == (Node *) NULL)
345 	return ((Node *) NULL);
346 
347     for (p = head->hashnext; p != head; p = p->hashnext)
348 	if (fncmp (p->key, key) == 0)
349 	    return (p);
350     return ((Node *) NULL);
351 }
352 
353 /*
354  * walk a list with a specific proc
355  */
356 int
357 walklist (list, proc, closure)
358     List *list;
359     int (*proc) PROTO ((Node *, void *));
360     void *closure;
361 {
362     Node *head, *p;
363     int err = 0;
364 
365     if (list == NULL)
366 	return (0);
367 
368     head = list->list;
369     for (p = head->next; p != head; p = p->next)
370 	err += proc (p, closure);
371     return (err);
372 }
373 
374 int
375 list_isempty (list)
376     List *list;
377 {
378     return list == NULL || list->list->next == list->list;
379 }
380 
381 static int (*client_comp) PROTO ((const Node *, const Node *));
382 static int qsort_comp PROTO ((const void *, const void *));
383 
384 static int
385 qsort_comp (elem1, elem2)
386     const void *elem1;
387     const void *elem2;
388 {
389     Node **node1 = (Node **) elem1;
390     Node **node2 = (Node **) elem2;
391     return client_comp (*node1, *node2);
392 }
393 
394 /*
395  * sort the elements of a list (in place)
396  */
397 void
398 sortlist (list, comp)
399     List *list;
400     int (*comp) PROTO ((const Node *, const Node *));
401 {
402     Node *head, *remain, *p, **array;
403     int i, n;
404 
405     /* save the old first element of the list */
406     head = list->list;
407     remain = head->next;
408 
409     /* count the number of nodes in the list */
410     n = 0;
411     for (p = remain; p != head; p = p->next)
412 	n++;
413 
414     /* allocate an array of nodes and populate it */
415     array = (Node **) xmalloc (sizeof(Node *) * n);
416     i = 0;
417     for (p = remain; p != head; p = p->next)
418 	array[i++] = p;
419 
420     /* sort the array of nodes */
421     client_comp = comp;
422     qsort (array, n, sizeof(Node *), qsort_comp);
423 
424     /* rebuild the list from beginning to end */
425     head->next = head->prev = head;
426     for (i = 0; i < n; i++)
427     {
428 	p = array[i];
429 	p->next = head;
430 	p->prev = head->prev;
431 	p->prev->next = p;
432 	head->prev = p;
433     }
434 
435     /* release the array of nodes */
436     free (array);
437 }
438 
439 /*
440  * compare two files list node (for sort)
441  */
442 int
443 fsortcmp (p, q)
444     const Node *p;
445     const Node *q;
446 {
447     return (strcmp (p->key, q->key));
448 }
449 
450 /* Debugging functions.  Quite useful to call from within gdb. */
451 
452 static char *nodetypestring PROTO ((Ntype));
453 
454 static char *
455 nodetypestring (type)
456     Ntype type;
457 {
458     switch (type) {
459     case NT_UNKNOWN:	return("UNKNOWN");
460     case HEADER:	return("HEADER");
461     case ENTRIES:	return("ENTRIES");
462     case FILES:		return("FILES");
463     case LIST:		return("LIST");
464     case RCSNODE:	return("RCSNODE");
465     case RCSVERS:	return("RCSVERS");
466     case DIRS:		return("DIRS");
467     case UPDATE:	return("UPDATE");
468     case LOCK:		return("LOCK");
469     case NDBMNODE:	return("NDBMNODE");
470     case FILEATTR:	return("FILEATTR");
471     case VARIABLE:	return("VARIABLE");
472     case RCSFIELD:	return("RCSFIELD");
473     case RCSCMPFLD:	return("RCSCMPFLD");
474     }
475 
476     return("<trash>");
477 }
478 
479 static int printnode PROTO ((Node *, void *));
480 static int
481 printnode (node, closure)
482      Node *node;
483      void *closure;
484 {
485     if (node == NULL)
486     {
487 	(void) printf("NULL node.\n");
488 	return(0);
489     }
490 
491     (void) printf("Node at 0x%p: type = %s, key = 0x%p = \"%s\", data = 0x%p, next = 0x%p, prev = 0x%p\n",
492 	   node, nodetypestring(node->type), node->key, node->key, node->data, node->next, node->prev);
493 
494     return(0);
495 }
496 
497 /* This is global, not static, so that its name is unique and to avoid
498    compiler warnings about it not being used.  But it is not used by CVS;
499    it exists so one can call it from a debugger.  */
500 void printlist PROTO ((List *));
501 
502 void
503 printlist (list)
504     List *list;
505 {
506     if (list == NULL)
507     {
508 	(void) printf("NULL list.\n");
509 	return;
510     }
511 
512     (void) printf("List at 0x%p: list = 0x%p, HASHSIZE = %d, next = 0x%p\n",
513 	   list, list->list, HASHSIZE, list->next);
514 
515     (void) walklist(list, printnode, NULL);
516 
517     return;
518 }
519