| // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) | 
 |  | 
 | /* | 
 |  * Generic non-thread safe hash map implementation. | 
 |  * | 
 |  * Copyright (c) 2019 Facebook | 
 |  */ | 
 | #include <stdint.h> | 
 | #include <stdlib.h> | 
 | #include <stdio.h> | 
 | #include <errno.h> | 
 | #include <linux/err.h> | 
 | #include "hashmap.h" | 
 |  | 
 | /* make sure libbpf doesn't use kernel-only integer typedefs */ | 
 | #pragma GCC poison u8 u16 u32 u64 s8 s16 s32 s64 | 
 |  | 
 | /* prevent accidental re-addition of reallocarray() */ | 
 | #pragma GCC poison reallocarray | 
 |  | 
 | /* start with 4 buckets */ | 
 | #define HASHMAP_MIN_CAP_BITS 2 | 
 |  | 
 | static void hashmap_add_entry(struct hashmap_entry **pprev, | 
 | 			      struct hashmap_entry *entry) | 
 | { | 
 | 	entry->next = *pprev; | 
 | 	*pprev = entry; | 
 | } | 
 |  | 
 | static void hashmap_del_entry(struct hashmap_entry **pprev, | 
 | 			      struct hashmap_entry *entry) | 
 | { | 
 | 	*pprev = entry->next; | 
 | 	entry->next = NULL; | 
 | } | 
 |  | 
 | void hashmap__init(struct hashmap *map, hashmap_hash_fn hash_fn, | 
 | 		   hashmap_equal_fn equal_fn, void *ctx) | 
 | { | 
 | 	map->hash_fn = hash_fn; | 
 | 	map->equal_fn = equal_fn; | 
 | 	map->ctx = ctx; | 
 |  | 
 | 	map->buckets = NULL; | 
 | 	map->cap = 0; | 
 | 	map->cap_bits = 0; | 
 | 	map->sz = 0; | 
 | } | 
 |  | 
 | struct hashmap *hashmap__new(hashmap_hash_fn hash_fn, | 
 | 			     hashmap_equal_fn equal_fn, | 
 | 			     void *ctx) | 
 | { | 
 | 	struct hashmap *map = malloc(sizeof(struct hashmap)); | 
 |  | 
 | 	if (!map) | 
 | 		return ERR_PTR(-ENOMEM); | 
 | 	hashmap__init(map, hash_fn, equal_fn, ctx); | 
 | 	return map; | 
 | } | 
 |  | 
 | void hashmap__clear(struct hashmap *map) | 
 | { | 
 | 	struct hashmap_entry *cur, *tmp; | 
 | 	size_t bkt; | 
 |  | 
 | 	hashmap__for_each_entry_safe(map, cur, tmp, bkt) { | 
 | 		free(cur); | 
 | 	} | 
 | 	free(map->buckets); | 
 | 	map->buckets = NULL; | 
 | 	map->cap = map->cap_bits = map->sz = 0; | 
 | } | 
 |  | 
 | void hashmap__free(struct hashmap *map) | 
 | { | 
 | 	if (!map) | 
 | 		return; | 
 |  | 
 | 	hashmap__clear(map); | 
 | 	free(map); | 
 | } | 
 |  | 
 | size_t hashmap__size(const struct hashmap *map) | 
 | { | 
 | 	return map->sz; | 
 | } | 
 |  | 
 | size_t hashmap__capacity(const struct hashmap *map) | 
 | { | 
 | 	return map->cap; | 
 | } | 
 |  | 
 | static bool hashmap_needs_to_grow(struct hashmap *map) | 
 | { | 
 | 	/* grow if empty or more than 75% filled */ | 
 | 	return (map->cap == 0) || ((map->sz + 1) * 4 / 3 > map->cap); | 
 | } | 
 |  | 
 | static int hashmap_grow(struct hashmap *map) | 
 | { | 
 | 	struct hashmap_entry **new_buckets; | 
 | 	struct hashmap_entry *cur, *tmp; | 
 | 	size_t new_cap_bits, new_cap; | 
 | 	size_t h, bkt; | 
 |  | 
 | 	new_cap_bits = map->cap_bits + 1; | 
 | 	if (new_cap_bits < HASHMAP_MIN_CAP_BITS) | 
 | 		new_cap_bits = HASHMAP_MIN_CAP_BITS; | 
 |  | 
 | 	new_cap = 1UL << new_cap_bits; | 
 | 	new_buckets = calloc(new_cap, sizeof(new_buckets[0])); | 
 | 	if (!new_buckets) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	hashmap__for_each_entry_safe(map, cur, tmp, bkt) { | 
 | 		h = hash_bits(map->hash_fn(cur->key, map->ctx), new_cap_bits); | 
 | 		hashmap_add_entry(&new_buckets[h], cur); | 
 | 	} | 
 |  | 
 | 	map->cap = new_cap; | 
 | 	map->cap_bits = new_cap_bits; | 
 | 	free(map->buckets); | 
 | 	map->buckets = new_buckets; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static bool hashmap_find_entry(const struct hashmap *map, | 
 | 			       const void *key, size_t hash, | 
 | 			       struct hashmap_entry ***pprev, | 
 | 			       struct hashmap_entry **entry) | 
 | { | 
 | 	struct hashmap_entry *cur, **prev_ptr; | 
 |  | 
 | 	if (!map->buckets) | 
 | 		return false; | 
 |  | 
 | 	for (prev_ptr = &map->buckets[hash], cur = *prev_ptr; | 
 | 	     cur; | 
 | 	     prev_ptr = &cur->next, cur = cur->next) { | 
 | 		if (map->equal_fn(cur->key, key, map->ctx)) { | 
 | 			if (pprev) | 
 | 				*pprev = prev_ptr; | 
 | 			*entry = cur; | 
 | 			return true; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return false; | 
 | } | 
 |  | 
 | int hashmap__insert(struct hashmap *map, const void *key, void *value, | 
 | 		    enum hashmap_insert_strategy strategy, | 
 | 		    const void **old_key, void **old_value) | 
 | { | 
 | 	struct hashmap_entry *entry; | 
 | 	size_t h; | 
 | 	int err; | 
 |  | 
 | 	if (old_key) | 
 | 		*old_key = NULL; | 
 | 	if (old_value) | 
 | 		*old_value = NULL; | 
 |  | 
 | 	h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits); | 
 | 	if (strategy != HASHMAP_APPEND && | 
 | 	    hashmap_find_entry(map, key, h, NULL, &entry)) { | 
 | 		if (old_key) | 
 | 			*old_key = entry->key; | 
 | 		if (old_value) | 
 | 			*old_value = entry->value; | 
 |  | 
 | 		if (strategy == HASHMAP_SET || strategy == HASHMAP_UPDATE) { | 
 | 			entry->key = key; | 
 | 			entry->value = value; | 
 | 			return 0; | 
 | 		} else if (strategy == HASHMAP_ADD) { | 
 | 			return -EEXIST; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (strategy == HASHMAP_UPDATE) | 
 | 		return -ENOENT; | 
 |  | 
 | 	if (hashmap_needs_to_grow(map)) { | 
 | 		err = hashmap_grow(map); | 
 | 		if (err) | 
 | 			return err; | 
 | 		h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits); | 
 | 	} | 
 |  | 
 | 	entry = malloc(sizeof(struct hashmap_entry)); | 
 | 	if (!entry) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	entry->key = key; | 
 | 	entry->value = value; | 
 | 	hashmap_add_entry(&map->buckets[h], entry); | 
 | 	map->sz++; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | bool hashmap__find(const struct hashmap *map, const void *key, void **value) | 
 | { | 
 | 	struct hashmap_entry *entry; | 
 | 	size_t h; | 
 |  | 
 | 	h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits); | 
 | 	if (!hashmap_find_entry(map, key, h, NULL, &entry)) | 
 | 		return false; | 
 |  | 
 | 	if (value) | 
 | 		*value = entry->value; | 
 | 	return true; | 
 | } | 
 |  | 
 | bool hashmap__delete(struct hashmap *map, const void *key, | 
 | 		     const void **old_key, void **old_value) | 
 | { | 
 | 	struct hashmap_entry **pprev, *entry; | 
 | 	size_t h; | 
 |  | 
 | 	h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits); | 
 | 	if (!hashmap_find_entry(map, key, h, &pprev, &entry)) | 
 | 		return false; | 
 |  | 
 | 	if (old_key) | 
 | 		*old_key = entry->key; | 
 | 	if (old_value) | 
 | 		*old_value = entry->value; | 
 |  | 
 | 	hashmap_del_entry(pprev, entry); | 
 | 	free(entry); | 
 | 	map->sz--; | 
 |  | 
 | 	return true; | 
 | } | 
 |  |