286 lines
9.6 KiB
C
286 lines
9.6 KiB
C
// calogKv.c -- calog key-value library (see calogKv.h). A process-wide, mutex-guarded array
|
|
// of {key bytes, key length, value}. kvSet/kvGet/kvHas/kvDelete/kvKeys are natives over that
|
|
// shared store; every stored value is a deep copy, so the store owns pure data.
|
|
//
|
|
// The registry state is STATIC (not heap) and its bookkeeping is never freed: the five
|
|
// natives stay reachable (via any context) right up until calogDestroy tears the broker
|
|
// registry down, so freeing the state in calogKvShutdown -- which the contract requires
|
|
// BEFORE calogDestroy -- would leave those natives dereferencing freed memory. Shutdown
|
|
// instead just frees the stored keys + values and empties the array; the small static
|
|
// bookkeeping persists for the process (no per-cycle leak), and a post-shutdown native call
|
|
// simply sees an empty store.
|
|
//
|
|
// Function values are refused (calogErrUnsupportedE): storing a CalogFnT would drag script
|
|
// callable lifecycle into a data store, so kv sidesteps it entirely and holds data only.
|
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include "calogKv.h"
|
|
|
|
#include <pthread.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define KV_INITIAL_CAP 8
|
|
#define KV_GROWTH 2
|
|
|
|
typedef struct KvEntryT {
|
|
char *keyBytes;
|
|
int64_t keyLen;
|
|
CalogValueT value;
|
|
} KvEntryT;
|
|
|
|
// The store (heap array), guarded by gMapMutex. refCount (guarded by gInitMutex) counts
|
|
// registered runtimes so the LAST calogKvShutdown frees the store's contents.
|
|
static pthread_mutex_t gMapMutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static KvEntryT *gEntries = NULL;
|
|
static int32_t gCount = 0;
|
|
static int32_t gCap = 0;
|
|
static pthread_mutex_t gInitMutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static int32_t gRefCount = 0;
|
|
|
|
static bool kvContainsFn(const CalogValueT *value, int32_t depth);
|
|
static int32_t kvDelete(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static int32_t kvFindLocked(const char *bytes, int64_t length);
|
|
static int32_t kvGet(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static int32_t kvHas(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static int32_t kvKeys(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static int32_t kvSet(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
|
|
|
|
int32_t calogKvRegister(CalogT *calog) {
|
|
pthread_mutex_lock(&gInitMutex);
|
|
gRefCount++;
|
|
pthread_mutex_unlock(&gInitMutex);
|
|
calogRegisterInline(calog, "kvSet", kvSet, NULL);
|
|
calogRegisterInline(calog, "kvGet", kvGet, NULL);
|
|
calogRegisterInline(calog, "kvHas", kvHas, NULL);
|
|
calogRegisterInline(calog, "kvDelete", kvDelete, NULL);
|
|
calogRegisterInline(calog, "kvKeys", kvKeys, NULL);
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
void calogKvShutdown(void) {
|
|
pthread_mutex_lock(&gInitMutex);
|
|
if (gRefCount > 0) {
|
|
gRefCount--;
|
|
}
|
|
if (gRefCount <= 0) {
|
|
int32_t index;
|
|
pthread_mutex_lock(&gMapMutex);
|
|
for (index = 0; index < gCount; index++) {
|
|
free(gEntries[index].keyBytes);
|
|
calogValueFree(&gEntries[index].value);
|
|
}
|
|
free(gEntries);
|
|
gEntries = NULL;
|
|
gCount = 0;
|
|
gCap = 0;
|
|
pthread_mutex_unlock(&gMapMutex);
|
|
}
|
|
pthread_mutex_unlock(&gInitMutex);
|
|
}
|
|
|
|
|
|
static bool kvContainsFn(const CalogValueT *value, int32_t depth) {
|
|
int64_t index;
|
|
|
|
// kv stores only data (copied by value); a function value -- at the top level OR nested in
|
|
// an aggregate -- would retain a CalogFnT into the process-wide store past its owner's life,
|
|
// so reject any value that carries one. Too-deep is rejected as unverifiable.
|
|
if (depth >= CALOG_MAX_DEPTH) {
|
|
return true;
|
|
}
|
|
if (value->type == calogFnE) {
|
|
return true;
|
|
}
|
|
if (value->type == calogAggE) {
|
|
for (index = 0; index < value->as.agg->arrayCount; index++) {
|
|
if (kvContainsFn(&value->as.agg->array[index], depth + 1)) {
|
|
return true;
|
|
}
|
|
}
|
|
for (index = 0; index < value->as.agg->pairCount; index++) {
|
|
if (kvContainsFn(&value->as.agg->pairs[index].key, depth + 1)) {
|
|
return true;
|
|
}
|
|
if (kvContainsFn(&value->as.agg->pairs[index].value, depth + 1)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
static int32_t kvDelete(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
int32_t index;
|
|
|
|
(void)userData;
|
|
calogValueNil(result);
|
|
if (argCount != 1 || args[0].type != calogStringE) {
|
|
return calogFail(result, calogErrArgE, "kvDelete expects (key)");
|
|
}
|
|
pthread_mutex_lock(&gMapMutex);
|
|
index = kvFindLocked(args[0].as.s.bytes, args[0].as.s.length);
|
|
if (index >= 0) {
|
|
free(gEntries[index].keyBytes);
|
|
calogValueFree(&gEntries[index].value);
|
|
gEntries[index] = gEntries[gCount - 1];
|
|
gCount--;
|
|
}
|
|
pthread_mutex_unlock(&gMapMutex);
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static int32_t kvFindLocked(const char *bytes, int64_t length) {
|
|
int32_t index;
|
|
|
|
for (index = 0; index < gCount; index++) {
|
|
if (gEntries[index].keyLen == length && memcmp(gEntries[index].keyBytes, bytes, (size_t)length) == 0) {
|
|
return index;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int32_t kvGet(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
int32_t index;
|
|
int32_t status;
|
|
|
|
(void)userData;
|
|
calogValueNil(result);
|
|
if (argCount != 1 || args[0].type != calogStringE) {
|
|
return calogFail(result, calogErrArgE, "kvGet expects (key)");
|
|
}
|
|
status = calogOkE;
|
|
pthread_mutex_lock(&gMapMutex);
|
|
index = kvFindLocked(args[0].as.s.bytes, args[0].as.s.length);
|
|
if (index >= 0) {
|
|
status = calogValueCopy(result, &gEntries[index].value);
|
|
}
|
|
pthread_mutex_unlock(&gMapMutex);
|
|
if (status != calogOkE) {
|
|
calogValueFree(result);
|
|
return calogFail(result, status, "kvGet: out of memory");
|
|
}
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static int32_t kvHas(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
int32_t index;
|
|
|
|
(void)userData;
|
|
calogValueNil(result);
|
|
if (argCount != 1 || args[0].type != calogStringE) {
|
|
return calogFail(result, calogErrArgE, "kvHas expects (key)");
|
|
}
|
|
pthread_mutex_lock(&gMapMutex);
|
|
index = kvFindLocked(args[0].as.s.bytes, args[0].as.s.length);
|
|
pthread_mutex_unlock(&gMapMutex);
|
|
calogValueBool(result, index >= 0);
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static int32_t kvKeys(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
CalogAggT *agg;
|
|
int32_t index;
|
|
int32_t status;
|
|
|
|
(void)args;
|
|
(void)argCount;
|
|
(void)userData;
|
|
calogValueNil(result);
|
|
status = calogAggCreate(&agg, calogListE);
|
|
if (status != calogOkE) {
|
|
return calogFail(result, status, "kvKeys: out of memory");
|
|
}
|
|
pthread_mutex_lock(&gMapMutex);
|
|
for (index = 0; index < gCount; index++) {
|
|
CalogValueT key;
|
|
status = calogValueString(&key, gEntries[index].keyBytes, gEntries[index].keyLen);
|
|
if (status != calogOkE) {
|
|
break;
|
|
}
|
|
status = calogAggPush(agg, &key);
|
|
if (status != calogOkE) {
|
|
calogValueFree(&key);
|
|
break;
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&gMapMutex);
|
|
if (status != calogOkE) {
|
|
calogAggFree(agg);
|
|
return calogFail(result, status, "kvKeys: out of memory");
|
|
}
|
|
calogValueAgg(result, agg);
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static int32_t kvSet(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
CalogValueT copy;
|
|
char *keyBytes;
|
|
int64_t keyLen;
|
|
int32_t index;
|
|
int32_t status;
|
|
|
|
(void)userData;
|
|
calogValueNil(result);
|
|
if (argCount != 2 || args[0].type != calogStringE) {
|
|
return calogFail(result, calogErrArgE, "kvSet expects (key, value)");
|
|
}
|
|
if (kvContainsFn(&args[1], 0)) {
|
|
return calogFail(result, calogErrUnsupportedE, "kv stores data, not functions");
|
|
}
|
|
status = calogValueCopy(©, &args[1]);
|
|
if (status != calogOkE) {
|
|
calogValueFree(©);
|
|
return calogFail(result, status, "kvSet: out of memory");
|
|
}
|
|
keyLen = args[0].as.s.length;
|
|
pthread_mutex_lock(&gMapMutex);
|
|
index = kvFindLocked(args[0].as.s.bytes, keyLen);
|
|
if (index >= 0) {
|
|
// Replace: drop the old value, keep the existing key bytes.
|
|
calogValueFree(&gEntries[index].value);
|
|
gEntries[index].value = copy;
|
|
pthread_mutex_unlock(&gMapMutex);
|
|
return calogOkE;
|
|
}
|
|
if (gCount == gCap) {
|
|
int32_t newCap;
|
|
KvEntryT *grown;
|
|
newCap = (gCap == 0) ? KV_INITIAL_CAP : gCap * KV_GROWTH;
|
|
grown = (KvEntryT *)realloc(gEntries, (size_t)newCap * sizeof(KvEntryT));
|
|
if (grown == NULL) {
|
|
pthread_mutex_unlock(&gMapMutex);
|
|
calogValueFree(©);
|
|
return calogFail(result, calogErrOomE, "kvSet: out of memory");
|
|
}
|
|
gEntries = grown;
|
|
gCap = newCap;
|
|
}
|
|
keyBytes = (char *)malloc((size_t)keyLen + 1);
|
|
if (keyBytes == NULL) {
|
|
pthread_mutex_unlock(&gMapMutex);
|
|
calogValueFree(©);
|
|
return calogFail(result, calogErrOomE, "kvSet: out of memory");
|
|
}
|
|
if (keyLen > 0) {
|
|
memcpy(keyBytes, args[0].as.s.bytes, (size_t)keyLen);
|
|
}
|
|
keyBytes[keyLen] = '\0';
|
|
gEntries[gCount].keyBytes = keyBytes;
|
|
gEntries[gCount].keyLen = keyLen;
|
|
gEntries[gCount].value = copy;
|
|
gCount++;
|
|
pthread_mutex_unlock(&gMapMutex);
|
|
return calogOkE;
|
|
}
|