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