Introduce Any DB abstraction and memdb on its top
authorJose Bollo <jose.bollo@iot.bzh>
Thu, 2 May 2019 15:10:08 +0000 (17:10 +0200)
committerJose Bollo <jose.bollo@iot.bzh>
Thu, 9 May 2019 13:12:03 +0000 (15:12 +0200)
Signed-off-by: Jose Bollo <jose.bollo@iot.bzh>
src/CMakeLists.txt
src/anydb.c [new file with mode: 0644]
src/anydb.h [new file with mode: 0644]
src/cyn.c
src/data.h
src/db.c
src/db.h
src/fdb.c [new file with mode: 0644]
src/fdb.h [new file with mode: 0644]
src/memdb.c [new file with mode: 0644]
src/memdb.h [new file with mode: 0644]

index cacd659..319ece1 100644 (file)
 ###########################################################################
 
 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 (file)
index 0000000..d284610
--- /dev/null
@@ -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 (file)
index 0000000..33f0447
--- /dev/null
@@ -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
+);
index 6f27860..84d3295 100644 (file)
--- 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;
        }
index 10cc599..8bd35b3 100644 (file)
 #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;
 
index 0bd4a09..98c96e7 100644 (file)
--- a/src/db.c
+++ b/src/db.c
 #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;
 }
 
index 61265fc..a17d240 100644 (file)
--- 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 (file)
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 (file)
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 (file)
index 0000000..0a1e044
--- /dev/null
@@ -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 (file)
index 0000000..0f1c9e7
--- /dev/null
@@ -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
+);