Switch to filedb
authorJose Bollo <jose.bollo@iot.bzh>
Mon, 13 May 2019 14:31:55 +0000 (16:31 +0200)
committerJose Bollo <jose.bollo@iot.bzh>
Mon, 13 May 2019 14:33:10 +0000 (16:33 +0200)
Signed-off-by: Jose Bollo <jose.bollo@iot.bzh>
src/CMakeLists.txt
src/anydb.c
src/anydb.h
src/db.c
src/db.h
src/fdb.c [deleted file]
src/fdb.h [deleted file]
src/filedb.c [new file with mode: 0644]
src/filedb.h [new file with mode: 0644]
src/memdb.c

index 319ece1..e8071e8 100644 (file)
@@ -23,7 +23,7 @@ set(SERVER_SOURCES
        dbinit.c
        expire.c
        fbuf.c
-       fdb.c
+       filedb.c
        main-cynarad.c
        memdb.c
        pollitem.c
index 94a5d58..995cd0a 100644 (file)
@@ -463,6 +463,33 @@ anydb_test(
        return s.score;
 }
 
+/******************************************************************************/
+/******************************************************************************/
+/*** IS EMPTY                                                               ***/
+/******************************************************************************/
+/******************************************************************************/
+
+static
+anydb_action_t
+is_empty_cb(
+       void *closure,
+       const anydb_key_t *key,
+       anydb_value_t *value
+) {
+       bool *result = closure;
+       *result = false;
+       return Anydb_Action_Stop;
+}
+
+bool
+anydb_is_empty(
+       anydb_t *db
+) {
+       bool result = true;
+       db->itf.apply(db->clodb, is_empty_cb, &result);
+       return result;
+}
+
 /******************************************************************************/
 /******************************************************************************/
 /*** CLEANUP                                                                ***/
index abc8c4c..843ef29 100644 (file)
@@ -70,9 +70,10 @@ 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
+       Anydb_Action_Stop,
+       Anydb_Action_Continue,
+       Anydb_Action_Update_And_Stop,
+       Anydb_Action_Remove_And_Continue
 };
 typedef enum anydb_action anydb_action_t;
 
@@ -92,6 +93,7 @@ struct anydb_itf
        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);
+       int (*sync)(void *clodb);
        void (*destroy)(void *clodb);
 };
 typedef struct anydb_itf anydb_itf_t;
@@ -157,9 +159,23 @@ anydb_cleanup(
        anydb_t *db
 );
 
+/** is the database empty? */
+extern
+bool
+anydb_is_empty(
+       anydb_t *db
+);
+
 /** destroy the database */
 extern
 void
 anydb_destroy(
        anydb_t *db
 );
+
+/** synchronize database */
+extern
+int
+anydb_sync(
+       anydb_t *db
+);
index 5d28246..e6eb3d1 100644 (file)
--- a/src/db.c
+++ b/src/db.c
 
 #include "data.h"
 #include "anydb.h"
-#include "fdb.h"
+#include "filedb.h"
 #include "memdb.h"
 #include "db.h"
 
 static anydb_t *memdb;
+static anydb_t *filedb;
 
 /** check whether the 'text' fit String_Any, String_Wide, NULL or ""  */
 static
@@ -54,7 +55,7 @@ db_open(
 
        rc = memdb_create(&memdb);
        if (!rc) {
-               rc = fdb_open(directory);
+               rc = filedb_create(&filedb, directory, "CYNARA");
                if (rc)
                        anydb_destroy(memdb);
        }
@@ -65,7 +66,7 @@ db_open(
 void
 db_close(
 ) {
-       fdb_close();
+       anydb_destroy(filedb);
        anydb_destroy(memdb);
 }
 
@@ -73,7 +74,7 @@ db_close(
 bool
 db_is_empty(
 ) {
-       return fdb_is_empty();
+       return anydb_is_empty(filedb);
 }
 
 /** enter atomic mode */
@@ -82,7 +83,7 @@ db_transaction_begin(
 ) {
        int rc1, rc2;
 
-       rc1 = fdb_backup();
+       rc1 = anydb_transaction(filedb, Anydb_Transaction_Start);
        rc2 = anydb_transaction(memdb, Anydb_Transaction_Start);
 
        return rc1 ?: rc2;
@@ -96,15 +97,15 @@ db_transaction_end(
        int rc1, rc2, rc3, rc4;
 
        if (commit) {
-               rc1 = 0;
+               rc1 = anydb_transaction(filedb, Anydb_Transaction_Commit);
                rc2 = anydb_transaction(memdb, Anydb_Transaction_Commit);
                rc3 = db_cleanup();
        } else {
-               rc1 = fdb_recover();
+               rc1 = anydb_transaction(filedb, Anydb_Transaction_Cancel);
                rc2 = anydb_transaction(memdb, Anydb_Transaction_Cancel);
                rc3 = 0;
        }
-       rc4 = fdb_sync();
+       rc4 = db_sync();
 
        return rc1 ?: rc2 ?: rc3 ?: rc4;
 }
@@ -120,7 +121,7 @@ db_for_all(
                const data_value_t *value),
        const data_key_t *key
 ) {
-       fdb_for_all(closure, callback, key);
+       anydb_for_all(filedb, closure, callback, key);
        anydb_for_all(memdb, closure, callback, key);
 }
 
@@ -129,7 +130,7 @@ int
 db_drop(
        const data_key_t *key
 ) {
-       fdb_drop(key);
+       anydb_drop(filedb, key);
        anydb_drop(memdb, key);
        return 0;
 }
@@ -141,7 +142,7 @@ db_set(
        const data_value_t *value
 ) {
        if (is_any_or_wide(key->session))
-               return fdb_set(key, value);
+               return anydb_set(filedb, key, value);
        else
                return anydb_set(memdb, key, value);
 }
@@ -156,7 +157,7 @@ db_test(
        data_value_t v1, v2;
 
        s1 = anydb_test(memdb, key, &v1);
-       s2 = fdb_test(key, &v2);
+       s2 = anydb_test(filedb, key, &v2);
        if (s2 > s1) {
                *value = v2;
                return s2;
@@ -169,8 +170,16 @@ db_test(
 int
 db_cleanup(
 ) {
-       fdb_cleanup();
+       anydb_cleanup(filedb);
        anydb_cleanup(memdb);
        return 0;
 }
 
+int
+db_sync(
+) {
+       int rc1 = anydb_sync(filedb);
+       int rc2 = anydb_sync(memdb);
+       return rc1 ?: rc2;
+}
+
index 995fd65..edb4f29 100644 (file)
--- a/src/db.h
+++ b/src/db.h
@@ -92,3 +92,8 @@ int
 db_cleanup(
 );
 
+/** cleanup the base */
+extern
+int
+db_sync(
+);
diff --git a/src/fdb.c b/src/fdb.c
deleted file mode 100644 (file)
index d13325a..0000000
--- a/src/fdb.c
+++ /dev/null
@@ -1,865 +0,0 @@
-/*
- * 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
deleted file mode 100644 (file)
index c3ae58b..0000000
--- a/src/fdb.h
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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/filedb.c b/src/filedb.c
new file mode 100644 (file)
index 0000000..2a4de33
--- /dev/null
@@ -0,0 +1,776 @@
+/*
+ * 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 "anydb.h"
+#include "fbuf.h"
+#include "filedb.h"
+
+#define MAX_NAME_LENGTH 32768
+
+/*
+ * 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 time2exp(x)  ((x) ? ((uint32_t)(((x) + 15) >> 4)) : 0)
+
+/**
+ * A rule is a set of 32 bits integers
+ */
+struct rule
+{
+       /** client string id */
+       uint32_t client;
+
+       /** user string id */
+       uint32_t user;
+
+       /** permission string id */
+       uint32_t permission;
+
+       /** 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
+#if !defined(DEFAULT_DB_NAME)
+#    define  DEFAULT_DB_NAME  "cynara"
+#endif
+static const char filedb_default_directory[] = DEFAULT_DB_DIR;
+static const char filedb_default_name[] = DEFAULT_DB_NAME;
+
+/** identification of names version 2
+ *    $> uuidgen --sha1 -n @url -N urn:AGL:cynara:db:names:2
+ *    $> uuid -v 5 ns:URL urn:AGL:cynara:db:names:2
+ */
+static const char uuid_names_v2[] = "6fa114d4-f3d9-58ab-a5d3-4674ee865c8d\n--\n";
+
+/** identification of rules version 2
+ *    $> uuidgen --sha1 -n @url -N urn:AGL:cynara:db:rules:2
+ *    $> uuid -v 5 ns:URL urn:AGL:cynara:db:rules:2
+ */
+static const char uuid_rules_v2[] = "6d48515a-3f64-52b1-9d15-4d13d073d48a\n--\n";
+
+/** length of the identification */
+static const int uuidlen = 40;
+
+
+struct filedb
+{
+       /** the file for the names */
+       fbuf_t fnames;
+
+       /** the file for the rules */
+       fbuf_t frules;
+
+       /** count of names */
+       uint32_t names_count;
+
+       /** the name indexes sorted */
+       uint32_t *names_sorted;
+
+       /** count of rules */
+       uint32_t rules_count;
+
+       /** the rules */
+       rule_t *rules;
+
+       /** is changed? */
+       bool is_changed;
+
+       /** needs cleanup? */
+       bool need_cleanup;
+
+       /** has backup? */
+       bool has_backup;
+
+       /** the anydb interface */
+       anydb_t db;
+};
+typedef struct filedb filedb_t;
+
+/** return the name of 'index' */
+static
+const char*
+name_at(
+       filedb_t *filedb,
+       uint32_t index
+) {
+       return (const char*)(filedb->fnames.buffer + index);
+}
+
+/** compare names. used by qsort and bsearch */
+static
+int
+cmpnames(
+       const void *pa,
+       const void *pb,
+       void *arg
+) {
+       uint32_t a = *(const uint32_t*)pa;
+       uint32_t b = *(const uint32_t*)pb;
+       filedb_t *filedb = arg;
+       return strcmp(name_at(filedb, a), name_at(filedb, b));
+}
+
+/** initialize names */
+static
+int
+init_names(
+       filedb_t *filedb
+) {
+       uint32_t pos, len, *ns, *p, all, nc;
+
+       all = 0;
+       nc = 0;
+       ns = NULL;
+
+       /* iterate over names */
+       pos = uuidlen;
+       while (pos < filedb->fnames.used) {
+               /* get name length */
+               len = (uint32_t)strlen(name_at(filedb, pos));
+               if (pos + len <= pos || pos + len > filedb->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_r(ns, nc, sizeof *ns, cmpnames, filedb);
+       filedb->names_sorted = ns;
+       filedb->names_count = nc;
+       return 0;
+
+bad_file:
+       fprintf(stderr, "bad file %s", filedb->fnames.name);
+       errno = ENOEXEC;
+error:
+       return -1;
+}
+
+/** init the rules from the file */
+static
+void
+init_rules(
+       filedb_t *filedb
+) {
+       filedb->rules = (rule_t*)(filedb->frules.buffer + uuidlen);
+       filedb->rules_count = (filedb->frules.used - uuidlen) / sizeof *filedb->rules;
+}
+
+/** open a fbuf */
+static
+int
+open_identify(
+       fbuf_t  *fb,
+       const char *directory,
+       const char *name,
+       const char *extension,
+       const char *id,
+       uint32_t idlen
+) {
+       char *file, *backup, *p;
+       size_t ldir, lext, lname;
+
+       ldir = strlen(directory);
+       lname = strlen(name);
+       lext = strlen(extension);
+       file = alloca(((ldir + lname + lext) << 1) + 7);
+       p = mempcpy(file, directory, ldir);
+       *p++ = '/';
+       p = mempcpy(p, name, lname);
+       *p++ = '.';
+       backup = mempcpy(p, extension, lext + 1);
+       p = mempcpy(backup, file, ldir + lname + lext + 2);
+       *p++ = '~';
+       *p = 0;
+       return fbuf_open_identify(fb, file, backup, id, idlen);
+}
+
+/** open the database for files 'names' and 'rules' (can be NULL) */
+static
+int
+opendb(
+       filedb_t *filedb,
+       const char *directory,
+       const char *name
+) {
+       int rc;
+
+       /* provide default directory */
+       if (directory == NULL)
+               directory = filedb_default_directory;
+
+       /* provide default name */
+       if (name == NULL)
+               name = filedb_default_name;
+
+       /* open the names */
+       rc = open_identify(&filedb->fnames, directory, name, "names", uuid_names_v2, uuidlen);
+       if (rc < 0)
+               goto error;
+
+       /* open the rules */
+       rc = open_identify(&filedb->frules, directory, name, "rules", uuid_rules_v2, uuidlen);
+       if (rc < 0)
+               goto error;
+
+       /* connect internals */
+       rc = init_names(filedb);
+       if (rc < 0)
+               goto error;
+
+       init_rules(filedb);
+       return 0;
+error:
+       return -1;
+}
+
+/** close the database */
+static
+void
+closedb(
+       filedb_t *filedb
+) {
+       assert(filedb->fnames.name && filedb->frules.name);
+       fbuf_close(&filedb->fnames);
+       fbuf_close(&filedb->frules);
+}
+
+/** synchronize db on files */
+static
+int
+syncdb(
+       filedb_t *filedb
+) {
+       int rc;
+
+       assert(filedb->fnames.name && filedb->frules.name);
+       if (!filedb->is_changed)
+               rc = 0;
+       else {
+               rc = fbuf_sync(&filedb->fnames);
+               if (rc == 0) {
+                       rc = fbuf_sync(&filedb->frules);
+                       if (rc == 0) {
+                               filedb->is_changed = false;
+                               filedb->has_backup = false;
+                       }
+               }
+       }
+       return rc;
+}
+
+/** make a backup of the database */
+static
+int
+backupdb(
+       filedb_t *filedb
+) {
+       int rc;
+
+       assert(filedb->fnames.name && filedb->frules.name);
+       if (filedb->has_backup)
+               rc = 0;
+       else {
+               rc = fbuf_backup(&filedb->fnames);
+               if (rc == 0) {
+                       rc = fbuf_backup(&filedb->frules);
+                       if (rc == 0) {
+                               filedb->has_backup = true;
+                               filedb->is_changed = false;
+                       }
+               }
+       }
+       return rc;
+}
+
+/** recover the database from latest backup */
+static
+int
+recoverdb(
+       filedb_t *filedb
+) {
+       int rc;
+
+       assert(filedb->fnames.name && filedb->frules.name);
+       if (!filedb->is_changed || !filedb->has_backup)
+               rc = 0;
+       else {
+               rc = fbuf_recover(&filedb->fnames);
+               if (rc < 0)
+                       goto error;
+
+               rc = fbuf_recover(&filedb->frules);
+               if (rc < 0)
+                       goto error;
+
+               rc = init_names(filedb);
+               if (rc < 0)
+                       goto error;
+
+               init_rules(filedb);
+               filedb->is_changed = false;
+               filedb->need_cleanup = false;
+       }
+       return rc;
+error:
+       fprintf(stderr, "db recovery impossible: %m");
+       exit(5);
+       return rc;
+}
+
+static
+int
+index_itf(
+       void *clodb,
+       anydb_idx_t *idx,
+       const char *name,
+       bool create
+) {
+       filedb_t *filedb = clodb;
+       uint32_t lo, up, m, i, *p;
+       int c;
+       const char *n;
+       size_t len;
+
+       /* dichotomic search */
+       lo = 0;
+       up = filedb->names_count;
+       while(lo < up) {
+               m = (lo + up) >> 1;
+               i = filedb->names_sorted[m];
+               n = name_at(filedb, i);
+               c = strcmp(n, name);
+
+               if (c == 0) {
+                       /* found */
+                       *idx = 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 = filedb->fnames.used;
+       c = fbuf_append(&filedb->fnames, name, 1 + (uint32_t)len);
+       if (c < 0)
+               return c;
+
+       /* add the name in sorted array */
+       up = filedb->names_count;
+       if (!(up & 1023)) {
+               p = realloc(filedb->names_sorted, (up + 1024) * sizeof *p);
+               if (p == NULL) {
+                       fprintf(stderr, "out of memory");
+                       return -1;
+               }
+               filedb->names_sorted = p;
+       }
+       memmove(&filedb->names_sorted[lo + 1], &filedb->names_sorted[lo], (up - lo) * sizeof *filedb->names_sorted);
+       filedb->names_count = up + 1;
+       *idx = filedb->names_sorted[lo] = i;
+       return 0;
+}
+
+static
+const char *
+string_itf(
+       void *clodb,
+       anydb_idx_t idx
+) {
+       filedb_t *filedb = clodb;
+
+       return name_at(filedb, idx);
+}
+
+static
+void
+apply_itf(
+       void *clodb,
+       anydb_action_t (*oper)(void *closure, const anydb_key_t *key, anydb_value_t *value),
+       void *closure
+) {
+       filedb_t *filedb = clodb;
+       anydb_action_t a;
+       rule_t *rule;
+       anydb_key_t key;
+       anydb_value_t value;
+       uint32_t i;
+
+       key.session = AnyIdx_Wide;
+       i = 0;
+       while (i < filedb->rules_count) {
+               rule = &filedb->rules[i];
+               key.client = rule->client;
+               key.user = rule->user;
+               key.permission = rule->permission;
+               value.value = rule->value;
+               value.expire = exp2time(rule->expire);
+               a = oper(closure, &key, &value);
+               switch (a) {
+               case Anydb_Action_Stop:
+                       return;
+               case Anydb_Action_Continue:
+                       i++;
+                       break;
+               case Anydb_Action_Update_And_Stop:
+                       rule->value = value.value;
+                       rule->expire = time2exp(value.expire);
+                       filedb->need_cleanup = true;
+                       filedb->is_changed = true;
+                       return;
+               case Anydb_Action_Remove_And_Continue:
+                       *rule = filedb->rules[--filedb->rules_count];
+                       filedb->is_changed = true;
+                       filedb->need_cleanup = true;
+                       filedb->frules.used -= (uint32_t)sizeof *rule;
+                       break;
+               }
+       }
+}
+
+static
+int
+transaction_itf(
+       void *clodb,
+       anydb_transaction_t oper
+) {
+       filedb_t *filedb = clodb;
+       int rc;
+
+       switch (oper) {
+       case Anydb_Transaction_Start:
+               rc = backupdb(filedb);
+               break;
+       case Anydb_Transaction_Commit:
+               rc = syncdb(filedb);
+               break;
+       case Anydb_Transaction_Cancel:
+               rc = recoverdb(filedb);
+               break;
+       }
+       return rc;
+}
+
+static
+int
+add_itf(
+       void *clodb,
+       const anydb_key_t *key,
+       const anydb_value_t *value
+) {
+       filedb_t *filedb = clodb;
+       int rc;
+       struct rule *rules;
+       uint32_t alloc;
+       uint32_t count;
+
+       alloc = filedb->frules.used + (uint32_t)sizeof *rules;
+       rc = fbuf_ensure_capacity(&filedb->frules, alloc);
+       if (rc)
+               return rc;
+       rules = (rule_t*)(filedb->frules.buffer + uuidlen);
+       filedb->rules = rules;
+       count = filedb->rules_count++;
+       rules = &rules[count];
+       rules->client = key->client;
+       rules->user = key->user;
+       rules->permission = key->permission;
+       rules->value = value->value;
+       rules->expire = time2exp(value->expire);
+       filedb->frules.used = alloc;
+       filedb->is_changed = true;
+       return 0;
+}
+
+static
+bool
+gc_dig(
+       uint32_t *array,
+       uint32_t count,
+       uint32_t item,
+       uint32_t *index
+) {
+       uint32_t lo, up, i;
+
+       /* dichotomic search */
+       lo = 0;
+       up = count;
+       while(lo < up) {
+               i = (lo + up) >> 1;
+               if (array[i] == item) {
+                       /* found */
+                       *index = i;
+                       return true;
+               }
+
+               /* dichotomic iteration */
+               if (array[i] < item)
+                       lo = i + 1;
+               else
+                       up = i;
+       }
+       *index = lo;
+       return false;
+}
+
+static
+uint32_t
+gc_add(
+       uint32_t *array,
+       uint32_t count,
+       uint32_t item
+) {
+       uint32_t index, i;
+
+       if (gc_dig(array, count, item, &index))
+               return count;
+
+       i = count;
+       while (i > index) {
+               array[i] = array[i - 1];
+               i = i - 1;
+       }
+       array[i] = item;
+       return count + 1;
+}
+
+static
+uint32_t
+gc_mark(
+       uint32_t *array,
+       uint32_t count,
+       uint32_t item
+) {
+       return item > AnyIdx_Max ? count : gc_add(array, count, item);
+}
+
+static
+bool
+gc_new(
+       uint32_t *array,
+       uint32_t count,
+       uint32_t item,
+       uint32_t *index
+) {
+       return item > AnyIdx_Max ? false : gc_dig(array, count, item, index);
+}
+
+static
+void
+gc_itf(
+       void *clodb
+) {
+       filedb_t *filedb = clodb;
+       uint32_t nr;
+       uint32_t nn;
+       struct rule *rules;
+       uint32_t *used;
+       uint32_t *sorted;
+       char *strings;
+       uint32_t ir, nu, idx, is, ios, lenz;
+
+       /* check cleanup required */
+       if (!filedb->need_cleanup)
+               return;
+       filedb->need_cleanup = false;
+
+       /* mark items */
+       nr = filedb->rules_count;
+       nn = filedb->names_count;
+       rules = filedb->rules;
+       used = alloca(nn * sizeof *used);
+       nu = 0;
+       for (ir = 0 ; ir < nr ; ir++) {
+               nu = gc_mark(used, nu, rules[ir].client);
+               nu = gc_mark(used, nu, rules[ir].user);
+               nu = gc_mark(used, nu, rules[ir].permission);
+               nu = gc_mark(used, nu, rules[ir].value);
+       }
+
+       /* pack if too much unused */
+       if (nu + (nu >> 2) <= nn)
+               return;
+
+       /* pack the names */
+       strings = (char*)filedb->fnames.buffer;
+       sorted = filedb->names_sorted;
+       is = ios = uuidlen;
+       while (is < filedb->fnames.used) {
+               /* get name length */
+               lenz = 1 + (uint32_t)strlen(strings + is);
+               if (gc_dig(used, nu, is, &idx)) {
+                       sorted[idx] = ios;
+                       if (is != ios)
+                               memcpy(strings + ios, strings + is, lenz);
+                       ios += lenz;
+               }
+               /* next */
+               is += lenz;
+       }
+
+       /* renum the rules */
+       for (ir = 0 ; ir < nr ; ir++) {
+               if (gc_new(used, nu, rules[ir].client, &idx))
+                       rules[ir].client = sorted[idx];
+               if (gc_new(used, nu, rules[ir].user, &idx))
+                       rules[ir].user = sorted[idx];
+               if (gc_new(used, nu, rules[ir].permission, &idx))
+                       rules[ir].permission = sorted[idx];
+               if (gc_new(used, nu, rules[ir].value, &idx))
+                       rules[ir].value = sorted[idx];
+       }
+
+       /* record and sort */
+       filedb->names_count = nu;
+       filedb->fnames.used = ios;
+       qsort_r(sorted, nu, sizeof *sorted, cmpnames, filedb);
+
+       /* set as changed */
+       filedb->is_changed = true;
+}
+
+static
+int
+sync_itf(
+       void *clodb
+) {
+       filedb_t *filedb = clodb;
+       return syncdb(filedb);
+}
+
+static
+void
+destroy_itf(
+       void *clodb
+) {
+       filedb_t *filedb = clodb;
+       if (filedb) {
+               if (filedb->frules.name)
+                       closedb(filedb);
+               free(filedb);
+       }
+}
+
+static
+void
+init(
+       filedb_t *filedb
+) {
+       filedb->db.clodb = filedb;
+
+       filedb->db.itf.index = index_itf;
+       filedb->db.itf.string = string_itf;
+       filedb->db.itf.transaction = transaction_itf;
+       filedb->db.itf.apply = apply_itf;
+       filedb->db.itf.add = add_itf;
+       filedb->db.itf.gc = gc_itf;
+       filedb->db.itf.sync = sync_itf;
+       filedb->db.itf.destroy = destroy_itf;
+}
+
+int
+filedb_create(
+       anydb_t **adb,
+       const char *directory,
+       const char *basename
+) {
+       int rc;
+       filedb_t *filedb;
+
+       *adb = NULL;
+       filedb = calloc(1, sizeof *filedb);
+       if (!filedb)
+               return -ENOMEM;
+
+       init(filedb);
+
+       rc = opendb(filedb, directory, basename);
+       if (rc)
+               free(filedb);
+       else
+               *adb = &filedb->db;
+       return rc;
+}
+
+/** synchronize database */
+int
+anydb_sync(
+       anydb_t *db
+) {
+       return db->itf.sync ? db->itf.sync(db->clodb) : 0;
+}
diff --git a/src/filedb.h b/src/filedb.h
new file mode 100644 (file)
index 0000000..d575e50
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * 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
+
+
+/** is the database empty */
+int
+filedb_create(
+       anydb_t **filedb,
+       const char *directory,
+       const char *basename
+);
index b07928f..285d7a4 100644 (file)
@@ -144,6 +144,8 @@ apply_itf(
                else
                        a = oper(closure, &rules[ir].key, &rules[ir].value);
                switch (a) {
+               case Anydb_Action_Stop:
+                       return;
                case Anydb_Action_Continue:
                        ir++;
                        break;
@@ -251,6 +253,25 @@ add_itf(
        return 0;
 }
 
+static
+void
+gc_mark(
+       anydb_idx_t *renum,
+       anydb_idx_t item
+) {
+       if (item <= AnyIdx_Max)
+               renum[item] = 1;
+}
+
+static
+anydb_idx_t
+gc_new(
+       anydb_idx_t *renum,
+       anydb_idx_t item
+) {
+       return item > AnyIdx_Max ? item : renum[item];
+}
+#include <stdio.h>
 static
 void
 gc_itf(
@@ -268,11 +289,11 @@ gc_itf(
                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;
+               gc_mark(renum, rules[i].key.client);
+               gc_mark(renum, rules[i].key.session);
+               gc_mark(renum, rules[i].key.user);
+               gc_mark(renum, rules[i].key.permission);
+               gc_mark(renum, rules[i].value.value);
        }
 
        for (i = j = 0 ; i < ns ; i++) {
@@ -287,16 +308,16 @@ gc_itf(
        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];
+                       rules[i].key.client = gc_new(renum, rules[i].key.client);
+                       rules[i].key.session = gc_new(renum, rules[i].key.session);
+                       rules[i].key.user = gc_new(renum, rules[i].key.user);
+                       rules[i].key.permission = gc_new(renum, rules[i].key.permission);
+                       rules[i].value.value = gc_new(renum, rules[i].value.value);
                }
        }
 
        i = memdb->strings.alloc;
-       while (ns + SBS > i)
+       while (ns + SBS < i)
                i -= SBS;
        if (i != memdb->strings.alloc) {
                memdb->strings.alloc = i;
@@ -304,7 +325,7 @@ gc_itf(
        }
 
        i = memdb->rules.alloc;
-       while (ns + RBS > i)
+       while (nr + RBS < i)
                i -= RBS;
        if (i != memdb->rules.alloc) {
                memdb->rules.alloc = i;
@@ -338,6 +359,7 @@ init(
        memdb->db.itf.apply = apply_itf;
        memdb->db.itf.add = add_itf;
        memdb->db.itf.gc = gc_itf;
+       memdb->db.itf.sync = 0;
        memdb->db.itf.destroy = destroy_itf;
 
        memdb->strings.alloc = 0;