xref: /curl/lib/hash.c (revision c0233a35)
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at https://curl.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  * SPDX-License-Identifier: curl
22  *
23  ***************************************************************************/
24 
25 #include "curl_setup.h"
26 
27 #include <curl/curl.h>
28 
29 #include "hash.h"
30 #include "llist.h"
31 #include "curl_memory.h"
32 
33 /* The last #include file should be: */
34 #include "memdebug.h"
35 
36 /* random patterns for API verification */
37 #define HASHINIT 0x7017e781
38 #define ITERINIT 0x5FEDCBA9
39 
40 static void
hash_element_dtor(void * user,void * element)41 hash_element_dtor(void *user, void *element)
42 {
43   struct Curl_hash *h = (struct Curl_hash *) user;
44   struct Curl_hash_element *e = (struct Curl_hash_element *) element;
45 
46   if(e->ptr) {
47     if(e->dtor)
48       e->dtor(e->key, e->key_len, e->ptr);
49     else
50       h->dtor(e->ptr);
51     e->ptr = NULL;
52   }
53 
54   e->key_len = 0;
55 
56   free(e);
57 }
58 
59 /* Initializes a hash structure.
60  * Return 1 on error, 0 is fine.
61  *
62  * @unittest: 1602
63  * @unittest: 1603
64  */
65 void
Curl_hash_init(struct Curl_hash * h,size_t slots,hash_function hfunc,comp_function comparator,Curl_hash_dtor dtor)66 Curl_hash_init(struct Curl_hash *h,
67                size_t slots,
68                hash_function hfunc,
69                comp_function comparator,
70                Curl_hash_dtor dtor)
71 {
72   DEBUGASSERT(h);
73   DEBUGASSERT(slots);
74   DEBUGASSERT(hfunc);
75   DEBUGASSERT(comparator);
76   DEBUGASSERT(dtor);
77 
78   h->table = NULL;
79   h->hash_func = hfunc;
80   h->comp_func = comparator;
81   h->dtor = dtor;
82   h->size = 0;
83   h->slots = slots;
84 #ifdef DEBUGBUILD
85   h->init = HASHINIT;
86 #endif
87 }
88 
89 static struct Curl_hash_element *
mk_hash_element(const void * key,size_t key_len,const void * p,Curl_hash_elem_dtor dtor)90 mk_hash_element(const void *key, size_t key_len, const void *p,
91                 Curl_hash_elem_dtor dtor)
92 {
93   /* allocate the struct plus memory after it to store the key */
94   struct Curl_hash_element *he = malloc(sizeof(struct Curl_hash_element) +
95                                         key_len);
96   if(he) {
97     /* copy the key */
98     memcpy(he->key, key, key_len);
99     he->key_len = key_len;
100     he->ptr = (void *) p;
101     he->dtor = dtor;
102   }
103   return he;
104 }
105 
106 #define FETCH_LIST(x,y,z) &x->table[x->hash_func(y, z, x->slots)]
107 
Curl_hash_add2(struct Curl_hash * h,void * key,size_t key_len,void * p,Curl_hash_elem_dtor dtor)108 void *Curl_hash_add2(struct Curl_hash *h, void *key, size_t key_len, void *p,
109                      Curl_hash_elem_dtor dtor)
110 {
111   struct Curl_hash_element  *he;
112   struct Curl_llist_node *le;
113   struct Curl_llist *l;
114 
115   DEBUGASSERT(h);
116   DEBUGASSERT(h->slots);
117   DEBUGASSERT(h->init == HASHINIT);
118   if(!h->table) {
119     size_t i;
120     h->table = malloc(h->slots * sizeof(struct Curl_llist));
121     if(!h->table)
122       return NULL; /* OOM */
123     for(i = 0; i < h->slots; ++i)
124       Curl_llist_init(&h->table[i], hash_element_dtor);
125   }
126 
127   l = FETCH_LIST(h, key, key_len);
128 
129   for(le = Curl_llist_head(l); le; le = Curl_node_next(le)) {
130     he = (struct Curl_hash_element *) Curl_node_elem(le);
131     if(h->comp_func(he->key, he->key_len, key, key_len)) {
132       Curl_node_uremove(le, (void *)h);
133       --h->size;
134       break;
135     }
136   }
137 
138   he = mk_hash_element(key, key_len, p, dtor);
139   if(he) {
140     Curl_llist_append(l, he, &he->list);
141     ++h->size;
142     return p; /* return the new entry */
143   }
144 
145   return NULL; /* failure */
146 }
147 
148 /* Insert the data in the hash. If there already was a match in the hash, that
149  * data is replaced. This function also "lazily" allocates the table if
150  * needed, as it is not done in the _init function (anymore).
151  *
152  * @unittest: 1305
153  * @unittest: 1602
154  * @unittest: 1603
155  */
156 void *
Curl_hash_add(struct Curl_hash * h,void * key,size_t key_len,void * p)157 Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p)
158 {
159   return Curl_hash_add2(h, key, key_len, p, NULL);
160 }
161 
162 /* Remove the identified hash entry.
163  * Returns non-zero on failure.
164  *
165  * @unittest: 1603
166  */
Curl_hash_delete(struct Curl_hash * h,void * key,size_t key_len)167 int Curl_hash_delete(struct Curl_hash *h, void *key, size_t key_len)
168 {
169   DEBUGASSERT(h);
170   DEBUGASSERT(h->slots);
171   DEBUGASSERT(h->init == HASHINIT);
172   if(h->table) {
173     struct Curl_llist_node *le;
174     struct Curl_llist *l = FETCH_LIST(h, key, key_len);
175 
176     for(le = Curl_llist_head(l); le; le = Curl_node_next(le)) {
177       struct Curl_hash_element *he = Curl_node_elem(le);
178       if(h->comp_func(he->key, he->key_len, key, key_len)) {
179         Curl_node_uremove(le, (void *) h);
180         --h->size;
181         return 0;
182       }
183     }
184   }
185   return 1;
186 }
187 
188 /* Retrieves a hash element.
189  *
190  * @unittest: 1603
191  */
192 void *
Curl_hash_pick(struct Curl_hash * h,void * key,size_t key_len)193 Curl_hash_pick(struct Curl_hash *h, void *key, size_t key_len)
194 {
195   DEBUGASSERT(h);
196   DEBUGASSERT(h->init == HASHINIT);
197   if(h->table) {
198     struct Curl_llist_node *le;
199     struct Curl_llist *l;
200     DEBUGASSERT(h->slots);
201     l = FETCH_LIST(h, key, key_len);
202     for(le = Curl_llist_head(l); le; le = Curl_node_next(le)) {
203       struct Curl_hash_element *he = Curl_node_elem(le);
204       if(h->comp_func(he->key, he->key_len, key, key_len)) {
205         return he->ptr;
206       }
207     }
208   }
209 
210   return NULL;
211 }
212 
213 /* Destroys all the entries in the given hash and resets its attributes,
214  * prepping the given hash for [static|dynamic] deallocation.
215  *
216  * @unittest: 1305
217  * @unittest: 1602
218  * @unittest: 1603
219  */
220 void
Curl_hash_destroy(struct Curl_hash * h)221 Curl_hash_destroy(struct Curl_hash *h)
222 {
223   DEBUGASSERT(h->init == HASHINIT);
224   if(h->table) {
225     size_t i;
226     for(i = 0; i < h->slots; ++i) {
227       Curl_llist_destroy(&h->table[i], (void *) h);
228     }
229     Curl_safefree(h->table);
230   }
231   h->size = 0;
232   h->slots = 0;
233 }
234 
235 /* Removes all the entries in the given hash.
236  *
237  * @unittest: 1602
238  */
239 void
Curl_hash_clean(struct Curl_hash * h)240 Curl_hash_clean(struct Curl_hash *h)
241 {
242   Curl_hash_clean_with_criterium(h, NULL, NULL);
243 }
244 
Curl_hash_count(struct Curl_hash * h)245 size_t Curl_hash_count(struct Curl_hash *h)
246 {
247   DEBUGASSERT(h->init == HASHINIT);
248   return h->size;
249 }
250 
251 /* Cleans all entries that pass the comp function criteria. */
252 void
Curl_hash_clean_with_criterium(struct Curl_hash * h,void * user,int (* comp)(void *,void *))253 Curl_hash_clean_with_criterium(struct Curl_hash *h, void *user,
254                                int (*comp)(void *, void *))
255 {
256   size_t i;
257 
258   if(!h || !h->table)
259     return;
260 
261   DEBUGASSERT(h->init == HASHINIT);
262   for(i = 0; i < h->slots; ++i) {
263     struct Curl_llist *list = &h->table[i];
264     struct Curl_llist_node *le =
265       Curl_llist_head(list); /* get first list entry */
266     while(le) {
267       struct Curl_hash_element *he = Curl_node_elem(le);
268       struct Curl_llist_node *lnext = Curl_node_next(le);
269       /* ask the callback function if we shall remove this entry or not */
270       if(!comp || comp(user, he->ptr)) {
271         Curl_node_uremove(le, (void *) h);
272         --h->size; /* one less entry in the hash now */
273       }
274       le = lnext;
275     }
276   }
277 }
278 
Curl_hash_str(void * key,size_t key_length,size_t slots_num)279 size_t Curl_hash_str(void *key, size_t key_length, size_t slots_num)
280 {
281   const char *key_str = (const char *) key;
282   const char *end = key_str + key_length;
283   size_t h = 5381;
284 
285   while(key_str < end) {
286     size_t j = (size_t)*key_str++;
287     h += h << 5;
288     h ^= j;
289   }
290 
291   return (h % slots_num);
292 }
293 
Curl_str_key_compare(void * k1,size_t key1_len,void * k2,size_t key2_len)294 size_t Curl_str_key_compare(void *k1, size_t key1_len,
295                             void *k2, size_t key2_len)
296 {
297   if((key1_len == key2_len) && !memcmp(k1, k2, key1_len))
298     return 1;
299 
300   return 0;
301 }
302 
Curl_hash_start_iterate(struct Curl_hash * hash,struct Curl_hash_iterator * iter)303 void Curl_hash_start_iterate(struct Curl_hash *hash,
304                              struct Curl_hash_iterator *iter)
305 {
306   DEBUGASSERT(hash->init == HASHINIT);
307   iter->hash = hash;
308   iter->slot_index = 0;
309   iter->current_element = NULL;
310 #ifdef DEBUGBUILD
311   iter->init = ITERINIT;
312 #endif
313 }
314 
315 struct Curl_hash_element *
Curl_hash_next_element(struct Curl_hash_iterator * iter)316 Curl_hash_next_element(struct Curl_hash_iterator *iter)
317 {
318   struct Curl_hash *h;
319   DEBUGASSERT(iter->init == ITERINIT);
320   h = iter->hash;
321   if(!h->table)
322     return NULL; /* empty hash, nothing to return */
323 
324   /* Get the next element in the current list, if any */
325   if(iter->current_element)
326     iter->current_element = Curl_node_next(iter->current_element);
327 
328   /* If we have reached the end of the list, find the next one */
329   if(!iter->current_element) {
330     size_t i;
331     for(i = iter->slot_index; i < h->slots; i++) {
332       if(Curl_llist_head(&h->table[i])) {
333         iter->current_element = Curl_llist_head(&h->table[i]);
334         iter->slot_index = i + 1;
335         break;
336       }
337     }
338   }
339 
340   if(iter->current_element) {
341     struct Curl_hash_element *he = Curl_node_elem(iter->current_element);
342     return he;
343   }
344   return NULL;
345 }
346 
347 #if 0 /* useful function for debugging hashes and their contents */
348 void Curl_hash_print(struct Curl_hash *h,
349                      void (*func)(void *))
350 {
351   struct Curl_hash_iterator iter;
352   struct Curl_hash_element *he;
353   size_t last_index = ~0;
354 
355   if(!h)
356     return;
357 
358   fprintf(stderr, "=Hash dump=\n");
359 
360   Curl_hash_start_iterate(h, &iter);
361 
362   he = Curl_hash_next_element(&iter);
363   while(he) {
364     if(iter.slot_index != last_index) {
365       fprintf(stderr, "index %d:", iter.slot_index);
366       if(last_index != ~0) {
367         fprintf(stderr, "\n");
368       }
369       last_index = iter.slot_index;
370     }
371 
372     if(func)
373       func(he->ptr);
374     else
375       fprintf(stderr, " [%p]", (void *)he->ptr);
376 
377     he = Curl_hash_next_element(&iter);
378   }
379   fprintf(stderr, "\n");
380 }
381 #endif
382 
Curl_hash_offt_init(struct Curl_hash * h,size_t slots,Curl_hash_dtor dtor)383 void Curl_hash_offt_init(struct Curl_hash *h,
384                          size_t slots,
385                          Curl_hash_dtor dtor)
386 {
387   Curl_hash_init(h, slots, Curl_hash_str, Curl_str_key_compare, dtor);
388 }
389 
Curl_hash_offt_set(struct Curl_hash * h,curl_off_t id,void * elem)390 void *Curl_hash_offt_set(struct Curl_hash *h, curl_off_t id, void *elem)
391 {
392   return Curl_hash_add(h, &id, sizeof(id), elem);
393 }
394 
Curl_hash_offt_remove(struct Curl_hash * h,curl_off_t id)395 int Curl_hash_offt_remove(struct Curl_hash *h, curl_off_t id)
396 {
397   return Curl_hash_delete(h, &id, sizeof(id));
398 }
399 
Curl_hash_offt_get(struct Curl_hash * h,curl_off_t id)400 void *Curl_hash_offt_get(struct Curl_hash *h, curl_off_t id)
401 {
402   return Curl_hash_pick(h, &id, sizeof(id));
403 }
404