mirror of
https://salsa.debian.org/ha-team/libqb
synced 2025-08-28 02:06:11 +00:00
570 lines
13 KiB
C
570 lines
13 KiB
C
/*
|
|
* Copyright (C) 2011 Red Hat, Inc.
|
|
*
|
|
* Author: Angus Salkeld <asalkeld@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 <assert.h>
|
|
|
|
#include <qb/qbdefs.h>
|
|
#include <qb/qbmap.h>
|
|
#include "map_int.h"
|
|
|
|
#define SKIPLIST_LEVEL_MAX 8
|
|
#define SKIPLIST_LEVEL_MIN 0
|
|
/* The amount of possible levels */
|
|
#define SKIPLIST_LEVEL_COUNT (SKIPLIST_LEVEL_MAX - SKIPLIST_LEVEL_MIN + 1)
|
|
|
|
struct skiplist_iter {
|
|
struct qb_map_iter i;
|
|
struct skiplist_node *n;
|
|
};
|
|
|
|
struct skiplist_node {
|
|
const char *key;
|
|
void *value;
|
|
int8_t level;
|
|
uint32_t refcount;
|
|
struct qb_list_head notifier_head;
|
|
|
|
/* An array of @level + 1 node pointers */
|
|
struct skiplist_node **forward;
|
|
};
|
|
|
|
struct skiplist {
|
|
struct qb_map map;
|
|
|
|
size_t length;
|
|
int8_t level;
|
|
struct skiplist_node *header;
|
|
};
|
|
|
|
/* An array of nodes that need to be updated after an insert or delete operation
|
|
*/
|
|
typedef struct skiplist_node *skiplist_update_t[SKIPLIST_LEVEL_COUNT];
|
|
|
|
static int8_t
|
|
skiplist_level_generate(void)
|
|
{
|
|
/* This constant is found by 1 / P, where P = 0.25. */
|
|
enum { P_INVERSE = 4 };
|
|
|
|
/* The original algorithm's random number is in the range [0, 1), so
|
|
* max M = 1. Its ceiling C = M * P = 1 * P = P.
|
|
*
|
|
* Our random number is in the range [0, UINT16_MAX], so M = UINT16_MAX. Therefore,
|
|
* C = UINT16_MAX * P = UINT16_MAX / P_INVERSE.
|
|
*/
|
|
enum { P_CEIL = UINT16_MAX / P_INVERSE };
|
|
int8_t level = SKIPLIST_LEVEL_MIN;
|
|
|
|
while ((uint16_t) (random()) < P_CEIL)
|
|
level++;
|
|
|
|
if (level < SKIPLIST_LEVEL_MAX)
|
|
return level;
|
|
|
|
return SKIPLIST_LEVEL_MAX;
|
|
}
|
|
|
|
static struct skiplist_node *
|
|
skiplist_node_next(const struct skiplist_node *node)
|
|
{
|
|
const struct skiplist_node *n = node;
|
|
do {
|
|
n = n->forward[SKIPLIST_LEVEL_MIN];
|
|
} while (n && n->refcount == 0);
|
|
return (struct skiplist_node *)n;
|
|
}
|
|
|
|
/*
|
|
* Create a new level @level node with value @value. The node should eventually
|
|
* be destroyed with @skiplist_node_destroy.
|
|
*
|
|
* return: a new node on success and NULL otherwise.
|
|
*/
|
|
static struct skiplist_node *
|
|
skiplist_node_new(const int8_t level, const char *key, const void *value)
|
|
{
|
|
struct skiplist_node *new_node = (struct skiplist_node *)
|
|
(malloc(sizeof(struct skiplist_node)));
|
|
|
|
if (!new_node)
|
|
return NULL;
|
|
|
|
new_node->value = (void *)value;
|
|
new_node->key = key;
|
|
new_node->level = level;
|
|
new_node->refcount = 1;
|
|
qb_list_init(&new_node->notifier_head);
|
|
|
|
/* A level 0 node still needs to hold 1 forward pointer, etc. */
|
|
new_node->forward = (struct skiplist_node **)
|
|
(calloc(level + 1, sizeof(struct skiplist_node *)));
|
|
|
|
if (new_node->forward == NULL) {
|
|
free(new_node);
|
|
return NULL;
|
|
}
|
|
|
|
return new_node;
|
|
}
|
|
|
|
static struct skiplist_node *
|
|
skiplist_header_node_new(void)
|
|
{
|
|
return skiplist_node_new(SKIPLIST_LEVEL_MAX, NULL, NULL);
|
|
}
|
|
|
|
/* An operation to perform after comparing a user value or search with a
|
|
* node's value
|
|
*/
|
|
typedef enum {
|
|
OP_GOTO_NEXT_LEVEL,
|
|
OP_GOTO_NEXT_NODE,
|
|
OP_FINISH,
|
|
} op_t;
|
|
|
|
static op_t
|
|
op_search(const struct skiplist *list,
|
|
const struct skiplist_node *fwd_node, const void *search)
|
|
{
|
|
int32_t cmp;
|
|
|
|
if (!fwd_node)
|
|
return OP_GOTO_NEXT_LEVEL;
|
|
|
|
cmp = strcmp(fwd_node->key, search);
|
|
if (cmp < 0) {
|
|
return OP_GOTO_NEXT_NODE;
|
|
} else if (cmp == 0) {
|
|
return OP_FINISH;
|
|
}
|
|
|
|
return OP_GOTO_NEXT_LEVEL;
|
|
}
|
|
|
|
static struct skiplist_node *
|
|
skiplist_lookup(struct skiplist *list, const char *key)
|
|
{
|
|
struct skiplist_node *cur_node = list->header;
|
|
int8_t level = list->level;
|
|
|
|
while (level >= SKIPLIST_LEVEL_MIN) {
|
|
struct skiplist_node *fwd_node = cur_node->forward[level];
|
|
|
|
switch (op_search(list, fwd_node, key)) {
|
|
case OP_FINISH:
|
|
return fwd_node;
|
|
case OP_GOTO_NEXT_NODE:
|
|
cur_node = fwd_node;
|
|
break;
|
|
case OP_GOTO_NEXT_LEVEL:
|
|
level--;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
skiplist_notify(struct skiplist *l, struct skiplist_node *n,
|
|
uint32_t event, char *key, void *old_value, void *value)
|
|
{
|
|
struct qb_list_head *list;
|
|
struct qb_map_notifier *tn;
|
|
|
|
/* node callbacks
|
|
*/
|
|
qb_list_for_each(list, &n->notifier_head) {
|
|
tn = qb_list_entry(list, struct qb_map_notifier, list);
|
|
|
|
if (tn->events & event) {
|
|
tn->callback(event, key, old_value, value,
|
|
tn->user_data);
|
|
}
|
|
}
|
|
/* global callbacks
|
|
*/
|
|
qb_list_for_each(list, &l->header->notifier_head) {
|
|
tn = qb_list_entry(list, struct qb_map_notifier, list);
|
|
|
|
if (tn->events & event) {
|
|
tn->callback(event, 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 void
|
|
skiplist_node_destroy(struct skiplist_node *node, struct skiplist *list)
|
|
{
|
|
struct qb_list_head *pos;
|
|
struct qb_list_head *next;
|
|
struct qb_map_notifier *tn;
|
|
|
|
skiplist_notify(list, node,
|
|
QB_MAP_NOTIFY_DELETED,
|
|
(char *)node->key, node->value, NULL);
|
|
|
|
qb_list_for_each_safe(pos, next, &node->notifier_head) {
|
|
tn = qb_list_entry(pos, struct qb_map_notifier, list);
|
|
qb_list_del(&tn->list);
|
|
free(tn);
|
|
}
|
|
|
|
free(node->forward);
|
|
free(node);
|
|
}
|
|
|
|
static void
|
|
skiplist_node_deref(struct skiplist_node *node, struct skiplist *list)
|
|
{
|
|
node->refcount--;
|
|
if (node->refcount == 0) {
|
|
skiplist_node_destroy(node, list);
|
|
}
|
|
}
|
|
|
|
static int32_t
|
|
skiplist_notify_add(qb_map_t * m, const char *key,
|
|
qb_map_notify_fn fn, int32_t events, void *user_data)
|
|
{
|
|
struct skiplist *t = (struct skiplist *)m;
|
|
struct qb_map_notifier *f;
|
|
struct skiplist_node *n;
|
|
struct qb_list_head *list;
|
|
int add_to_tail = QB_FALSE;
|
|
|
|
if (key) {
|
|
n = skiplist_lookup(t, key);
|
|
} else {
|
|
n = t->header;
|
|
}
|
|
if (events & QB_MAP_NOTIFY_FREE) {
|
|
add_to_tail = QB_TRUE;
|
|
}
|
|
if (n) {
|
|
qb_list_for_each(list, &n->notifier_head) {
|
|
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->callback == fn &&
|
|
f->user_data == user_data) {
|
|
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, &n->notifier_head);
|
|
} else {
|
|
qb_list_add(&f->list, &n->notifier_head);
|
|
}
|
|
return 0;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int32_t
|
|
skiplist_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 skiplist *t = (struct skiplist *)m;
|
|
struct skiplist_node *n;
|
|
struct qb_map_notifier *f;
|
|
struct qb_list_head *head = NULL;
|
|
struct qb_list_head *list;
|
|
struct qb_list_head *next;
|
|
int32_t found = QB_FALSE;
|
|
|
|
if (key) {
|
|
n = skiplist_lookup(t, key);
|
|
if (n) {
|
|
head = &n->notifier_head;
|
|
}
|
|
} else {
|
|
head = &t->header->notifier_head;
|
|
}
|
|
if (head == NULL) {
|
|
return -ENOENT;
|
|
}
|
|
qb_list_for_each_safe(list, next, head) {
|
|
f = qb_list_entry(list, struct qb_map_notifier, list);
|
|
|
|
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 void
|
|
skiplist_destroy(struct qb_map *map)
|
|
{
|
|
struct skiplist *list = (struct skiplist *)map;
|
|
struct skiplist_node *cur_node;
|
|
struct skiplist_node *fwd_node;
|
|
|
|
for (cur_node = skiplist_node_next(list->header);
|
|
cur_node; cur_node = fwd_node) {
|
|
fwd_node = skiplist_node_next(cur_node);
|
|
skiplist_node_destroy(cur_node, list);
|
|
}
|
|
skiplist_node_destroy(list->header, list);
|
|
free(list);
|
|
}
|
|
|
|
static void
|
|
skiplist_put(struct qb_map *map, const char *key, const void *value)
|
|
{
|
|
struct skiplist *list = (struct skiplist *)map;
|
|
struct skiplist_node *new_node;
|
|
int8_t level = list->level;
|
|
skiplist_update_t update;
|
|
int8_t update_level;
|
|
int8_t new_node_level;
|
|
struct skiplist_node *cur_node = list->header;
|
|
char *old_k;
|
|
char *old_v;
|
|
|
|
while ((update_level = level) >= SKIPLIST_LEVEL_MIN) {
|
|
struct skiplist_node *fwd_node = cur_node->forward[level];
|
|
|
|
switch (op_search(list, fwd_node, key)) {
|
|
case OP_FINISH:
|
|
old_k = (char *)fwd_node->key;
|
|
old_v = (char *)fwd_node->value;
|
|
fwd_node->value = (void *)value;
|
|
fwd_node->key = (void *)key;
|
|
skiplist_notify(list, fwd_node,
|
|
QB_MAP_NOTIFY_REPLACED,
|
|
old_k, old_v, fwd_node->value);
|
|
return;
|
|
|
|
case OP_GOTO_NEXT_NODE:
|
|
cur_node = fwd_node;
|
|
break;
|
|
case OP_GOTO_NEXT_LEVEL:
|
|
level--;
|
|
}
|
|
|
|
update[update_level] = cur_node;
|
|
}
|
|
|
|
new_node_level = skiplist_level_generate();
|
|
|
|
if (new_node_level > list->level) {
|
|
for (level = list->level + 1; level <= new_node_level; level++)
|
|
update[level] = list->header;
|
|
|
|
list->level = new_node_level;
|
|
}
|
|
|
|
new_node = skiplist_node_new(new_node_level, key, value);
|
|
|
|
assert(new_node != NULL);
|
|
skiplist_notify(list, new_node,
|
|
QB_MAP_NOTIFY_INSERTED,
|
|
(char*)new_node->key, NULL, new_node->value);
|
|
|
|
/* Drop @new_node into @list. */
|
|
for (level = SKIPLIST_LEVEL_MIN; level <= new_node_level; level++) {
|
|
new_node->forward[level] = update[level]->forward[level];
|
|
update[level]->forward[level] = new_node;
|
|
}
|
|
|
|
list->length++;
|
|
}
|
|
|
|
static int32_t
|
|
skiplist_rm(struct qb_map *map, const char *key)
|
|
{
|
|
struct skiplist *list = (struct skiplist *)map;
|
|
struct skiplist_node *found_node;
|
|
struct skiplist_node *cur_node = list->header;
|
|
int8_t level = list->level;
|
|
int8_t update_level;
|
|
skiplist_update_t update;
|
|
|
|
while ((update_level = level) >= SKIPLIST_LEVEL_MIN) {
|
|
struct skiplist_node *fwd_node = cur_node->forward[level];
|
|
|
|
switch (op_search(list, fwd_node, key)) {
|
|
case OP_GOTO_NEXT_NODE:
|
|
cur_node = fwd_node;
|
|
break;
|
|
case OP_GOTO_NEXT_LEVEL:
|
|
default:
|
|
level--;
|
|
break;
|
|
}
|
|
|
|
update[update_level] = cur_node;
|
|
}
|
|
|
|
/* The immediate forward node should be the matching node... */
|
|
found_node = skiplist_node_next(cur_node);
|
|
|
|
/* ...unless we're at the end of the list or the value doesn't exist. */
|
|
if (!found_node || strcmp(found_node->key, key) != 0) {
|
|
return QB_FALSE;
|
|
}
|
|
|
|
/* Splice found_node out of list. */
|
|
for (level = SKIPLIST_LEVEL_MIN; level <= list->level; level++)
|
|
if (update[level]->forward[level] == found_node)
|
|
update[level]->forward[level] =
|
|
found_node->forward[level];
|
|
|
|
skiplist_node_deref(found_node, list);
|
|
|
|
/* Remove unused levels from @list -- stop removing levels as soon as a
|
|
* used level is found. Unused levels can occur if @found_node had the
|
|
* highest level.
|
|
*/
|
|
for (level = list->level; level >= SKIPLIST_LEVEL_MIN; level--) {
|
|
if (list->header->forward[level])
|
|
break;
|
|
|
|
list->level--;
|
|
}
|
|
|
|
list->length--;
|
|
|
|
return QB_TRUE;
|
|
}
|
|
|
|
static void *
|
|
skiplist_get(struct qb_map *map, const char *key)
|
|
{
|
|
struct skiplist *list = (struct skiplist *)map;
|
|
struct skiplist_node *n = skiplist_lookup(list, key);
|
|
if (n) {
|
|
return n->value;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static qb_map_iter_t *
|
|
skiplist_iter_create(struct qb_map *map, const char *prefix)
|
|
{
|
|
struct skiplist_iter *i = malloc(sizeof(struct skiplist_iter));
|
|
struct skiplist *list = (struct skiplist *)map;
|
|
if (i == NULL) {
|
|
return NULL;
|
|
}
|
|
i->i.m = map;
|
|
i->n = list->header;
|
|
i->n->refcount++;
|
|
return (qb_map_iter_t *) i;
|
|
}
|
|
|
|
static const char *
|
|
skiplist_iter_next(qb_map_iter_t * i, void **value)
|
|
{
|
|
struct skiplist_iter *si = (struct skiplist_iter *)i;
|
|
struct skiplist_node *p = si->n;
|
|
|
|
if (p == NULL) {
|
|
return NULL;
|
|
}
|
|
si->n = skiplist_node_next(p);
|
|
if (si->n == NULL) {
|
|
skiplist_node_deref(p, (struct skiplist *)i->m);
|
|
return NULL;
|
|
}
|
|
si->n->refcount++;
|
|
skiplist_node_deref(p, (struct skiplist *)i->m);
|
|
*value = si->n->value;
|
|
return si->n->key;
|
|
}
|
|
|
|
static void
|
|
skiplist_iter_free(qb_map_iter_t * i)
|
|
{
|
|
free(i);
|
|
}
|
|
|
|
static size_t
|
|
skiplist_count_get(struct qb_map *map)
|
|
{
|
|
struct skiplist *list = (struct skiplist *)map;
|
|
return list->length;
|
|
}
|
|
|
|
qb_map_t *
|
|
qb_skiplist_create(void)
|
|
{
|
|
struct skiplist *sl = malloc(sizeof(struct skiplist));
|
|
if (sl == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
srand(time(NULL));
|
|
|
|
sl->map.put = skiplist_put;
|
|
sl->map.get = skiplist_get;
|
|
sl->map.rm = skiplist_rm;
|
|
sl->map.count_get = skiplist_count_get;
|
|
sl->map.iter_create = skiplist_iter_create;
|
|
sl->map.iter_next = skiplist_iter_next;
|
|
sl->map.iter_free = skiplist_iter_free;
|
|
sl->map.destroy = skiplist_destroy;
|
|
sl->map.notify_add = skiplist_notify_add;
|
|
sl->map.notify_del = skiplist_notify_del;
|
|
sl->level = SKIPLIST_LEVEL_MIN;
|
|
sl->length = 0;
|
|
sl->header = skiplist_header_node_new();
|
|
|
|
return (qb_map_t *) sl;
|
|
}
|