mirror of
https://salsa.debian.org/ha-team/libqb
synced 2025-12-29 00:41:21 +00:00
At the moment if you have multiple notifiers it becomes impossible to figure out when it is safe to free the values. This new callback can only be added once and is called once, making it better suited to freeing memory. It is also called after the deleted and replaced notifiers. Signed-off-by: Angus Salkeld <asalkeld@redhat.com>
480 lines
11 KiB
C
480 lines
11 KiB
C
/*
|
|
* Copyright (C) 2010 Red Hat, Inc.
|
|
*
|
|
* Author: Steven Dake <sdake@redhat.com>
|
|
*
|
|
* This file is part of libqb.
|
|
*
|
|
* libqb is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 2.1 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* libqb is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with libqb. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "os_base.h"
|
|
|
|
#include <qb/qbmap.h>
|
|
#include "util_int.h"
|
|
#include "map_int.h"
|
|
#include <qb/qblist.h>
|
|
|
|
#define FNV_32_PRIME ((uint32_t)0x01000193)
|
|
|
|
struct hash_node {
|
|
struct qb_list_head list;
|
|
void *value;
|
|
const char *key;
|
|
uint32_t refcount;
|
|
struct qb_list_head notifier_head;
|
|
};
|
|
|
|
struct hash_bucket {
|
|
struct qb_list_head list_head;
|
|
};
|
|
|
|
struct hash_table {
|
|
struct qb_map map;
|
|
size_t count;
|
|
uint32_t order;
|
|
uint32_t hash_buckets_len;
|
|
struct hash_bucket hash_buckets[0];
|
|
struct qb_list_head notifier_head;
|
|
};
|
|
|
|
struct hashtable_iter {
|
|
struct qb_map_iter i;
|
|
struct hash_node *node;
|
|
uint32_t bucket;
|
|
};
|
|
|
|
static void hashtable_notify(struct hash_table *t, struct hash_node *n,
|
|
uint32_t event, const char *key,
|
|
void *old_value, void *value);
|
|
|
|
static uint32_t
|
|
hash_fnv(const void *value, uint32_t valuelen, uint32_t order)
|
|
{
|
|
uint8_t *cd = (uint8_t *) value;
|
|
uint8_t *ed = (uint8_t *) value + valuelen;
|
|
uint32_t hash_result = 0x811c9dc5;
|
|
int32_t res;
|
|
|
|
while (cd < ed) {
|
|
hash_result ^= *cd;
|
|
hash_result *= FNV_32_PRIME;
|
|
cd++;
|
|
}
|
|
res = ((hash_result >> order) ^ hash_result) & ((1 << order) - 1);
|
|
return (res);
|
|
}
|
|
|
|
static uint32_t
|
|
qb_hash_string(const void *key, uint32_t order)
|
|
{
|
|
char *str = (char *)key;
|
|
return hash_fnv(key, strlen(str), order);
|
|
}
|
|
|
|
static struct hash_node *
|
|
hashtable_lookup(struct hash_table *t, const char *key)
|
|
{
|
|
uint32_t hash_entry;
|
|
struct qb_list_head *list;
|
|
struct hash_node *hash_node;
|
|
|
|
hash_entry = qb_hash_string(key, t->order);
|
|
|
|
for (list = t->hash_buckets[hash_entry].list_head.next;
|
|
list != &t->hash_buckets[hash_entry].list_head;
|
|
list = list->next) {
|
|
|
|
hash_node = qb_list_entry(list, struct hash_node, list);
|
|
if (strcmp(hash_node->key, key) == 0) {
|
|
return hash_node;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void *
|
|
hashtable_get(struct qb_map *map, const char *key)
|
|
{
|
|
struct hash_table *t = (struct hash_table *)map;
|
|
struct hash_node *n = hashtable_lookup(t, key);
|
|
if (n) {
|
|
return n->value;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
hashtable_node_deref(struct qb_map *map, struct hash_node *hash_node)
|
|
{
|
|
struct hash_table *t = (struct hash_table *)map;
|
|
|
|
hash_node->refcount--;
|
|
if (hash_node->refcount > 0) {
|
|
return;
|
|
}
|
|
hashtable_notify(t, hash_node,
|
|
QB_MAP_NOTIFY_DELETED,
|
|
hash_node->key, hash_node->value, NULL);
|
|
qb_list_del(&hash_node->list);
|
|
free(hash_node);
|
|
}
|
|
|
|
static int32_t
|
|
hashtable_rm_with_hash(struct qb_map *map, const char *key, uint32_t hash_entry)
|
|
{
|
|
struct hash_table *hash_table = (struct hash_table *)map;
|
|
struct qb_list_head *list;
|
|
struct hash_node *hash_node;
|
|
|
|
for (list = hash_table->hash_buckets[hash_entry].list_head.next;
|
|
list != &hash_table->hash_buckets[hash_entry].list_head;
|
|
list = list->next) {
|
|
|
|
hash_node = qb_list_entry(list, struct hash_node, list);
|
|
if (strcmp(hash_node->key, key) == 0) {
|
|
hashtable_node_deref(map, hash_node);
|
|
hash_table->count--;
|
|
return QB_TRUE;
|
|
}
|
|
}
|
|
|
|
return QB_FALSE;
|
|
}
|
|
|
|
static int32_t
|
|
hashtable_rm(struct qb_map *map, const char *key)
|
|
{
|
|
struct hash_table *hash_table = (struct hash_table *)map;
|
|
uint32_t hash_entry;
|
|
|
|
hash_entry = qb_hash_string(key, hash_table->order);
|
|
return hashtable_rm_with_hash(map, key, hash_entry);
|
|
}
|
|
|
|
static void
|
|
hashtable_put(struct qb_map *map, const char *key, const void *value)
|
|
{
|
|
struct hash_table *hash_table = (struct hash_table *)map;
|
|
uint32_t hash_entry;
|
|
struct hash_node *hash_node = NULL;
|
|
struct hash_node *node_try;
|
|
struct qb_list_head *list;
|
|
|
|
hash_entry = qb_hash_string(key, hash_table->order);
|
|
|
|
for (list = hash_table->hash_buckets[hash_entry].list_head.next;
|
|
list != &hash_table->hash_buckets[hash_entry].list_head;
|
|
list = list->next) {
|
|
|
|
node_try = qb_list_entry(list, struct hash_node, list);
|
|
if (strcmp(node_try->key, key) == 0) {
|
|
hash_node = node_try;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (hash_node == NULL) {
|
|
hash_node = calloc(1, sizeof(struct hash_node));
|
|
if (hash_node == NULL) {
|
|
errno = ENOMEM;
|
|
return;
|
|
}
|
|
|
|
hash_table->count++;
|
|
hash_node->refcount = 1;
|
|
hash_node->key = key;
|
|
hash_node->value = (void *)value;
|
|
qb_list_init(&hash_node->list);
|
|
qb_list_add_tail(&hash_node->list,
|
|
&hash_table->hash_buckets[hash_entry].
|
|
list_head);
|
|
qb_list_init(&hash_node->notifier_head);
|
|
|
|
hashtable_notify(hash_table, hash_node,
|
|
QB_MAP_NOTIFY_INSERTED,
|
|
hash_node->key, NULL, hash_node->value);
|
|
} else {
|
|
char *old_k = (char *)hash_node->key;
|
|
char *old_v = (void *)hash_node->value;
|
|
hash_node->key = key;
|
|
hash_node->value = (void *)value;
|
|
|
|
hashtable_notify(hash_table, hash_node,
|
|
QB_MAP_NOTIFY_REPLACED,
|
|
old_k, old_v, hash_node->value);
|
|
}
|
|
}
|
|
|
|
static void
|
|
hashtable_notify(struct hash_table *t, struct hash_node *n,
|
|
uint32_t event, const char *key,
|
|
void *old_value, void *value)
|
|
{
|
|
struct qb_list_head *list;
|
|
struct qb_map_notifier *tn;
|
|
|
|
for (list = n->notifier_head.next;
|
|
list != &n->notifier_head; list = list->next) {
|
|
tn = qb_list_entry(list, struct qb_map_notifier, list);
|
|
|
|
if (tn->events & event) {
|
|
tn->callback(event, (char *)key, old_value, value,
|
|
tn->user_data);
|
|
}
|
|
}
|
|
for (list = t->notifier_head.next;
|
|
list != &t->notifier_head; list = list->next) {
|
|
tn = qb_list_entry(list, struct qb_map_notifier, list);
|
|
|
|
if (tn->events & event) {
|
|
tn->callback(event, (char *)key, old_value, value,
|
|
tn->user_data);
|
|
}
|
|
if (((event & QB_MAP_NOTIFY_DELETED) ||
|
|
(event & QB_MAP_NOTIFY_REPLACED)) &&
|
|
(tn->events & QB_MAP_NOTIFY_FREE)) {
|
|
tn->callback(QB_MAP_NOTIFY_FREE, (char *)key,
|
|
old_value, value, tn->user_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int32_t
|
|
hashtable_notify_add(qb_map_t * m, const char *key,
|
|
qb_map_notify_fn fn, int32_t events, void *user_data)
|
|
{
|
|
struct hash_table *t = (struct hash_table *)m;
|
|
struct qb_map_notifier *f;
|
|
struct hash_node *n;
|
|
struct qb_list_head *head = NULL;
|
|
struct qb_list_head *list;
|
|
int add_to_tail = QB_FALSE;
|
|
|
|
if (key) {
|
|
n = hashtable_lookup(t, key);
|
|
if (n) {
|
|
head = &n->notifier_head;
|
|
}
|
|
} else {
|
|
head = &t->notifier_head;
|
|
}
|
|
if (head == NULL) {
|
|
return -ENOENT;
|
|
}
|
|
if (events & QB_MAP_NOTIFY_FREE) {
|
|
add_to_tail = QB_TRUE;
|
|
}
|
|
|
|
for (list = head->next; list != head; list = list->next) {
|
|
f = qb_list_entry(list, struct qb_map_notifier, list);
|
|
|
|
if (events & QB_MAP_NOTIFY_FREE &&
|
|
f->events == events) {
|
|
/* only one free notifier */
|
|
return -EEXIST;
|
|
}
|
|
if (f->events == events &&
|
|
f->user_data == user_data &&
|
|
f->callback == fn) {
|
|
return -EEXIST;
|
|
}
|
|
}
|
|
|
|
f = malloc(sizeof(struct qb_map_notifier));
|
|
if (f == NULL) {
|
|
return -errno;
|
|
}
|
|
f->events = events;
|
|
f->user_data = user_data;
|
|
f->callback = fn;
|
|
qb_list_init(&f->list);
|
|
|
|
if (add_to_tail) {
|
|
qb_list_add_tail(&f->list, head);
|
|
} else {
|
|
qb_list_add(&f->list, head);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int32_t
|
|
hashtable_notify_del(qb_map_t * m, const char *key,
|
|
qb_map_notify_fn fn, int32_t events,
|
|
int32_t cmp_userdata, void *user_data)
|
|
{
|
|
struct hash_table *t = (struct hash_table *)m;
|
|
struct qb_map_notifier *f;
|
|
struct hash_node *n;
|
|
struct qb_list_head *head = NULL;
|
|
struct qb_list_head *list;
|
|
struct qb_list_head *next;
|
|
int32_t found = QB_FALSE;
|
|
|
|
if (key) {
|
|
n = hashtable_lookup(t, key);
|
|
if (n) {
|
|
head = &n->notifier_head;
|
|
}
|
|
} else {
|
|
head = &t->notifier_head;
|
|
}
|
|
if (head == NULL) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
for (list = head->next; list != head; list = next) {
|
|
f = qb_list_entry(list, struct qb_map_notifier, list);
|
|
next = list->next;
|
|
|
|
if (f->events == events && f->callback == fn) {
|
|
if (cmp_userdata && (f->user_data == user_data)) {
|
|
found = QB_TRUE;
|
|
qb_list_del(&f->list);
|
|
free(f);
|
|
} else if (!cmp_userdata) {
|
|
found = QB_TRUE;
|
|
qb_list_del(&f->list);
|
|
free(f);
|
|
}
|
|
}
|
|
}
|
|
if (found) {
|
|
return 0;
|
|
} else {
|
|
return -ENOENT;
|
|
}
|
|
}
|
|
|
|
static size_t
|
|
hashtable_count_get(struct qb_map *map)
|
|
{
|
|
struct hash_table *hash_table = (struct hash_table *)map;
|
|
return hash_table->count;
|
|
}
|
|
|
|
static qb_map_iter_t *
|
|
hashtable_iter_create(struct qb_map *map, const char *prefix)
|
|
{
|
|
struct hashtable_iter *i = malloc(sizeof(struct hashtable_iter));
|
|
if (i == NULL) {
|
|
return NULL;
|
|
}
|
|
i->i.m = map;
|
|
i->node = NULL;
|
|
i->bucket = 0;
|
|
return (qb_map_iter_t *) i;
|
|
}
|
|
|
|
static const char *
|
|
hashtable_iter_next(qb_map_iter_t * it, void **value)
|
|
{
|
|
struct hashtable_iter *hi = (struct hashtable_iter *)it;
|
|
struct hash_table *hash_table = (struct hash_table *)hi->i.m;
|
|
struct qb_list_head *ln;
|
|
struct hash_node *hash_node = NULL;
|
|
int found = QB_FALSE;
|
|
int cont = QB_TRUE;
|
|
int b;
|
|
|
|
if (hi->node == NULL) {
|
|
cont = QB_FALSE;
|
|
}
|
|
for (b = hi->bucket; b < hash_table->hash_buckets_len && !found; b++) {
|
|
if (cont) {
|
|
ln = hi->node->list.next;
|
|
cont = QB_FALSE;
|
|
} else {
|
|
ln = hash_table->hash_buckets[b].list_head.next;
|
|
}
|
|
for (; ln != &hash_table->hash_buckets[b].list_head;
|
|
ln = ln->next) {
|
|
hash_node = qb_list_entry(ln, struct hash_node, list);
|
|
if (hash_node->refcount > 0) {
|
|
found = QB_TRUE;
|
|
hash_node->refcount++;
|
|
hi->bucket = b;
|
|
*value = hash_node->value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hi->node) {
|
|
hashtable_node_deref(hi->i.m, hi->node);
|
|
}
|
|
if (!found) {
|
|
return NULL;
|
|
}
|
|
hi->node = hash_node;
|
|
return hash_node->key;
|
|
}
|
|
|
|
static void
|
|
hashtable_iter_free(qb_map_iter_t * i)
|
|
{
|
|
free(i);
|
|
}
|
|
|
|
static void
|
|
hashtable_destroy(struct qb_map *map)
|
|
{
|
|
struct hash_table *hash_table = (struct hash_table *)map;
|
|
|
|
free(hash_table);
|
|
}
|
|
|
|
qb_map_t *
|
|
qb_hashtable_create(size_t max_size)
|
|
{
|
|
int32_t i;
|
|
int32_t order;
|
|
int32_t n = max_size;
|
|
uint64_t size;
|
|
struct hash_table *ht;
|
|
|
|
for (i = 0; n; i++) {
|
|
n >>= 1;
|
|
}
|
|
order = QB_MAX(i, 3);
|
|
|
|
size = sizeof(struct hash_table) +
|
|
(sizeof(struct hash_bucket) * (1 << order));
|
|
|
|
ht = calloc(1, size);
|
|
if (ht == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
ht->map.put = hashtable_put;
|
|
ht->map.get = hashtable_get;
|
|
ht->map.rm = hashtable_rm;
|
|
ht->map.count_get = hashtable_count_get;
|
|
ht->map.iter_create = hashtable_iter_create;
|
|
ht->map.iter_next = hashtable_iter_next;
|
|
ht->map.iter_free = hashtable_iter_free;
|
|
ht->map.destroy = hashtable_destroy;
|
|
ht->map.notify_add = hashtable_notify_add;
|
|
ht->map.notify_del = hashtable_notify_del;
|
|
ht->count = 0;
|
|
ht->order = order;
|
|
qb_list_init(&ht->notifier_head);
|
|
|
|
ht->hash_buckets_len = 1 << order;
|
|
for (i = 0; i < ht->hash_buckets_len; i++) {
|
|
qb_list_init(&ht->hash_buckets[i].list_head);
|
|
}
|
|
return (qb_map_t *) ht;
|
|
}
|