Add kvtab_foreach walk primitive (P0-14) to kv.c/kv.h, then implement kvmgr_capture_keys in kvmgr.c that deep-copies all key→index mappings into a malloc'd slot_key_t[] array safe to read after the write lock is released during checkpoint serialization (Part 3.8). Key design points: - Both kvtab_foreach and kvmgr_capture_keys take var (RTTI data pointer) and use RTTI_DATA_TO_OBJ internally — the RTTI framework's makeInstance returns the data region pointer, not the meta_obj_t header pointer - String keys are copied via strncpy into slot_key_t.key[XCE_MAXNAME_LEN] - Integer keys are formatted as decimal strings via snprintf - Two-pass approach: count live entries first, allocate, then fill - Tests cover string kvmgr (3 entries), empty kvmgr, and deep-copy safety
289 lines
6.1 KiB
C
289 lines
6.1 KiB
C
#include <errno.h>
|
|
#include <rtti.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "kv.h"
|
|
#include "utils.h"
|
|
|
|
typedef struct kvent_ kvent_t;
|
|
|
|
static uint32_t hash_int_key(void* key)
|
|
{
|
|
uint64_t intkey = (uint64_t)(long)key;
|
|
|
|
return hash64(intkey);
|
|
}
|
|
|
|
static int assign_int_key(void** dst, void* src)
|
|
{
|
|
*dst = src;
|
|
return 0;
|
|
}
|
|
|
|
static int cmp_int_key(void* key1, void* key2)
|
|
{
|
|
uint64_t intkey1 = (uint64_t)(long)key1;
|
|
uint64_t intkey2 = (uint64_t)(long)key2;
|
|
|
|
if (intkey1 > intkey2) {
|
|
return 1;
|
|
} else if (intkey2 > intkey1) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
kv_key_ops_t int_key_ops = { .hash = (kv_key_hash_fn_t)hash_int_key,
|
|
.assign = (kv_key_assign_fn_t)assign_int_key,
|
|
.cmp = (kv_key_cmp_fn_t)cmp_int_key };
|
|
|
|
static uint32_t hash_string_key(void* key) { return hash_string((char*)key); }
|
|
|
|
static int cmp_string_key(void* key1, void* key2)
|
|
{
|
|
return strcmp((char*)key1, (char*)key2);
|
|
}
|
|
|
|
static int assign_string_key(char** dst, char* src)
|
|
{
|
|
char* p = strdup(src);
|
|
|
|
if (p == NULL) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
*dst = p;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void destroy_string_key(char* s)
|
|
{
|
|
if (s == NULL) {
|
|
return;
|
|
}
|
|
|
|
free(s);
|
|
}
|
|
|
|
kv_key_ops_t string_key_ops
|
|
= { .hash = (kv_key_hash_fn_t)hash_string_key,
|
|
.cmp = (kv_key_cmp_fn_t)cmp_string_key,
|
|
.assign = (kv_key_assign_fn_t)assign_string_key,
|
|
.destroy = (kv_key_destroy_fn_t)destroy_string_key };
|
|
|
|
struct kvent_ {
|
|
kvent_t* next;
|
|
void* key;
|
|
void* value;
|
|
};
|
|
|
|
typedef struct {
|
|
uint32_t nslots;
|
|
kv_key_ops_t* keyops;
|
|
destroy_fn_t val_killer;
|
|
kvent_t** slots;
|
|
} kvtab_t;
|
|
|
|
static size_t kvtab_datasize(va_list ap)
|
|
{
|
|
size_t sz = sizeof(kvtab_t);
|
|
return sz;
|
|
}
|
|
|
|
static int kvtab_init(meta_obj_t* obj, va_list ap)
|
|
{
|
|
kvtab_t* kvtab = RTTI_OBJ_TO_DATA(kvtab_t, obj);
|
|
kv_params_t* params = va_arg(ap, kv_params_t*);
|
|
|
|
if ((params == NULL) || (params->nslots <= 1) || (params->keyops == NULL)
|
|
|| (params->keyops->hash == NULL) || (params->keyops->cmp == NULL)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
kvtab->slots = (kvent_t**)calloc(params->nslots, sizeof(*(kvtab->slots)));
|
|
if (kvtab->slots == NULL) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
kvtab->nslots = params->nslots;
|
|
kvtab->keyops = params->keyops;
|
|
kvtab->val_killer = params->datakiller;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void kvtab_destroy(meta_obj_t* obj)
|
|
{
|
|
kvtab_t* kvtab = RTTI_OBJ_TO_DATA(kvtab_t, obj);
|
|
int i;
|
|
|
|
for (i = 0; i < kvtab->nslots; i++) {
|
|
kvent_t* e = NULL;
|
|
|
|
while ((e = kvtab->slots[i]) != NULL) {
|
|
kvtab->slots[i] = e->next;
|
|
e->next = NULL;
|
|
|
|
if (kvtab->val_killer != NULL) {
|
|
kvtab->val_killer(e->value);
|
|
}
|
|
|
|
if (kvtab->keyops->destroy != NULL) {
|
|
kvtab->keyops->destroy(e->key);
|
|
}
|
|
|
|
FREEUP(e);
|
|
}
|
|
}
|
|
|
|
FREEUP(kvtab->slots);
|
|
}
|
|
|
|
static kvent_t* kvtab_get(kvtab_t* t, void* key)
|
|
{
|
|
uint32_t hc;
|
|
kvent_t* e = NULL;
|
|
|
|
hc = t->keyops->hash(key) % t->nslots;
|
|
e = t->slots[hc];
|
|
|
|
while (e != NULL) {
|
|
if (t->keyops->cmp(e->key, key) == 0) {
|
|
break;
|
|
}
|
|
|
|
e = e->next;
|
|
}
|
|
|
|
return e;
|
|
}
|
|
|
|
static int kvtab_add(meta_obj_t* obj, void* key, void* value)
|
|
{
|
|
kvtab_t* t = RTTI_OBJ_TO_DATA(kvtab_t, obj);
|
|
kvent_t* e = NULL;
|
|
uint32_t hc;
|
|
|
|
e = kvtab_get(t, key);
|
|
if (e != NULL) {
|
|
return -EEXIST;
|
|
}
|
|
|
|
e = (kvent_t*)calloc(1, sizeof(*e));
|
|
if (e == NULL) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (t->keyops->assign == NULL) {
|
|
e->key = key;
|
|
} else {
|
|
int rc;
|
|
|
|
rc = t->keyops->assign(&(e->key), key);
|
|
if (rc < 0) {
|
|
free(e);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
e->value = value;
|
|
hc = t->keyops->hash(key) % t->nslots;
|
|
|
|
e->next = t->slots[hc];
|
|
t->slots[hc] = e;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int kvtab_del(meta_obj_t* obj, void* key)
|
|
{
|
|
kvtab_t* t = RTTI_OBJ_TO_DATA(kvtab_t, obj);
|
|
kvent_t** e = NULL;
|
|
uint32_t hc;
|
|
|
|
hc = t->keyops->hash(key) % t->nslots;
|
|
e = &(t->slots[hc]);
|
|
while ((*e) != NULL) {
|
|
if (t->keyops->cmp((*e)->key, key) == 0) {
|
|
kvent_t* e1 = *e;
|
|
|
|
*e = e1->next;
|
|
if (t->val_killer != NULL) {
|
|
t->val_killer(e1->value);
|
|
}
|
|
|
|
if (t->keyops->destroy != NULL) {
|
|
t->keyops->destroy(e1->key);
|
|
}
|
|
|
|
FREEUP(e1);
|
|
|
|
return 1;
|
|
}
|
|
|
|
e = &((*e)->next);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int kvtab_find(meta_obj_t* obj, void* key, void** value)
|
|
{
|
|
kvtab_t* t = RTTI_OBJ_TO_DATA(kvtab_t, obj);
|
|
kvent_t* e = NULL;
|
|
|
|
if (value == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
e = kvtab_get(t, key);
|
|
if (e == NULL) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
*value = e->value;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* kvtab_foreach — walk every live entry and invoke cb(key, index, arg).
|
|
* index is recovered by casting the stored void* value back to int.
|
|
* Caller must hold any necessary external lock before calling.
|
|
*/
|
|
int kvtab_foreach(var kvtab_var, kvtab_foreach_fn_t cb, void* arg)
|
|
{
|
|
if (kvtab_var == NULL || cb == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
meta_obj_t* obj = RTTI_DATA_TO_OBJ(kvtab_var);
|
|
kvtab_t* t = RTTI_OBJ_TO_DATA(kvtab_t, obj);
|
|
int count = 0;
|
|
uint32_t i;
|
|
|
|
for (i = 0; i < t->nslots; i++) {
|
|
kvent_t* e = t->slots[i];
|
|
while (e != NULL) {
|
|
int idx = (int)(long)e->value;
|
|
cb(e->key, idx, arg);
|
|
count++;
|
|
e = e->next;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
typeinfo_t kvtab_type
|
|
= { .name = "kvtab",
|
|
.privateSize = 0,
|
|
.dataSize = (datasize_fn_t)kvtab_datasize,
|
|
.init = (obj_init_fn_t)kvtab_init,
|
|
.destroy = (obj_destroy_fn_t)kvtab_destroy,
|
|
.interfaces
|
|
= { .kv = &(intf_kv_t) { .add_key = (key_add_fn_t)kvtab_add,
|
|
.del_key = (key_del_fn_t)kvtab_del,
|
|
.find_key = (key_find_fn_t)kvtab_find } } };
|