summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/CMakeLists.txt3
-rw-r--r--src/anydb.c490
-rw-r--r--src/anydb.h142
-rw-r--r--src/cyn.c4
-rw-r--r--src/data.h6
-rw-r--r--src/db.c803
-rw-r--r--src/db.h9
-rw-r--r--src/fdb.c865
-rw-r--r--src/fdb.h99
-rw-r--r--src/memdb.c279
-rw-r--r--src/memdb.h24
11 files changed, 1950 insertions, 774 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index cacd659..319ece1 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -17,12 +17,15 @@
###########################################################################
set(SERVER_SOURCES
+ anydb.c
cyn.c
db.c
dbinit.c
expire.c
fbuf.c
+ fdb.c
main-cynarad.c
+ memdb.c
pollitem.c
prot.c
queue.c
diff --git a/src/anydb.c b/src/anydb.c
new file mode 100644
index 0000000..d284610
--- /dev/null
+++ b/src/anydb.c
@@ -0,0 +1,490 @@
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author José Bollo <jose.bollo@iot.bzh>
+ *
+ * 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 <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+
+#include "data.h"
+#include "anydb.h"
+
+
+#define CLIENT_MATCH_SCORE 1
+#define SESSION_MATCH_SCORE 1
+#define USER_MATCH_SCORE 1
+#define PERMISSION_MATCH_SCORE 1
+
+/******************************************************************************/
+/******************************************************************************/
+/*** UTILITIES ***/
+/******************************************************************************/
+/******************************************************************************/
+
+/** check whether the 'text' fit String_Any, NULL or "" */
+static
+bool
+is_any(
+ const char *text
+) {
+ return text == NULL || text[0] == 0 || (!text[1] && text[0] == Data_Any_Char);
+}
+
+/** check whether the 'text' fit String_Any, String_Wide, NULL or "" */
+static
+bool
+is_any_or_wide(
+ const char *text
+) {
+ return text == NULL || text[0] == 0
+ || (!text[1] && (text[0] == Data_Any_Char || text[0] == Data_Wide_Char));
+}
+
+/** return the name of 'index' */
+static
+const char*
+string(
+ anydb_t *db,
+ anydb_idx_t idx
+) {
+ if (idx == AnyIdx_Any)
+ return Data_Any_String;
+ if (idx == AnyIdx_Wide)
+ return Data_Wide_String;
+ return db->itf.string(db->clodb, idx);
+}
+
+/** search the index of 'name' and create it if 'create' */
+static
+int
+idx(
+ anydb_t *db,
+ anydb_idx_t *idx,
+ const char *name,
+ bool create
+) {
+ /* special names */
+ if (!name || !name[0]) {
+ *idx = AnyIdx_Any;
+ return 0;
+ }
+ if (!name[1]) {
+ if (name[0] == Data_Any_Char) {
+ *idx = AnyIdx_Any;
+ return 0;
+ }
+ if (name[0] == Data_Wide_Char) {
+ *idx = AnyIdx_Wide;
+ return 0;
+ }
+ }
+
+ /* other case */
+ return db->itf.index(db->clodb, idx, name, create);
+}
+
+/** search the index of 'name' and create it if 'create' */
+static
+int
+idx_but_any(
+ anydb_t *db,
+ anydb_idx_t *idx,
+ const char *name,
+ bool create
+) {
+ if (is_any_or_wide(name)) {
+ *idx = AnyIdx_Wide;
+ return 0;
+ }
+
+ /* other case */
+ return db->itf.index(db->clodb, idx, name, create);
+}
+
+/** search the index of 'name' and create it if 'create' */
+static
+anydb_idx_t
+idx_or_none_but_any(
+ anydb_t *db,
+ const char *name,
+ bool create
+) {
+ anydb_idx_t idx;
+
+ if (idx_but_any(db, &idx, name, create))
+ idx = AnyIdx_None;
+ return idx;
+}
+
+/******************************************************************************/
+/******************************************************************************/
+/*** FOR ALL ***/
+/******************************************************************************/
+/******************************************************************************/
+
+struct for_all_s
+{
+ anydb_t *db;
+ void *closure;
+ void (*callback)(
+ void *closure,
+ const data_key_t *key,
+ const data_value_t *value);
+ anydb_idx_t idxcli;
+ anydb_idx_t idxses;
+ anydb_idx_t idxusr;
+ const char *strperm;
+ time_t now;
+};
+
+static
+anydb_action_t
+for_all_cb(
+ void *closure,
+ const anydb_key_t *key,
+ anydb_value_t *value
+) {
+ struct for_all_s *s = closure;
+ data_key_t k;
+ data_value_t v;
+
+ if (value->expire && value->expire <= s->now)
+ return Anydb_Action_Remove_And_Continue;
+
+ if ((s->idxcli == AnyIdx_Any || s->idxcli == key->client)
+ && (s->idxses == AnyIdx_Any || s->idxses == key->session)
+ && (s->idxusr == AnyIdx_Any || s->idxusr == key->user)) {
+ k.permission = string(s->db, key->permission);
+ if (!s->strperm || !strcasecmp(s->strperm, k.permission)) {
+ k.client = string(s->db, key->client);
+ k.session = string(s->db, key->session);
+ k.user = string(s->db, key->user);
+ v.value = string(s->db, value->value);
+ v.expire = value->expire;
+ s->callback(s->closure, &k, &v);
+ }
+ }
+ return Anydb_Action_Continue;
+}
+
+/** enumerate */
+void
+anydb_for_all(
+ anydb_t *db,
+ void *closure,
+ void (*callback)(
+ void *closure,
+ const data_key_t *key,
+ const data_value_t *value),
+ const data_key_t *key
+) {
+ struct for_all_s s;
+
+ s.db = db;
+ s.closure = closure;
+ s.callback = callback;
+
+ if (idx(db, &s.idxcli, key->client, false)
+ || idx(db, &s.idxses, key->session, false)
+ || idx(db, &s.idxusr, key->user, false))
+ return; /* nothing to do! because one of the idx doesn't exist */
+ s.strperm = is_any(key->permission) ? NULL : key->permission;
+
+ s.now = time(NULL);
+ db->itf.apply(db->clodb, for_all_cb, &s);
+}
+
+/******************************************************************************/
+/******************************************************************************/
+/*** DROP ***/
+/******************************************************************************/
+/******************************************************************************/
+
+struct drop_s
+{
+ anydb_t *db;
+ anydb_idx_t idxcli;
+ anydb_idx_t idxses;
+ anydb_idx_t idxusr;
+ const char *strperm;
+ time_t now;
+};
+
+static
+anydb_action_t
+drop_cb(
+ void *closure,
+ const anydb_key_t *key,
+ anydb_value_t *value
+) {
+ struct drop_s *s = closure;
+
+ if (value->expire && value->expire <= s->now)
+ return Anydb_Action_Remove_And_Continue;
+
+ if ((s->idxcli == AnyIdx_Any || s->idxcli == key->client)
+ && (s->idxses == AnyIdx_Any || s->idxses == key->session)
+ && (s->idxusr == AnyIdx_Any || s->idxusr == key->user)
+ && (!s->strperm || !strcasecmp(s->strperm, string(s->db, key->permission))))
+ return Anydb_Action_Remove_And_Continue;
+
+ return Anydb_Action_Continue;
+}
+
+/** drop rules */
+int
+anydb_drop(
+ anydb_t *db,
+ const data_key_t *key
+) {
+ struct drop_s s;
+
+ s.db = db;
+
+ if (idx(db, &s.idxcli, key->client, false)
+ || idx(db, &s.idxses, key->session, false)
+ || idx(db, &s.idxusr, key->user, false))
+ return 0; /* nothing to do! because one of the idx doesn't exist */
+ s.strperm = is_any(key->permission) ? NULL : key->permission;
+
+ s.now = time(NULL);
+ db->itf.apply(db->clodb, drop_cb, &s);
+ return 0;
+}
+
+/******************************************************************************/
+/******************************************************************************/
+/*** ADD ***/
+/******************************************************************************/
+/******************************************************************************/
+
+struct set_s
+{
+ anydb_t *db;
+ anydb_idx_t idxcli;
+ anydb_idx_t idxses;
+ anydb_idx_t idxusr;
+ anydb_idx_t idxval;
+ time_t expire;
+ const char *strperm;
+ time_t now;
+};
+
+static
+anydb_action_t
+set_cb(
+ void *closure,
+ const anydb_key_t *key,
+ anydb_value_t *value
+) {
+ struct set_s *s = closure;
+
+ if (value->expire && value->expire <= s->now)
+ return Anydb_Action_Remove_And_Continue;
+
+ if (s->idxcli == key->client
+ && s->idxses == key->session
+ && s->idxusr == key->user
+ && !strcasecmp(s->strperm, string(s->db, key->permission))) {
+ value->value = s->idxval;
+ value->expire = s->expire;
+ s->db = NULL;
+ return Anydb_Action_Update_And_Stop;
+ }
+
+ return Anydb_Action_Continue;
+}
+
+int
+anydb_set(
+ anydb_t *db,
+ const data_key_t *key,
+ const data_value_t *value
+) {
+ int rc;
+ struct set_s s;
+ anydb_key_t k;
+ anydb_value_t v;
+
+ s.db = db;
+ s.strperm = key->permission;
+ s.expire = value->expire;
+
+ rc = idx_but_any(db, &s.idxcli, key->client, true);
+ if (rc)
+ goto error;
+ rc = idx_but_any(db, &s.idxses, key->session, true);
+ if (rc)
+ goto error;
+ rc = idx_but_any(db, &s.idxusr, key->user, true);
+ if (rc)
+ goto error;
+ rc = idx(db, &s.idxval, value->value, true);
+ if (rc)
+ goto error;
+
+ s.now = time(NULL);
+ db->itf.apply(db->clodb, set_cb, &s);
+ if (s.db) {
+ if (idx(db, &k.permission, s.strperm, true))
+ goto error;
+ k.client = s.idxcli;
+ k.user = s.idxusr;
+ k.session = s.idxses;
+ v.value = s.idxval;
+ v.expire = s.expire;
+ rc = db->itf.add(db->clodb, &k, &v);
+ if (rc)
+ goto error;
+ }
+ return 0;
+error:
+ return rc;
+}
+
+/******************************************************************************/
+/******************************************************************************/
+/*** TEST ***/
+/******************************************************************************/
+/******************************************************************************/
+
+struct test_s
+{
+ anydb_t *db;
+ anydb_idx_t idxcli;
+ anydb_idx_t idxses;
+ anydb_idx_t idxusr;
+ const char *strperm;
+ int score;
+ anydb_idx_t idxval;
+ time_t expire;
+ time_t now;
+};
+
+static
+anydb_action_t
+test_cb(
+ void *closure,
+ const anydb_key_t *key,
+ anydb_value_t *value
+) {
+ struct test_s *s = closure;
+ int sc;
+
+ if (value->expire && value->expire <= s->now)
+ return Anydb_Action_Remove_And_Continue;
+
+ if ((s->idxcli == key->client || key->client == AnyIdx_Wide)
+ && (s->idxses == key->session || key->session == AnyIdx_Wide)
+ && (s->idxusr == key->user || key->user == AnyIdx_Wide)
+ && (AnyIdx_Wide == key->permission
+ || !strcasecmp(s->strperm, string(s->db, key->permission)))) {
+ sc = 1;
+ if (key->client != AnyIdx_Wide)
+ sc += CLIENT_MATCH_SCORE;
+ if (key->session != AnyIdx_Wide)
+ sc += SESSION_MATCH_SCORE;
+ if (key->user != AnyIdx_Wide)
+ sc += USER_MATCH_SCORE;
+ if (key->permission != AnyIdx_Wide)
+ sc += PERMISSION_MATCH_SCORE;
+ if (sc > s->score) {
+ s->score = sc;
+ s->idxval = value->value;
+ s->expire = value->expire;
+ }
+ }
+ return Anydb_Action_Continue;
+}
+
+int
+anydb_test(
+ anydb_t *db,
+ const data_key_t *key,
+ data_value_t *value
+) {
+ struct test_s s;
+
+ s.db = db;
+ s.now = time(NULL);
+ s.strperm = key->permission;
+ s.expire = value->expire;
+
+ s.idxcli = idx_or_none_but_any(db, key->client, true);
+ s.idxses = idx_or_none_but_any(db, key->session, true);
+ s.idxusr = idx_or_none_but_any(db, key->user, true);
+
+ s.expire = -1;
+ s.idxval = AnyIdx_Invalid;
+ s.score = 0;
+
+ db->itf.apply(db->clodb, test_cb, &s);
+
+ if (s.score) {
+ value->value = string(db, s.idxval);
+ value->expire = s.expire;
+ } else {
+ value->value = NULL;
+ value->expire = 0;
+ }
+ return s.score;
+}
+
+/******************************************************************************/
+/******************************************************************************/
+/*** CLEANUP ***/
+/******************************************************************************/
+/******************************************************************************/
+
+static
+anydb_action_t
+cleanup_cb(
+ void *closure,
+ const anydb_key_t *key,
+ anydb_value_t *value
+) {
+ if (value->expire && value->expire <= *(time_t*)closure)
+ return Anydb_Action_Remove_And_Continue;
+
+ return Anydb_Action_Continue;
+}
+
+void
+anydb_cleanup(
+ anydb_t *db
+) {
+ time_t t;
+
+ t = time(NULL);
+ db->itf.apply(db->clodb, cleanup_cb, &t);
+ db->itf.gc(db->clodb);
+}
+
+/******************************************************************************/
+/******************************************************************************/
+/*** DESTROY ***/
+/******************************************************************************/
+/******************************************************************************/
+
+void
+anydb_destroy(
+ anydb_t *db
+) {
+ db->itf.destroy(db->clodb);
+}
diff --git a/src/anydb.h b/src/anydb.h
new file mode 100644
index 0000000..33f0447
--- /dev/null
+++ b/src/anydb.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author José Bollo <jose.bollo@iot.bzh>
+ *
+ * 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.
+ */
+
+#pragma once
+
+/**
+ * An index is an integer
+ */
+typedef uint32_t anydb_idx_t;
+
+#define AnyIdx_Invalid ((anydb_idx_t)0xffffffffu)
+#define AnyIdx_Any ((anydb_idx_t)0xfffffffeu)
+#define AnyIdx_Wide ((anydb_idx_t)0xfffffffdu)
+#define AnyIdx_None ((anydb_idx_t)0xfffffffcu)
+#define AnyIdx_Max ((anydb_idx_t)0xfffffff7u)
+
+/**
+ * A key is a set of index
+ */
+struct anydb_key {
+ /** client string id */
+ anydb_idx_t client;
+
+ /** session string id */
+ anydb_idx_t session;
+
+ /** user string id */
+ anydb_idx_t user;
+
+ /** permission string id */
+ anydb_idx_t permission;
+};
+typedef struct anydb_key anydb_key_t;
+
+/**
+ * A rule is a set of 32 bits integers
+ */
+struct anydb_value
+{
+ /** value string id */
+ anydb_idx_t value;
+
+ /** expiration */
+ time_t expire;
+};
+typedef struct anydb_value anydb_value_t;
+
+/**
+ */
+enum anydb_action
+{
+ Anydb_Action_Continue = 0,
+ Anydb_Action_Update_And_Stop = 1,
+ Anydb_Action_Remove_And_Continue = 2
+};
+typedef enum anydb_action anydb_action_t;
+
+struct anydb_itf
+{
+ int (*index)(void *clodb, anydb_idx_t *idx, const char *name, bool create);
+ const char *(*string)(void *clodb, anydb_idx_t idx);
+ void (*apply)(void *clodb, anydb_action_t (*oper)(void *closure, const anydb_key_t *key, anydb_value_t *value), void *closure);
+ int (*add)(void *clodb, const anydb_key_t *key, const anydb_value_t *value);
+ void (*gc)(void *clodb);
+ void (*destroy)(void *clodb);
+};
+typedef struct anydb_itf anydb_itf_t;
+
+struct anydb
+{
+ void *clodb;
+ anydb_itf_t itf;
+};
+typedef struct anydb anydb_t;
+
+
+/** enumerate */
+extern
+void
+anydb_for_all(
+ anydb_t *db,
+ void *closure,
+ void (*callback)(
+ void *closure,
+ const data_key_t *key,
+ const data_value_t *value),
+ const data_key_t *key
+);
+
+/** drop rules */
+extern
+int
+anydb_drop(
+ anydb_t *db,
+ const data_key_t *key
+);
+
+/** set a rules */
+extern
+int
+anydb_set(
+ anydb_t *db,
+ const data_key_t *key,
+ const data_value_t *value
+);
+
+/** test a rule, returns 0 or the score: count of exact keys */
+extern
+int
+anydb_test(
+ anydb_t *db,
+ const data_key_t *key,
+ data_value_t *value
+);
+
+/** drop rules */
+extern
+void
+anydb_cleanup(
+ anydb_t *db
+);
+
+/** destroy the database */
+extern
+void
+anydb_destroy(
+ anydb_t *db
+);
diff --git a/src/cyn.c b/src/cyn.c
index 6f27860..84d3295 100644
--- a/src/cyn.c
+++ b/src/cyn.c
@@ -239,9 +239,7 @@ cyn_test(
int rc;
rc = db_test(key, value);
- if (rc > 0)
- rc = 0;
- else {
+ if (rc <= 0) {
value->value = DEFAULT;
value->expire = 0;
}
diff --git a/src/data.h b/src/data.h
index 10cc599..8bd35b3 100644
--- a/src/data.h
+++ b/src/data.h
@@ -22,6 +22,12 @@
#define ASK "ask"
#define DEFAULT DENY
+#define Data_Any_Char '#'
+#define Data_Wide_Char '*'
+
+#define Data_Any_String "#"
+#define Data_Wide_String "*"
+
typedef struct data_key data_key_t;
typedef struct data_value data_value_t;
diff --git a/src/db.c b/src/db.c
index 0bd4a09..98c96e7 100644
--- a/src/db.c
+++ b/src/db.c
@@ -27,392 +27,22 @@
#include <errno.h>
#include "data.h"
-#include "fbuf.h"
-#include "db.h"
-#include "rcyn-client.h"
+#include "anydb.h"
+#include "fdb.h"
+#include "memdb.h"
-#define NOEXPIRE 0
-#define NOIDX 0
+static anydb_t *memdb;
-#define ANYIDX 40
-#define ANYSTR "#"
-
-#define WIDEIDX 42
-#define WIDESTR "*"
-
-/*
- * for the first version, save enougth time up to 4149
- * 4149 = 1970 + (4294967296 * 16) / (365 * 24 * 60 * 60)
- *
- * in the next version, time will be relative to a stored base
- */
-#define exp2time(x) (((time_t)(x)) << 4)
-#define time2expl(x) ((uint32_t)((x) >> 4))
-#define time2exph(x) time2expl((x) + 15)
-
-/**
- * A query is a set of 32 bits integers
- */
-struct key_ids {
- /** client string id */
- uint32_t client;
-
- /** user string id */
- uint32_t user;
-
- /** permission string id */
- uint32_t permission;
-};
-typedef struct key_ids key_ids_t;
-
-/**
- * A rule is a set of 32 bits integers
- */
-struct rule
-{
- /** key part */
- key_ids_t key;
-
- /** value string id */
- uint32_t value;
-
- /** expiration */
- uint32_t expire;
-};
-typedef struct rule rule_t;
-
-/*
- * The cynara-agl database is made of 2 memory mapped files:
- * - names: the zero terminated names
- * - rules: the rules based on name indexes as 32bits indexes
- * These files are normally in /var/lib/cynara
- */
-#if !defined(DEFAULT_DB_DIR)
-# define DEFAULT_DB_DIR "/var/lib/cynara"
-#endif
-static const char db_default_directory[] = DEFAULT_DB_DIR;
-
-/** the file for the names */
-static fbuf_t fnames;
-
-/** the file for the rules */
-static fbuf_t frules;
-
-/** identification of names version 1
- * $> uuidgen --sha1 -n @url -N urn:AGL:cynara:db:names:1
- * $> uuid -v 5 urn:AGL:cynara:db:names:1
- */
-static const char uuid_names_v1[] = "e9481f9e-b2f4-5716-90cf-c286d98d1868\n--\n";
-
-/** identification of rules version 1
- * $> uuidgen --sha1 -n @url -N urn:AGL:cynara:db:rules:1
- * $> uuid -v 5 ns:URL urn:AGL:cynara:db:rules:1
- */
-static const char uuid_rules_v1[] = "8f7a5b21-48b1-57af-96c9-d5d7192be370\n--\n";
-
-/** length of the identification */
-static const int uuidlen = 40;
-
-/** count of names */
-static uint32_t names_count;
-
-/** the name indexes sorted */
-static uint32_t *names_sorted;
-
-/** count of rules */
-static uint32_t rules_count;
-
-/** the rules */
-static rule_t *rules;
-
-/** return the name of 'index' */
-static
-const char*
-name_at(
- uint32_t index
-) {
- return (const char*)(fnames.buffer + index);
-}
-
-/** compare names. used by qsort and bsearch */
-static
-int
-cmpnames(
- const void *pa,
- const void *pb
-) {
- uint32_t a = *(const uint32_t*)pa;
- uint32_t b = *(const uint32_t*)pb;
- return strcmp(name_at(a), name_at(b));
-}
-
-/** search the index of 'name' and create it if 'needed' */
-int
-db_get_name_index(
- uint32_t *index,
- const char *name,
- bool needed
-) {
- uint32_t lo, up, m, i, *p;
- int c;
- const char *n;
- size_t len;
-
- /* special names */
- if (!name || !name[0])
- name = ANYSTR;
-
- /* dichotomic search */
- lo = 0;
- up = names_count;
- while(lo < up) {
- m = (lo + up) >> 1;
- i = names_sorted[m];
- n = name_at(i);
- c = strcmp(n, name);
-
- if (c == 0) {
- /* found */
- *index = i;
- return 0;
- }
-
- /* dichotomic iteration */
- if (c < 0)
- lo = m + 1;
- else
- up = m;
- }
-
- /* not found */
- if (!needed) {
- errno = ENOENT;
- return -1;
- }
-
- /* check length */
- len = strnlen(name, MAX_NAME_LENGTH + 1);
- if (len > MAX_NAME_LENGTH) {
- errno = EINVAL;
- return -1;
- }
-
- /* add the name in the file */
- i = fnames.used;
- c = fbuf_append(&fnames, name, 1 + (uint32_t)len);
- if (c < 0)
- return c;
-
- /* add the name in sorted array */
- up = names_count;
- if (!(up & 1023)) {
- p = realloc(names_sorted, (up + 1024) * sizeof *names_sorted);
- if (p == NULL) {
- fprintf(stderr, "out of memory");
- return -1;
- }
- names_sorted = p;
- }
- memmove(&names_sorted[lo + 1], &names_sorted[lo], (up - lo) * sizeof *names_sorted);
- names_count = up + 1;
- *index = names_sorted[lo] = i;
- return 0;
-}
-
-/** initialize names */
-static
-int
-init_names(
-) {
- int rc;
- uint32_t pos, len, *ns, *p, all, nc;
-
- all = 0;
- nc = 0;
- ns = NULL;
-
- /* iterate over names */
- pos = uuidlen;
- while (pos < fnames.used) {
- /* get name length */
- len = (uint32_t)strlen(name_at(pos));
- if (pos + len <= pos || pos + len > fnames.used) {
- free(ns);
- goto bad_file;
- }
- /* store the position */
- if (all <= nc) {
- all += 1024;
- p = realloc(ns, all * sizeof *ns);
- if (p == NULL) {
- free(ns);
- fprintf(stderr, "out of memory");
- goto error;
- }
- ns = p;
- }
- ns[nc++] = pos;
- /* next */
- pos += len + 1;
- }
-
- /* sort and record */
- qsort(ns, nc, sizeof *ns, cmpnames);
- names_sorted = ns;
- names_count = nc;
-
- /* predefined symbols */
- rc = db_get_name_index(&pos, ANYSTR, true);
- if (rc < 0)
- goto error;
- if (pos != ANYIDX)
- goto bad_file;
- rc = db_get_name_index(&pos, WIDESTR, true);
- if (rc < 0)
- goto error;
- if (pos != WIDEIDX)
- goto bad_file;
-
- return 0;
-bad_file:
- fprintf(stderr, "bad file %s", fnames.name);
- errno = ENOEXEC;
-error:
- return -1;
-}
-
-/** check whether the 'text' fit ANYSTR, NULL or "" */
-static
-bool
-is_any(
- const char *text
-) {
- return text == NULL || text[0] == 0 || 0 == strcmp(text, ANYSTR);
-}
-
-/** check whether the 'text' fit ANYSTR, WIDESTR, NULL or "" */
+/** check whether the 'text' fit String_Any, String_Wide, NULL or "" */
static
bool
is_any_or_wide(
const char *text
) {
- return is_any(text) || 0 == strcmp(text, WIDESTR);
-}
-
-/** set the 'value' to the rule at 'index' */
-static
-void
-touch_at(
- uint32_t index
-) {
- uint32_t pos;
-
- pos = (uint32_t)(((void*)&rules[index]) - frules.buffer);
- if (pos < frules.saved)
- frules.saved = pos;
+ return text == NULL || text[0] == 0
+ || (!text[1] && (text[0] == Data_Any_Char || text[0] == Data_Wide_Char));
}
-/** set the 'value' to the rule at 'index' */
-static
-void
-set_at(
- uint32_t index,
- uint32_t value,
- uint32_t expire
-) {
- assert(index < rules_count);
- rules[index].value = value;
- rules[index].expire = expire;
- touch_at(index);
-}
-
-/** drop the rule at 'index' */
-static
-void
-drop_at(
- uint32_t index
-) {
- uint32_t pos;
-
- assert(index < rules_count);
- if (index < --rules_count)
- rules[index] = rules[rules_count];
- pos = (uint32_t)(((void*)&rules[rules_count]) - frules.buffer);
- frules.used = pos;
- touch_at(index);
-}
-
-/** add the rule 'client' x 'user' x 'permission' x 'value' */
-static
-int
-add_rule(
- uint32_t client,
- uint32_t user,
- uint32_t permission,
- uint32_t value,
- uint32_t expire
-) {
- int rc;
- uint32_t c;
- rule_t *rule;
-
- c = frules.used + (uint32_t)sizeof *rule;
- rc = fbuf_ensure_capacity(&frules, c);
- if (rc)
- return rc;
- rules = (rule_t*)(frules.buffer + uuidlen);
- rule = &rules[rules_count++];
- rule->key.client = client;
- rule->key.user = user;
- rule->key.permission = permission;
- rule->value = value;
- rule->expire = expire;
- frules.used = c;
- return 0;
-}
-
-/** init the rules from the file */
-static
-void
-init_rules(
-) {
- rules = (rule_t*)(frules.buffer + uuidlen);
- rules_count = (frules.used - uuidlen) / sizeof *rules;
-}
-
-/** open a fbuf */
-static
-int
-open_identify(
- fbuf_t *fb,
- const char *directory,
- const char *name,
- const char *id,
- uint32_t idlen
-) {
- int rc;
- char *file, *backup;
- size_t sd, sn;
-
- sd = strlen(directory);
- sn = strlen(name);
- file = malloc(((sd + sn) << 1) + 5);
- if (!file)
- rc = -ENOMEM;
- else {
-
- memcpy(file, directory, sd);
- file[sd] = '/';
- memcpy(&file[sd + 1], name, sn + 1);
- backup = &file[sd + sn + 2];
- memcpy(backup, file, sd + sn + 1);
- backup[sd + sn + 1] = '~';
- backup[sd + sn + 2] = 0;
- rc = fbuf_open_identify(fb, file, backup, id, idlen);
- free(file);
- }
- return rc;
-}
/** open the database for files 'names' and 'rules' (can be NULL) */
int
@@ -421,115 +51,49 @@ db_open(
) {
int rc;
- /* provide default directory */
- if (directory == NULL)
- directory = db_default_directory;
-
- /* open the names */
- rc = open_identify(&fnames, directory, "cynara.names", uuid_names_v1, uuidlen);
- if (rc < 0)
- goto error;
-
- /* open the rules */
- rc = open_identify(&frules, directory, "cynara.rules", uuid_rules_v1, uuidlen);
- if (rc < 0)
- goto error;
-
- /* connect internals */
- rc = init_names();
- if (rc < 0)
- goto error;
-
- init_rules();
- return 0;
-error:
- return -1;
+ rc = memdb_create(&memdb);
+ if (!rc) {
+ rc = fdb_open(directory);
+ if (rc)
+ anydb_destroy(memdb);
+ }
+ return rc;
}
/** close the database */
void
db_close(
) {
- assert(fnames.name && frules.name);
- fbuf_close(&fnames);
- fbuf_close(&frules);
+ fdb_close();
+ anydb_destroy(memdb);
}
/** is the database empty */
bool
db_is_empty(
) {
- return !rules_count;
+ return fdb_is_empty();
}
/** synchronize db on files */
int
db_sync(
) {
- int rc;
-
- assert(fnames.name && frules.name);
- rc = fbuf_sync(&fnames);
- if (rc == 0)
- rc = fbuf_sync(&frules);
- return rc;
+ return fdb_sync();
}
/** make a backup of the database */
int
db_backup(
) {
- int rc;
-
- assert(fnames.name && frules.name);
- rc = fbuf_backup(&fnames);
- if (rc == 0)
- rc = fbuf_backup(&frules);
- return rc;
+ return fdb_backup();
}
/** recover the database from latest backup */
int
db_recover(
) {
- int rc;
-
- assert(fnames.name && frules.name);
-
- rc = fbuf_recover(&fnames);
- if (rc < 0)
- goto error;
-
- rc = fbuf_recover(&frules);
- if (rc < 0)
- goto error;
-
- rc = init_names();
- if (rc < 0)
- goto error;
-
- init_rules();
- return 0;
-error:
- fprintf(stderr, "db recovery impossible: %m");
- exit(5);
- return rc;
-}
-
-static int get_query_ids(
- const data_key_t *in,
- key_ids_t *out,
- bool create
-) {
- int rc;
-
- rc = db_get_name_index(&out->client, in->client, create);
- if (rc) goto end;
- rc = db_get_name_index(&out->user, in->user, create);
- if (rc) goto end;
- rc = db_get_name_index(&out->permission, in->permission, create);
-end:
- return rc;
+ return fdb_recover();
}
/** enumerate */
@@ -542,30 +106,8 @@ db_for_all(
const data_value_t *value),
const data_key_t *key
) {
- uint32_t ucli, uusr, i;
- int anyperm;
- data_key_t k;
- data_value_t v;
-
- if (!is_any_or_wide(key->session)
- || db_get_name_index(&ucli, key->client, false)
- || db_get_name_index(&uusr, key->user, false))
- return; /* nothing to do! */
-
- anyperm = is_any(key->permission);
- for (i = 0; i < rules_count; i++) {
- if ((ucli == ANYIDX || ucli == rules[i].key.client)
- && (uusr == ANYIDX || uusr == rules[i].key.user)
- && (anyperm || !strcasecmp(key->permission, name_at(rules[i].key.permission)))) {
- k.client = name_at(rules[i].key.client);
- k.session = WIDESTR;
- k.user = name_at(rules[i].key.user);
- k.permission = name_at(rules[i].key.permission);
- v.value = name_at(rules[i].value);
- v.expire = exp2time(rules[i].expire);
- callback(closure, &k, &v);
- }
- }
+ fdb_for_all(closure, callback, key);
+ anydb_for_all(memdb, closure, callback, key);
}
/** drop rules */
@@ -573,24 +115,8 @@ int
db_drop(
const data_key_t *key
) {
- uint32_t ucli, uusr, i;
- bool anyperm;
-
- if (!is_any_or_wide(key->session)
- || db_get_name_index(&ucli, key->client, false)
- || db_get_name_index(&uusr, key->user, false))
- return 0; /* nothing to do! */
-
- anyperm = is_any(key->permission);
- i = 0;
- while (i < rules_count) {
- if ((ucli == ANYIDX || ucli == rules[i].key.client)
- && (uusr == ANYIDX || uusr == rules[i].key.user)
- && (anyperm || !strcasecmp(key->permission, name_at(rules[i].key.permission))))
- drop_at(i);
- else
- i++;
- }
+ fdb_drop(key);
+ anydb_drop(memdb, key);
return 0;
}
@@ -600,52 +126,10 @@ db_set(
const data_key_t *key,
const data_value_t *value
) {
- int rc;
- uint32_t ucli, uusr, uperm, uval, i;
- const char *perm;
-
- /* check the session */
- if (!is_any_or_wide(key->session)) {
- errno = EINVAL;
- rc = -1;
- goto error;
- }
-
- /* normalise the perm */
- perm = is_any_or_wide(key->permission) ? WIDESTR : key->permission;
-
- /* get/create strings */
- rc = db_get_name_index(&ucli, is_any_or_wide(key->client) ? WIDESTR : key->client, true);
- if (rc)
- goto error;
- rc = db_get_name_index(&uusr, is_any_or_wide(key->user) ? WIDESTR : key->user, true);
- if (rc)
- goto error;
- rc = db_get_name_index(&uval, value->value, true);
- if (rc)
- goto error;
-
- /* search the existing rule */
- for (i = 0; i < rules_count; i++) {
- if (ucli == rules[i].key.client
- && uusr == rules[i].key.user
- && !strcasecmp(perm, name_at(rules[i].key.permission))) {
- /* found */
- set_at(i, uval, time2exph(value->expire));
- return 0;
- }
- }
-
- /* create the rule */
- rc = db_get_name_index(&uperm, perm, true);
- if (rc)
- goto error;
-
- rc = add_rule(ucli, uusr, uperm, uval, time2exph(value->expire));
-
- return 0;
-error:
- return rc;
+ if (is_any_or_wide(key->session))
+ return fdb_set(key, value);
+ else
+ return anydb_set(memdb, key, value);
}
/** check rules */
@@ -654,230 +138,25 @@ db_test(
const data_key_t *key,
data_value_t *value
) {
- const char *perm;
- uint32_t ucli, uusr, i, score, sc, now;
- rule_t *rule, *found;
-
- /* normalize the items */
- if (db_get_name_index(&ucli, is_any_or_wide(key->client) ? WIDESTR : key->client, false))
- ucli = NOIDX;
- if (db_get_name_index(&uusr, is_any_or_wide(key->user) ? WIDESTR : key->user, false))
- uusr = NOIDX;
- perm = is_any_or_wide(key->permission) ? WIDESTR : key->permission;
-
- /* search the existing rule */
- now = time2expl(time(NULL));
- found = NULL;
- score = 0;
- for (i = 0 ; i < rules_count ; i++) {
- rule = &rules[i];
- if ((!rule->expire || rule->expire >= now)
- && (ucli == rule->key.client || WIDEIDX == rule->key.client)
- && (uusr == rule->key.user || WIDEIDX == rule->key.user)
- && (WIDEIDX == rule->key.permission
- || !strcasecmp(perm, name_at(rule->key.permission)))) {
- /* found */
- sc = 1 + (rule->key.client != WIDEIDX)
- + (rule->key.user != WIDEIDX)
- + (rule->key.permission != WIDEIDX);
- if (sc > score) {
- score = sc;
- found = rule;
- }
- }
+ int s1, s2;
+ data_value_t v1, v2;
+
+ s1 = anydb_test(memdb, key, &v1);
+ s2 = fdb_test(key, &v2);
+ if (s2 > s1) {
+ *value = v2;
+ return s2;
+ } else {
+ *value = v1;
+ return s1;
}
- if (!found) {
- value->value = NULL;
- value->expire = 0;
- return 0;
- }
-
- value->value = name_at(found->value);
- value->expire = exp2time(found->expire);
-
- return 1;
-}
-
-typedef struct gc gc_t;
-struct gc
-{
- uint32_t *befores;
- uint32_t *afters;
-};
-
-/** compare indexes. used by qsort and bsearch */
-static
-int
-cmpidxs(
- const void *pa,
- const void *pb
-) {
- uint32_t a = *(const uint32_t*)pa;
- uint32_t b = *(const uint32_t*)pb;
- return a < b ? -1 : a != b;
-}
-
-static
-uint32_t*
-gc_after_ptr(
- gc_t *gc,
- uint32_t *idx
-) {
- uint32_t *p = bsearch(idx, gc->befores, names_count, sizeof *gc->befores, cmpidxs);
- assert(p != NULL);
- return &gc->afters[p - gc->befores];
-}
-
-static
-void
-gc_mark(
- gc_t *gc,
- uint32_t *idx
-) {
- *gc_after_ptr(gc, idx) = 1;
-}
-
-static
-void
-gc_mark_id(
- gc_t *gc,
- uint32_t idx
-) {
- gc_mark(gc, &idx);
-}
-
-static
-int
-gc_after(
- gc_t *gc,
- uint32_t *idx
-) {
- uint32_t idbef, idaft;
-
- idbef = *idx;
- idaft = *gc_after_ptr(gc, idx);
- *idx = idaft;
- return (int)(idbef - idaft);
-}
-
-static
-int
-gc_init(
- gc_t *gc
-) {
- gc->befores = malloc((sizeof *gc->befores + sizeof *gc->afters) * names_count);
- if (gc->befores == NULL)
- return -ENOMEM;
-
- names_count = names_count;
- memcpy(gc->befores, names_sorted, names_count * sizeof *gc->befores);
- qsort(gc->befores, names_count, sizeof *gc->befores, cmpidxs);
- gc->afters = &gc->befores[names_count];
- memset(gc->afters, 0, names_count * sizeof *gc->afters);
-
- gc_mark_id(gc, ANYIDX);
- gc_mark_id(gc, WIDEIDX);
- return 0;
-}
-
-static
-void
-gc_end(
- gc_t *gc
-) {
- free(gc->befores);
-}
-
-static
-int
-gc_pack(
- gc_t *gc
-) {
- uint32_t i, j, n, next, prev;
- char *strings;
-
- /* skip the unchanged initial part */
- n = names_count;
- i = 0;
- while (i < n && gc->afters[i])
- i++;
-
- /* at end means no change */
- if (i == n)
- return 0;
-
- /* pack the strings */
- strings = fnames.buffer;
- j = i;
- memcpy(gc->afters, gc->befores, j * sizeof *gc->afters);
- next = gc->befores[i++];
- fnames.saved = next;
- while (i < n) {
- if (gc->afters[i]) {
- gc->befores[j] = prev = gc->befores[i];
- gc->afters[j++] = next;
- while ((strings[next++] = strings[prev++]));
- }
- i++;
- }
- fnames.used = next;
- names_count = j;
- memcpy(names_sorted, gc->afters, j * sizeof *gc->afters);
- qsort(names_sorted, j, sizeof *names_sorted, cmpnames);
-
- return 1;
}
int
db_cleanup(
) {
- int rc, chg;
- uint32_t i, now;
- gc_t gc;
- rule_t *rule;
-
- /* init garbage collector */
- rc= gc_init(&gc);
- if (rc < 0)
- return rc;
-
- /* default now */
- now = time2expl(time(NULL));
-
- /* remove expired entries and mark string ids of remaining ones */
- i = 0;
- while (i < rules_count) {
- rule = &rules[i];
- if (rule->expire && now >= rule->expire)
- drop_at(i);
- else {
- gc_mark(&gc, &rule->key.client);
- gc_mark(&gc, &rule->key.user);
- gc_mark(&gc, &rule->key.permission);
- gc_mark(&gc, &rule->value);
- i++;
- }
- }
-
- /* pack the strings */
- if (gc_pack(&gc)) {
- /* replace the ids if changed */
- i = 0;
- while (i < rules_count) {
- rule = &rules[i];
- chg = gc_after(&gc, &rule->key.client);
- chg |= gc_after(&gc, &rule->key.user);
- chg |= gc_after(&gc, &rule->key.permission);
- chg |= gc_after(&gc, &rule->value);
- if (chg)
- touch_at(i);
- i++;
- }
- }
-
- /* terminate */
- gc_end(&gc);
-
+ fdb_cleanup();
+ anydb_cleanup(memdb);
return 0;
}
diff --git a/src/db.h b/src/db.h
index 61265fc..a17d240 100644
--- a/src/db.h
+++ b/src/db.h
@@ -44,15 +44,6 @@ int
db_sync(
);
-/** get an index for a name */
-extern
-int
-db_get_name_index(
- uint32_t *index,
- const char *name,
- bool needed
-);
-
/** make a backup of the database */
extern
int
diff --git a/src/fdb.c b/src/fdb.c
new file mode 100644
index 0000000..d13325a
--- /dev/null
+++ b/src/fdb.c
@@ -0,0 +1,865 @@
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author José Bollo <jose.bollo@iot.bzh>
+ *
+ * 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 <assert.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdalign.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+#include <errno.h>
+
+#include "data.h"
+#include "fbuf.h"
+#include "fdb.h"
+
+#define NOEXPIRE 0
+#define NOIDX 0
+
+#define ANYIDX 40
+#define ANYSTR "#"
+
+#define WIDEIDX 42
+#define WIDESTR "*"
+
+/*
+ * for the first version, save enougth time up to 4149
+ * 4149 = 1970 + (4294967296 * 16) / (365 * 24 * 60 * 60)
+ *
+ * in the next version, time will be relative to a stored base
+ */
+#define exp2time(x) (((time_t)(x)) << 4)
+#define time2expl(x) ((uint32_t)((x) >> 4))
+#define time2exph(x) time2expl((x) + 15)
+
+/**
+ * A query is a set of 32 bits integers
+ */
+struct key_ids {
+ /** client string id */
+ uint32_t client;
+
+ /** user string id */
+ uint32_t user;
+
+ /** permission string id */
+ uint32_t permission;
+};
+typedef struct key_ids key_ids_t;
+
+/**
+ * A rule is a set of 32 bits integers
+ */
+struct rule
+{
+ /** key part */
+ key_ids_t key;
+
+ /** value string id */
+ uint32_t value;
+
+ /** expiration */
+ uint32_t expire;
+};
+typedef struct rule rule_t;
+
+/*
+ * The cynara-agl database is made of 2 memory mapped files:
+ * - names: the zero terminated names
+ * - rules: the rules based on name indexes as 32bits indexes
+ * These files are normally in /var/lib/cynara
+ */
+#if !defined(DEFAULT_DB_DIR)
+# define DEFAULT_DB_DIR "/var/lib/cynara"
+#endif
+static const char fdb_default_directory[] = DEFAULT_DB_DIR;
+
+/** the file for the names */
+static fbuf_t fnames;
+
+/** the file for the rules */
+static fbuf_t frules;
+
+/** identification of names version 1
+ * $> uuidgen --sha1 -n @url -N urn:AGL:cynara:db:names:1
+ * $> uuid -v 5 urn:AGL:cynara:db:names:1
+ */
+static const char uuid_names_v1[] = "e9481f9e-b2f4-5716-90cf-c286d98d1868\n--\n";
+
+/** identification of rules version 1
+ * $> uuidgen --sha1 -n @url -N urn:AGL:cynara:db:rules:1
+ * $> uuid -v 5 ns:URL urn:AGL:cynara:db:rules:1
+ */
+static const char uuid_rules_v1[] = "8f7a5b21-48b1-57af-96c9-d5d7192be370\n--\n";
+
+/** length of the identification */
+static const int uuidlen = 40;
+
+/** count of names */
+static uint32_t names_count;
+
+/** the name indexes sorted */
+static uint32_t *names_sorted;
+
+/** count of rules */
+static uint32_t rules_count;
+
+/** the rules */
+static rule_t *rules;
+
+/** return the name of 'index' */
+static
+const char*
+name_at(
+ uint32_t index
+) {
+ return (const char*)(fnames.buffer + index);
+}
+
+/** compare names. used by qsort and bsearch */
+static
+int
+cmpnames(
+ const void *pa,
+ const void *pb
+) {
+ uint32_t a = *(const uint32_t*)pa;
+ uint32_t b = *(const uint32_t*)pb;
+ return strcmp(name_at(a), name_at(b));
+}
+
+/** search the index of 'name' and create it if 'create' */
+int
+fdb_get_name_index(
+ uint32_t *index,
+ const char *name,
+ bool create
+) {
+ uint32_t lo, up, m, i, *p;
+ int c;
+ const char *n;
+ size_t len;
+
+ /* special names */
+ if (!name || !name[0])
+ name = ANYSTR;
+
+ /* dichotomic search */
+ lo = 0;
+ up = names_count;
+ while(lo < up) {
+ m = (lo + up) >> 1;
+ i = names_sorted[m];
+ n = name_at(i);
+ c = strcmp(n, name);
+
+ if (c == 0) {
+ /* found */
+ *index = i;
+ return 0;
+ }
+
+ /* dichotomic iteration */
+ if (c < 0)
+ lo = m + 1;
+ else
+ up = m;
+ }
+
+ /* not found */
+ if (!create) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ /* check length */
+ len = strnlen(name, MAX_NAME_LENGTH + 1);
+ if (len > MAX_NAME_LENGTH) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* add the name in the file */
+ i = fnames.used;
+ c = fbuf_append(&fnames, name, 1 + (uint32_t)len);
+ if (c < 0)
+ return c;
+
+ /* add the name in sorted array */
+ up = names_count;
+ if (!(up & 1023)) {
+ p = realloc(names_sorted, (up + 1024) * sizeof *names_sorted);
+ if (p == NULL) {
+ fprintf(stderr, "out of memory");
+ return -1;
+ }
+ names_sorted = p;
+ }
+ memmove(&names_sorted[lo + 1], &names_sorted[lo], (up - lo) * sizeof *names_sorted);
+ names_count = up + 1;
+ *index = names_sorted[lo] = i;
+ return 0;
+}
+
+/** initialize names */
+static
+int
+init_names(
+) {
+ int rc;
+ uint32_t pos, len, *ns, *p, all, nc;
+
+ all = 0;
+ nc = 0;
+ ns = NULL;
+
+ /* iterate over names */
+ pos = uuidlen;
+ while (pos < fnames.used) {
+ /* get name length */
+ len = (uint32_t)strlen(name_at(pos));
+ if (pos + len <= pos || pos + len > fnames.used) {
+ free(ns);
+ goto bad_file;
+ }
+ /* store the position */
+ if (all <= nc) {
+ all += 1024;
+ p = realloc(ns, all * sizeof *ns);
+ if (p == NULL) {
+ free(ns);
+ fprintf(stderr, "out of memory");
+ goto error;
+ }
+ ns = p;
+ }
+ ns[nc++] = pos;
+ /* next */
+ pos += len + 1;
+ }
+
+ /* sort and record */
+ qsort(ns, nc, sizeof *ns, cmpnames);
+ names_sorted = ns;
+ names_count = nc;
+
+ /* predefined symbols */
+ rc = fdb_get_name_index(&pos, ANYSTR, true);
+ if (rc < 0)
+ goto error;
+ if (pos != ANYIDX)
+ goto bad_file;
+ rc = fdb_get_name_index(&pos, WIDESTR, true);
+ if (rc < 0)
+ goto error;
+ if (pos != WIDEIDX)
+ goto bad_file;
+
+ return 0;
+bad_file:
+ fprintf(stderr, "bad file %s", fnames.name);
+ errno = ENOEXEC;
+error:
+ return -1;
+}
+
+/** check whether the 'text' fit ANYSTR, NULL or "" */
+static
+bool
+is_any(
+ const char *text
+) {
+ return text == NULL || text[0] == 0 || 0 == strcmp(text, ANYSTR);
+}
+
+/** check whether the 'text' fit ANYSTR, WIDESTR, NULL or "" */
+static
+bool
+is_any_or_wide(
+ const char *text
+) {
+ return is_any(text) || 0 == strcmp(text, WIDESTR);
+}
+
+/** set the 'value' to the rule at 'index' */
+static
+void
+touch_at(
+ uint32_t index
+) {
+ uint32_t pos;
+
+ pos = (uint32_t)(((void*)&rules[index]) - frules.buffer);
+ if (pos < frules.saved)
+ frules.saved = pos;
+}
+
+/** set the 'value' to the rule at 'index' */
+static
+void
+set_at(
+ uint32_t index,
+ uint32_t value,
+ uint32_t expire
+) {
+ assert(index < rules_count);
+ rules[index].value = value;
+ rules[index].expire = expire;
+ touch_at(index);
+}
+
+/** drop the rule at 'index' */
+static
+void
+drop_at(
+ uint32_t index
+) {
+ uint32_t pos;
+
+ assert(index < rules_count);
+ if (index < --rules_count)
+ rules[index] = rules[rules_count];
+ pos = (uint32_t)(((void*)&rules[rules_count]) - frules.buffer);
+ frules.used = pos;
+ touch_at(index);
+}
+
+/** add the rule 'client' x 'user' x 'permission' x 'value' */
+static
+int
+add_rule(
+ uint32_t client,
+ uint32_t user,
+ uint32_t permission,
+ uint32_t value,
+ uint32_t expire
+) {
+ int rc;
+ uint32_t c;
+ rule_t *rule;
+
+ c = frules.used + (uint32_t)sizeof *rule;
+ rc = fbuf_ensure_capacity(&frules, c);
+ if (rc)
+ return rc;
+ rules = (rule_t*)(frules.buffer + uuidlen);
+ rule = &rules[rules_count++];
+ rule->key.client = client;
+ rule->key.user = user;
+ rule->key.permission = permission;
+ rule->value = value;
+ rule->expire = expire;
+ frules.used = c;
+ return 0;
+}
+
+/** init the rules from the file */
+static
+void
+init_rules(
+) {
+ rules = (rule_t*)(frules.buffer + uuidlen);
+ rules_count = (frules.used - uuidlen) / sizeof *rules;
+}
+
+/** open a fbuf */
+static
+int
+open_identify(
+ fbuf_t *fb,
+ const char *directory,
+ const char *name,
+ const char *id,
+ uint32_t idlen
+) {
+ int rc;
+ char *file, *backup;
+ size_t sd, sn;
+
+ sd = strlen(directory);
+ sn = strlen(name);
+ file = malloc(((sd + sn) << 1) + 5);
+ if (!file)
+ rc = -ENOMEM;
+ else {
+
+ memcpy(file, directory, sd);
+ file[sd] = '/';
+ memcpy(&file[sd + 1], name, sn + 1);
+ backup = &file[sd + sn + 2];
+ memcpy(backup, file, sd + sn + 1);
+ backup[sd + sn + 1] = '~';
+ backup[sd + sn + 2] = 0;
+ rc = fbuf_open_identify(fb, file, backup, id, idlen);
+ free(file);
+ }
+ return rc;
+}
+
+/** open the database for files 'names' and 'rules' (can be NULL) */
+int
+fdb_open(
+ const char *directory
+) {
+ int rc;
+
+ /* provide default directory */
+ if (directory == NULL)
+ directory = fdb_default_directory;
+
+ /* open the names */
+ rc = open_identify(&fnames, directory, "cynara.names", uuid_names_v1, uuidlen);
+ if (rc < 0)
+ goto error;
+
+ /* open the rules */
+ rc = open_identify(&frules, directory, "cynara.rules", uuid_rules_v1, uuidlen);
+ if (rc < 0)
+ goto error;
+
+ /* connect internals */
+ rc = init_names();
+ if (rc < 0)
+ goto error;
+
+ init_rules();
+ return 0;
+error:
+ return -1;
+}
+
+/** close the database */
+void
+fdb_close(
+) {
+ assert(fnames.name && frules.name);
+ fbuf_close(&fnames);
+ fbuf_close(&frules);
+}
+
+/** is the database empty */
+bool
+fdb_is_empty(
+) {
+ return !rules_count;
+}
+
+/** synchronize db on files */
+int
+fdb_sync(
+) {
+ int rc;
+
+ assert(fnames.name && frules.name);
+ rc = fbuf_sync(&fnames);
+ if (rc == 0)
+ rc = fbuf_sync(&frules);
+ return rc;
+}
+
+/** make a backup of the database */
+int
+fdb_backup(
+) {
+ int rc;
+
+ assert(fnames.name && frules.name);
+ rc = fbuf_backup(&fnames);
+ if (rc == 0)
+ rc = fbuf_backup(&frules);
+ return rc;
+}
+
+/** recover the database from latest backup */
+int
+fdb_recover(
+) {
+ int rc;
+
+ assert(fnames.name && frules.name);
+
+ rc = fbuf_recover(&fnames);
+ if (rc < 0)
+ goto error;
+
+ rc = fbuf_recover(&frules);
+ if (rc < 0)
+ goto error;
+
+ rc = init_names();
+ if (rc < 0)
+ goto error;
+
+ init_rules();
+ return 0;
+error:
+ fprintf(stderr, "db recovery impossible: %m");
+ exit(5);
+ return rc;
+}
+
+/** enumerate */
+void
+fdb_for_all(
+ void *closure,
+ void (*callback)(
+ void *closure,
+ const data_key_t *key,
+ const data_value_t *value),
+ const data_key_t *key
+) {
+ uint32_t ucli, uusr, i;
+ int anyperm;
+ data_key_t k;
+ data_value_t v;
+
+ if (!is_any_or_wide(key->session)
+ || fdb_get_name_index(&ucli, key->client, false)
+ || fdb_get_name_index(&uusr, key->user, false))
+ return; /* nothing to do! */
+
+ anyperm = is_any(key->permission);
+ for (i = 0; i < rules_count; i++) {
+ if ((ucli == ANYIDX || ucli == rules[i].key.client)
+ && (uusr == ANYIDX || uusr == rules[i].key.user)
+ && (anyperm || !strcasecmp(key->permission, name_at(rules[i].key.permission)))) {
+ k.client = name_at(rules[i].key.client);
+ k.session = WIDESTR;
+ k.user = name_at(rules[i].key.user);
+ k.permission = name_at(rules[i].key.permission);
+ v.value = name_at(rules[i].value);
+ v.expire = exp2time(rules[i].expire);
+ callback(closure, &k, &v);
+ }
+ }
+}
+
+/** drop rules */
+int
+fdb_drop(
+ const data_key_t *key
+) {
+ uint32_t ucli, uusr, i;
+ bool anyperm;
+
+ if (!is_any_or_wide(key->session)
+ || fdb_get_name_index(&ucli, key->client, false)
+ || fdb_get_name_index(&uusr, key->user, false))
+ return 0; /* nothing to do! */
+
+ anyperm = is_any(key->permission);
+ i = 0;
+ while (i < rules_count) {
+ if ((ucli == ANYIDX || ucli == rules[i].key.client)
+ && (uusr == ANYIDX || uusr == rules[i].key.user)
+ && (anyperm || !strcasecmp(key->permission, name_at(rules[i].key.permission))))
+ drop_at(i);
+ else
+ i++;
+ }
+ return 0;
+}
+
+/** set rules */
+int
+fdb_set(
+ const data_key_t *key,
+ const data_value_t *value
+) {
+ int rc;
+ uint32_t ucli, uusr, uperm, uval, i;
+ const char *perm;
+
+ /* check the session */
+ if (!is_any_or_wide(key->session)) {
+ errno = EINVAL;
+ rc = -1;
+ goto error;
+ }
+
+ /* normalise the perm */
+ perm = is_any_or_wide(key->permission) ? WIDESTR : key->permission;
+
+ /* get/create strings */
+ rc = fdb_get_name_index(&ucli, is_any_or_wide(key->client) ? WIDESTR : key->client, true);
+ if (rc)
+ goto error;
+ rc = fdb_get_name_index(&uusr, is_any_or_wide(key->user) ? WIDESTR : key->user, true);
+ if (rc)
+ goto error;
+ rc = fdb_get_name_index(&uval, value->value, true);
+ if (rc)
+ goto error;
+
+ /* search the existing rule */
+ for (i = 0; i < rules_count; i++) {
+ if (ucli == rules[i].key.client
+ && uusr == rules[i].key.user
+ && !strcasecmp(perm, name_at(rules[i].key.permission))) {
+ /* found */
+ set_at(i, uval, time2exph(value->expire));
+ return 0;
+ }
+ }
+
+ /* create the rule */
+ rc = fdb_get_name_index(&uperm, perm, true);
+ if (rc)
+ goto error;
+
+ rc = add_rule(ucli, uusr, uperm, uval, time2exph(value->expire));
+
+ return 0;
+error:
+ return rc;
+}
+
+/** check rules */
+int
+fdb_test(
+ const data_key_t *key,
+ data_value_t *value
+) {
+ const char *perm;
+ uint32_t ucli, uusr, i, score, sc, now;
+ rule_t *rule, *found;
+
+ /* normalize the items */
+ if (fdb_get_name_index(&ucli, is_any_or_wide(key->client) ? WIDESTR : key->client, false))
+ ucli = NOIDX;
+ if (fdb_get_name_index(&uusr, is_any_or_wide(key->user) ? WIDESTR : key->user, false))
+ uusr = NOIDX;
+ perm = is_any_or_wide(key->permission) ? WIDESTR : key->permission;
+
+ /* search the existing rule */
+ now = time2expl(time(NULL));
+ found = NULL;
+ score = 0;
+ for (i = 0 ; i < rules_count ; i++) {
+ rule = &rules[i];
+ if ((!rule->expire || rule->expire >= now)
+ && (ucli == rule->key.client || WIDEIDX == rule->key.client)
+ && (uusr == rule->key.user || WIDEIDX == rule->key.user)
+ && (WIDEIDX == rule->key.permission
+ || !strcasecmp(perm, name_at(rule->key.permission)))) {
+ /* found */
+ sc = 1 + (rule->key.client != WIDEIDX)
+ + (rule->key.user != WIDEIDX)
+ + (rule->key.permission != WIDEIDX);
+ if (sc > score) {
+ score = sc;
+ found = rule;
+ }
+ }
+ }
+ if (!found) {
+ value->value = NULL;
+ value->expire = 0;
+ return 0;
+ }
+
+ value->value = name_at(found->value);
+ value->expire = exp2time(found->expire);
+
+ return 1;
+}
+
+typedef struct gc gc_t;
+struct gc
+{
+ uint32_t *befores;
+ uint32_t *afters;
+};
+
+/** compare indexes. used by qsort and bsearch */
+static
+int
+cmpidxs(
+ const void *pa,
+ const void *pb
+) {
+ uint32_t a = *(const uint32_t*)pa;
+ uint32_t b = *(const uint32_t*)pb;
+ return a < b ? -1 : a != b;
+}
+
+static
+uint32_t*
+gc_after_ptr(
+ gc_t *gc,
+ uint32_t *idx
+) {
+ uint32_t *p = bsearch(idx, gc->befores, names_count, sizeof *gc->befores, cmpidxs);
+ assert(p != NULL);
+ return &gc->afters[p - gc->befores];
+}
+
+static
+void
+gc_mark(
+ gc_t *gc,
+ uint32_t *idx
+) {
+ *gc_after_ptr(gc, idx) = 1;
+}
+
+static
+void
+gc_mark_id(
+ gc_t *gc,
+ uint32_t idx
+) {
+ gc_mark(gc, &idx);
+}
+
+static
+int
+gc_after(
+ gc_t *gc,
+ uint32_t *idx
+) {
+ uint32_t idbef, idaft;
+
+ idbef = *idx;
+ idaft = *gc_after_ptr(gc, idx);
+ *idx = idaft;
+ return (int)(idbef - idaft);
+}
+
+static
+int
+gc_init(
+ gc_t *gc
+) {
+ gc->befores = malloc((sizeof *gc->befores + sizeof *gc->afters) * names_count);
+ if (gc->befores == NULL)
+ return -ENOMEM;
+
+ names_count = names_count;
+ memcpy(gc->befores, names_sorted, names_count * sizeof *gc->befores);
+ qsort(gc->befores, names_count, sizeof *gc->befores, cmpidxs);
+ gc->afters = &gc->befores[names_count];
+ memset(gc->afters, 0, names_count * sizeof *gc->afters);
+
+ gc_mark_id(gc, ANYIDX);
+ gc_mark_id(gc, WIDEIDX);
+ return 0;
+}
+
+static
+void
+gc_end(
+ gc_t *gc
+) {
+ free(gc->befores);
+}
+
+static
+int
+gc_pack(
+ gc_t *gc
+) {
+ uint32_t i, j, n, next, prev;
+ char *strings;
+
+ /* skip the unchanged initial part */
+ n = names_count;
+ i = 0;
+ while (i < n && gc->afters[i])
+ i++;
+
+ /* at end means no change */
+ if (i == n)
+ return 0;
+
+ /* pack the strings */
+ strings = fnames.buffer;
+ j = i;
+ memcpy(gc->afters, gc->befores, j * sizeof *gc->afters);
+ next = gc->befores[i++];
+ fnames.saved = next;
+ while (i < n) {
+ if (gc->afters[i]) {
+ gc->befores[j] = prev = gc->befores[i];
+ gc->afters[j++] = next;
+ while ((strings[next++] = strings[prev++]));
+ }
+ i++;
+ }
+ fnames.used = next;
+ names_count = j;
+ memcpy(names_sorted, gc->afters, j * sizeof *gc->afters);
+ qsort(names_sorted, j, sizeof *names_sorted, cmpnames);
+
+ return 1;
+}
+
+int
+fdb_cleanup(
+) {
+ int rc, chg;
+ uint32_t i, now;
+ gc_t gc;
+ rule_t *rule;
+
+ /* init garbage collector */
+ rc= gc_init(&gc);
+ if (rc < 0)
+ return rc;
+
+ /* default now */
+ now = time2expl(time(NULL));
+
+ /* remove expired entries and mark string ids of remaining ones */
+ i = 0;
+ while (i < rules_count) {
+ rule = &rules[i];
+ if (rule->expire && now >= rule->expire)
+ drop_at(i);
+ else {
+ gc_mark(&gc, &rule->key.client);
+ gc_mark(&gc, &rule->key.user);
+ gc_mark(&gc, &rule->key.permission);
+ gc_mark(&gc, &rule->value);
+ i++;
+ }
+ }
+
+ /* pack the strings */
+ if (gc_pack(&gc)) {
+ /* replace the ids if changed */
+ i = 0;
+ while (i < rules_count) {
+ rule = &rules[i];
+ chg = gc_after(&gc, &rule->key.client);
+ chg |= gc_after(&gc, &rule->key.user);
+ chg |= gc_after(&gc, &rule->key.permission);
+ chg |= gc_after(&gc, &rule->value);
+ if (chg)
+ touch_at(i);
+ i++;
+ }
+ }
+
+ /* terminate */
+ gc_end(&gc);
+
+ return 0;
+}
diff --git a/src/fdb.h b/src/fdb.h
new file mode 100644
index 0000000..c3ae58b
--- /dev/null
+++ b/src/fdb.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author José Bollo <jose.bollo@iot.bzh>
+ *
+ * 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.
+ */
+
+#pragma once
+
+#define MAX_NAME_LENGTH 32767
+
+/** open the database for files 'names' and 'rules' (can be NULL) */
+extern
+int
+fdb_open(
+ const char *directory
+);
+
+/** close the database */
+extern
+void
+fdb_close(
+);
+
+/** is the database empty */
+extern
+bool
+fdb_is_empty(
+);
+
+/** sync the database */
+extern
+int
+fdb_sync(
+);
+
+/** make a backup of the database */
+extern
+int
+fdb_backup(
+);
+
+/** recover the database from latest backup */
+extern
+int
+fdb_recover(
+);
+
+/** enumerate */
+extern
+void
+fdb_for_all(
+ void *closure,
+ void (*callback)(
+ void *closure,
+ const data_key_t *key,
+ const data_value_t *value),
+ const data_key_t *key
+);
+
+/** erase rules */
+extern
+int
+fdb_drop(
+ const data_key_t *key
+);
+
+/** set rules */
+extern
+int
+fdb_set(
+ const data_key_t *key,
+ const data_value_t *value
+);
+
+/** check rules */
+extern
+int
+fdb_test(
+ const data_key_t *key,
+ data_value_t *value
+);
+
+/** cleanup the base */
+extern
+int
+fdb_cleanup(
+);
+
diff --git a/src/memdb.c b/src/memdb.c
new file mode 100644
index 0000000..0a1e044
--- /dev/null
+++ b/src/memdb.c
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author José Bollo <jose.bollo@iot.bzh>
+ *
+ * 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 <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+
+#include "data.h"
+#include "anydb.h"
+
+#define RBS 20
+#define SBS 30
+
+struct rule
+{
+ anydb_key_t key;
+ anydb_value_t value;
+};
+
+struct memdb
+{
+ /* first for the fun */
+ anydb_t db;
+
+ /* strings */
+ struct {
+ uint32_t alloc;
+ uint32_t count;
+ char **values;
+ } strings;
+
+ /* rules */
+ struct {
+ uint32_t alloc;
+ uint32_t count;
+ struct rule *values;
+ } rules;
+};
+typedef struct memdb memdb_t;
+
+static
+int
+index_itf(
+ void *clodb,
+ anydb_idx_t *idx,
+ const char *name,
+ bool create
+) {
+ memdb_t *memdb = clodb;
+ char *s, **strings = memdb->strings.values;
+ anydb_idx_t i;
+
+ /* search */
+ i = 0;
+ while (i < memdb->strings.count) {
+ if (!strcmp(name, strings[i])) {
+ *idx = i;
+ return 0;
+ }
+ i++;
+ }
+
+ /* not found */
+ if (!create) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ /* create */
+ s = strdup(name);
+ if (s == NULL)
+ return -ENOMEM;
+ if (memdb->strings.count == memdb->strings.alloc) {
+ strings = realloc(strings, (memdb->strings.alloc + SBS) * sizeof *strings);
+ if (!strings) {
+ free(s);
+ return -ENOMEM;
+ }
+ memdb->strings.values = strings;
+ memdb->strings.alloc += SBS;
+ }
+ i = memdb->strings.count;
+ *idx = i;
+ strings[i] = s;
+ memdb->strings.count = i + 1;
+ return 0;
+}
+
+static
+const char *
+string_itf(
+ void *clodb,
+ anydb_idx_t idx
+) {
+ memdb_t *memdb = clodb;
+
+ return memdb->strings.values[idx];
+}
+
+static
+void
+apply_itf(
+ void *clodb,
+ anydb_action_t (*oper)(void *closure, const anydb_key_t *key, anydb_value_t *value),
+ void *closure
+) {
+ memdb_t *memdb = clodb;
+ struct rule *rules = memdb->rules.values;
+ uint32_t ir;
+ anydb_action_t a;
+
+ ir = 0;
+ while (ir < memdb->rules.count) {
+ a = oper(closure, &rules[ir].key, &rules[ir].value);
+ switch (a) {
+ case Anydb_Action_Continue:
+ ir++;
+ break;
+ case Anydb_Action_Update_And_Stop:
+ return;
+ case Anydb_Action_Remove_And_Continue:
+ rules[ir] = rules[--memdb->rules.count];
+ break;
+ }
+ }
+}
+
+static
+int
+add_itf(
+ void *clodb,
+ const anydb_key_t *key,
+ const anydb_value_t *value
+) {
+ memdb_t *memdb = clodb;
+ struct rule *rules = memdb->rules.values;
+
+ if (memdb->rules.count == memdb->rules.alloc) {
+ rules = realloc(rules, (memdb->rules.alloc + RBS) * sizeof *rules);
+ if (!rules)
+ return -ENOMEM;
+ memdb->rules.alloc += RBS;
+ memdb->rules.values = rules;
+ }
+ rules[memdb->rules.count].key = *key;
+ rules[memdb->rules.count].value = *value;
+ memdb->rules.count++;
+ return 0;
+}
+
+static
+void
+gc_itf(
+ void *clodb
+) {
+ memdb_t *memdb = clodb;
+ uint32_t nr = memdb->rules.count;
+ uint32_t ns = memdb->strings.count;
+ char **strings = memdb->strings.values;
+ struct rule *rules = memdb->rules.values;
+ anydb_idx_t *renum = alloca(ns * sizeof *renum);
+ uint32_t i, j;
+
+ for (i = 0 ; i < ns ; i++)
+ renum[i] = 0;
+
+ for (i = 0 ; i < nr ; i++) {
+ renum[rules[i].key.client] = 1;
+ renum[rules[i].key.session] = 1;
+ renum[rules[i].key.user] = 1;
+ renum[rules[i].key.permission] = 1;
+ renum[rules[i].value.value] = 1;
+ }
+
+ for (i = j = 0 ; i < ns ; i++) {
+ if (renum[i]) {
+ strings[j] = strings[i];
+ renum[i] = j++;
+ } else {
+ free(strings[i]);
+ renum[i] = AnyIdx_Invalid;
+ }
+ }
+ if (ns != j) {
+ memdb->strings.count = ns = j;
+ for (i = 0 ; i < nr ; i++) {
+ rules[i].key.client = renum[rules[i].key.client];
+ rules[i].key.session = renum[rules[i].key.session];
+ rules[i].key.user = renum[rules[i].key.user];
+ rules[i].key.permission = renum[rules[i].key.permission];
+ rules[i].value.value = renum[rules[i].value.value];
+ }
+ }
+
+ i = memdb->strings.alloc;
+ while (ns + SBS > i)
+ i -= SBS;
+ if (i != memdb->strings.alloc) {
+ memdb->strings.alloc = i;
+ memdb->strings.values = realloc(strings, i * sizeof *strings);
+ }
+
+ i = memdb->rules.alloc;
+ while (ns + RBS > i)
+ i -= RBS;
+ if (i != memdb->rules.alloc) {
+ memdb->rules.alloc = i;
+ memdb->rules.values = realloc(rules, i * sizeof *strings);
+ }
+}
+
+static
+void
+destroy_itf(
+ void *clodb
+) {
+ memdb_t *memdb = clodb;
+ if (memdb) {
+ free(memdb->strings.values);
+ free(memdb->rules.values);
+ free(memdb);
+ }
+}
+
+static
+void
+init(
+ memdb_t *memdb
+) {
+ memdb->db.clodb = memdb;
+
+ memdb->db.itf.index = index_itf;
+ memdb->db.itf.string = string_itf;
+ memdb->db.itf.apply = apply_itf;
+ memdb->db.itf.add = add_itf;
+ memdb->db.itf.gc = gc_itf;
+ memdb->db.itf.destroy = destroy_itf;
+
+ memdb->strings.alloc = 0;
+ memdb->strings.count = 0;
+ memdb->strings.values = NULL;
+
+ memdb->rules.alloc = 0;
+ memdb->rules.count = 0;
+ memdb->rules.values = NULL;
+}
+
+int
+memdb_create(
+ anydb_t **memdb
+) {
+ memdb_t *mdb;
+
+ mdb = malloc(sizeof *mdb);
+ if (!mdb) {
+ *memdb = NULL;
+ return -ENOMEM;
+ }
+ init(mdb);
+ *memdb = &mdb->db;
+ return 0;
+}
diff --git a/src/memdb.h b/src/memdb.h
new file mode 100644
index 0000000..0f1c9e7
--- /dev/null
+++ b/src/memdb.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author José Bollo <jose.bollo@iot.bzh>
+ *
+ * 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.
+ */
+
+#pragma once
+
+extern
+int
+memdb_create(
+ anydb_t **memdb
+);