+/// Wipe an existing configuration directory
+bool config_destroy(const char *confbase, const char *conf_subdir) {
+ assert(conf_subdir);
+
+ if(!confbase) {
+ return false;
+ }
+
+ struct stat st;
+
+ char path[PATH_MAX];
+
+ // Check the presence of configuration base sub directory.
+ snprintf(path, sizeof(path), "%s" SLASH "%s", confbase, conf_subdir);
+
+ if(stat(path, &st)) {
+ if(errno == ENOENT) {
+ return true;
+ } else {
+ logger(NULL, MESHLINK_ERROR, "Cannot stat %s: %s\n", path, strerror(errno));
+ 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));
+ meshlink_errno = MESHLINK_ESTORAGE;
+ return false;
+ }
+ }
+
+ snprintf(path, sizeof(path), "%s" SLASH "%s", confbase, conf_subdir);
+ deltree(path);
+ return true;
+}
+
+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);
+ return false;
+ }
+
+ // Delete if already exists and create a new destination directory
+ deltree(dst_dir_name);
+
+ if(mkdir(dst_dir_name, 0700)) {
+ logger(NULL, MESHLINK_ERROR, "Could not create directory %s\n", dst_filename);
+ return false;
+ }
+
+ while((ent = readdir(src_dir))) {
+ if(ent->d_name[0] == '.') {
+ continue;
+ }
+
+ 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);
+ 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));
+ return false;
+ }
+
+ FILE *f = fopen(src_filename, "r");
+
+ if(!f) {
+ logger(NULL, MESHLINK_ERROR, "Failed to open `%s': %s\n", src_filename, strerror(errno));
+ 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);
+ return false;
+ }
+
+ if(fclose(f)) {
+ logger(NULL, MESHLINK_ERROR, "Failed to close `%s': %s\n", src_filename, strerror(errno));
+ config_free(&config);
+ 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);
+ 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);
+ return false;
+ }
+
+ if(fclose(f)) {
+ logger(NULL, MESHLINK_ERROR, "Failed to close `%s': %s", dst_filename, strerror(errno));
+ config_free(&config);
+ return false;
+ }
+
+ config_free(&config);
+
+ struct utimbuf times;
+ times.modtime = st.st_mtime;
+ times.actime = st.st_atime;
+
+ if(utime(dst_filename, ×)) {
+ logger(NULL, MESHLINK_ERROR, "Failed to utime `%s': %s", dst_filename, strerror(errno));
+ return false;
+ }
+ }
+ }
+
+ 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);
+
+ 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);
+
+ return copytree(src_filename, src_key, dst_filename, dst_key);
+}
+
+/// Check the presence of the main configuration file.
+bool main_config_exists(meshlink_handle_t *mesh, const char *conf_subdir) {
+ assert(conf_subdir);
+
+ if(!mesh->confbase) {
+ return false;
+ }
+
+ char path[PATH_MAX];
+ make_main_path(mesh, conf_subdir, path, sizeof(path));
+ return access(path, F_OK) == 0;
+}
+
+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) {
+ return false;
+ }
+
+ char old_path[PATH_MAX];
+ char new_path[PATH_MAX];
+
+ 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;
+}
+
+bool config_sync(meshlink_handle_t *mesh, const char *conf_subdir) {
+ assert(conf_subdir);
+
+ if(!mesh->confbase) {
+ return true;
+ }
+
+ char path[PATH_MAX];
+ snprintf(path, sizeof(path), "%s" SLASH "%s" SLASH "hosts", mesh->confbase, conf_subdir);
+
+ if(!sync_path(path)) {
+ return false;
+ }
+
+ snprintf(path, sizeof(path), "%s" SLASH "%s", mesh->confbase, conf_subdir);
+
+ if(!sync_path(path)) {
+ 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;
+
+ if(mesh->config_key && main_config_decrypt(mesh, "current")) {
+ confbase_decryptable = 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;
+ }
+
+ if(!config_rename(mesh, "new", "current")) {
+ return false;
+ }
+
+ confbase_decryptable = true;
+ }
+ }
+
+ if(mesh->config_key && !confbase_decryptable && main_config_exists(mesh, "old")) {
+ confbase_exists = true;
+
+ if(main_config_decrypt(mesh, "old")) {
+ if(!config_destroy(mesh->confbase, "current")) {
+ return false;
+ }
+
+ if(!config_rename(mesh, "old", "current")) {
+ return false;
+ }
+
+ confbase_decryptable = true;
+ }
+ }
+
+ // Cleanup if current is existing with old and new
+ if(confbase_exists && confbase_decryptable) {
+ config_destroy(mesh->confbase, "old");
+ config_destroy(mesh->confbase, "new");
+ }
+
+ return confbase_exists;
+}
+
+/// Lock the main configuration file.
+bool main_config_lock(meshlink_handle_t *mesh) {
+ if(!mesh->confbase) {
+ return true;
+ }
+
+ char path[PATH_MAX];
+ make_main_path(mesh, "current", path, sizeof(path));
+
+ mesh->conffile = fopen(path, "r");
+
+ if(!mesh->conffile) {
+ logger(NULL, MESHLINK_ERROR, "Cannot not open %s: %s\n", path, strerror(errno));
+ meshlink_errno = MESHLINK_ESTORAGE;
+ return false;
+ }
+
+#ifdef FD_CLOEXEC
+ fcntl(fileno(mesh->conffile), F_SETFD, FD_CLOEXEC);
+#endif
+
+#ifdef HAVE_MINGW
+ // TODO: use _locking()?
+#else
+
+ if(flock(fileno(mesh->conffile), LOCK_EX | LOCK_NB) != 0) {
+ logger(NULL, MESHLINK_ERROR, "Cannot lock %s: %s\n", path, strerror(errno));
+ fclose(mesh->conffile);
+ mesh->conffile = NULL;
+ meshlink_errno = MESHLINK_EBUSY;
+ return false;
+ }
+
+#endif
+
+ return true;