]> git.meshlink.io Git - meshlink-tiny/commitdiff
Use a key/value store with configurable storage callbacks. feature/storage-callbacks
authorGuus Sliepen <guus@meshlink.io>
Sun, 14 Nov 2021 15:35:09 +0000 (16:35 +0100)
committerGuus Sliepen <guus@meshlink.io>
Thu, 2 Dec 2021 21:42:40 +0000 (22:42 +0100)
For greater flexibility with how configuration files are stored, we
assume a key/value store, and allow the application to provide callbacks
for storing and loading values, and listing all the keys. By default, we
use the filesystem as a backing store, with one file per key in a flat
directory.

13 files changed:
examples/chat.c
src/conf.c
src/conf.h
src/devtools.c
src/devtools.h
src/meshlink-tiny.h
src/meshlink.c
src/meshlink.sym
src/meshlink_internal.h
src/net_setup.c
test/basic.c
test/encrypted.c
test/invite-join.c

index 5d06538c3915a8a943ecfa7c2fde23a090513fa0..75d6acce6c811e2ce1af14d5bb9d80c5099e609b 100644 (file)
@@ -1,7 +1,13 @@
+#define _POSIX_C_SOURCE 200809L
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <strings.h>
+#include <assert.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
 #include "../src/meshlink-tiny.h"
 
 static void log_message(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) {
@@ -150,6 +156,55 @@ static void parse_input(meshlink_handle_t *mesh, char *buf) {
        printf("Message sent to '%s'.\n", destination->name);
 }
 
+static char *flatten(const char *filename) {
+       char *result = strdup(filename);
+       assert(result);
+
+       for(char *c = result; *c; c++) {
+               if(*c == '/') {
+                       *c = ':';
+               }
+       }
+
+       return result;
+}
+
+static bool load_cb(meshlink_handle_t *mesh, const char *key, void *data, size_t *len) {
+       fprintf(stderr, "load_cb(%s, %s, %p, %zu)\n", mesh->name, key, data, *len);
+       FILE *f = fopen(flatten(key), "r");
+       assert(f);
+       fread(data, 1, *len, f);
+       fseek(f, 0, SEEK_END);
+       *len = ftell(f);
+       assert(!fclose(f));
+       return true;
+}
+
+static bool store_cb(meshlink_handle_t *mesh, const char *key, const void *data, size_t len) {
+       fprintf(stderr, "store_cb(%s, %s, %p, %zu)\n", mesh->name, key, data, len);
+       FILE *f = fopen(flatten(key), "w");
+       assert(f);
+       assert(fwrite(data, len, 1, f) == 1);
+       assert(!fclose(f));
+       return true;
+}
+
+static bool ls_cb(meshlink_handle_t *mesh, meshlink_ls_entry_cb_t entry_cb) {
+       fprintf(stderr, "ls_cb()");
+       DIR *dir = opendir(".");
+       struct dirent *ent;
+
+       while((ent = readdir(dir))) {
+               if(ent->d_name[0] != '.') {
+                       entry_cb(mesh, ent->d_name, 0);
+               }
+       }
+
+       closedir(dir);
+
+       return true;
+}
+
 int main(int argc, char *argv[]) {
        const char *confbase = ".chat";
        const char *nick = NULL;
@@ -163,9 +218,15 @@ int main(int argc, char *argv[]) {
                nick = argv[2];
        }
 
-       meshlink_set_log_cb(NULL, MESHLINK_INFO, log_message);
+       assert(mkdir(confbase, 0700) == 0);
+       assert(chdir(confbase) == 0);
 
-       meshlink_handle_t *mesh = meshlink_open(confbase, nick, "chat", DEV_CLASS_STATIONARY);
+       meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_message);
+       meshlink_open_params_t *params = meshlink_open_params_init(confbase, nick, "chat", DEV_CLASS_STATIONARY);
+       //assert(meshlink_open_params_set_storage_key(params, "12345", 5));
+       assert(meshlink_open_params_set_storage_callbacks(params, load_cb, store_cb, ls_cb));
+       meshlink_handle_t *mesh = meshlink_open_ex(params);
+       meshlink_open_params_free(params);
 
        if(!mesh) {
                fprintf(stderr, "Could not open MeshLink: %s\n", meshlink_strerror(meshlink_errno));
index cae7bd1e90de73c44e4db0acc0677acd7705c2ab..925171d2fa6335645aa739736fce92c7f525b8f2 100644 (file)
@@ -18,6 +18,7 @@
 */
 
 #include "system.h"
+
 #include <assert.h>
 #include <sys/types.h>
 #include <utime.h>
 #include "crypto.h"
 #include "logger.h"
 #include "meshlink_internal.h"
-#include "xalloc.h"
 #include "packmsg.h"
+#include "protocol.h"
+#include "xalloc.h"
 
-/// Generate a path to the main configuration file.
-static void make_main_path(meshlink_handle_t *mesh, const char *conf_subdir, char *path, size_t len) {
-       assert(conf_subdir);
-       assert(path);
-       assert(len);
-
-       snprintf(path, len, "%s" SLASH "%s" SLASH "meshlink.conf", mesh->confbase, conf_subdir);
-}
-
-/// Generate a path to a host configuration file.
-static void make_host_path(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, char *path, size_t len) {
-       assert(conf_subdir);
-       assert(name);
-       assert(path);
-       assert(len);
-
-       snprintf(path, len, "%s" SLASH "%s" SLASH "hosts" SLASH "%s", mesh->confbase, conf_subdir, name);
-}
-
-/// Remove a directory recursively
-static bool deltree(const char *dirname) {
-       assert(dirname);
-
-       DIR *d = opendir(dirname);
-
-       if(d) {
-               struct dirent *ent;
-
-               while((ent = readdir(d))) {
-                       if(ent->d_name[0] == '.') {
-                               if(!ent->d_name[1] || (ent->d_name[1] == '.' && !ent->d_name[2])) {
-                                       continue;
-                               }
-                       }
-
-                       char filename[PATH_MAX];
-                       snprintf(filename, sizeof(filename), "%s" SLASH "%s", dirname, ent->d_name);
-
-                       if(unlink(filename)) {
-                               if(!deltree(filename)) {
-                                       return false;
-                               }
-                       }
-               }
-
-               closedir(d);
-       } else {
-               return errno == ENOENT;
-       }
-
-       return rmdir(dirname) == 0;
-}
-
-bool sync_path(const char *pathname) {
+static bool sync_path(const char *pathname) {
        assert(pathname);
 
        int fd = open(pathname, O_RDONLY);
@@ -110,434 +59,265 @@ bool sync_path(const char *pathname) {
        return true;
 }
 
-/// Try decrypting the main configuration file from the given sub-directory.
-static bool main_config_decrypt(meshlink_handle_t *mesh, const char *conf_subdir) {
-       assert(mesh->config_key);
-       assert(mesh->confbase);
-       assert(conf_subdir);
-
-       config_t config;
-
-       if(!main_config_read(mesh, conf_subdir, &config, mesh->config_key)) {
-               logger(mesh, MESHLINK_ERROR, "Could not read main configuration file");
-               return false;
-       }
-
-       packmsg_input_t in = {config.buf, config.len};
-
-       uint32_t version = packmsg_get_uint32(&in);
-       config_free(&config);
-
-       return version == MESHLINK_CONFIG_VERSION;
+static bool invalidate_config_file(meshlink_handle_t *mesh, const char *name, size_t len) {
+       (void)len;
+       config_t empty_config = {NULL, 0};
+       return config_store(mesh, name, &empty_config);
 }
 
-/// Create a fresh configuration directory
-bool config_init(meshlink_handle_t *mesh, const char *conf_subdir) {
-       assert(conf_subdir);
-
-       if(!mesh->confbase) {
+/// Wipe an existing configuration directory
+bool config_destroy(const struct meshlink_open_params *params) {
+       if(!params->confbase) {
                return true;
        }
 
-       char path[PATH_MAX];
-
-       // Create "current" sub-directory in the confbase
-       snprintf(path, sizeof(path), "%s" SLASH "%s", mesh->confbase, conf_subdir);
+       FILE *lockfile = NULL;
 
-       if(!deltree(path)) {
-               logger(mesh, MESHLINK_DEBUG, "Could not delete directory %s: %s\n", path, strerror(errno));
-               return false;
-       }
+       if(!params->load_cb) {
+               /* Exit early if the confbase directory itself doesn't exist */
+               if(access(params->confbase, F_OK) && errno == ENOENT) {
+                       return true;
+               }
 
-       if(mkdir(path, 0700)) {
-               logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", path, strerror(errno));
-               return false;
-       }
+               /* Take the lock the same way meshlink_open() would. */
+               lockfile = fopen(params->lock_filename, "w+");
 
-       make_host_path(mesh, conf_subdir, "", path, sizeof(path));
+               if(!lockfile) {
+                       logger(NULL, MESHLINK_ERROR, "Could not open lock file %s: %s", params->lock_filename, strerror(errno));
+                       meshlink_errno = MESHLINK_ESTORAGE;
+                       return false;
+               }
 
-       if(mkdir(path, 0700)) {
-               logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", path, strerror(errno));
-               return false;
-       }
+#ifdef FD_CLOEXEC
+               fcntl(fileno(lockfile), F_SETFD, FD_CLOEXEC);
+#endif
 
-       return true;
-}
+#ifdef HAVE_MINGW
+               // TODO: use _locking()?
+#else
 
-/// Wipe an existing configuration directory
-bool config_destroy(const char *confbase, const char *conf_subdir) {
-       assert(conf_subdir);
+               if(flock(fileno(lockfile), LOCK_EX | LOCK_NB) != 0) {
+                       logger(NULL, MESHLINK_ERROR, "Configuration directory %s still in use\n", params->lock_filename);
+                       fclose(lockfile);
+                       meshlink_errno = MESHLINK_EBUSY;
+                       return false;
+               }
 
-       if(!confbase) {
-               return true;
+#endif
        }
 
-       struct stat st;
-
-       char path[PATH_MAX];
+       {
+               meshlink_handle_t tmp_mesh;
+               memset(&tmp_mesh, 0, sizeof tmp_mesh);
 
-       // Check the presence of configuration base sub directory.
-       snprintf(path, sizeof(path), "%s" SLASH "%s", confbase, conf_subdir);
+               tmp_mesh.confbase = params->confbase;
+               tmp_mesh.name = params->name;
+               tmp_mesh.load_cb = params->load_cb;
+               tmp_mesh.store_cb = params->store_cb;
+               tmp_mesh.ls_cb = params->ls_cb;
 
-       if(stat(path, &st)) {
-               if(errno == ENOENT) {
-                       return true;
-               } else {
-                       logger(NULL, MESHLINK_ERROR, "Cannot stat %s: %s\n", path, strerror(errno));
+               if(!config_ls(&tmp_mesh, invalidate_config_file)) {
+                       logger(NULL, MESHLINK_ERROR, "Cannot remove configuration files\n");
+                       fclose(lockfile);
                        meshlink_errno = MESHLINK_ESTORAGE;
                        return false;
                }
        }
 
-       // Remove meshlink.conf
-       snprintf(path, sizeof(path), "%s" SLASH "%s" SLASH "meshlink.conf", confbase, conf_subdir);
-
-       if(unlink(path)) {
-               if(errno != ENOENT) {
-                       logger(NULL, MESHLINK_ERROR, "Cannot delete %s: %s\n", path, strerror(errno));
+       if(!params->load_cb) {
+               if(unlink(params->lock_filename) && errno != ENOENT) {
+                       logger(NULL, MESHLINK_ERROR, "Cannot remove lock file %s: %s\n", params->lock_filename, strerror(errno));
+                       fclose(lockfile);
                        meshlink_errno = MESHLINK_ESTORAGE;
                        return false;
                }
-       }
-
-       snprintf(path, sizeof(path), "%s" SLASH "%s", confbase, conf_subdir);
-
-       if(!deltree(path)) {
-               logger(NULL, MESHLINK_ERROR, "Cannot delete %s: %s\n", path, strerror(errno));
-               meshlink_errno = MESHLINK_ESTORAGE;
-               return false;
-       }
-
-       return sync_path(confbase);
-}
-
-static bool copytree(const char *src_dir_name, const void *src_key, const char *dst_dir_name, const void *dst_key) {
-       assert(src_dir_name);
-       assert(dst_dir_name);
-
-       char src_filename[PATH_MAX];
-       char dst_filename[PATH_MAX];
-       struct dirent *ent;
-
-       DIR *src_dir = opendir(src_dir_name);
-
-       if(!src_dir) {
-               logger(NULL, MESHLINK_ERROR, "Could not open directory file %s\n", src_dir_name);
-               meshlink_errno = MESHLINK_ESTORAGE;
-               return false;
-       }
-
-       // Delete if already exists and create a new destination directory
-       if(!deltree(dst_dir_name)) {
-               logger(NULL, MESHLINK_ERROR, "Cannot delete %s: %s\n", dst_dir_name, strerror(errno));
-               meshlink_errno = MESHLINK_ESTORAGE;
-               return false;
-       }
 
-       if(mkdir(dst_dir_name, 0700)) {
-               logger(NULL, MESHLINK_ERROR, "Could not create directory %s\n", dst_filename);
-               meshlink_errno = MESHLINK_ESTORAGE;
-               return false;
-       }
+               fclose(lockfile);
 
-       while((ent = readdir(src_dir))) {
-               if(ent->d_name[0] == '.') {
-                       continue;
+               if(!sync_path(params->confbase)) {
+                       logger(NULL, MESHLINK_ERROR, "Cannot sync directory %s: %s\n", params->confbase, strerror(errno));
+                       meshlink_errno = MESHLINK_ESTORAGE;
+                       return false;
                }
 
-               snprintf(dst_filename, sizeof(dst_filename), "%s" SLASH "%s", dst_dir_name, ent->d_name);
-               snprintf(src_filename, sizeof(src_filename), "%s" SLASH "%s", src_dir_name, ent->d_name);
-
-               if(ent->d_type == DT_DIR) {
-                       if(!copytree(src_filename, src_key, dst_filename, dst_key)) {
-                               logger(NULL, MESHLINK_ERROR, "Copying %s to %s failed\n", src_filename, dst_filename);
-                               meshlink_errno = MESHLINK_ESTORAGE;
-                               return false;
-                       }
-
-                       if(!sync_path(dst_filename)) {
-                               return false;
-                       }
-               } else if(ent->d_type == DT_REG) {
-                       struct stat st;
-                       config_t config;
-
-                       if(stat(src_filename, &st)) {
-                               logger(NULL, MESHLINK_ERROR, "Could not stat file `%s': %s\n", src_filename, strerror(errno));
-                               meshlink_errno = MESHLINK_ESTORAGE;
-                               return false;
-                       }
-
-                       FILE *f = fopen(src_filename, "r");
-
-                       if(!f) {
-                               logger(NULL, MESHLINK_ERROR, "Failed to open `%s': %s\n", src_filename, strerror(errno));
-                               meshlink_errno = MESHLINK_ESTORAGE;
-                               return false;
-                       }
-
-                       if(!config_read_file(NULL, f, &config, src_key)) {
-                               logger(NULL, MESHLINK_ERROR, "Failed to read `%s': %s\n", src_filename, strerror(errno));
-                               fclose(f);
-                               meshlink_errno = MESHLINK_ESTORAGE;
-                               return false;
-                       }
-
-                       if(fclose(f)) {
-                               logger(NULL, MESHLINK_ERROR, "Failed to close `%s': %s\n", src_filename, strerror(errno));
-                               config_free(&config);
-                               meshlink_errno = MESHLINK_ESTORAGE;
-                               return false;
-                       }
-
-                       f = fopen(dst_filename, "w");
-
-                       if(!f) {
-                               logger(NULL, MESHLINK_ERROR, "Failed to open `%s': %s", dst_filename, strerror(errno));
-                               config_free(&config);
-                               meshlink_errno = MESHLINK_ESTORAGE;
-                               return false;
-                       }
-
-                       if(!config_write_file(NULL, f, &config, dst_key)) {
-                               logger(NULL, MESHLINK_ERROR, "Failed to write `%s': %s", dst_filename, strerror(errno));
-                               config_free(&config);
-                               fclose(f);
-                               meshlink_errno = MESHLINK_ESTORAGE;
-                               return false;
-                       }
-
-                       if(fclose(f)) {
-                               logger(NULL, MESHLINK_ERROR, "Failed to close `%s': %s", dst_filename, strerror(errno));
-                               config_free(&config);
-                               meshlink_errno = MESHLINK_ESTORAGE;
-                               return false;
-                       }
-
-                       config_free(&config);
-
-                       struct utimbuf times;
-                       times.modtime = st.st_mtime;
-                       times.actime = st.st_atime;
-
-                       if(utime(dst_filename, &times)) {
-                               logger(NULL, MESHLINK_ERROR, "Failed to utime `%s': %s", dst_filename, strerror(errno));
-                               meshlink_errno = MESHLINK_ESTORAGE;
-                               return false;
-                       }
-               }
+               rmdir(params->confbase);
        }
 
-       closedir(src_dir);
        return true;
 }
 
-bool config_copy(meshlink_handle_t *mesh, const char *src_dir_name, const void *src_key, const char *dst_dir_name, const void *dst_key) {
-       assert(src_dir_name);
-       assert(dst_dir_name);
+/// Read a blob of data.
+static bool load(meshlink_handle_t *mesh, const char *key, void *data, size_t *len) {
+       logger(mesh, MESHLINK_DEBUG, "load(%s, %p, %zu)", key ? key : "(null)", data, *len);
 
-       char src_filename[PATH_MAX];
-       char dst_filename[PATH_MAX];
-
-       snprintf(dst_filename, sizeof(dst_filename), "%s" SLASH "%s", mesh->confbase, dst_dir_name);
-       snprintf(src_filename, sizeof(src_filename), "%s" SLASH "%s", mesh->confbase, src_dir_name);
+       if(mesh->load_cb) {
+               if(!mesh->load_cb(mesh, key, data, len)) {
+                       logger(mesh, MESHLINK_ERROR, "Failed to open `%s'\n", key);
+                       meshlink_errno = MESHLINK_ESTORAGE;
+                       return false;
+               } else {
+                       return true;
+               }
+       }
 
-       return copytree(src_filename, src_key, dst_filename, dst_key);
-}
+       char filename[PATH_MAX];
+       snprintf(filename, sizeof(filename), "%s/%s", mesh->confbase, key);
 
-/// Check the presence of the main configuration file.
-bool main_config_exists(meshlink_handle_t *mesh, const char *conf_subdir) {
-       assert(conf_subdir);
+       FILE *f = fopen(filename, "r");
 
-       if(!mesh->confbase) {
+       if(!f) {
+               logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s\n", filename, strerror(errno));
+               meshlink_errno = MESHLINK_ESTORAGE;
                return false;
        }
 
-       char path[PATH_MAX];
-       make_main_path(mesh, conf_subdir, path, sizeof(path));
-       return access(path, F_OK) == 0;
-}
+       long actual_len;
 
-bool config_rename(meshlink_handle_t *mesh, const char *old_conf_subdir, const char *new_conf_subdir) {
-       assert(old_conf_subdir);
-       assert(new_conf_subdir);
-
-       if(!mesh->confbase) {
+       if(fseek(f, 0, SEEK_END) || (actual_len = ftell(f)) <= 0 || fseek(f, 0, SEEK_SET)) {
+               logger(mesh, MESHLINK_ERROR, "Cannot get config file size: %s\n", strerror(errno));
+               meshlink_errno = MESHLINK_ESTORAGE;
+               fclose(f);
                return false;
        }
 
-       char old_path[PATH_MAX];
-       char new_path[PATH_MAX];
+       size_t todo = (size_t)actual_len < *len ? (size_t)actual_len : *len;
+       *len = actual_len;
 
-       snprintf(old_path, sizeof(old_path), "%s" SLASH "%s", mesh->confbase, old_conf_subdir);
-       snprintf(new_path, sizeof(new_path), "%s" SLASH "%s", mesh->confbase, new_conf_subdir);
-
-       return rename(old_path, new_path) == 0 && sync_path(mesh->confbase);
-}
-
-bool config_sync(meshlink_handle_t *mesh, const char *conf_subdir) {
-       assert(conf_subdir);
-
-       if(!mesh->confbase || mesh->storage_policy == MESHLINK_STORAGE_DISABLED) {
+       if(!data) {
+               fclose(f);
                return true;
        }
 
-       char path[PATH_MAX];
-       snprintf(path, sizeof(path), "%s" SLASH "%s" SLASH "hosts", mesh->confbase, conf_subdir);
-
-       if(!sync_path(path)) {
+       if(fread(data, todo, 1, f) != 1) {
+               logger(mesh, MESHLINK_ERROR, "Cannot read config file: %s\n", strerror(errno));
+               meshlink_errno = MESHLINK_ESTORAGE;
+               fclose(f);
                return false;
        }
 
-       snprintf(path, sizeof(path), "%s" SLASH "%s", mesh->confbase, conf_subdir);
-
-       if(!sync_path(path)) {
+       if(fclose(f)) {
+               logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", filename, strerror(errno));
+               meshlink_errno = MESHLINK_ESTORAGE;
                return false;
        }
 
        return true;
 }
 
-bool meshlink_confbase_exists(meshlink_handle_t *mesh) {
-       if(!mesh->confbase) {
-               return false;
-       }
-
-       bool confbase_exists = false;
-       bool confbase_decryptable = false;
-
-       if(main_config_exists(mesh, "current")) {
-               confbase_exists = true;
+/// Store a blob of data.
+static bool store(meshlink_handle_t *mesh, const char *key, const void *data, size_t len) {
+       logger(mesh, MESHLINK_DEBUG, "store(%s, %p, %zu)", key ? key : "(null)", data, len);
 
-               if(mesh->config_key && main_config_decrypt(mesh, "current")) {
-                       confbase_decryptable = true;
+       if(mesh->store_cb) {
+               if(!mesh->store_cb(mesh, key, data, len)) {
+                       logger(mesh, MESHLINK_ERROR, "Cannot write config file: %s", strerror(errno));
+                       meshlink_errno = MESHLINK_ESTORAGE;
+                       return false;
+               } else {
+                       return true;
                }
        }
 
-       if(mesh->config_key && !confbase_decryptable && main_config_exists(mesh, "new")) {
-               confbase_exists = true;
-
-               if(main_config_decrypt(mesh, "new")) {
-                       if(!config_destroy(mesh->confbase, "current")) {
-                               return false;
-                       }
+       char filename[PATH_MAX];
+       snprintf(filename, sizeof(filename), "%s" SLASH "%s", mesh->confbase, key);
 
-                       if(!config_rename(mesh, "new", "current")) {
-                               return false;
-                       }
-
-                       confbase_decryptable = true;
+       if(!len) {
+               if(unlink(filename) && errno != ENOENT) {
+                       logger(mesh, MESHLINK_ERROR, "Failed to remove `%s': %s", filename, strerror(errno));
+                       meshlink_errno = MESHLINK_ESTORAGE;
+                       return false;
+               } else {
+                       return true;
                }
        }
 
-       if(mesh->config_key && !confbase_decryptable && main_config_exists(mesh, "old")) {
-               confbase_exists = true;
+       char tmp_filename[PATH_MAX];
+       snprintf(tmp_filename, sizeof(tmp_filename), "%s" SLASH "%s.tmp", mesh->confbase, key);
 
-               if(main_config_decrypt(mesh, "old")) {
-                       if(!config_destroy(mesh->confbase, "current")) {
-                               return false;
-                       }
+       FILE *f = fopen(tmp_filename, "w");
 
-                       if(!config_rename(mesh, "old", "current")) {
-                               return false;
-                       }
-
-                       confbase_decryptable = true;
-               }
+       if(!f) {
+               logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", tmp_filename, strerror(errno));
+               meshlink_errno = MESHLINK_ESTORAGE;
+               return false;
        }
 
-       // Cleanup if current is existing with old and new
-       if(confbase_exists && confbase_decryptable) {
-               if(!config_destroy(mesh->confbase, "old") || !config_destroy(mesh->confbase, "new")) {
-                       return false;
-               }
+       if(fwrite(data, len, 1, f) != 1) {
+               logger(mesh, MESHLINK_ERROR, "Cannot write config file: %s", strerror(errno));
+               meshlink_errno = MESHLINK_ESTORAGE;
+               fclose(f);
+               return false;
        }
 
-       return confbase_exists;
-}
-
-/// Lock the main configuration file. Creates confbase if necessary.
-bool main_config_lock(meshlink_handle_t *mesh, const char *lock_filename) {
-       if(!mesh->confbase) {
-               return true;
+       if(fflush(f)) {
+               logger(mesh, MESHLINK_ERROR, "Failed to flush file: %s", strerror(errno));
+               meshlink_errno = MESHLINK_ESTORAGE;
+               fclose(f);
+               return false;
        }
 
-       assert(lock_filename);
-
-       if(mkdir(mesh->confbase, 0700) && errno != EEXIST) {
-               logger(NULL, MESHLINK_ERROR, "Cannot create configuration directory %s: %s", mesh->confbase, strerror(errno));
-               meshlink_close(mesh);
+       if(fsync(fileno(f))) {
+               logger(mesh, MESHLINK_ERROR, "Failed to sync file: %s\n", strerror(errno));
                meshlink_errno = MESHLINK_ESTORAGE;
-               return NULL;
+               fclose(f);
+               return false;
        }
 
-       mesh->lockfile = fopen(lock_filename, "w+");
-
-       if(!mesh->lockfile) {
-               logger(NULL, MESHLINK_ERROR, "Cannot not open %s: %s\n", lock_filename, strerror(errno));
+       if(fclose(f)) {
+               logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", filename, strerror(errno));
                meshlink_errno = MESHLINK_ESTORAGE;
                return false;
        }
 
-#ifdef FD_CLOEXEC
-       fcntl(fileno(mesh->lockfile), F_SETFD, FD_CLOEXEC);
-#endif
-
-#ifdef HAVE_FLOCK
-
-       if(flock(fileno(mesh->lockfile), LOCK_EX | LOCK_NB) != 0) {
-               logger(NULL, MESHLINK_ERROR, "Cannot lock %s: %s\n", lock_filename, strerror(errno));
-               fclose(mesh->lockfile);
-               mesh->lockfile = NULL;
-               meshlink_errno = MESHLINK_EBUSY;
+       if(rename(tmp_filename, filename)) {
+               logger(mesh, MESHLINK_ERROR, "Failed to rename `%s' to `%s': %s", tmp_filename, filename, strerror(errno));
+               meshlink_errno = MESHLINK_ESTORAGE;
                return false;
        }
 
-#endif
-
        return true;
 }
 
-/// Unlock the main configuration file.
-void main_config_unlock(meshlink_handle_t *mesh) {
-       if(mesh->lockfile) {
-               fclose(mesh->lockfile);
-               mesh->lockfile = NULL;
+/// Read a configuration file, decrypting it if necessary.
+bool config_load(meshlink_handle_t *mesh, const char *name, config_t *config) {
+       size_t buflen = 256;
+       uint8_t *buf = xmalloc(buflen);
+       size_t len = buflen;
+
+       if(!load(mesh, name, buf, &len)) {
+               return false;
        }
-}
 
-/// Read a configuration file from a FILE handle.
-bool config_read_file(meshlink_handle_t *mesh, FILE *f, config_t *config, const void *key) {
-       assert(f);
+       buf = xrealloc(buf, len);
 
-       long len;
+       if(len > buflen) {
+               buflen = len;
 
-       if(fseek(f, 0, SEEK_END) || !(len = ftell(f)) || fseek(f, 0, SEEK_SET)) {
-               logger(mesh, MESHLINK_ERROR, "Cannot get config file size: %s\n", strerror(errno));
-               meshlink_errno = MESHLINK_ESTORAGE;
-               return false;
+               if(!load(mesh, name, (void **)&buf, &len) || len != buflen) {
+                       meshlink_errno = MESHLINK_ESTORAGE;
+                       return false;
+               }
        }
 
-       uint8_t *buf = xmalloc(len);
+       if(mesh->config_key) {
+               if(len < 12 + 16) {
+                       logger(mesh, MESHLINK_ERROR, "Cannot decrypt config file\n");
+                       meshlink_errno = MESHLINK_ESTORAGE;
+                       config_free(config);
+                       return false;
+               }
 
-       if(fread(buf, len, 1, f) != 1) {
-               logger(mesh, MESHLINK_ERROR, "Cannot read config file: %s\n", strerror(errno));
-               meshlink_errno = MESHLINK_ESTORAGE;
-               return false;
-       }
+               size_t decrypted_len = len - 12 - 16;
+               uint8_t *decrypted = xmalloc(decrypted_len);
 
-       if(key) {
-               uint8_t *decrypted = xmalloc(len);
-               size_t decrypted_len = len;
                chacha_poly1305_ctx_t *ctx = chacha_poly1305_init();
-               chacha_poly1305_set_key(ctx, key);
+               chacha_poly1305_set_key(ctx, mesh->config_key);
 
-               if(len > 12 && chacha_poly1305_decrypt_iv96(ctx, buf, buf + 12, len - 12, decrypted, &decrypted_len)) {
+               if(chacha_poly1305_decrypt_iv96(ctx, buf, buf + 12, len - 12, decrypted, &decrypted_len)) {
                        chacha_poly1305_exit(ctx);
                        free(buf);
-                       config->buf = decrypted;
-                       config->len = decrypted_len;
-                       return true;
+                       buf = decrypted;
+                       len = decrypted_len;
                } else {
                        logger(mesh, MESHLINK_ERROR, "Cannot decrypt config file\n");
                        meshlink_errno = MESHLINK_ESTORAGE;
@@ -554,55 +334,35 @@ bool config_read_file(meshlink_handle_t *mesh, FILE *f, config_t *config, const
        return true;
 }
 
-/// Write a configuration file to a FILE handle.
-bool config_write_file(meshlink_handle_t *mesh, FILE *f, const config_t *config, const void *key) {
-       assert(f);
+bool config_exists(meshlink_handle_t *mesh, const char *name) {
+       size_t len = 0;
 
-       if(key) {
-               uint8_t buf[config->len + 16];
-               size_t len = sizeof(buf);
-               uint8_t seqbuf[12];
-               randomize(&seqbuf, sizeof(seqbuf));
-               chacha_poly1305_ctx_t *ctx = chacha_poly1305_init();
-               chacha_poly1305_set_key(ctx, key);
-               bool success = false;
+       return load(mesh, name, NULL, &len) && len;
+}
 
-               if(chacha_poly1305_encrypt_iv96(ctx, seqbuf, config->buf, config->len, buf, &len)) {
-                       success = fwrite(seqbuf, sizeof(seqbuf), 1, f) == 1 && fwrite(buf, len, 1, f) == 1;
+/// Write a configuration file, encrypting it if necessary.
+bool config_store(meshlink_handle_t *mesh, const char *name, const config_t *config) {
+       if(mesh->config_key) {
+               size_t encrypted_len = config->len + 16; // length of encrypted data
+               uint8_t encrypted[12 + encrypted_len]; // store sequence number at the start
 
-                       if(!success) {
-                               logger(mesh, MESHLINK_ERROR, "Cannot write config file: %s", strerror(errno));
-                       }
+               randomize(encrypted, 12);
+               chacha_poly1305_ctx_t *ctx = chacha_poly1305_init();
+               chacha_poly1305_set_key(ctx, mesh->config_key);
 
-                       meshlink_errno = MESHLINK_ESTORAGE;
-               } else {
+               if(!chacha_poly1305_encrypt_iv96(ctx, encrypted, config->buf, config->len, encrypted + 12, &encrypted_len)) {
                        logger(mesh, MESHLINK_ERROR, "Cannot encrypt config file\n");
                        meshlink_errno = MESHLINK_ESTORAGE;
+                       chacha_poly1305_exit(ctx);
+                       return false;
                }
 
                chacha_poly1305_exit(ctx);
-               return success;
-       }
 
-       if(fwrite(config->buf, config->len, 1, f) != 1) {
-               logger(mesh, MESHLINK_ERROR, "Cannot write config file: %s", strerror(errno));
-               meshlink_errno = MESHLINK_ESTORAGE;
-               return false;
+               return store(mesh, name, encrypted, 12 + encrypted_len);
        }
 
-       if(fflush(f)) {
-               logger(mesh, MESHLINK_ERROR, "Failed to flush file: %s", strerror(errno));
-               meshlink_errno = MESHLINK_ESTORAGE;
-               return false;
-       }
-
-       if(fsync(fileno(f))) {
-               logger(mesh, MESHLINK_ERROR, "Failed to sync file: %s\n", strerror(errno));
-               meshlink_errno = MESHLINK_ESTORAGE;
-               return false;
-       }
-
-       return true;
+       return store(mesh, name, config->buf, config->len);
 }
 
 /// Free resources of a loaded configuration file.
@@ -614,66 +374,24 @@ void config_free(config_t *config) {
        config->len = 0;
 }
 
-/// Check the presence of a host configuration file.
-bool config_exists(meshlink_handle_t *mesh, const char *conf_subdir, const char *name) {
-       assert(conf_subdir);
-
-       if(!mesh->confbase) {
-               return false;
-       }
-
-       char path[PATH_MAX];
-       make_host_path(mesh, conf_subdir, name, path, sizeof(path));
-
-       return access(path, F_OK) == 0;
-}
-
-/// Read a host configuration file.
-bool config_read(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, config_t *config, void *key) {
-       assert(conf_subdir);
+bool config_ls(meshlink_handle_t *mesh, config_scan_action_t action) {
+       logger(mesh, MESHLINK_DEBUG, "ls(%p)", (void *)(intptr_t)action);
 
        if(!mesh->confbase) {
-               return false;
-       }
-
-       char path[PATH_MAX];
-       make_host_path(mesh, conf_subdir, name, path, sizeof(path));
-
-       FILE *f = fopen(path, "r");
-
-       if(!f) {
-               logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
-               return false;
-       }
-
-       if(!config_read_file(mesh, f, config, key)) {
-               logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
-               fclose(f);
-               return false;
+               return true;
        }
 
-       fclose(f);
-
-       return true;
-}
-
-bool config_scan_all(meshlink_handle_t *mesh, const char *conf_subdir, const char *conf_type, config_scan_action_t action, void *arg) {
-       assert(conf_subdir);
-       assert(conf_type);
-
-       if(!mesh->confbase) {
-               return true;
+       if(mesh->ls_cb) {
+               return mesh->ls_cb(mesh, action);
        }
 
        DIR *dir;
        struct dirent *ent;
-       char dname[PATH_MAX];
-       snprintf(dname, sizeof(dname), "%s" SLASH "%s" SLASH "%s", mesh->confbase, conf_subdir, conf_type);
 
-       dir = opendir(dname);
+       dir = opendir(mesh->confbase);
 
        if(!dir) {
-               logger(mesh, MESHLINK_ERROR, "Could not open %s: %s", dname, strerror(errno));
+               logger(mesh, MESHLINK_ERROR, "Could not open %s: %s", mesh->confbase, strerror(errno));
                meshlink_errno = MESHLINK_ESTORAGE;
                return false;
        }
@@ -683,7 +401,7 @@ bool config_scan_all(meshlink_handle_t *mesh, const char *conf_subdir, const cha
                        continue;
                }
 
-               if(!action(mesh, ent->d_name, arg)) {
+               if(!action(mesh, ent->d_name, 0)) {
                        closedir(dir);
                        return false;
                }
@@ -693,141 +411,142 @@ bool config_scan_all(meshlink_handle_t *mesh, const char *conf_subdir, const cha
        return true;
 }
 
-/// Write a host configuration file.
-bool config_write(meshlink_handle_t *mesh, const char *conf_subdir, const char *name, const config_t *config, void *key) {
-       assert(conf_subdir);
-       assert(name);
-       assert(config);
+/// Re-encrypt a configuration file.
+static bool change_key(meshlink_handle_t *mesh, const char *name, size_t len) {
+       (void)len;
+       config_t config;
 
-       if(!mesh->confbase) {
-               return true;
+       if(!config_load(mesh, name, &config)) {
+               return false;
        }
 
-       char path[PATH_MAX];
-       char tmp_path[PATH_MAX + 4];
-       make_host_path(mesh, conf_subdir, name, path, sizeof(path));
-       snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", path);
-
-       FILE *f = fopen(tmp_path, "w");
+       size_t name_len = strlen(name);
+       char new_name[name_len + 3];
+       memcpy(new_name, name, name_len);
 
-       if(!f) {
-               logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", tmp_path, strerror(errno));
-               meshlink_errno = MESHLINK_ESTORAGE;
-               return false;
+       if(name_len == 13 && name[8] == '.') {
+               // Update meshlink.conf in-place
+               new_name[name_len] = 0;
+       } else {
+               memcpy(new_name + name_len, ".r", 3);
        }
 
-       if(!config_write_file(mesh, f, config, key)) {
-               logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", tmp_path, strerror(errno));
-               fclose(f);
-               return false;
-       }
+       void *orig_key = mesh->config_key;
+       mesh->config_key = mesh->config_new_key;
+       bool result = config_store(mesh, new_name, &config);
+       mesh->config_key = orig_key;
 
-       if(fclose(f)) {
-               logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", tmp_path, strerror(errno));
-               meshlink_errno = MESHLINK_ESTORAGE;
-               return false;
-       }
+       return result;
+}
 
-       if(rename(tmp_path, path)) {
-               logger(mesh, MESHLINK_ERROR, "Failed to rename `%s' to `%s': %s", tmp_path, path, strerror(errno));
-               meshlink_errno = MESHLINK_ESTORAGE;
-               return false;
+static bool change_node_key(meshlink_handle_t *mesh, const char *name, size_t len) {
+       if(!check_id(name)) {
+               return true;
        }
 
-       return true;
+       return change_key(mesh, name, len);
 }
 
-/// Delete a host configuration file.
-bool config_delete(meshlink_handle_t *mesh, const char *conf_subdir, const char *name) {
-       assert(conf_subdir);
-       assert(name);
+static bool cleanup_old_file(meshlink_handle_t *mesh, const char *name, size_t len) {
+       (void)len;
+       size_t name_len = strlen(name);
 
-       if(!mesh->confbase) {
+       if(name_len < 3 || name[name_len - 2] != '.' || name[name_len - 1] != 'r') {
                return true;
        }
 
-       char path[PATH_MAX];
-       make_host_path(mesh, conf_subdir, name, path, sizeof(path));
+       config_t config;
 
-       if(unlink(path) && errno != ENOENT) {
-               logger(mesh, MESHLINK_ERROR, "Failed to unlink `%s': %s", path, strerror(errno));
-               meshlink_errno = MESHLINK_ESTORAGE;
+       if(!config_load(mesh, name, &config)) {
                return false;
        }
 
-       return true;
+       char new_name[name_len - 1];
+       memcpy(new_name, name, name_len - 2);
+       new_name[name_len - 2] = '\0';
+
+       return config_store(mesh, new_name, &config) && store(mesh, name, NULL, 0);
 }
 
-/// Read the main configuration file.
-bool main_config_read(meshlink_handle_t *mesh, const char *conf_subdir, config_t *config, void *key) {
-       assert(conf_subdir);
-       assert(config);
+bool config_cleanup_old_files(meshlink_handle_t *mesh) {
+       return config_ls(mesh, cleanup_old_file);
+}
 
-       if(!mesh->confbase) {
+bool config_change_key(meshlink_handle_t *mesh, void *new_key) {
+       extern bool (*devtool_keyrotate_probe)(int stage);
+       mesh->config_new_key = new_key;
+
+       if(!config_ls(mesh, change_node_key)) {
                return false;
        }
 
-       char path[PATH_MAX];
-       make_main_path(mesh, conf_subdir, path, sizeof(path));
-
-       FILE *f = fopen(path, "r");
-
-       if(!f) {
-               logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
+       if(!devtool_keyrotate_probe(1)) {
                return false;
        }
 
-       if(!config_read_file(mesh, f, config, key)) {
-               logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
-               fclose(f);
+       if(!change_key(mesh, "meshlink.conf", 0)) {
                return false;
        }
 
-       fclose(f);
+       free(mesh->config_key);
+       mesh->config_key = new_key;
+
+       if(!devtool_keyrotate_probe(2)) {
+               return true;
+       }
+
+       config_cleanup_old_files(mesh);
+
+       devtool_keyrotate_probe(3);
 
        return true;
 }
 
-/// Write the main configuration file.
-bool main_config_write(meshlink_handle_t *mesh, const char *conf_subdir, const config_t *config, void *key) {
-       assert(conf_subdir);
-       assert(config);
-
+/// Initialize the configuration directory
+bool config_init(meshlink_handle_t *mesh, const struct meshlink_open_params *params) {
        if(!mesh->confbase) {
                return true;
        }
 
-       char path[PATH_MAX];
-       char tmp_path[PATH_MAX + 4];
-       make_main_path(mesh, conf_subdir, path, sizeof(path));
-       snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", path);
+       if(!mesh->load_cb) {
+               if(mkdir(mesh->confbase, 0700) && errno != EEXIST) {
+                       logger(NULL, MESHLINK_ERROR, "Cannot create configuration directory %s: %s", mesh->confbase, strerror(errno));
+                       meshlink_close(mesh);
+                       meshlink_errno = MESHLINK_ESTORAGE;
+                       return NULL;
+               }
 
-       FILE *f = fopen(tmp_path, "w");
+               mesh->lockfile = fopen(params->lock_filename, "w+");
 
-       if(!f) {
-               logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", tmp_path, strerror(errno));
-               meshlink_errno = MESHLINK_ESTORAGE;
-               return false;
-       }
+               if(!mesh->lockfile) {
+                       logger(NULL, MESHLINK_ERROR, "Cannot not open %s: %s\n", params->lock_filename, strerror(errno));
+                       meshlink_errno = MESHLINK_ESTORAGE;
+                       return false;
+               }
 
-       if(!config_write_file(mesh, f, config, key)) {
-               logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", tmp_path, strerror(errno));
-               fclose(f);
-               return false;
-       }
+#ifdef FD_CLOEXEC
+               fcntl(fileno(mesh->lockfile), F_SETFD, FD_CLOEXEC);
+#endif
 
-       if(rename(tmp_path, path)) {
-               logger(mesh, MESHLINK_ERROR, "Failed to rename `%s' to `%s': %s", tmp_path, path, strerror(errno));
-               meshlink_errno = MESHLINK_ESTORAGE;
-               fclose(f);
-               return false;
-       }
+#ifdef HAVE_FLOCK
 
-       if(fclose(f)) {
-               logger(mesh, MESHLINK_ERROR, "Failed to close `%s': %s", tmp_path, strerror(errno));
-               meshlink_errno = MESHLINK_ESTORAGE;
-               return false;
+               if(flock(fileno(mesh->lockfile), LOCK_EX | LOCK_NB) != 0) {
+                       logger(NULL, MESHLINK_ERROR, "Cannot lock %s: %s\n", params->lock_filename, strerror(errno));
+                       fclose(mesh->lockfile);
+                       mesh->lockfile = NULL;
+                       meshlink_errno = MESHLINK_EBUSY;
+                       return false;
+               }
+
+#endif
        }
 
        return true;
 }
+
+void config_exit(meshlink_handle_t *mesh) {
+       if(mesh->lockfile) {
+               fclose(mesh->lockfile);
+               mesh->lockfile = NULL;
+       }
+}
\ No newline at end of file
index 4560f2a2012f387f3443b3af2c511a973cb328c6..23ad90080daeb56ef1159f2dba8a70a21a78db3a 100644 (file)
 */
 
 struct meshlink_handle;
+struct meshlink_open_params;
 
 typedef struct config_t {
        const uint8_t *buf;
        size_t len;
 } config_t;
 
-typedef bool (*config_scan_action_t)(struct meshlink_handle *mesh, const char *name, void *arg);
+typedef bool (*config_scan_action_t)(struct meshlink_handle *mesh, const char *name, size_t len);
 
-bool config_read_file(struct meshlink_handle *mesh, FILE *f, struct config_t *, const void *key) __attribute__((__warn_unused_result__));
-bool config_write_file(struct meshlink_handle *mesh, FILE *f, const struct config_t *, const void *key) __attribute__((__warn_unused_result__));
 void config_free(struct config_t *config);
 
-bool meshlink_confbase_exists(struct meshlink_handle *mesh) __attribute__((__warn_unused_result__));
-
-bool config_init(struct meshlink_handle *mesh, const char *conf_subdir) __attribute__((__warn_unused_result__));
-bool config_destroy(const char *confbase, const char *conf_subdir) __attribute__((__warn_unused_result__));
-bool config_copy(struct meshlink_handle *mesh, const char *src_dir_name, const void *src_key, const char *dst_dir_name, const void *dst_key) __attribute__((__warn_unused_result__));
-bool config_rename(struct meshlink_handle *mesh, const char *old_conf_subdir, const char *new_conf_subdir) __attribute__((__warn_unused_result__));
-bool config_sync(struct meshlink_handle *mesh, const char *conf_subdir) __attribute__((__warn_unused_result__));
-bool sync_path(const char *path) __attribute__((__warn_unused_result__));
-
-bool main_config_exists(struct meshlink_handle *mesh, const char *conf_subdir) __attribute__((__warn_unused_result__));
-bool main_config_lock(struct meshlink_handle *mesh, const char *lock_filename) __attribute__((__warn_unused_result__));
-void main_config_unlock(struct meshlink_handle *mesh);
-bool main_config_read(struct meshlink_handle *mesh, const char *conf_subdir, struct config_t *, void *key) __attribute__((__warn_unused_result__));
-bool main_config_write(struct meshlink_handle *mesh, const char *conf_subdir, const struct config_t *, void *key) __attribute__((__warn_unused_result__));
-
-bool config_exists(struct meshlink_handle *mesh, const char *conf_subdir, const char *name) __attribute__((__warn_unused_result__));
-bool config_read(struct meshlink_handle *mesh, const char *conf_subdir, const char *name, struct config_t *, void *key) __attribute__((__warn_unused_result__));
-bool config_write(struct meshlink_handle *mesh, const char *conf_subdir, const char *name, const struct config_t *, void *key) __attribute__((__warn_unused_result__));
-bool config_delete(struct meshlink_handle *mesh, const char *conf_subdir, const char *name) __attribute__((__warn_unused_result__));
-bool config_scan_all(struct meshlink_handle *mesh, const char *conf_subdir, const char *conf_type, config_scan_action_t action, void *arg) __attribute__((__warn_unused_result__));
+bool config_init(struct meshlink_handle *mesh, const struct meshlink_open_params *params) __attribute__((__warn_unused_result__));
+void config_exit(struct meshlink_handle *mesh);
+bool config_destroy(const struct meshlink_open_params *params) __attribute__((__warn_unused_result__));
+
+bool config_load(struct meshlink_handle *mesh, const char *name, struct config_t *) __attribute__((__warn_unused_result__));
+bool config_store(struct meshlink_handle *mesh, const char *name, const struct config_t *) __attribute__((__warn_unused_result__));
+bool config_ls(struct meshlink_handle *mesh, config_scan_action_t action) __attribute__((__warn_unused_result__));
+
+bool config_exists(struct meshlink_handle *mesh, const char *name) __attribute__((__warn_unused_result__));
+
+bool config_change_key(struct meshlink_handle *mesh, void *new_key) __attribute__((__warn_unused_result__));
+bool config_cleanup_old_files(struct meshlink_handle *mesh);
 
 #endif
index 1a647210420484c82fe4c80088d129c24f41ab3c..cebd79cc34ec1fa08a57ead1ef2311707c770893 100644 (file)
@@ -33,9 +33,9 @@ static void nop_probe(void) {
        return;
 }
 
-static void keyrotate_nop_probe(int stage) {
+static bool keyrotate_nop_probe(int stage) {
        (void)stage;
-       return;
+       return true;
 }
 
 static void inviter_commits_first_nop_probe(bool stage) {
@@ -49,7 +49,7 @@ static void sptps_renewal_nop_probe(meshlink_node_t *node) {
 }
 
 void (*devtool_trybind_probe)(void) = nop_probe;
-void (*devtool_keyrotate_probe)(int stage) = keyrotate_nop_probe;
+bool (*devtool_keyrotate_probe)(int stage) = keyrotate_nop_probe;
 void (*devtool_set_inviter_commits_first)(bool inviter_commited_first) = inviter_commits_first_nop_probe;
 void (*devtool_sptps_renewal_probe)(meshlink_node_t *node) = sptps_renewal_nop_probe;
 
index 32b58ff494e0cdd6bfc706426003b15a4e5117c3..4a5a17e976c9c73fd8c25246185a0ceeb729ec38 100644 (file)
@@ -57,8 +57,10 @@ extern void (*devtool_trybind_probe)(void);
  *  On assigning a debug function variable invokes callback for each stage from the key rotate API.
  *
  *  @param stage Debug stage number.
+ *
+ *  @return True if key rotation should continue as normal, false to introduce an error.
  */
-extern void (*devtool_keyrotate_probe)(int stage);
+extern bool (*devtool_keyrotate_probe)(int stage);
 
 /// Debug function pointer variable for SPTPS key renewal
 /** This function pointer variable is a userspace tracepoint or debugger callback for
index 953a0e930d541cc14bdc0db13e863bf11c73fdc6..ee3d32ba4c21e3752e3361c4a02e6d75246116e5 100644 (file)
@@ -160,6 +160,73 @@ void meshlink_open_params_free(meshlink_open_params_t *params);
  */
 bool meshlink_open_params_set_netns(meshlink_open_params_t *params, int netns) __attribute__((__warn_unused_result__));
 
+/// A callback that loads a configuration object.
+/** @param mesh         A handle which represents an instance of MeshLink.
+ *  @param key          The key under which the configuration object was stored before.
+ *  @param[out] data    A pointer to a pointer to the buffer where the object has to be copied into.
+ *                      No more than len bytes should be written to this buffer.
+ *  @param[in,out] len  A pointer to the size of object.
+ *                      MeshLink sets this to the size of the buffer.
+ *                      The application must write the actual size of the object to it,
+ *                      regardless of whether it is larger or smaller than the buffer.
+ *
+ *  @return             This function must return true if the object was read and copied into the buffer succesfully,
+ *                      false otherwise.
+ *                      If the buffer provided by MeshLink was too small for the whole object, but no other errors
+ *                      occured, true must be returned.
+ */
+typedef bool (*meshlink_load_cb_t)(struct meshlink_handle *mesh, const char *filename, void *data, size_t *len);
+
+/// A callback that stores a configuration object.
+/** @param mesh      A handle which represents an instance of MeshLink.
+ *  @param key       The key under which the object should be stored.
+ *  @param data      A pointer to the buffer holding the data that must be stored.
+ *  @param len       The size of the buffer that must be stored.
+ *
+ *  @return          This function must return true if the file was written succesfully, false otherwise.
+ */
+typedef bool (*meshlink_store_cb_t)(struct meshlink_handle *mesh, const char *filename, const void *data, size_t len);
+
+/// A callback that reports the presence of a configuration object.
+/** @param mesh      A handle which represents an instance of MeshLink.
+ *  @param key       The key the object is stored under.
+ *  @param len       The size of the object if easy to obtain, zero otherwise.
+ *
+ *  @return          The return value. If it is false, the ls callback should immediately stop and return false as well.
+ */
+typedef bool (*meshlink_ls_entry_cb_t)(struct meshlink_handle *mesh, const char *key, size_t len);
+
+/// A callback that lists all configuration files.
+/** @param mesh      A handle which represents an instance of MeshLink.
+ *  @param entry_cb  A callback that must be called once for every configuration file found.
+ *
+ *  @return          This function must return true if all configuration files were listed
+ *                   and all entry callbacks return true as well, false otherwise.
+ */
+typedef bool (*meshlink_ls_cb_t)(struct meshlink_handle *mesh, meshlink_ls_entry_cb_t entry_cb);
+
+/// Set the callbacks MeshLink should use for local storage.
+/** This function allows the application to provide callbacks which handle loading and storing configuration files.
+ *  If storage callbacks are used, the application is in control of how and where configuration files are stored.
+ *  The application should then also ensure that only one MeshLink instance can access the configuration files at a time.
+ *
+ *  The callbacks get a relative filename which may contain directory separators, but there is no requirement that
+ *  the application also stores the files in directories. The only requirement is that a file stored with a given filename
+ *  can be loaded back using the same filename.
+ *
+ *  When storing files, it must ensure stores are done atomically.
+ *
+ *  If a storage key is set, MeshLink will take care of encrypting and decrypting the buffers passed to the storage callbacks.
+ *
+ *  @param params   A pointer to a meshlink_open_params_t which must have been created earlier with meshlink_open_params_init().
+ *  @param load_cb  A pointer to the function that will be called when MeshLink wants to load a configuration file.
+ *  @param store_cb A pointer to the function that will be called when MeshLink wants to store a configuration file.
+ *  @param ls_cb    A pointer to the function that will be called when MeshLink wants to know which configuration files are available.
+ *
+ *  @return         This function will return true if the callbacks have been successfully updated, false otherwise.
+ */
+bool meshlink_open_params_set_storage_callbacks(meshlink_open_params_t *params, meshlink_load_cb_t load_cb, meshlink_store_cb_t store_cb, meshlink_ls_cb_t ls_cb) __attribute__((__warn_unused_result__));
+
 /// Set the encryption key MeshLink should use for local storage.
 /** This function changes the open parameters to use the given key for encrypting MeshLink's own configuration files.
  *
index aadbaf337cca4798500544ed19c67aef67fa1bcd..4d7491a8f288fa2a84fd2593361892abc73cee58 100644 (file)
@@ -154,6 +154,11 @@ static bool write_main_config_files(meshlink_handle_t *mesh) {
                return true;
        }
 
+       /* Write our own host config file */
+       if(!node_write_config(mesh, mesh->self, true)) {
+               return false;
+       }
+
        uint8_t buf[BUFSIZE];
 
        /* Write the main config file */
@@ -171,16 +176,7 @@ static bool write_main_config_files(meshlink_handle_t *mesh) {
 
        config_t config = {buf, packmsg_output_size(&out, buf)};
 
-       if(!main_config_write(mesh, "current", &config, mesh->config_key)) {
-               return false;
-       }
-
-       /* Write our own host config file */
-       if(!node_write_config(mesh, mesh->self, true)) {
-               return false;
-       }
-
-       return true;
+       return config_store(mesh, "meshlink.conf", &config);
 }
 
 typedef struct {
@@ -230,11 +226,6 @@ static bool finalize_join(join_state_t *state, const void *buf, uint16_t len) {
        mesh->self->name = xstrdup(name);
        mesh->self->devclass = devclass == DEV_CLASS_UNKNOWN ? mesh->devclass : devclass;
 
-       // Initialize configuration directory
-       if(!config_init(mesh, "current")) {
-               return false;
-       }
-
        if(!write_main_config_files(mesh)) {
                return false;
        }
@@ -303,11 +294,6 @@ static bool finalize_join(join_state_t *state, const void *buf, uint16_t len) {
                node_add(mesh, n);
        }
 
-       /* Ensure the configuration directory metadata is on disk */
-       if(!config_sync(mesh, "current") || (mesh->confbase && !sync_path(mesh->confbase))) {
-               return false;
-       }
-
        if(!mesh->inviter_commits_first) {
                devtool_set_inviter_commits_first(false);
        }
@@ -500,22 +486,20 @@ static struct timespec idle(event_loop_t *loop, void *data) {
        };
 }
 
-static bool meshlink_setup(meshlink_handle_t *mesh) {
-       if(!config_destroy(mesh->confbase, "new")) {
-               logger(mesh, MESHLINK_ERROR, "Could not delete configuration in %s/new: %s\n", mesh->confbase, strerror(errno));
-               meshlink_errno = MESHLINK_ESTORAGE;
-               return false;
-       }
+static bool invalidate_config_file(meshlink_handle_t *mesh, const char *name, size_t len) {
+       (void)len;
 
-       if(!config_destroy(mesh->confbase, "old")) {
-               logger(mesh, MESHLINK_ERROR, "Could not delete configuration in %s/old: %s\n", mesh->confbase, strerror(errno));
-               meshlink_errno = MESHLINK_ESTORAGE;
-               return false;
+       if(!check_id(name)) {
+               return true;
        }
 
-       if(!config_init(mesh, "current")) {
-               logger(mesh, MESHLINK_ERROR, "Could not set up configuration in %s/current: %s\n", mesh->confbase, strerror(errno));
-               meshlink_errno = MESHLINK_ESTORAGE;
+       config_t empty_config = {NULL, 0};
+       return config_store(mesh, name, &empty_config);
+}
+
+static bool meshlink_setup(meshlink_handle_t *mesh) {
+       // Remove stray config files
+       if(!config_ls(mesh, invalidate_config_file)) {
                return false;
        }
 
@@ -540,18 +524,13 @@ static bool meshlink_setup(meshlink_handle_t *mesh) {
                return false;
        }
 
-       /* Ensure the configuration directory metadata is on disk */
-       if(!config_sync(mesh, "current")) {
-               return false;
-       }
-
        return true;
 }
 
 static bool meshlink_read_config(meshlink_handle_t *mesh) {
        config_t config;
 
-       if(!main_config_read(mesh, "current", &config, mesh->config_key)) {
+       if(!config_load(mesh, "meshlink.conf", &config)) {
                logger(NULL, MESHLINK_ERROR, "Could not read main configuration file!");
                return false;
        }
@@ -586,6 +565,8 @@ static bool meshlink_read_config(meshlink_handle_t *mesh) {
        mesh->private_key = ecdsa_set_private_key(private_key);
        config_free(&config);
 
+       config_cleanup_old_files(mesh);
+
        /* Create a node for ourself and read our host configuration file */
 
        mesh->self = new_node();
@@ -676,6 +657,31 @@ bool meshlink_open_params_set_netns(meshlink_open_params_t *params, int netns) {
        return true;
 }
 
+bool meshlink_open_params_set_storage_callbacks(meshlink_open_params_t *params, meshlink_load_cb_t load_cb, meshlink_store_cb_t store_cb, meshlink_ls_cb_t ls_cb) {
+       logger(NULL, MESHLINK_DEBUG, "meshlink_set_storage_callbacks(%p, %p)", (void *)(intptr_t)load_cb, (void *)(intptr_t)store_cb);
+
+       if(!params) {
+               meshlink_errno = MESHLINK_EINVAL;
+               return false;
+       }
+
+       if(load_cb && (!store_cb || !ls_cb)) {
+               meshlink_errno = MESHLINK_EINVAL;
+               return false;
+       }
+
+       if(!load_cb && (store_cb || ls_cb)) {
+               meshlink_errno = MESHLINK_EINVAL;
+               return false;
+       }
+
+       params->load_cb = load_cb;
+       params->store_cb = store_cb;
+       params->ls_cb = ls_cb;
+
+       return true;
+}
+
 bool meshlink_open_params_set_storage_key(meshlink_open_params_t *params, const void *key, size_t keylen) {
        logger(NULL, MESHLINK_DEBUG, "meshlink_open_params_set_storage_key(%p, %zu)", key, keylen);
 
@@ -737,8 +743,7 @@ bool meshlink_encrypted_key_rotate(meshlink_handle_t *mesh, const void *new_key,
        }
 
        // Create hash for the new key
-       void *new_config_key;
-       new_config_key = xmalloc(CHACHA_POLY1305_KEYLEN);
+       void *new_config_key = xmalloc(CHACHA_POLY1305_KEYLEN);
 
        if(!prf(new_key, new_keylen, "MeshLink configuration key", 26, new_config_key, CHACHA_POLY1305_KEYLEN)) {
                logger(mesh, MESHLINK_ERROR, "Error creating new configuration key!\n");
@@ -747,51 +752,12 @@ bool meshlink_encrypted_key_rotate(meshlink_handle_t *mesh, const void *new_key,
                return false;
        }
 
-       // Copy contents of the "current" confbase sub-directory to "new" confbase sub-directory with the new key
-
-       if(!config_copy(mesh, "current", mesh->config_key, "new", new_config_key)) {
-               logger(mesh, MESHLINK_ERROR, "Could not set up configuration in %s/old: %s\n", mesh->confbase, strerror(errno));
-               meshlink_errno = MESHLINK_ESTORAGE;
-               pthread_mutex_unlock(&mesh->mutex);
-               return false;
-       }
-
-       devtool_keyrotate_probe(1);
-
-       // Rename confbase/current/ to confbase/old
-
-       if(!config_rename(mesh, "current", "old")) {
-               logger(mesh, MESHLINK_ERROR, "Cannot rename %s/current to %s/old\n", mesh->confbase, mesh->confbase);
-               meshlink_errno = MESHLINK_ESTORAGE;
-               pthread_mutex_unlock(&mesh->mutex);
-               return false;
-       }
-
-       devtool_keyrotate_probe(2);
-
-       // Rename confbase/new/ to confbase/current
-
-       if(!config_rename(mesh, "new", "current")) {
-               logger(mesh, MESHLINK_ERROR, "Cannot rename %s/new to %s/current\n", mesh->confbase, mesh->confbase);
-               meshlink_errno = MESHLINK_ESTORAGE;
-               pthread_mutex_unlock(&mesh->mutex);
-               return false;
-       }
-
-       devtool_keyrotate_probe(3);
-
-       // Cleanup the "old" confbase sub-directory
-
-       if(!config_destroy(mesh->confbase, "old")) {
+       if(!config_change_key(mesh, new_config_key)) {
+               logger(mesh, MESHLINK_ERROR, "Error rotating the configuration key!\n");
                pthread_mutex_unlock(&mesh->mutex);
                return false;
        }
 
-       // Change the mesh handle key with new key
-
-       free(mesh->config_key);
-       mesh->config_key = new_config_key;
-
        pthread_mutex_unlock(&mesh->mutex);
 
        return true;
@@ -965,6 +931,9 @@ meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) {
        mesh->log_cb = global_log_cb;
        mesh->log_level = global_log_level;
        mesh->packet = xmalloc(sizeof(vpn_packet_t));
+       mesh->load_cb = params->load_cb;
+       mesh->store_cb = params->store_cb;
+       mesh->ls_cb = params->ls_cb;
 
        randomize(&mesh->prng_state, sizeof(mesh->prng_state));
 
@@ -1006,7 +975,7 @@ meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) {
        meshlink_queue_init(&mesh->outpacketqueue);
 
        // Atomically lock the configuration directory.
-       if(!main_config_lock(mesh, params->lock_filename)) {
+       if(!config_init(mesh, params)) {
                meshlink_close(mesh);
                return NULL;
        }
@@ -1015,24 +984,23 @@ meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) {
 
        bool new_configuration = false;
 
-       if(!meshlink_confbase_exists(mesh)) {
+       if(!meshlink_read_config(mesh)) {
                if(!mesh->name) {
-                       logger(NULL, MESHLINK_ERROR, "No configuration files found!\n");
+                       logger(NULL, MESHLINK_ERROR, "No valid configuration files found!\n");
                        meshlink_close(mesh);
                        meshlink_errno = MESHLINK_ESTORAGE;
                        return NULL;
                }
 
-               if(!meshlink_setup(mesh)) {
-                       logger(NULL, MESHLINK_ERROR, "Cannot create initial configuration\n");
+               if(config_exists(mesh, "meshlink.conf")) {
+                       logger(NULL, MESHLINK_ERROR, "Cannot read existing configuration!\n");
                        meshlink_close(mesh);
+                       meshlink_errno = MESHLINK_ESTORAGE;
                        return NULL;
                }
 
-               new_configuration = true;
-       } else {
-               if(!meshlink_read_config(mesh)) {
-                       logger(NULL, MESHLINK_ERROR, "Cannot read main configuration\n");
+               if(!meshlink_setup(mesh)) {
+                       logger(NULL, MESHLINK_ERROR, "Cannot create initial configuration\n");
                        meshlink_close(mesh);
                        return NULL;
                }
@@ -1279,7 +1247,7 @@ void meshlink_close(meshlink_handle_t *mesh) {
        free(mesh->packet);
        ecdsa_free(mesh->private_key);
 
-       main_config_unlock(mesh);
+       config_exit(mesh);
 
        pthread_mutex_unlock(&mesh->mutex);
        pthread_mutex_destroy(&mesh->mutex);
@@ -1297,63 +1265,7 @@ bool meshlink_destroy_ex(const meshlink_open_params_t *params) {
                return false;
        }
 
-       if(!params->confbase) {
-               /* Ephemeral instances */
-               return true;
-       }
-
-       /* Exit early if the confbase directory itself doesn't exist */
-       if(access(params->confbase, F_OK) && errno == ENOENT) {
-               return true;
-       }
-
-       /* Take the lock the same way meshlink_open() would. */
-       FILE *lockfile = fopen(params->lock_filename, "w+");
-
-       if(!lockfile) {
-               logger(NULL, MESHLINK_ERROR, "Could not open lock file %s: %s", params->lock_filename, strerror(errno));
-               meshlink_errno = MESHLINK_ESTORAGE;
-               return false;
-       }
-
-#ifdef FD_CLOEXEC
-       fcntl(fileno(lockfile), F_SETFD, FD_CLOEXEC);
-#endif
-
-#ifdef HAVE_MINGW
-       // TODO: use _locking()?
-#else
-
-       if(flock(fileno(lockfile), LOCK_EX | LOCK_NB) != 0) {
-               logger(NULL, MESHLINK_ERROR, "Configuration directory %s still in use\n", params->lock_filename);
-               fclose(lockfile);
-               meshlink_errno = MESHLINK_EBUSY;
-               return false;
-       }
-
-#endif
-
-       if(!config_destroy(params->confbase, "current") || !config_destroy(params->confbase, "new") || !config_destroy(params->confbase, "old")) {
-               logger(NULL, MESHLINK_ERROR, "Cannot remove sub-directories in %s: %s\n", params->confbase, strerror(errno));
-               return false;
-       }
-
-       if(unlink(params->lock_filename)) {
-               logger(NULL, MESHLINK_ERROR, "Cannot remove lock file %s: %s\n", params->lock_filename, strerror(errno));
-               fclose(lockfile);
-               meshlink_errno = MESHLINK_ESTORAGE;
-               return false;
-       }
-
-       fclose(lockfile);
-
-       if(!sync_path(params->confbase)) {
-               logger(NULL, MESHLINK_ERROR, "Cannot sync directory %s: %s\n", params->confbase, strerror(errno));
-               meshlink_errno = MESHLINK_ESTORAGE;
-               return false;
-       }
-
-       return true;
+       return config_destroy(params);
 }
 
 bool meshlink_destroy(const char *confbase) {
@@ -1688,8 +1600,7 @@ bool meshlink_set_canonical_address(meshlink_handle_t *mesh, meshlink_node_t *no
        }
 
        pthread_mutex_unlock(&mesh->mutex);
-
-       return config_sync(mesh, "current");
+       return true;
 }
 
 bool meshlink_clear_canonical_address(meshlink_handle_t *mesh, meshlink_node_t *node) {
@@ -1714,8 +1625,7 @@ bool meshlink_clear_canonical_address(meshlink_handle_t *mesh, meshlink_node_t *
        }
 
        pthread_mutex_unlock(&mesh->mutex);
-
-       return config_sync(mesh, "current");
+       return true;
 }
 
 bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) {
@@ -2168,10 +2078,6 @@ bool meshlink_import(meshlink_handle_t *mesh, const char *data) {
                return false;
        }
 
-       if(!config_sync(mesh, "current")) {
-               return false;
-       }
-
        return true;
 }
 
index 44c99d018de58d6b8640a011edf4631688141abd..226e682ffc4b6bf8ff5a93f0d8e039176b3cc120 100644 (file)
@@ -40,6 +40,7 @@ meshlink_open_params_free
 meshlink_open_params_init
 meshlink_open_params_set_lock_filename
 meshlink_open_params_set_netns
+meshlink_open_params_set_storage_callbacks
 meshlink_open_params_set_storage_key
 meshlink_open_params_set_storage_policy
 meshlink_reset_timers
index f77d700692f410ec34b6284583e5e253ee9de6de..032873a2fe91d09f9465a442a7f9f39ac326222d 100644 (file)
@@ -59,6 +59,9 @@ struct meshlink_open_params {
        const void *key;
        size_t keylen;
        meshlink_storage_policy_t storage_policy;
+       meshlink_load_cb_t load_cb;
+       meshlink_store_cb_t store_cb;
+       meshlink_ls_cb_t ls_cb;
 };
 
 /// Device class traits
@@ -135,6 +138,11 @@ struct meshlink_handle {
        void *config_key;
        char *external_address_url;
        meshlink_storage_policy_t storage_policy;
+       meshlink_load_cb_t load_cb;
+       meshlink_store_cb_t store_cb;
+       meshlink_ls_cb_t ls_cb;
+
+       void *config_new_key;
 
        // Thread management
        pthread_t thread;
index 7f8804b17be524cdfcc5d8d3abc9ad7cee20677b..8364e4f238090f94b8a10266919ee35698480e13 100644 (file)
@@ -33,7 +33,7 @@
 
 /// Helper function to start parsing a host config file
 static bool node_get_config(meshlink_handle_t *mesh, node_t *n, config_t *config, packmsg_input_t *in) {
-       if(!config_read(mesh, "current", n->name, config, mesh->config_key)) {
+       if(!config_load(mesh, n->name, config)) {
                return false;
        }
 
@@ -260,7 +260,7 @@ bool node_write_config(meshlink_handle_t *mesh, node_t *n, bool new_key) {
 
        config_t config = {buf, packmsg_output_size(&out, buf)};
 
-       if(!config_write(mesh, "current", n->name, &config, mesh->config_key)) {
+       if(!config_store(mesh, n->name, &config)) {
                call_error_cb(mesh, MESHLINK_ESTORAGE);
                return false;
        }
@@ -269,19 +269,11 @@ bool node_write_config(meshlink_handle_t *mesh, node_t *n, bool new_key) {
        return true;
 }
 
-static bool load_node(meshlink_handle_t *mesh, const char *name, void *priv) {
-       (void)priv;
+static bool load_node(meshlink_handle_t *mesh, const char *name, size_t len) {
+       (void)len;
 
        if(!check_id(name)) {
-               // Check if this is a temporary file, if so remove it
-               const char *suffix = strstr(name, ".tmp");
-
-               if(suffix && !suffix[4]) {
-                       char filename[PATH_MAX];
-                       snprintf(filename, sizeof(filename), "%s" SLASH "current" SLASH "hosts", mesh->confbase);
-                       unlink(filename);
-               }
-
+               // TODO: detect partial key rotation
                return true;
        }
 
@@ -324,7 +316,7 @@ static bool setup_myself(meshlink_handle_t *mesh) {
 
        node_add(mesh, mesh->self);
 
-       if(!config_scan_all(mesh, "current", "hosts", load_node, NULL)) {
+       if(!config_ls(mesh, load_node)) {
                logger(mesh, MESHLINK_WARNING, "Could not scan all host config files");
        }
 
index 1fd39ecdd9b0e17b2dd3f841570583026c3839e6..0682c03d8c19e43a2769203dfb7417658704769a 100644 (file)
@@ -87,24 +87,14 @@ int main(void) {
 
        // Check that messing with the config directory will create a new instance.
 
-       assert(unlink("basic_conf/current/meshlink.conf") == 0);
+       assert(unlink("basic_conf/meshlink.conf") == 0);
        mesh = meshlink_open("basic_conf", "bar", "basic", DEV_CLASS_BACKBONE);
        assert(mesh);
        assert(!meshlink_get_node(mesh, "foo"));
        self = meshlink_get_self(mesh);
        assert(self);
        assert(!strcmp(self->name, "bar"));
-       assert(access("basic_conf/new", X_OK) == -1 && errno == ENOENT);
-       meshlink_close(mesh);
-
-       assert(rename("basic_conf/current", "basic_conf/new") == 0);
-       mesh = meshlink_open("basic_conf", "baz", "basic", DEV_CLASS_BACKBONE);
-       assert(mesh);
-       assert(!meshlink_get_node(mesh, "bar"));
-       self = meshlink_get_self(mesh);
-       assert(self);
-       assert(!strcmp(self->name, "baz"));
-       assert(access("basic_conf/new", X_OK) == -1 && errno == ENOENT);
+       assert(access("basic_conf/foo", X_OK) == -1 && errno == ENOENT);
        meshlink_close(mesh);
 
        // Destroy the mesh.
@@ -114,14 +104,18 @@ int main(void) {
        // Check that the configuration directory is completely empty.
 
        DIR *dir = opendir("basic_conf");
-       assert(dir);
-       struct dirent *ent;
 
-       while((ent = readdir(dir))) {
-               assert(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."));
-       }
+       if(dir) {
+               struct dirent *ent;
 
-       closedir(dir);
+               while((ent = readdir(dir))) {
+                       assert(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."));
+               }
+
+               closedir(dir);
+       } else {
+               assert(errno == ENOENT);
+       }
 
        // Check that we can destroy it again.
 
index 4f354e4396b660bb24e6e233a00402b1c66d7af5..28ac327374ca3325b460e19db5c50175808e615e 100644 (file)
 #include <dirent.h>
 
 #include "meshlink-tiny.h"
+#include "devtools.h"
 #include "utils.h"
 
+static int keyrotate_fail_stage;
+
+static bool keyrotate_probe(int stage) {
+       fprintf(stderr, "%d\n", stage);
+       return stage != keyrotate_fail_stage;
+}
+
 int main(void) {
        meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
+       devtool_keyrotate_probe = keyrotate_probe;
 
        // Open a new meshlink instance.
 
@@ -34,6 +43,44 @@ int main(void) {
        mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "right", 5);
        assert(mesh);
 
+       // Rotate the key
+
+       assert(meshlink_encrypted_key_rotate(mesh, "newkey", 6));
+
+       // Check that we cannot open it with the old key
+
+       meshlink_close(mesh);
+       mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "right", 5);
+       assert(!mesh);
+       mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6);
+       assert(mesh);
+
+       // Check how key rotation failures are handled
+
+       keyrotate_fail_stage = 1; // Fail before committing to the new key
+       assert(!meshlink_encrypted_key_rotate(mesh, "newkey2", 7));
+       meshlink_close(mesh);
+       mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey2", 7);
+       assert(!mesh);
+       mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6);
+       assert(mesh);
+
+       keyrotate_fail_stage = 2; // Fail after committing to the new key, before cleaning up old files
+       assert(meshlink_encrypted_key_rotate(mesh, "newkey3", 7));
+       meshlink_close(mesh);
+       mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6);
+       assert(!mesh);
+       mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey3", 7);
+       assert(mesh);
+
+       keyrotate_fail_stage = 3; // Fail after committing to the new key and cleaning up old files
+       assert(meshlink_encrypted_key_rotate(mesh, "newkey4", 7));
+       meshlink_close(mesh);
+       mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey3", 7);
+       assert(!mesh);
+       mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey4", 7);
+       assert(mesh);
+
        // That's it.
 
        meshlink_close(mesh);
@@ -43,13 +90,18 @@ int main(void) {
        assert(meshlink_destroy("encrypted_conf"));
 
        DIR *dir = opendir("encrypted_conf");
-       assert(dir);
-       struct dirent *ent;
 
-       while((ent = readdir(dir))) {
-               fprintf(stderr, "%s\n", ent->d_name);
-               assert(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."));
+       if(dir) {
+               struct dirent *ent;
+
+               while((ent = readdir(dir))) {
+                       fprintf(stderr, "%s\n", ent->d_name);
+                       assert(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."));
+               }
+
+               closedir(dir);
+       } else {
+               assert(errno == ENOENT);
        }
 
-       closedir(dir);
 }
index c0253049de25b02076c0ad8595bed4b02235e29d..b6c34fcbd974f36af8a79da20d63c0b8742c03ff 100644 (file)
@@ -34,7 +34,7 @@ static void status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reach
 
 static void invitee_commits_first_cb(bool inviter_first) {
        // Check that eight has committed foo's host config file, but foo hasn't committed eight's
-       assert(access("invite_join_conf.8/current/hosts/foo", F_OK) == 0);
+       assert(access("invite_join_conf.8/foo", F_OK) == 0);
        assert(access("invite_join_conf.1/current/hosts/eight", F_OK) == -1 && errno == ENOENT);
        set_sync_flag(&commits_first_flag, !inviter_first);
 }
@@ -42,7 +42,7 @@ static void invitee_commits_first_cb(bool inviter_first) {
 static void inviter_commits_first_cb(bool inviter_first) {
        // Check that foo has committed nine's host config file, but nine hasn't committed foo's
        assert(access("invite_join_conf.1/current/hosts/nine", F_OK) == 0);
-       assert(access("invite_join_conf.9/current/hosts/foo", F_OK) == -1 && errno == ENOENT);
+       assert(access("invite_join_conf.9/foo", F_OK) == -1 && errno == ENOENT);
        set_sync_flag(&commits_first_flag, inviter_first);
 }
 
@@ -55,7 +55,7 @@ int main(void) {
 
        meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
 
-       assert(meshlink_destroy("invite_join_conf.1"));
+       assert(full_meshlink_destroy("invite_join_conf.1"));
        assert(meshlink_destroy("invite_join_conf.2"));
        assert(meshlink_destroy("invite_join_conf.3"));
        assert(meshlink_destroy("invite_join_conf.4"));