/* * This file is part of the Distributed Network Block Device 3 * * Copyright(c) 2011-2012 Johann Latocha * * 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 #include #include #include #include #include #include #include #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); } srand(time(NULL)); _rpc_password = g_key_file_get_string(_config_handle, "settings", "password", NULL); _cache_dir = g_key_file_get_string(_config_handle, "settings", "cache_dir", NULL); if (_cache_dir == NULL) memlogf("[WARNING] No cache dir set! Automatic replication will not work."); else if (access(_cache_dir, R_OK | W_OK) != 0) memlogf("[WARNING] Cache dir '%s' is not readable or writable", _cache_dir); gchar **groups = NULL; gsize section_count; groups = g_key_file_get_groups(_config_handle, §ion_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) { if (image->file) { if (strncmp(image->file, "/dev/dnbd", 9) == 0) return ERROR_IMAGE_NOT_FOUND; int fh = open(image->file, O_RDONLY); if (fh < 0) return ERROR_IMAGE_NOT_FOUND; close(fh); } // Lock here to prevent concurrent add calls to mess rids up. Cannot happen currently // as RPC 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) { if (image->file == NULL && newimage->filesize == 0) newimage->filesize = image->filesize; _dnbd3_images = g_slist_prepend(_dnbd3_images, newimage); } else { pthread_spin_unlock(&_spinlock); return ERROR_SEE_LOG; } image = NULL; // Adding image was successful, write config file g_key_file_set_integer(_config_handle, newimage->config_group, "rid", newimage->rid); if (newimage->file) g_key_file_set_string(_config_handle, newimage->config_group, "file", newimage->file); if (newimage->cache_file) g_key_file_set_string(_config_handle, newimage->config_group, "cache", newimage->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 = image->delete_soft; existing_image->delete_hard = image->delete_hard; 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 char name[strlen(name_orig) + 1]; 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; } char *slash = strrchr(image_name, '/'); if (slash == NULL) { memlogf("[ERROR] Invalid image name: '%s'", image_name); return NULL; } else { *slash = '\0'; if (!is_valid_imagename(slash+1)) { memlogf("[ERROR] Invalid image name: '%s'", slash+1); return NULL; } if (!is_valid_namespace(image_name)) { memlogf("[ERROR] Invalid namespace: '%s'", image_name); *slash = '/'; return NULL; } *slash = '/'; } // 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; } image->low_name = strdup(image_name); strtolower(image->low_name); memlogf("[INFO] Loading image '%s'", image->low_name); if (dnbd3_get_image(image->low_name, rid, 0)) { memlogf("[ERROR] Duplicate image in config: '%s' rid:%d", image_name, rid); goto error; } image->config_group = strdup(image_name); image->rid = rid; image->relayed = (image_file == NULL || image_file == '\0'); if (image_file && strncmp(image_file, "/dev/dnbd", 9) == 0) { printf("[BUG BUG BUG] Image file is %s\n", image_file); image->relayed = TRUE; } 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) { printf("[DEBUG] Size known %llu for %s\n", (unsigned long long)image->filesize, image->cache_file); 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); } } } // 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]; printf("[DEBUG] Scanning for deletable images\n"); 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) { printf("[DEBUG] HARD %s\n", image->low_name); // 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) { printf("[DEBUG] SOFT %s\n", image->low_name); // 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; if (image_iterator == NULL) break; } } // END image iteration pthread_spin_unlock(&_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; } #ifndef WITH_IPV6 if (server.host.type == AF_INET6) { printf("[DEBUG] Ignoring IPv6 trusted server.\n"); return NULL; } #endif GSList *iterator; for (iterator = _trusted_servers; iterator; iterator = iterator->next) { dnbd3_trusted_server_t *comp = iterator->data; if (is_same_server(&comp->host, &server.host)) 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); dnbd3_trusted_server_t *copy = malloc(sizeof(server)); memcpy(copy, &server, sizeof(*copy)); copy->comment = strdup(groupname+6); _trusted_servers = g_slist_prepend(_trusted_servers, copy); free(groupname); 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); remove_trailing_slash(nslow); 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")); size_t len = strlen(server->comment) + 7; char groupname[len]; snprintf(groupname, len, "trust:%s", server->comment); if (ns->auto_replicate && ns->recursive) g_key_file_set_string(_config_handle, groupname, ns->name, "replicate,recursive"); else if (ns->auto_replicate) g_key_file_set_string(_config_handle, groupname, ns->name, "replicate"); else if (ns->recursive) g_key_file_set_string(_config_handle, groupname, ns->name, "recursive"); else g_key_file_set_string(_config_handle, groupname, ns->name, "-"); return TRUE; } /** * Remove trusted namespace from given trusted server. * !! Lock before calling this function !! * @return TRUE if element could be removed * !! Assume that the _trusted_servers list changed after calling this, * since the trusted server gets removed from it if this was the last namespace * it contained */ int dnbd3_del_trusted_namespace(dnbd3_trusted_server_t *server, char *namespace) { int nslen = strlen(namespace) + 1; char nslow[nslen]; memcpy(nslow, namespace, nslen); remove_trailing_slash(nslow); strtolower(nslow); GSList *iterator; for (iterator = server->namespaces; iterator; iterator = iterator->next) { dnbd3_namespace_t *cmp = iterator->data; if (strcmp(nslow, cmp->name) == 0) { // TODO: Remove from config file free(cmp->name); free(cmp); server->namespaces = g_slist_remove(server->namespaces, cmp); if (server->namespaces == NULL) { g_free(server->comment); _trusted_servers = g_slist_remove(_trusted_servers, server); free(server); } return TRUE; } } return FALSE; } /** * Gives the closest match of a namespace rule that can be applied to * the given namespace * Returns NULL if none * !! Lock before calling this function !! */ dnbd3_namespace_t *dnbd3_get_trust_level(dnbd3_host_t *host, char *namespace) { dnbd3_trusted_server_t *server = NULL; GSList *iterator; for (iterator = _trusted_servers; iterator; iterator = iterator->next) { dnbd3_trusted_server_t *comp = iterator->data; if (!is_same_server(host, &comp->host)) continue; server = comp; break; } if (server == NULL) return NULL; dnbd3_namespace_t *best = NULL; int bestlen = 0; char nslow[strlen(namespace)+1]; strcpy(nslow, namespace); remove_trailing_slash(nslow); strtolower(nslow); for (iterator = server->namespaces; iterator; iterator = iterator-> next) { dnbd3_namespace_t *comp = iterator->data; const int cmplen = strlen(comp->name); if (strncmp(nslow, comp->name, cmplen) != 0) // names do not match at all continue; if (nslow[cmplen] == '/' && !comp->recursive) // partial match, but recursion is disabled continue; if (nslow[cmplen] != '\0' && nslow[cmplen] != '/') // in mid-string continue; if (cmplen < bestlen) // Match is not better than one found before continue; bestlen = cmplen; best = comp; } return best; }