/*
|
|
* @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
|
* @copyright (C) 2002-2020 ProcessOne, SARL. All Rights Reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
*/
|
|
|
|
#include <erl_nif.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include "uthash.h"
|
|
|
|
void __free(void *ptr, size_t size) {
|
|
enif_free(ptr);
|
|
}
|
|
|
|
#undef uthash_malloc
|
|
#undef uthash_free
|
|
#define uthash_malloc enif_alloc
|
|
#define uthash_free __free
|
|
|
|
/****************************************************************
|
|
* Structures/Globals definitions *
|
|
****************************************************************/
|
|
typedef struct __tree_t {
|
|
char *key;
|
|
char *val;
|
|
int refc;
|
|
struct __tree_t *sub;
|
|
UT_hash_handle hh;
|
|
} tree_t;
|
|
|
|
typedef struct {
|
|
tree_t *tree;
|
|
char *name;
|
|
ErlNifRWLock *lock;
|
|
} state_t;
|
|
|
|
typedef struct {
|
|
char *name;
|
|
state_t *state;
|
|
UT_hash_handle hh;
|
|
} registry_t;
|
|
|
|
static ErlNifResourceType *tree_state_t = NULL;
|
|
static registry_t *registry = NULL;
|
|
static ErlNifRWLock *registry_lock = NULL;
|
|
|
|
/****************************************************************
|
|
* MQTT Tree Manipulation *
|
|
****************************************************************/
|
|
tree_t *tree_new(char *key, size_t len) {
|
|
tree_t *tree = enif_alloc(sizeof(tree_t));
|
|
if (tree) {
|
|
memset(tree, 0, sizeof(tree_t));
|
|
if (key && len) {
|
|
tree->key = enif_alloc(len);
|
|
if (tree->key) {
|
|
memcpy(tree->key, key, len);
|
|
} else {
|
|
enif_free(tree);
|
|
tree = NULL;
|
|
}
|
|
}
|
|
}
|
|
return tree;
|
|
}
|
|
|
|
void tree_free(tree_t *t) {
|
|
tree_t *found, *iter;
|
|
if (t) {
|
|
enif_free(t->key);
|
|
enif_free(t->val);
|
|
HASH_ITER(hh, t->sub, found, iter) {
|
|
HASH_DEL(t->sub, found);
|
|
tree_free(found);
|
|
}
|
|
memset(t, 0, sizeof(tree_t));
|
|
enif_free(t);
|
|
}
|
|
}
|
|
|
|
void tree_clear(tree_t *root) {
|
|
tree_t *found, *iter;
|
|
HASH_ITER(hh, root->sub, found, iter) {
|
|
HASH_DEL(root->sub, found);
|
|
tree_free(found);
|
|
}
|
|
}
|
|
|
|
int tree_add(tree_t *root, char *path, size_t size) {
|
|
int i = 0;
|
|
size_t len;
|
|
tree_t *t = root;
|
|
tree_t *found, *new;
|
|
|
|
while (i<=size) {
|
|
len = strlen(path+i) + 1;
|
|
HASH_FIND_STR(t->sub, path+i, found);
|
|
if (found) {
|
|
i += len;
|
|
t = found;
|
|
} else {
|
|
new = tree_new(path+i, len);
|
|
if (new) {
|
|
HASH_ADD_STR(t->sub, key, new);
|
|
i += len;
|
|
t = new;
|
|
} else
|
|
return ENOMEM;
|
|
}
|
|
}
|
|
|
|
if (!t->val) {
|
|
t->val = enif_alloc(size+1);
|
|
if (t->val) {
|
|
t->val[size] = 0;
|
|
for (i=0; i<size; i++) {
|
|
char c = path[i];
|
|
t->val[i] = c ? c : '/';
|
|
}
|
|
} else
|
|
return ENOMEM;
|
|
}
|
|
t->refc++;
|
|
return 0;
|
|
}
|
|
|
|
int tree_del(tree_t *root, char *path, size_t i, size_t size) {
|
|
tree_t *found;
|
|
|
|
if (i<=size) {
|
|
HASH_FIND_STR(root->sub, path+i, found);
|
|
if (found) {
|
|
i += strlen(path+i) + 1;
|
|
int deleted = tree_del(found, path, i, size);
|
|
if (deleted) {
|
|
HASH_DEL(root->sub, found);
|
|
tree_free(found);
|
|
}
|
|
}
|
|
} else if (root->refc) {
|
|
root->refc--;
|
|
if (!root->refc) {
|
|
enif_free(root->val);
|
|
root->val = NULL;
|
|
}
|
|
}
|
|
|
|
return !root->refc && !root->sub;
|
|
}
|
|
|
|
void tree_size(tree_t *tree, size_t *size) {
|
|
tree_t *found, *iter;
|
|
|
|
HASH_ITER(hh, tree->sub, found, iter) {
|
|
if (found->refc) (*size)++;
|
|
tree_size(found, size);
|
|
}
|
|
}
|
|
|
|
int tree_refc(tree_t *tree, char *path, size_t i, size_t size) {
|
|
tree_t *found;
|
|
|
|
if (i<=size) {
|
|
HASH_FIND_STR(tree->sub, path+i, found);
|
|
if (found) {
|
|
i += strlen(path+i) + 1;
|
|
return tree_refc(found, path, i, size);
|
|
} else {
|
|
return 0;
|
|
}
|
|
} else
|
|
return tree->refc;
|
|
}
|
|
|
|
/****************************************************************
|
|
* Registration *
|
|
****************************************************************/
|
|
void delete_registry_entry(registry_t *entry) {
|
|
/* registry_lock must be RW-locked! */
|
|
HASH_DEL(registry, entry);
|
|
entry->state->name = NULL;
|
|
enif_release_resource(entry->state);
|
|
enif_free(entry->name);
|
|
enif_free(entry);
|
|
}
|
|
|
|
int register_tree(char *name, state_t *state) {
|
|
registry_t *entry, *found;
|
|
|
|
entry = enif_alloc(sizeof(registry_t));
|
|
if (!entry) return ENOMEM;
|
|
|
|
entry->name = enif_alloc(strlen(name) + 1);
|
|
if (!entry->name) {
|
|
free(entry);
|
|
return ENOMEM;
|
|
}
|
|
|
|
entry->state = state;
|
|
strcpy(entry->name, name);
|
|
enif_rwlock_rwlock(registry_lock);
|
|
HASH_FIND_STR(registry, name, found);
|
|
if (found) {
|
|
enif_rwlock_rwunlock(registry_lock);
|
|
enif_free(entry->name);
|
|
enif_free(entry);
|
|
return EINVAL;
|
|
} else {
|
|
if (state->name) {
|
|
/* Unregistering previously registered name */
|
|
HASH_FIND_STR(registry, state->name, found);
|
|
if (found)
|
|
delete_registry_entry(found);
|
|
}
|
|
enif_keep_resource(state);
|
|
HASH_ADD_STR(registry, name, entry);
|
|
state->name = entry->name;
|
|
enif_rwlock_rwunlock(registry_lock);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int unregister_tree(char *name) {
|
|
registry_t *entry;
|
|
int ret;
|
|
|
|
enif_rwlock_rwlock(registry_lock);
|
|
HASH_FIND_STR(registry, name, entry);
|
|
if (entry) {
|
|
delete_registry_entry(entry);
|
|
ret = 0;
|
|
} else {
|
|
ret = EINVAL;
|
|
}
|
|
enif_rwlock_rwunlock(registry_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************
|
|
* NIF helpers *
|
|
****************************************************************/
|
|
static ERL_NIF_TERM cons(ErlNifEnv *env, char *str, ERL_NIF_TERM tail)
|
|
{
|
|
if (str) {
|
|
size_t len = strlen(str);
|
|
ERL_NIF_TERM head;
|
|
unsigned char *buf = enif_make_new_binary(env, len, &head);
|
|
if (buf) {
|
|
memcpy(buf, str, len);
|
|
return enif_make_list_cell(env, head, tail);
|
|
}
|
|
}
|
|
return tail;
|
|
}
|
|
|
|
static void match(ErlNifEnv *env, tree_t *root,
|
|
char *path, size_t i, size_t size, ERL_NIF_TERM *acc)
|
|
{
|
|
tree_t *found;
|
|
size_t len = 0;
|
|
|
|
if (i<=size) {
|
|
HASH_FIND_STR(root->sub, path+i, found);
|
|
if (found) {
|
|
len = strlen(path+i) + 1;
|
|
match(env, found, path, i+len, size, acc);
|
|
};
|
|
if (i || path[0] != '$') {
|
|
HASH_FIND_STR(root->sub, "+", found);
|
|
if (found) {
|
|
len = strlen(path+i) + 1;
|
|
match(env, found, path, i+len, size, acc);
|
|
}
|
|
HASH_FIND_STR(root->sub, "#", found);
|
|
if (found) {
|
|
*acc = cons(env, found->val, *acc);
|
|
}
|
|
}
|
|
} else {
|
|
*acc = cons(env, root->val, *acc);
|
|
HASH_FIND_STR(root->sub, "#", found);
|
|
if (found)
|
|
*acc = cons(env, found->val, *acc);
|
|
}
|
|
}
|
|
|
|
static void to_list(ErlNifEnv *env, tree_t *root, ERL_NIF_TERM *acc)
|
|
{
|
|
tree_t *found, *iter;
|
|
|
|
HASH_ITER(hh, root->sub, found, iter) {
|
|
if (found->val) {
|
|
size_t len = strlen(found->val);
|
|
ERL_NIF_TERM refc = enif_make_int(env, found->refc);
|
|
ERL_NIF_TERM val;
|
|
unsigned char *buf = enif_make_new_binary(env, len, &val);
|
|
if (buf) {
|
|
memcpy(buf, found->val, len);
|
|
*acc = enif_make_list_cell(env, enif_make_tuple2(env, val, refc), *acc);
|
|
}
|
|
};
|
|
to_list(env, found, acc);
|
|
}
|
|
}
|
|
|
|
static ERL_NIF_TERM dump(ErlNifEnv *env, tree_t *tree)
|
|
{
|
|
tree_t *found, *iter;
|
|
ERL_NIF_TERM tail, head;
|
|
|
|
tail = enif_make_list(env, 0);
|
|
HASH_ITER(hh, tree->sub, found, iter) {
|
|
head = dump(env, found);
|
|
tail = enif_make_list_cell(env, head, tail);
|
|
}
|
|
if (tree->key) {
|
|
ERL_NIF_TERM part, path;
|
|
part = enif_make_string(env, tree->key, ERL_NIF_LATIN1);
|
|
if (tree->val)
|
|
path = enif_make_string(env, tree->val, ERL_NIF_LATIN1);
|
|
else
|
|
path = enif_make_atom(env, "none");
|
|
return enif_make_tuple4(env, part, path, enif_make_int(env, tree->refc), tail);
|
|
} else
|
|
return tail;
|
|
}
|
|
|
|
static ERL_NIF_TERM raise(ErlNifEnv *env, int err)
|
|
{
|
|
switch (err) {
|
|
case ENOMEM:
|
|
return enif_raise_exception(env, enif_make_atom(env, "enomem"));
|
|
default:
|
|
return enif_make_badarg(env);
|
|
}
|
|
}
|
|
|
|
void prep_path(char *path, ErlNifBinary *bin) {
|
|
int i;
|
|
unsigned char c;
|
|
path[bin->size] = 0;
|
|
for (i=0; i<bin->size; i++) {
|
|
c = bin->data[i];
|
|
path[i] = (c == '/') ? 0 : c;
|
|
}
|
|
}
|
|
|
|
/****************************************************************
|
|
* Constructors/Destructors *
|
|
****************************************************************/
|
|
static state_t *init_tree_state(ErlNifEnv *env) {
|
|
state_t *state = enif_alloc_resource(tree_state_t, sizeof(state_t));
|
|
if (state) {
|
|
memset(state, 0, sizeof(state_t));
|
|
state->tree = tree_new(NULL, 0);
|
|
state->lock = enif_rwlock_create("mqtree_lock");
|
|
if (state->tree && state->lock)
|
|
return state;
|
|
else
|
|
enif_release_resource(state);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void destroy_tree_state(ErlNifEnv *env, void *data) {
|
|
state_t *state = (state_t *) data;
|
|
if (state) {
|
|
tree_free(state->tree);
|
|
if (state->lock) enif_rwlock_destroy(state->lock);
|
|
}
|
|
memset(state, 0, sizeof(state_t));
|
|
}
|
|
|
|
/****************************************************************
|
|
* NIF definitions *
|
|
****************************************************************/
|
|
static int load(ErlNifEnv* env, void** priv, ERL_NIF_TERM max) {
|
|
registry_lock = enif_rwlock_create("mqtree_registry");
|
|
if (registry_lock) {
|
|
ErlNifResourceFlags flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER;
|
|
tree_state_t = enif_open_resource_type(env, NULL, "mqtree_state",
|
|
destroy_tree_state,
|
|
flags, NULL);
|
|
return 0;
|
|
}
|
|
return ENOMEM;
|
|
}
|
|
|
|
static void unload(ErlNifEnv* env, void* priv) {
|
|
if (registry_lock) {
|
|
enif_rwlock_destroy(registry_lock);
|
|
registry_lock = NULL;
|
|
}
|
|
}
|
|
|
|
static ERL_NIF_TERM new_0(ErlNifEnv* env, int argc,
|
|
const ERL_NIF_TERM argv[])
|
|
{
|
|
ERL_NIF_TERM result;
|
|
state_t *state = init_tree_state(env);
|
|
if (state) {
|
|
result = enif_make_resource(env, state);
|
|
enif_release_resource(state);
|
|
} else
|
|
result = raise(env, ENOMEM);
|
|
|
|
return result;
|
|
}
|
|
|
|
static ERL_NIF_TERM insert_2(ErlNifEnv* env, int argc,
|
|
const ERL_NIF_TERM argv[])
|
|
{
|
|
state_t *state;
|
|
ErlNifBinary path_bin;
|
|
|
|
if (!enif_get_resource(env, argv[0], tree_state_t, (void *) &state) ||
|
|
!enif_inspect_iolist_as_binary(env, argv[1], &path_bin))
|
|
return raise(env, EINVAL);
|
|
|
|
if (!path_bin.size)
|
|
return enif_make_atom(env, "ok");
|
|
|
|
char path[path_bin.size+1];
|
|
prep_path(path, &path_bin);
|
|
enif_rwlock_rwlock(state->lock);
|
|
int ret = tree_add(state->tree, path, path_bin.size);
|
|
enif_rwlock_rwunlock(state->lock);
|
|
|
|
if (!ret)
|
|
return enif_make_atom(env, "ok");
|
|
else
|
|
return raise(env, ret);
|
|
}
|
|
|
|
static ERL_NIF_TERM delete_2(ErlNifEnv* env, int argc,
|
|
const ERL_NIF_TERM argv[])
|
|
{
|
|
state_t *state;
|
|
ErlNifBinary path_bin;
|
|
|
|
if (!enif_get_resource(env, argv[0], tree_state_t, (void *) &state) ||
|
|
!enif_inspect_iolist_as_binary(env, argv[1], &path_bin))
|
|
return raise(env, EINVAL);
|
|
|
|
if (!path_bin.size)
|
|
return enif_make_atom(env, "ok");
|
|
|
|
char path[path_bin.size+1];
|
|
prep_path(path, &path_bin);
|
|
enif_rwlock_rwlock(state->lock);
|
|
tree_del(state->tree, path, 0, path_bin.size);
|
|
enif_rwlock_rwunlock(state->lock);
|
|
|
|
return enif_make_atom(env, "ok");
|
|
}
|
|
|
|
static ERL_NIF_TERM match_2(ErlNifEnv* env, int argc,
|
|
const ERL_NIF_TERM argv[])
|
|
{
|
|
state_t *state;
|
|
ErlNifBinary path_bin;
|
|
ERL_NIF_TERM result = enif_make_list(env, 0);
|
|
|
|
if (!enif_get_resource(env, argv[0], tree_state_t, (void *) &state) ||
|
|
!enif_inspect_iolist_as_binary(env, argv[1], &path_bin))
|
|
return raise(env, EINVAL);
|
|
|
|
if (!path_bin.size)
|
|
return result;
|
|
|
|
|
|
|
|
char path[path_bin.size+1];
|
|
prep_path(path, &path_bin);
|
|
enif_rwlock_rlock(state->lock);
|
|
match(env, state->tree, path, 0, path_bin.size, &result);
|
|
enif_rwlock_runlock(state->lock);
|
|
|
|
return result;
|
|
}
|
|
|
|
static ERL_NIF_TERM refc_2(ErlNifEnv* env, int argc,
|
|
const ERL_NIF_TERM argv[])
|
|
{
|
|
state_t *state;
|
|
ErlNifBinary path_bin;
|
|
|
|
if (!enif_get_resource(env, argv[0], tree_state_t, (void *) &state) ||
|
|
!enif_inspect_iolist_as_binary(env, argv[1], &path_bin))
|
|
return raise(env, EINVAL);
|
|
|
|
if (!path_bin.size)
|
|
return enif_make_int(env, 0);
|
|
|
|
char path[path_bin.size+1];
|
|
prep_path(path, &path_bin);
|
|
enif_rwlock_rlock(state->lock);
|
|
int refc = tree_refc(state->tree, path, 0, path_bin.size);
|
|
enif_rwlock_runlock(state->lock);
|
|
|
|
return enif_make_int(env, refc);
|
|
}
|
|
|
|
static ERL_NIF_TERM clear_1(ErlNifEnv* env, int argc,
|
|
const ERL_NIF_TERM argv[])
|
|
{
|
|
state_t *state;
|
|
if (!enif_get_resource(env, argv[0], tree_state_t, (void *) &state))
|
|
return raise(env, EINVAL);
|
|
|
|
enif_rwlock_rwlock(state->lock);
|
|
tree_clear(state->tree);
|
|
enif_rwlock_rwunlock(state->lock);
|
|
|
|
return enif_make_atom(env, "ok");
|
|
}
|
|
|
|
static ERL_NIF_TERM size_1(ErlNifEnv* env, int argc,
|
|
const ERL_NIF_TERM argv[])
|
|
{
|
|
state_t *state;
|
|
size_t size = 0;
|
|
if (!enif_get_resource(env, argv[0], tree_state_t, (void *) &state))
|
|
return raise(env, EINVAL);
|
|
|
|
enif_rwlock_rlock(state->lock);
|
|
tree_size(state->tree, &size);
|
|
enif_rwlock_runlock(state->lock);
|
|
|
|
return enif_make_uint64(env, (ErlNifUInt64) size);
|
|
}
|
|
|
|
static ERL_NIF_TERM is_empty_1(ErlNifEnv* env, int argc,
|
|
const ERL_NIF_TERM argv[])
|
|
{
|
|
state_t *state;
|
|
if (!enif_get_resource(env, argv[0], tree_state_t, (void *) &state))
|
|
return raise(env, EINVAL);
|
|
|
|
enif_rwlock_rlock(state->lock);
|
|
char *ret = state->tree->sub ? "false" : "true";
|
|
enif_rwlock_runlock(state->lock);
|
|
|
|
return enif_make_atom(env, ret);
|
|
}
|
|
|
|
static ERL_NIF_TERM to_list_1(ErlNifEnv* env, int argc,
|
|
const ERL_NIF_TERM argv[])
|
|
{
|
|
state_t *state;
|
|
ERL_NIF_TERM result = enif_make_list(env, 0);
|
|
|
|
if (!enif_get_resource(env, argv[0], tree_state_t, (void *) &state))
|
|
return raise(env, EINVAL);
|
|
|
|
enif_rwlock_rlock(state->lock);
|
|
to_list(env, state->tree, &result);
|
|
enif_rwlock_runlock(state->lock);
|
|
|
|
return result;
|
|
}
|
|
|
|
static ERL_NIF_TERM dump_1(ErlNifEnv* env, int argc,
|
|
const ERL_NIF_TERM argv[])
|
|
{
|
|
state_t *state;
|
|
if (!enif_get_resource(env, argv[0], tree_state_t, (void *) &state))
|
|
return raise(env, EINVAL);
|
|
|
|
enif_rwlock_rlock(state->lock);
|
|
ERL_NIF_TERM result = dump(env, state->tree);
|
|
enif_rwlock_runlock(state->lock);
|
|
|
|
return result;
|
|
}
|
|
|
|
static ERL_NIF_TERM register_2(ErlNifEnv* env, int argc,
|
|
const ERL_NIF_TERM argv[])
|
|
{
|
|
state_t *state;
|
|
unsigned int len;
|
|
int ret;
|
|
|
|
if (!enif_get_atom_length(env, argv[0], &len, ERL_NIF_LATIN1) ||
|
|
!enif_get_resource(env, argv[1], tree_state_t, (void *) &state))
|
|
return raise(env, EINVAL);
|
|
|
|
char name[len+1];
|
|
enif_get_atom(env, argv[0], name, len+1, ERL_NIF_LATIN1);
|
|
if (!strcmp(name, "undefined"))
|
|
return raise(env, EINVAL);
|
|
|
|
ret = register_tree(name, state);
|
|
if (ret)
|
|
return raise(env, ret);
|
|
else
|
|
return enif_make_atom(env, "ok");
|
|
}
|
|
|
|
static ERL_NIF_TERM unregister_1(ErlNifEnv* env, int argc,
|
|
const ERL_NIF_TERM argv[])
|
|
{
|
|
unsigned int len;
|
|
int ret;
|
|
|
|
if (!enif_get_atom_length(env, argv[0], &len, ERL_NIF_LATIN1))
|
|
return raise(env, EINVAL);
|
|
|
|
char name[len+1];
|
|
enif_get_atom(env, argv[0], name, len+1, ERL_NIF_LATIN1);
|
|
ret = unregister_tree(name);
|
|
if (ret)
|
|
return raise(env, ret);
|
|
else
|
|
return enif_make_atom(env, "ok");
|
|
}
|
|
|
|
static ERL_NIF_TERM whereis_1(ErlNifEnv* env, int argc,
|
|
const ERL_NIF_TERM argv[])
|
|
{
|
|
unsigned int len;
|
|
registry_t *entry;
|
|
ERL_NIF_TERM result;
|
|
|
|
if (!enif_get_atom_length(env, argv[0], &len, ERL_NIF_LATIN1))
|
|
return raise(env, EINVAL);
|
|
|
|
char name[len+1];
|
|
enif_get_atom(env, argv[0], name, len+1, ERL_NIF_LATIN1);
|
|
enif_rwlock_rlock(registry_lock);
|
|
HASH_FIND_STR(registry, name, entry);
|
|
if (entry)
|
|
result = enif_make_resource(env, entry->state);
|
|
else
|
|
result = enif_make_atom(env, "undefined");
|
|
enif_rwlock_runlock(registry_lock);
|
|
|
|
return result;
|
|
}
|
|
|
|
static ERL_NIF_TERM registered_0(ErlNifEnv* env, int argc,
|
|
const ERL_NIF_TERM argv[])
|
|
{
|
|
registry_t *entry, *iter;
|
|
ERL_NIF_TERM result = enif_make_list(env, 0);
|
|
|
|
enif_rwlock_rlock(registry_lock);
|
|
HASH_ITER(hh, registry, entry, iter) {
|
|
result = enif_make_list_cell(env, enif_make_atom(env, entry->name), result);
|
|
}
|
|
enif_rwlock_runlock(registry_lock);
|
|
|
|
return result;
|
|
}
|
|
|
|
static ErlNifFunc nif_funcs[] =
|
|
{
|
|
{"new", 0, new_0},
|
|
{"insert", 2, insert_2},
|
|
{"delete", 2, delete_2},
|
|
{"match", 2, match_2},
|
|
{"refc", 2, refc_2},
|
|
{"clear", 1, clear_1},
|
|
{"size", 1, size_1},
|
|
{"is_empty", 1, is_empty_1},
|
|
{"to_list", 1, to_list_1},
|
|
{"dump", 1, dump_1},
|
|
{"register", 2, register_2},
|
|
{"unregister", 1, unregister_1},
|
|
{"whereis", 1, whereis_1},
|
|
{"registered", 0, registered_0}
|
|
};
|
|
|
|
ERL_NIF_INIT(mqtree, nif_funcs, load, NULL, NULL, unload)
|