162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Implementation of the hash table type.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci#include <linux/kernel.h>
862306a36Sopenharmony_ci#include <linux/slab.h>
962306a36Sopenharmony_ci#include <linux/errno.h>
1062306a36Sopenharmony_ci#include "hashtab.h"
1162306a36Sopenharmony_ci#include "security.h"
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_cistatic struct kmem_cache *hashtab_node_cachep __ro_after_init;
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci/*
1662306a36Sopenharmony_ci * Here we simply round the number of elements up to the nearest power of two.
1762306a36Sopenharmony_ci * I tried also other options like rounding down or rounding to the closest
1862306a36Sopenharmony_ci * power of two (up or down based on which is closer), but I was unable to
1962306a36Sopenharmony_ci * find any significant difference in lookup/insert performance that would
2062306a36Sopenharmony_ci * justify switching to a different (less intuitive) formula. It could be that
2162306a36Sopenharmony_ci * a different formula is actually more optimal, but any future changes here
2262306a36Sopenharmony_ci * should be supported with performance/memory usage data.
2362306a36Sopenharmony_ci *
2462306a36Sopenharmony_ci * The total memory used by the htable arrays (only) with Fedora policy loaded
2562306a36Sopenharmony_ci * is approximately 163 KB at the time of writing.
2662306a36Sopenharmony_ci */
2762306a36Sopenharmony_cistatic u32 hashtab_compute_size(u32 nel)
2862306a36Sopenharmony_ci{
2962306a36Sopenharmony_ci	return nel == 0 ? 0 : roundup_pow_of_two(nel);
3062306a36Sopenharmony_ci}
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ciint hashtab_init(struct hashtab *h, u32 nel_hint)
3362306a36Sopenharmony_ci{
3462306a36Sopenharmony_ci	u32 size = hashtab_compute_size(nel_hint);
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	/* should already be zeroed, but better be safe */
3762306a36Sopenharmony_ci	h->nel = 0;
3862306a36Sopenharmony_ci	h->size = 0;
3962306a36Sopenharmony_ci	h->htable = NULL;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	if (size) {
4262306a36Sopenharmony_ci		h->htable = kcalloc(size, sizeof(*h->htable), GFP_KERNEL);
4362306a36Sopenharmony_ci		if (!h->htable)
4462306a36Sopenharmony_ci			return -ENOMEM;
4562306a36Sopenharmony_ci		h->size = size;
4662306a36Sopenharmony_ci	}
4762306a36Sopenharmony_ci	return 0;
4862306a36Sopenharmony_ci}
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ciint __hashtab_insert(struct hashtab *h, struct hashtab_node **dst,
5162306a36Sopenharmony_ci		     void *key, void *datum)
5262306a36Sopenharmony_ci{
5362306a36Sopenharmony_ci	struct hashtab_node *newnode;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	newnode = kmem_cache_zalloc(hashtab_node_cachep, GFP_KERNEL);
5662306a36Sopenharmony_ci	if (!newnode)
5762306a36Sopenharmony_ci		return -ENOMEM;
5862306a36Sopenharmony_ci	newnode->key = key;
5962306a36Sopenharmony_ci	newnode->datum = datum;
6062306a36Sopenharmony_ci	newnode->next = *dst;
6162306a36Sopenharmony_ci	*dst = newnode;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	h->nel++;
6462306a36Sopenharmony_ci	return 0;
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_civoid hashtab_destroy(struct hashtab *h)
6862306a36Sopenharmony_ci{
6962306a36Sopenharmony_ci	u32 i;
7062306a36Sopenharmony_ci	struct hashtab_node *cur, *temp;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	for (i = 0; i < h->size; i++) {
7362306a36Sopenharmony_ci		cur = h->htable[i];
7462306a36Sopenharmony_ci		while (cur) {
7562306a36Sopenharmony_ci			temp = cur;
7662306a36Sopenharmony_ci			cur = cur->next;
7762306a36Sopenharmony_ci			kmem_cache_free(hashtab_node_cachep, temp);
7862306a36Sopenharmony_ci		}
7962306a36Sopenharmony_ci		h->htable[i] = NULL;
8062306a36Sopenharmony_ci	}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	kfree(h->htable);
8362306a36Sopenharmony_ci	h->htable = NULL;
8462306a36Sopenharmony_ci}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ciint hashtab_map(struct hashtab *h,
8762306a36Sopenharmony_ci		int (*apply)(void *k, void *d, void *args),
8862306a36Sopenharmony_ci		void *args)
8962306a36Sopenharmony_ci{
9062306a36Sopenharmony_ci	u32 i;
9162306a36Sopenharmony_ci	int ret;
9262306a36Sopenharmony_ci	struct hashtab_node *cur;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	for (i = 0; i < h->size; i++) {
9562306a36Sopenharmony_ci		cur = h->htable[i];
9662306a36Sopenharmony_ci		while (cur) {
9762306a36Sopenharmony_ci			ret = apply(cur->key, cur->datum, args);
9862306a36Sopenharmony_ci			if (ret)
9962306a36Sopenharmony_ci				return ret;
10062306a36Sopenharmony_ci			cur = cur->next;
10162306a36Sopenharmony_ci		}
10262306a36Sopenharmony_ci	}
10362306a36Sopenharmony_ci	return 0;
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci#ifdef CONFIG_SECURITY_SELINUX_DEBUG
10762306a36Sopenharmony_civoid hashtab_stat(struct hashtab *h, struct hashtab_info *info)
10862306a36Sopenharmony_ci{
10962306a36Sopenharmony_ci	u32 i, chain_len, slots_used, max_chain_len;
11062306a36Sopenharmony_ci	struct hashtab_node *cur;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	slots_used = 0;
11362306a36Sopenharmony_ci	max_chain_len = 0;
11462306a36Sopenharmony_ci	for (i = 0; i < h->size; i++) {
11562306a36Sopenharmony_ci		cur = h->htable[i];
11662306a36Sopenharmony_ci		if (cur) {
11762306a36Sopenharmony_ci			slots_used++;
11862306a36Sopenharmony_ci			chain_len = 0;
11962306a36Sopenharmony_ci			while (cur) {
12062306a36Sopenharmony_ci				chain_len++;
12162306a36Sopenharmony_ci				cur = cur->next;
12262306a36Sopenharmony_ci			}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci			if (chain_len > max_chain_len)
12562306a36Sopenharmony_ci				max_chain_len = chain_len;
12662306a36Sopenharmony_ci		}
12762306a36Sopenharmony_ci	}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	info->slots_used = slots_used;
13062306a36Sopenharmony_ci	info->max_chain_len = max_chain_len;
13162306a36Sopenharmony_ci}
13262306a36Sopenharmony_ci#endif /* CONFIG_SECURITY_SELINUX_DEBUG */
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ciint hashtab_duplicate(struct hashtab *new, struct hashtab *orig,
13562306a36Sopenharmony_ci		int (*copy)(struct hashtab_node *new,
13662306a36Sopenharmony_ci			struct hashtab_node *orig, void *args),
13762306a36Sopenharmony_ci		int (*destroy)(void *k, void *d, void *args),
13862306a36Sopenharmony_ci		void *args)
13962306a36Sopenharmony_ci{
14062306a36Sopenharmony_ci	struct hashtab_node *cur, *tmp, *tail;
14162306a36Sopenharmony_ci	u32 i;
14262306a36Sopenharmony_ci	int rc;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	memset(new, 0, sizeof(*new));
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	new->htable = kcalloc(orig->size, sizeof(*new->htable), GFP_KERNEL);
14762306a36Sopenharmony_ci	if (!new->htable)
14862306a36Sopenharmony_ci		return -ENOMEM;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	new->size = orig->size;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	for (i = 0; i < orig->size; i++) {
15362306a36Sopenharmony_ci		tail = NULL;
15462306a36Sopenharmony_ci		for (cur = orig->htable[i]; cur; cur = cur->next) {
15562306a36Sopenharmony_ci			tmp = kmem_cache_zalloc(hashtab_node_cachep,
15662306a36Sopenharmony_ci						GFP_KERNEL);
15762306a36Sopenharmony_ci			if (!tmp)
15862306a36Sopenharmony_ci				goto error;
15962306a36Sopenharmony_ci			rc = copy(tmp, cur, args);
16062306a36Sopenharmony_ci			if (rc) {
16162306a36Sopenharmony_ci				kmem_cache_free(hashtab_node_cachep, tmp);
16262306a36Sopenharmony_ci				goto error;
16362306a36Sopenharmony_ci			}
16462306a36Sopenharmony_ci			tmp->next = NULL;
16562306a36Sopenharmony_ci			if (!tail)
16662306a36Sopenharmony_ci				new->htable[i] = tmp;
16762306a36Sopenharmony_ci			else
16862306a36Sopenharmony_ci				tail->next = tmp;
16962306a36Sopenharmony_ci			tail = tmp;
17062306a36Sopenharmony_ci			new->nel++;
17162306a36Sopenharmony_ci		}
17262306a36Sopenharmony_ci	}
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	return 0;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci error:
17762306a36Sopenharmony_ci	for (i = 0; i < new->size; i++) {
17862306a36Sopenharmony_ci		for (cur = new->htable[i]; cur; cur = tmp) {
17962306a36Sopenharmony_ci			tmp = cur->next;
18062306a36Sopenharmony_ci			destroy(cur->key, cur->datum, args);
18162306a36Sopenharmony_ci			kmem_cache_free(hashtab_node_cachep, cur);
18262306a36Sopenharmony_ci		}
18362306a36Sopenharmony_ci	}
18462306a36Sopenharmony_ci	kfree(new->htable);
18562306a36Sopenharmony_ci	memset(new, 0, sizeof(*new));
18662306a36Sopenharmony_ci	return -ENOMEM;
18762306a36Sopenharmony_ci}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_civoid __init hashtab_cache_init(void)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci		hashtab_node_cachep = kmem_cache_create("hashtab_node",
19262306a36Sopenharmony_ci			sizeof(struct hashtab_node),
19362306a36Sopenharmony_ci			0, SLAB_PANIC, NULL);
19462306a36Sopenharmony_ci}
195