]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/hashmap.c
util: more modernizations
[thirdparty/systemd.git] / src / shared / hashmap.c
CommitLineData
d6c9574f 1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
60918275 2
a7334b09
LP
3/***
4 This file is part of systemd.
5
6 Copyright 2010 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
a7334b09
LP
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
5430f7f2 16 Lesser General Public License for more details.
a7334b09 17
5430f7f2 18 You should have received a copy of the GNU Lesser General Public License
a7334b09
LP
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
60918275
LP
22#include <assert.h>
23#include <stdlib.h>
24#include <string.h>
25#include <errno.h>
26
27#include "util.h"
28#include "hashmap.h"
29#include "macro.h"
30
31#define NBUCKETS 127
32
33struct hashmap_entry {
34 const void *key;
35 void *value;
60918275
LP
36 struct hashmap_entry *bucket_next, *bucket_previous;
37 struct hashmap_entry *iterate_next, *iterate_previous;
38};
39
40struct Hashmap {
41 hash_func_t hash_func;
42 compare_func_t compare_func;
43
44 struct hashmap_entry *iterate_list_head, *iterate_list_tail;
45 unsigned n_entries;
39c2a6f1
LP
46
47 bool from_pool;
60918275
LP
48};
49
50#define BY_HASH(h) ((struct hashmap_entry**) ((uint8_t*) (h) + ALIGN(sizeof(Hashmap))))
51
39c2a6f1
LP
52struct pool {
53 struct pool *next;
54 unsigned n_tiles;
55 unsigned n_used;
56};
57
6a39419f 58static struct pool *first_hashmap_pool = NULL;
39c2a6f1
LP
59static void *first_hashmap_tile = NULL;
60
6a39419f 61static struct pool *first_entry_pool = NULL;
39c2a6f1
LP
62static void *first_entry_tile = NULL;
63
64static void* allocate_tile(struct pool **first_pool, void **first_tile, size_t tile_size) {
65 unsigned i;
66
67 if (*first_tile) {
68 void *r;
69
70 r = *first_tile;
71 *first_tile = * (void**) (*first_tile);
72 return r;
73 }
74
75 if (_unlikely_(!*first_pool) || _unlikely_((*first_pool)->n_used >= (*first_pool)->n_tiles)) {
76 unsigned n;
77 size_t size;
78 struct pool *p;
79
80 n = *first_pool ? (*first_pool)->n_tiles : 0;
81 n = MAX(512U, n * 2);
82 size = PAGE_ALIGN(ALIGN(sizeof(struct pool)) + n*tile_size);
83 n = (size - ALIGN(sizeof(struct pool))) / tile_size;
84
85 p = malloc(size);
86 if (!p)
87 return NULL;
88
89 p->next = *first_pool;
90 p->n_tiles = n;
91 p->n_used = 0;
92
93 *first_pool = p;
94 }
95
96 i = (*first_pool)->n_used++;
97
98 return ((uint8_t*) (*first_pool)) + ALIGN(sizeof(struct pool)) + i*tile_size;
99}
100
101static void deallocate_tile(void **first_tile, void *p) {
102 * (void**) p = *first_tile;
103 *first_tile = p;
104}
105
106#ifndef __OPTIMIZE__
107
108static void drop_pool(struct pool *p) {
109 while (p) {
110 struct pool *n;
111 n = p->next;
112 free(p);
113 p = n;
114 }
115}
116
117__attribute__((destructor)) static void cleanup_pool(void) {
118 /* Be nice to valgrind */
119
120 drop_pool(first_hashmap_pool);
121 drop_pool(first_entry_pool);
122}
123
124#endif
125
60918275 126unsigned string_hash_func(const void *p) {
7dfe96ee
LP
127 unsigned hash = 5381;
128 const signed char *c;
129
130 /* DJB's hash function */
60918275
LP
131
132 for (c = p; *c; c++)
7dfe96ee 133 hash = (hash << 5) + hash + (unsigned) *c;
60918275
LP
134
135 return hash;
136}
137
138int string_compare_func(const void *a, const void *b) {
139 return strcmp(a, b);
140}
141
142unsigned trivial_hash_func(const void *p) {
143 return PTR_TO_UINT(p);
144}
145
146int trivial_compare_func(const void *a, const void *b) {
147 return a < b ? -1 : (a > b ? 1 : 0);
148}
149
150Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func) {
39c2a6f1 151 bool b;
60918275 152 Hashmap *h;
39c2a6f1
LP
153 size_t size;
154
155 b = is_main_thread();
156
157 size = ALIGN(sizeof(Hashmap)) + NBUCKETS * sizeof(struct hashmap_entry*);
60918275 158
39c2a6f1
LP
159 if (b) {
160 h = allocate_tile(&first_hashmap_pool, &first_hashmap_tile, size);
161 if (!h)
162 return NULL;
163
164 memset(h, 0, size);
165 } else {
166 h = malloc0(size);
167
168 if (!h)
169 return NULL;
170 }
60918275
LP
171
172 h->hash_func = hash_func ? hash_func : trivial_hash_func;
173 h->compare_func = compare_func ? compare_func : trivial_compare_func;
174
175 h->n_entries = 0;
176 h->iterate_list_head = h->iterate_list_tail = NULL;
177
39c2a6f1
LP
178 h->from_pool = b;
179
60918275
LP
180 return h;
181}
182
034c6ed7
LP
183int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func) {
184 assert(h);
185
186 if (*h)
187 return 0;
188
189 if (!(*h = hashmap_new(hash_func, compare_func)))
190 return -ENOMEM;
191
192 return 0;
193}
194
101d8e63
LP
195static void link_entry(Hashmap *h, struct hashmap_entry *e, unsigned hash) {
196 assert(h);
197 assert(e);
198
199 /* Insert into hash table */
200 e->bucket_next = BY_HASH(h)[hash];
201 e->bucket_previous = NULL;
202 if (BY_HASH(h)[hash])
203 BY_HASH(h)[hash]->bucket_previous = e;
204 BY_HASH(h)[hash] = e;
205
206 /* Insert into iteration list */
207 e->iterate_previous = h->iterate_list_tail;
208 e->iterate_next = NULL;
209 if (h->iterate_list_tail) {
210 assert(h->iterate_list_head);
211 h->iterate_list_tail->iterate_next = e;
212 } else {
213 assert(!h->iterate_list_head);
214 h->iterate_list_head = e;
215 }
216 h->iterate_list_tail = e;
217
218 h->n_entries++;
219 assert(h->n_entries >= 1);
220}
221
222static void unlink_entry(Hashmap *h, struct hashmap_entry *e, unsigned hash) {
60918275
LP
223 assert(h);
224 assert(e);
225
226 /* Remove from iteration list */
227 if (e->iterate_next)
228 e->iterate_next->iterate_previous = e->iterate_previous;
229 else
230 h->iterate_list_tail = e->iterate_previous;
231
232 if (e->iterate_previous)
233 e->iterate_previous->iterate_next = e->iterate_next;
234 else
235 h->iterate_list_head = e->iterate_next;
236
237 /* Remove from hash table bucket list */
238 if (e->bucket_next)
239 e->bucket_next->bucket_previous = e->bucket_previous;
240
241 if (e->bucket_previous)
242 e->bucket_previous->bucket_next = e->bucket_next;
101d8e63 243 else
60918275 244 BY_HASH(h)[hash] = e->bucket_next;
60918275
LP
245
246 assert(h->n_entries >= 1);
247 h->n_entries--;
248}
249
101d8e63
LP
250static void remove_entry(Hashmap *h, struct hashmap_entry *e) {
251 unsigned hash;
252
253 assert(h);
254 assert(e);
255
256 hash = h->hash_func(e->key) % NBUCKETS;
257
258 unlink_entry(h, e, hash);
39c2a6f1
LP
259
260 if (h->from_pool)
261 deallocate_tile(&first_entry_tile, e);
262 else
263 free(e);
101d8e63
LP
264}
265
60918275
LP
266void hashmap_free(Hashmap*h) {
267
268 if (!h)
269 return;
270
11dd41ce 271 hashmap_clear(h);
60918275 272
39c2a6f1
LP
273 if (h->from_pool)
274 deallocate_tile(&first_hashmap_tile, h);
275 else
276 free(h);
60918275
LP
277}
278
449ddb2d 279void hashmap_free_free(Hashmap *h) {
61b1477c
LP
280 if (!h)
281 return;
282
9946996c 283 hashmap_clear_free(h);
449ddb2d
LP
284 hashmap_free(h);
285}
286
11dd41ce
LP
287void hashmap_clear(Hashmap *h) {
288 if (!h)
289 return;
290
291 while (h->iterate_list_head)
292 remove_entry(h, h->iterate_list_head);
293}
294
9946996c
LP
295void hashmap_clear_free(Hashmap *h) {
296 void *p;
297
61b1477c
LP
298 if (!h)
299 return;
9946996c
LP
300
301 while ((p = hashmap_steal_first(h)))
302 free(p);
303}
304
60918275
LP
305static struct hashmap_entry *hash_scan(Hashmap *h, unsigned hash, const void *key) {
306 struct hashmap_entry *e;
307 assert(h);
308 assert(hash < NBUCKETS);
309
310 for (e = BY_HASH(h)[hash]; e; e = e->bucket_next)
311 if (h->compare_func(e->key, key) == 0)
312 return e;
313
314 return NULL;
315}
316
317int hashmap_put(Hashmap *h, const void *key, void *value) {
318 struct hashmap_entry *e;
319 unsigned hash;
320
321 assert(h);
322
323 hash = h->hash_func(key) % NBUCKETS;
324
3158713e
LP
325 if ((e = hash_scan(h, hash, key))) {
326
327 if (e->value == value)
328 return 0;
329
60918275 330 return -EEXIST;
3158713e 331 }
60918275 332
39c2a6f1
LP
333 if (h->from_pool)
334 e = allocate_tile(&first_entry_pool, &first_entry_tile, sizeof(struct hashmap_entry));
335 else
336 e = new(struct hashmap_entry, 1);
337
338 if (!e)
60918275
LP
339 return -ENOMEM;
340
341 e->key = key;
342 e->value = value;
343
101d8e63 344 link_entry(h, e, hash);
60918275 345
48507e66 346 return 1;
60918275
LP
347}
348
3158713e
LP
349int hashmap_replace(Hashmap *h, const void *key, void *value) {
350 struct hashmap_entry *e;
351 unsigned hash;
352
353 assert(h);
354
355 hash = h->hash_func(key) % NBUCKETS;
356
357 if ((e = hash_scan(h, hash, key))) {
101d8e63 358 e->key = key;
3158713e
LP
359 e->value = value;
360 return 0;
361 }
362
363 return hashmap_put(h, key, value);
364}
365
60918275
LP
366void* hashmap_get(Hashmap *h, const void *key) {
367 unsigned hash;
368 struct hashmap_entry *e;
369
370 if (!h)
371 return NULL;
372
373 hash = h->hash_func(key) % NBUCKETS;
374
375 if (!(e = hash_scan(h, hash, key)))
376 return NULL;
377
378 return e->value;
379}
380
96342de6
LN
381bool hashmap_contains(Hashmap *h, const void *key) {
382 unsigned hash;
96342de6
LN
383
384 if (!h)
385 return false;
386
387 hash = h->hash_func(key) % NBUCKETS;
388
9f89986d 389 if (!hash_scan(h, hash, key))
96342de6
LN
390 return false;
391
392 return true;
393}
394
60918275
LP
395void* hashmap_remove(Hashmap *h, const void *key) {
396 struct hashmap_entry *e;
397 unsigned hash;
398 void *data;
399
400 if (!h)
401 return NULL;
402
403 hash = h->hash_func(key) % NBUCKETS;
404
405 if (!(e = hash_scan(h, hash, key)))
406 return NULL;
407
408 data = e->value;
409 remove_entry(h, e);
410
411 return data;
412}
413
101d8e63
LP
414int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value) {
415 struct hashmap_entry *e;
416 unsigned old_hash, new_hash;
417
418 if (!h)
419 return -ENOENT;
420
421 old_hash = h->hash_func(old_key) % NBUCKETS;
422 if (!(e = hash_scan(h, old_hash, old_key)))
423 return -ENOENT;
424
425 new_hash = h->hash_func(new_key) % NBUCKETS;
426 if (hash_scan(h, new_hash, new_key))
427 return -EEXIST;
428
429 unlink_entry(h, e, old_hash);
430
431 e->key = new_key;
432 e->value = value;
433
434 link_entry(h, e, new_hash);
435
436 return 0;
437}
438
8fe914ec
LP
439int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value) {
440 struct hashmap_entry *e, *k;
441 unsigned old_hash, new_hash;
442
443 if (!h)
444 return -ENOENT;
445
446 old_hash = h->hash_func(old_key) % NBUCKETS;
447 if (!(e = hash_scan(h, old_hash, old_key)))
448 return -ENOENT;
449
450 new_hash = h->hash_func(new_key) % NBUCKETS;
451
452 if ((k = hash_scan(h, new_hash, new_key)))
453 if (e != k)
454 remove_entry(h, k);
455
456 unlink_entry(h, e, old_hash);
457
458 e->key = new_key;
459 e->value = value;
460
461 link_entry(h, e, new_hash);
462
463 return 0;
464}
465
3158713e
LP
466void* hashmap_remove_value(Hashmap *h, const void *key, void *value) {
467 struct hashmap_entry *e;
468 unsigned hash;
469
470 if (!h)
471 return NULL;
472
473 hash = h->hash_func(key) % NBUCKETS;
474
475 if (!(e = hash_scan(h, hash, key)))
476 return NULL;
477
478 if (e->value != value)
479 return NULL;
480
481 remove_entry(h, e);
482
483 return value;
484}
485
034c6ed7 486void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key) {
60918275
LP
487 struct hashmap_entry *e;
488
034c6ed7 489 assert(i);
60918275
LP
490
491 if (!h)
492 goto at_end;
493
034c6ed7 494 if (*i == ITERATOR_LAST)
60918275
LP
495 goto at_end;
496
034c6ed7 497 if (*i == ITERATOR_FIRST && !h->iterate_list_head)
60918275
LP
498 goto at_end;
499
034c6ed7 500 e = *i == ITERATOR_FIRST ? h->iterate_list_head : (struct hashmap_entry*) *i;
60918275
LP
501
502 if (e->iterate_next)
034c6ed7 503 *i = (Iterator) e->iterate_next;
60918275 504 else
034c6ed7 505 *i = ITERATOR_LAST;
60918275
LP
506
507 if (key)
508 *key = e->key;
509
510 return e->value;
511
512at_end:
034c6ed7 513 *i = ITERATOR_LAST;
60918275
LP
514
515 if (key)
516 *key = NULL;
517
518 return NULL;
519}
520
034c6ed7 521void *hashmap_iterate_backwards(Hashmap *h, Iterator *i, const void **key) {
60918275
LP
522 struct hashmap_entry *e;
523
034c6ed7 524 assert(i);
60918275
LP
525
526 if (!h)
527 goto at_beginning;
528
034c6ed7 529 if (*i == ITERATOR_FIRST)
60918275
LP
530 goto at_beginning;
531
034c6ed7 532 if (*i == ITERATOR_LAST && !h->iterate_list_tail)
60918275
LP
533 goto at_beginning;
534
034c6ed7 535 e = *i == ITERATOR_LAST ? h->iterate_list_tail : (struct hashmap_entry*) *i;
60918275
LP
536
537 if (e->iterate_previous)
034c6ed7 538 *i = (Iterator) e->iterate_previous;
60918275 539 else
034c6ed7 540 *i = ITERATOR_FIRST;
60918275
LP
541
542 if (key)
543 *key = e->key;
544
545 return e->value;
546
547at_beginning:
034c6ed7 548 *i = ITERATOR_FIRST;
60918275
LP
549
550 if (key)
551 *key = NULL;
552
553 return NULL;
554}
555
034c6ed7
LP
556void *hashmap_iterate_skip(Hashmap *h, const void *key, Iterator *i) {
557 unsigned hash;
558 struct hashmap_entry *e;
559
560 if (!h)
561 return NULL;
562
563 hash = h->hash_func(key) % NBUCKETS;
564
565 if (!(e = hash_scan(h, hash, key)))
566 return NULL;
567
568 *i = (Iterator) e;
569
570 return e->value;
571}
572
60918275
LP
573void* hashmap_first(Hashmap *h) {
574
575 if (!h)
576 return NULL;
577
578 if (!h->iterate_list_head)
579 return NULL;
580
581 return h->iterate_list_head->value;
582}
583
2e4a6ff4
LP
584void* hashmap_first_key(Hashmap *h) {
585
586 if (!h)
587 return NULL;
588
589 if (!h->iterate_list_head)
590 return NULL;
591
592 return (void*) h->iterate_list_head->key;
593}
594
60918275
LP
595void* hashmap_last(Hashmap *h) {
596
597 if (!h)
598 return NULL;
599
600 if (!h->iterate_list_tail)
601 return NULL;
602
603 return h->iterate_list_tail->value;
604}
605
606void* hashmap_steal_first(Hashmap *h) {
607 void *data;
608
609 if (!h)
610 return NULL;
611
612 if (!h->iterate_list_head)
613 return NULL;
614
615 data = h->iterate_list_head->value;
616 remove_entry(h, h->iterate_list_head);
617
618 return data;
619}
620
22be093f
LP
621void* hashmap_steal_first_key(Hashmap *h) {
622 void *key;
623
624 if (!h)
625 return NULL;
626
627 if (!h->iterate_list_head)
628 return NULL;
629
630 key = (void*) h->iterate_list_head->key;
631 remove_entry(h, h->iterate_list_head);
632
633 return key;
634}
635
60918275
LP
636unsigned hashmap_size(Hashmap *h) {
637
638 if (!h)
639 return 0;
640
641 return h->n_entries;
642}
643
644bool hashmap_isempty(Hashmap *h) {
645
646 if (!h)
647 return true;
648
649 return h->n_entries == 0;
650}
91cdde8a
LP
651
652int hashmap_merge(Hashmap *h, Hashmap *other) {
653 struct hashmap_entry *e;
654
655 assert(h);
656
657 if (!other)
658 return 0;
659
660 for (e = other->iterate_list_head; e; e = e->iterate_next) {
661 int r;
662
663 if ((r = hashmap_put(h, e->key, e->value)) < 0)
664 if (r != -EEXIST)
665 return r;
666 }
667
668 return 0;
669}
670
101d8e63
LP
671void hashmap_move(Hashmap *h, Hashmap *other) {
672 struct hashmap_entry *e, *n;
673
674 assert(h);
675
676 /* The same as hashmap_merge(), but every new item from other
677 * is moved to h. This function is guaranteed to succeed. */
678
679 if (!other)
680 return;
681
682 for (e = other->iterate_list_head; e; e = n) {
683 unsigned h_hash, other_hash;
684
685 n = e->iterate_next;
686
687 h_hash = h->hash_func(e->key) % NBUCKETS;
688
689 if (hash_scan(h, h_hash, e->key))
690 continue;
691
692 other_hash = other->hash_func(e->key) % NBUCKETS;
693
694 unlink_entry(other, e, other_hash);
695 link_entry(h, e, h_hash);
696 }
697}
698
699int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key) {
700 unsigned h_hash, other_hash;
701 struct hashmap_entry *e;
702
703 if (!other)
704 return 0;
705
706 assert(h);
707
708 h_hash = h->hash_func(key) % NBUCKETS;
709 if (hash_scan(h, h_hash, key))
710 return -EEXIST;
711
712 other_hash = other->hash_func(key) % NBUCKETS;
713 if (!(e = hash_scan(other, other_hash, key)))
714 return -ENOENT;
715
716 unlink_entry(other, e, other_hash);
717 link_entry(h, e, h_hash);
718
719 return 0;
720}
721
91cdde8a
LP
722Hashmap *hashmap_copy(Hashmap *h) {
723 Hashmap *copy;
724
725 assert(h);
726
727 if (!(copy = hashmap_new(h->hash_func, h->compare_func)))
728 return NULL;
729
730 if (hashmap_merge(copy, h) < 0) {
731 hashmap_free(copy);
732 return NULL;
733 }
734
735 return copy;
736}
db1413d7
KS
737
738char **hashmap_get_strv(Hashmap *h) {
739 char **sv;
740 Iterator it;
729e3769 741 char *item;
db1413d7
KS
742 int n;
743
729e3769
LP
744 sv = new(char*, h->n_entries+1);
745 if (!sv)
db1413d7
KS
746 return NULL;
747
748 n = 0;
729e3769
LP
749 HASHMAP_FOREACH(item, h, it)
750 sv[n++] = item;
db1413d7
KS
751 sv[n] = NULL;
752
753 return sv;
754}