summaryrefslogtreecommitdiffstats
path: root/src/server/saveload.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/saveload.c')
-rw-r--r--src/server/saveload.c655
1 files changed, 655 insertions, 0 deletions
diff --git a/src/server/saveload.c b/src/server/saveload.c
new file mode 100644
index 0000000..6af9b39
--- /dev/null
+++ b/src/server/saveload.c
@@ -0,0 +1,655 @@
+/*
+ * This file is part of the Distributed Network Block Device 3
+ *
+ * Copyright(c) 2011-2012 Johann Latocha <johann@latocha.de>
+ *
+ * This file may be licensed under the terms of of the
+ * GNU General Public License Version 2 (the ``GPL'').
+ *
+ * Software distributed under the License is distributed
+ * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
+ * express or implied. See the GPL for the specific language
+ * governing rights and limitations.
+ *
+ * You should have received a copy of the GPL along with this
+ * program. If not, go to http://www.gnu.org/licenses/gpl.html
+ * or write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <pthread.h>
+#include <string.h>
+#include <glib.h>
+#include <math.h>
+#include <netinet/in.h>
+
+#include "server.h"
+#include "saveload.h"
+#include "memlog.h"
+#include "helper.h"
+
+// Keep parsed config file in memory so it doesn't need to be parsed again every time it's modified
+static GKeyFile *_config_handle = NULL;
+
+static dnbd3_image_t *prepare_image(char *image_name, int rid, char *image_file, char *cache_file);
+//static char* get_local_image_name(char *global_name);
+
+
+void dnbd3_load_config()
+{
+ gint i, j;
+
+ if (_config_handle != NULL)
+ {
+ printf("dnbd3_load_config() called more than once\n\n");
+ exit(EXIT_FAILURE);
+ }
+
+ _config_handle = g_key_file_new();
+ if (!g_key_file_load_from_file(_config_handle, _config_file_name, G_KEY_FILE_NONE, NULL))
+ {
+ printf("ERROR: Config file not found: %s\n", _config_file_name);
+ exit(EXIT_FAILURE);
+ }
+
+ _local_namespace = g_key_file_get_string(_config_handle, "settings", "default_namespace", NULL);
+ if (_local_namespace && !is_valid_namespace(_local_namespace))
+ {
+ memlogf("[ERROR] Ignoring default namespace: '%s' is not a valid namespace", _local_namespace);
+ g_free(_local_namespace);
+ _local_namespace = NULL;
+ }
+
+ srand(time(NULL));
+
+ _ipc_password = g_key_file_get_string(_config_handle, "settings", "password", NULL);
+
+ gchar **groups = NULL;
+ gsize section_count;
+ groups = g_key_file_get_groups(_config_handle, &section_count);
+
+ for (i = 0; i < section_count; i++)
+ {
+ // Ignore settings section
+ if (strcmp(groups[i], "settings") == 0)
+ continue;
+
+ // List of trusted servers/namespaces
+ if (strncmp(groups[i], "trust:", 6) == 0)
+ {
+ gchar *addr = g_key_file_get_string(_config_handle, groups[i], "address", NULL);
+ if (addr == NULL)
+ continue;
+ dnbd3_trusted_server_t *server = dnbd3_get_trusted_server(addr, TRUE, groups[i]+6);
+ g_free(addr);
+ if (server == NULL)
+ continue;
+ gsize key_count;
+ gchar **keys = g_key_file_get_keys(_config_handle, groups[i], &key_count, NULL);
+ for (j = 0; j < key_count; ++j)
+ {
+ if (strcmp(keys[j], "address") == 0)
+ continue;
+ char *flags = g_key_file_get_string(_config_handle, groups[i], keys[j], NULL);
+ g_key_file_remove_key(_config_handle, groups[i], keys[j], NULL);
+ dnbd3_add_trusted_namespace(server, keys[j], flags);
+ g_free(flags);
+ }
+ g_strfreev(keys);
+ continue;
+ }
+
+ // An actual image definition
+
+ int rid = g_key_file_get_integer(_config_handle, groups[i], "rid", NULL);
+ if (rid <= 0)
+ {
+ memlogf("[ERROR] Invalid rid '%d' for image '%s'", rid, groups[i]);
+ continue;
+ }
+
+ const time_t delsoft = g_key_file_get_int64(_config_handle, groups[i], "delete_soft", NULL);
+ const time_t delhard = g_key_file_get_int64(_config_handle, groups[i], "delete_hard", NULL);
+ if ((delsoft != 0 && delsoft < time(NULL)) || (delhard != 0 && delhard < time(NULL)))
+ {
+ memlogf("[INFO] Ignoring image '%s' as its deletion is due", groups[i]);
+ continue;
+ }
+
+ char *image_file = g_key_file_get_string(_config_handle, groups[i], "file", NULL);
+ char *cache_file = g_key_file_get_string(_config_handle, groups[i], "cache", NULL);
+
+ dnbd3_image_t *image = prepare_image(groups[i], rid, image_file, cache_file);
+ if (image)
+ {
+ _dnbd3_images = g_slist_prepend(_dnbd3_images, image);
+ }
+
+ g_free(image_file);
+ g_free(cache_file);
+ }
+
+ g_strfreev(groups);
+}
+
+int dnbd3_add_image(dnbd3_image_t *image)
+{
+ // Lock here to prevent concurrent add calls to mess rids up. Cannot happen currently
+ // as IPC clients are not threaded and they're the only place where this is called,
+ // but better be safe for the future...
+ pthread_spin_lock(&_spinlock);
+ if (image->rid == 0)
+ {
+ const dnbd3_image_t *latest = dnbd3_get_image(image->config_group, 0, 0);
+ if (latest)
+ image->rid = latest->rid + 1;
+ else
+ image->rid = 1;
+ }
+
+ dnbd3_image_t *newimage = prepare_image(image->config_group, image->rid, image->file, image->cache_file);
+ if (newimage)
+ {
+ _dnbd3_images = g_slist_prepend(_dnbd3_images, image);
+ }
+ else
+ {
+ pthread_spin_unlock(&_spinlock);
+ return ERROR_SEE_LOG;
+ }
+
+ // Adding image was successful, write config file
+ g_key_file_set_integer(_config_handle, image->config_group, "rid", image->rid);
+ g_key_file_set_string(_config_handle, image->config_group, "file", image->file);
+ //g_key_file_set_string(_config_handle, image->name, "servers", image->serverss); // TODO: Save servers as string
+ g_key_file_set_string(_config_handle, image->config_group, "cache", image->cache_file);
+
+ pthread_spin_unlock(&_spinlock);
+
+ const int ret = dnbd3_save_config();
+ if (ret == ERROR_OK)
+ memlogf("[INFO] Added new image '%s' (rid %d)", newimage->config_group, newimage->rid);
+ else
+ memlogf("[INFO] Added new image '%s' (rid %d), but config file could not be written (%s)", newimage->config_group, newimage->rid, _config_file_name);
+ return ret;
+}
+
+int dnbd3_del_image(dnbd3_image_t *image)
+{
+ if (image->rid <= 0) // Require a specific rid on deletion
+ return ERROR_RID;
+ pthread_spin_lock(&_spinlock);
+ dnbd3_image_t *existing_image = dnbd3_get_image(image->config_group, image->rid, 0);
+ if(existing_image == NULL)
+ {
+ pthread_spin_unlock(&_spinlock);
+ return ERROR_IMAGE_NOT_FOUND;
+ }
+
+ existing_image->delete_soft = 1; // TODO: Configurable.
+ existing_image->delete_hard = time(NULL) + 86400; // TODO: Configurable
+ g_key_file_set_int64(_config_handle, image->config_group, "delete_soft", existing_image->delete_soft);
+ g_key_file_set_int64(_config_handle, image->config_group, "delete_hard", existing_image->delete_hard);
+
+ pthread_spin_unlock(&_spinlock);
+ dnbd3_exec_delete(FALSE);
+ existing_image = NULL;
+
+ const int ret = dnbd3_save_config();
+ if (ret == ERROR_OK)
+ memlogf("[INFO] Marked for deletion: '%s' (rid %d)", image->config_group, image->rid);
+ else
+ memlogf("[WARNING] Marked for deletion: '%s' (rid %d), but config file could not be written (%s)", image->config_group, image->rid, _config_file_name);
+ return ret;
+}
+
+int dnbd3_save_config()
+{
+ pthread_spin_lock(&_spinlock);
+ char *data = (char *)g_key_file_to_data(_config_handle, NULL, NULL);
+ if (data == NULL)
+ {
+ pthread_spin_unlock(&_spinlock);
+ memlogf("[ERROR] g_key_file_to_data() failed");
+ return ERROR_UNSPECIFIED_ERROR;
+ }
+
+ FILE *f = fopen(_config_file_name, "w");
+ if (f < 0)
+ {
+ pthread_spin_unlock(&_spinlock);
+ g_free(data);
+ return ERROR_CONFIG_FILE_PERMISSIONS;
+ }
+ fputs("# Do not edit this file while dnbd3-server is running\n", f);
+ fputs(data, f);
+ fclose(f);
+ pthread_spin_unlock(&_spinlock);
+ g_free(data);
+ return 0;
+}
+
+dnbd3_image_t *dnbd3_get_image(char *name_orig, int rid, const char do_lock)
+{
+ dnbd3_image_t *result = NULL, *image;
+ GSList *iterator;
+ // For comparison, make sure the name is global and lowercased
+ int slen;
+ int islocal = (strchr(name_orig, '/') == NULL);
+ if (islocal)
+ slen = strlen(name_orig) + strlen(_local_namespace) + 2;
+ else
+ slen = strlen(name_orig) + 1;
+ char name[slen];
+ if (islocal)
+ sprintf(name, "%s/%s", _local_namespace, name_orig);
+ else
+ strcpy(name, name_orig);
+ strtolower(name);
+ // Now find the image
+ if (do_lock)
+ pthread_spin_lock(&_spinlock);
+ for (iterator = _dnbd3_images; iterator; iterator = iterator->next)
+ {
+ image = iterator->data;
+ if (rid != 0) // rid was specified
+ {
+ if (image->rid == rid && strcmp(name, image->low_name) == 0)
+ {
+ result = image;
+ break;
+ }
+ }
+ else // search max. rid available
+ {
+ if (strcmp(name, image->low_name) == 0 && (result == NULL || result->rid < image->rid))
+ {
+ result = image;
+ }
+ }
+ }
+ if (do_lock)
+ pthread_spin_unlock(&_spinlock);
+ return result;
+}
+
+void dnbd3_handle_sigpipe(int signum)
+{
+ memlogf("ERROR: SIGPIPE received (%s)", strsignal(signum));
+}
+
+void dnbd3_handle_sigterm(int signum)
+{
+ memlogf("INFO: SIGTERM or SIGINT received (%s)", strsignal(signum));
+ dnbd3_cleanup();
+}
+
+/**
+ * Prepare image to be added to image list. Returns a pointer to a newly allocated image struct
+ * on success, NULL otherwise.
+ * Note: This function calls dnbd3_get_image without locking, so make sure you lock
+ * before calling this function while the server is active.
+ */
+static dnbd3_image_t *prepare_image(char *image_name, int rid, char *image_file, char *cache_file)
+{
+ int j;
+ if (image_name == NULL)
+ {
+ memlogf("[ERROR] Null Image-Name");
+ return NULL;
+ }
+ if (!is_valid_imagename(image_name))
+ {
+ memlogf("[ERROR] Invalid image name: '%s'", image_name);
+ return NULL;
+ }
+
+ if (strchr(image_name, '.') == NULL && _local_namespace == NULL)
+ {
+ memlogf("[ERROR] Image '%s' has local name and no default namespace is defined; entry ignored.", image_name);
+ return NULL;
+ }
+
+ // Allocate image struct and zero it out by using g_new0
+ dnbd3_image_t *image = g_new0(dnbd3_image_t, 1);
+ if (image == NULL)
+ {
+ memlogf("[ERROR] Could not allocate dnbd3_image_t while reading config");
+ return NULL;
+ }
+
+ if (strchr(image_name, '/') == NULL)
+ {
+ // Local image, build global name
+ image->low_name = calloc(strlen(_local_namespace) + strlen(image_name) + 2, sizeof(char));
+ sprintf(image->low_name, "%s/%s", _local_namespace, image_name);
+ }
+ else
+ {
+ image->low_name = strdup(image_name);
+ }
+
+ if (dnbd3_get_image(image->low_name, rid, 0))
+ {
+ memlogf("[ERROR] Duplicate image in config: '%s' rid:%d", image_name, rid);
+ goto error;
+ }
+
+ strtolower(image->low_name);
+ image->config_group = strdup(image_name);
+
+ image->rid = rid;
+ image->relayed = (image_file == NULL || image_file == '\0');
+
+ if (image->relayed) // Image is relayed (this server acts as proxy)
+ {
+ if (strchr(image_name, '/') == NULL)
+ {
+ memlogf("[ERROR] Relayed image without global name in config: '%s'", image_name);
+ goto error;
+ }
+ if (cache_file && *cache_file)
+ image->cache_file = strdup(cache_file);
+ }
+ else // Image is a local one, open file to get size
+ {
+ image->file = strdup(image_file);
+ int fd = open(image->file, O_RDONLY);
+ if (fd < 0)
+ {
+ memlogf("[ERROR] Image file not found: '%s'", image->file);
+ goto error;
+ }
+ const off_t size = lseek(fd, 0, SEEK_END);
+ if (size <= 0)
+ {
+ memlogf("[ERROR] File '%s' of image '%s' has size '%lld'. Image ignored.",
+ image->file, image_name, (long long)size);
+ goto error;
+ }
+ image->filesize = (uint64_t)size;
+ if (image->filesize & 4095)
+ {
+ memlogf("[WARNING] Size of image '%s' is not a multiple of 4096. Last incomplete block will be ignored!",
+ image->file);
+ image->filesize &= ~(uint64_t)4095;
+ }
+ close(fd);
+ image->working = 1;
+ }
+
+ if (image->cache_file)
+ {
+ // Determine size of cached image
+ int fd = open(image->cache_file, O_RDONLY);
+ if (fd >= 0)
+ {
+ const off_t size = lseek(fd, 0, SEEK_END);
+ if (size > 0)
+ image->filesize = (uint64_t)size;
+ close(fd);
+ }
+ if (image->filesize & 4095)
+ {
+ // Cache files should always be truncated to 4kib boundaries already
+ memlogf("[WARNING] Size of cache file '%s' is not a multiple of 4096. Something's fishy!", image->cache_file);
+ image->filesize = 0;
+ }
+ else if (image->filesize > 0)
+ {
+ const size_t map_len_bytes = IMGSIZE_TO_MAPBYTES(image->filesize);
+ image->cache_map = calloc(map_len_bytes, sizeof(uint8_t));
+ // read cache map from file
+ char tmp[strlen(image->cache_file) + 5];
+ strcpy(tmp, image->cache_file);
+ strcat(tmp, ".map");
+ fd = open(tmp, O_RDONLY);
+ if (fd >= 0)
+ {
+ const off_t size = lseek(fd, 0, SEEK_END);
+ if (size != map_len_bytes)
+ {
+ memlogf("[DEBUG] Cache-Map of %s is corrupted (%d != %d)", image_name, (int)size, (int)map_len_bytes);
+ }
+ else
+ {
+ lseek(fd, 0, SEEK_SET);
+ read(fd, image->cache_map, map_len_bytes);
+ // If the whole image is cached, mark it as working right away without waiting for an upstream server
+ image->working = 1;
+ for (j = 0; j < map_len_bytes - 1; ++j)
+ {
+ if (image->cache_map[j] != 0xFF)
+ {
+ image->working = 0;
+ break;
+ }
+ }
+ const int blocks_in_last_byte = (image->filesize >> 12) & 7;
+ uint8_t last_byte = 0;
+ if (blocks_in_last_byte == 0)
+ last_byte = 0xFF;
+ else
+ for (j = 0; j < blocks_in_last_byte; ++j)
+ last_byte |= (1 << j);
+ if ((image->cache_map[map_len_bytes - 1] & last_byte) != last_byte)
+ image->working = 0;
+ else
+ memlogf("[INFO] Instantly publishing relayed image '%s' because the local cache copy is complete", image_name);
+ }
+ close(fd);
+ }
+
+ /*
+ // TODO: Do this as soon as a connection to a upstream server is established
+ // open cache file
+ fd = open(_images[i].cache_file, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
+ if (fd < 1)
+ memlogf("ERROR: Could't create cache file '%s'", _images[i].cache_file);
+
+ if (_images[i].filesize != lseek(fd, 0, SEEK_END))
+ fallocate(fd, 0, 0, _images[i].filesize);
+
+ close(fd);
+ */
+ }
+ } // end cache_file handling
+ return image;
+error:
+ // Free stuff. Some pointers might be zero, but calling free() on those is safe.
+ free(image->cache_map);
+ free(image->config_group);
+ free(image->low_name);
+ free(image->file);
+ free(image->cache_file);
+ g_free(image);
+ return NULL;
+}
+
+/**
+ * Iterate over all images and delete them if appropriate.
+ */
+void dnbd3_exec_delete(int save_if_changed)
+{
+ int changed = FALSE;
+ const time_t now = time(NULL);
+ GSList *image_iterator, *client_iterator;
+ char ipstr[100];
+
+ pthread_spin_lock(&_spinlock);
+ for (image_iterator = _dnbd3_images; image_iterator; image_iterator = image_iterator->next)
+ {
+ dnbd3_image_t *image = image_iterator->data;
+ int delete_now = TRUE;
+ if (image->delete_hard != 0 && image->delete_hard < now)
+ {
+ // Drop all clients still using it
+ for (client_iterator = _dnbd3_clients; client_iterator; client_iterator = client_iterator->next)
+ {
+ dnbd3_client_t *client = client_iterator->data;
+ if (client->image != image)
+ continue;
+ // Kill client's connection
+ *ipstr = '\0';
+ host_to_string(&client->host, ipstr, 100);
+ memlogf("[INFO] delete_hard of %s reached; dropping client %s", image->config_group, ipstr);
+ const int fd = client->sock;
+ client->sock = -1;
+ close(fd);
+ delete_now = FALSE; // Wait for all clients being actually dropped; deletion will happen on next dnbd3_exec_delete()
+ }
+ } // END delete_hard image
+ else if (image->delete_soft != 0 && image->delete_soft < now && image->atime + 3600 < now)
+ {
+ // Image should be soft-deleted
+ // Check if it is still in use
+ for (client_iterator = _dnbd3_clients; client_iterator; client_iterator = client_iterator->next)
+ {
+ const dnbd3_client_t *client = client_iterator->data;
+ if (client->image == image)
+ {
+ // Yep, still in use, keep it
+ delete_now = FALSE;
+ break;
+ }
+ }
+ }
+ else // Neither hard nor soft delete, keep it
+ delete_now = FALSE;
+ if (delete_now)
+ {
+ // Image was not in use and should be deleted, free it!
+ memlogf("[INFO] Freeing end-of-life image %s", image->config_group);
+ changed = TRUE;
+ _dnbd3_images = g_slist_remove(_dnbd3_images, image); // Remove from image list
+ g_key_file_remove_group(_config_handle, image->config_group, NULL); // Also remove from config file
+ // Free any allocated memory
+ free(image->cache_map);
+ free(image->config_group);
+ free(image->low_name);
+ free(image->file);
+ free(image->cache_file);
+ g_free(image);
+ // Restart iteration as it would be messed up now
+ image_iterator = _dnbd3_images;
+ }
+ } // END image iteration
+ pthread_spin_lock(&_spinlock);
+
+ if (changed && save_if_changed)
+ dnbd3_save_config();
+}
+
+/**
+ * Return pointer to trusted_server matching given address.
+ * If not found and create_if_not_found is TRUE, a new entry will be created,
+ * added to the list and then returned
+ * Returns NULL otherwise, or if the address could not be parsed
+ * !! Lock before calling this function !!
+ */
+dnbd3_trusted_server_t *dnbd3_get_trusted_server(char *address, char create_if_not_found, char *comment)
+{
+ dnbd3_trusted_server_t server;
+ memset(&server, 0, sizeof(server));
+ if (!parse_address(address, &server.host))
+ {
+ memlogf("[WARNING] Could not parse address '%s' of trusted server", address);
+ return NULL;
+ }
+ GSList *iterator;
+ for (iterator = _trusted_servers; iterator; iterator = iterator->next)
+ {
+ dnbd3_trusted_server_t *comp = iterator->data;
+ if (is_same_server(comp, &server))
+ return comp;
+ }
+ if (!create_if_not_found)
+ return NULL;
+ char *groupname = NULL;
+ if (comment == NULL)
+ {
+ groupname = malloc(50);
+ snprintf(groupname, 50, "trust:%x%x", rand(), (int)clock());
+ }
+ else
+ {
+ const size_t len = strlen(comment) + 8;
+ groupname = malloc(len);
+ snprintf(groupname, len, "trust:%s", comment);
+ }
+ char addrbuffer[50];
+ host_to_string(&server.host, addrbuffer, 50);
+ g_key_file_set_string(_config_handle, groupname, "address", addrbuffer);
+ free(groupname);
+ dnbd3_trusted_server_t *copy = malloc(sizeof(server));
+ memcpy(copy, &server, sizeof(*copy));
+ _trusted_servers = g_slist_prepend(_trusted_servers, copy);
+ return copy;
+}
+
+/**
+ * Add new trusted namespace to given trusted server, using given flags.
+ * Overwrites any existing entry for the given server and namespace
+ * !! Lock before calling this function !!
+ */
+int dnbd3_add_trusted_namespace(dnbd3_trusted_server_t *server, char *namespace, char *flags)
+{
+ int nslen = strlen(namespace) + 1;
+ char nslow[nslen];
+ memcpy(nslow, namespace, nslen);
+ strtolower(nslow);
+ GSList *iterator;
+ dnbd3_namespace_t *ns = NULL;
+ for (iterator = server->namespaces; iterator; iterator = iterator->next)
+ {
+ dnbd3_namespace_t *cmp = iterator->data;
+ if (strcmp(nslow, cmp->name) == 0)
+ {
+ ns = cmp;
+ break;
+ }
+ }
+ if (ns == NULL)
+ {
+ ns = calloc(1, sizeof(*ns));
+ ns->name = strdup(nslow);
+ server->namespaces = g_slist_prepend(server->namespaces, ns);
+ }
+ ns->auto_replicate = (flags && strstr(flags, "replicate"));
+ ns->recursive = (flags && strstr(flags, "recursive"));
+ return TRUE;
+}
+
+/**
+ * Return local image name for a global image name
+ * eg. "uni-freiburg/rz/ubuntu 12.04" -> "ubuntu 12.04"
+ * ONLY IF the local name space really is "uni-freiburg/rz"
+ * Returns NULL otherwise
+ * The returned pointer points to memory inside the passed
+ * string (if not NULL), so do not modify or free
+ * / <---
+static char* get_local_image_name(char *global_name)
+{
+ if (_local_namespace == NULL)
+ return NULL; // No local namespace defined, so it cannot be local
+ char *first_slash = strchr(global_name, '/');
+ if (first_slash == NULL)
+ return global_name; // Already local
+ const size_t buflen = strlen(_local_namespace) + 1;
+ if (first_slash - global_name + 1 != buflen)
+ return NULL; // Namespaces have different length, cannot be same
+ char namespace[buflen];
+ char passedname[buflen];
+ strcpy(namespace, _local_namespace);
+ strncpy(passedname, global_name, buflen);
+ passedname[buflen] = '\0';
+ strtolower(namespace);
+ strtolower(passedname);
+ if (strcmp(namespace, passedname) == 0)
+ return global_name + buflen; // points somewhere into passed buffer
+ return NULL;
+} //*/