diff options
Diffstat (limited to 'src/server/utils.c')
-rw-r--r-- | src/server/utils.c | 420 |
1 files changed, 320 insertions, 100 deletions
diff --git a/src/server/utils.c b/src/server/utils.c index 0be1569..15e51f8 100644 --- a/src/server/utils.c +++ b/src/server/utils.c @@ -23,14 +23,124 @@ #include <sys/stat.h> #include <pthread.h> #include <string.h> +#include <glib.h> +#include <netinet/in.h> +#include <arpa/inet.h> #include "server.h" #include "utils.h" +#include "memlog.h" + +/** + * Parse IPv4 or IPv6 address in string representation to a suitable format usable by the BSD socket library + * @string eg. "1.2.3.4" or "2a01::10:5", optially with port appended, eg "1.2.3.4:6666" or "2a01::10:5:6666" + * @af will contain either AF_INET or AF_INET6 + * @addr will contain the address in network representation + * @port will contain the port in network representation, defaulting to #define PORT if none was given + * returns 1 on success, 0 in failure. contents of af, addr and port are undefined in the latter case + */ +static char parse_address(char *string, uint8_t *af, uint8_t *addr, uint16_t *port) +{ + struct in_addr v4; + struct in6_addr v6; + + // Try IPv4 without port + if (1 == inet_pton(AF_INET, string, &v4)) + { + *af = AF_INET; + memcpy(addr, &v4, 4); + *port = htons(PORT); + return 1; + } + // Try IPv6 without port + if (1 == inet_pton(AF_INET6, string, &v6)) + { + *af = AF_INET6; + memcpy(addr, &v6, 16); + *port = htons(PORT); + return 1; + } + + // Scan for port + char *portpos = NULL, *ptr = string; + while (*ptr) + { + if (*ptr == ':') portpos = ptr; + ++ptr; + } + if (portpos == NULL) return 0; // No port in string + // Consider IP being surrounded by [ ] + if (*string == '[' && *(portpos-1) == ']') + { + ++string; + *(portpos-1) = '\0'; + } + *portpos++ = '\0'; + int p = atoi(portpos); + if (p < 1 || p > 65535) return 0; // Invalid port + *port = htons((uint16_t)p); + + // Try IPv4 with port + if (1 == inet_pton(AF_INET, string, &v4)) + { + *af = AF_INET; + memcpy(addr, &v4, 4); + return 1; + } + // Try IPv6 with port + if (1 == inet_pton(AF_INET6, string, &v6)) + { + *af = AF_INET6; + memcpy(addr, &v6, 16); + return 1; + } + + // FAIL + return 0; +} + +static char is_valid_namespace(char *namespace) +{ + if (*namespace == '\0' || *namespace == '/') return 0; // Invalid: Length = 0 or starting with a slash + while (*namespace) + { + if (*namespace != '/' && *namespace != '-' + && (*namespace < 'a' || *namespace > 'z') + && (*namespace < 'A' || *namespace > 'Z')) return 0; + ++namespace; + } + if (*(namespace - 1) == '/') return 0; // Invalid: Ends in a slash + return 1; +} + +static char is_valid_imagename(char *namespace) +{ + if (*namespace == '\0' || *namespace == ' ') return 0; // Invalid: Length = 0 or starting with a space + while (*namespace) + { // Check for invalid chars + if (*namespace != '.' && *namespace != '-' && *namespace != ' ' + && *namespace != '(' && *namespace != ')' + && (*namespace < 'a' || *namespace > 'z') + && (*namespace < 'A' || *namespace > 'Z')) return 0; + ++namespace; + } + if (*(namespace - 1) == ' ') return 0; // Invalid: Ends in a space + return 1; +} + +static void strtolower(char *string) +{ + while (*string) + { + if (*string >= 'A' && *string <= 'Z') *string += 32; + ++string; + } +} void dnbd3_load_config(char *file) { int fd; - gint i; + gint i, j, k; GKeyFile* gkf; gkf = g_key_file_new(); @@ -40,111 +150,209 @@ void dnbd3_load_config(char *file) exit(EXIT_FAILURE); } + char *namespace = g_key_file_get_string(gkf, "settings", "default_namespace", NULL); + if (namespace && !is_valid_namespace(namespace)) + { + memlogf("[ERROR] Ignoring default namespace: '%s' is not a valid namespace", namespace); + g_free(namespace); + namespace = NULL; + } + gchar **groups = NULL; - groups = g_key_file_get_groups(gkf, &_num_images); - _images = calloc(_num_images, sizeof(dnbd3_image_t)); + gsize section_count; + groups = g_key_file_get_groups(gkf, §ion_count); - for (i = 0; i < _num_images; i++) + for (i = 0; i < section_count; i++) { - _images[i].group = malloc(strlen(groups[i])); - strcpy(_images[i].group, groups[i]); - _images[i].file = g_key_file_get_string(gkf, groups[i], "file", NULL); - _images[i].servers = g_key_file_get_string_list(gkf, groups[i], "servers", &_images[i].num_servers, NULL); - _images[i].serverss = g_key_file_get_string(gkf, groups[i], "servers", NULL); - _images[i].vid = g_key_file_get_integer(gkf, groups[i], "vid", NULL); - _images[i].rid = g_key_file_get_integer(gkf, groups[i], "rid", NULL); - _images[i].cache_file = g_key_file_get_string(gkf, groups[i], "cache", NULL); - _images[i].atime = 0; - - if (_images[i].num_servers > NUMBER_SERVERS) - printf("WARN: Max allowed servers %i\n", NUMBER_SERVERS); - - fd = open(_images[i].file, O_RDONLY); - if (fd > 0) - _images[i].filesize = lseek(fd, 0, SEEK_END); + // Special group + if (strcmp(groups[i], "settings") == 0 || strcmp(groups[i], "trusted") == 0) + { + continue; + } + + // An actual image definition + + if (!is_valid_imagename(groups[i])) + { + memlogf("[ERROR] Invalid image name: '%s'", groups[i]); + continue; + } + + int rid = g_key_file_get_integer(gkf, groups[i], "rid", NULL); + if (rid <= 0) + { + memlogf("[ERROR] Invalid rid '%d' for image '%s'", rid, groups[i]); + continue; + } + + if (strchr(groups[i], '.') == NULL && namespace == NULL) + { + memlogf("[ERROR] Image '%s' has local name and no default namespace is defined; entry ignored.", groups[i]); + continue; + } + + dnbd3_image_t *image = g_new0(dnbd3_image_t, 1); + if (image == NULL) + { + memlogf("[ERROR] Could not allocate dnbd3_image_t while reading config"); + continue; + } + + if (strchr(groups[i], '/') == NULL) + { // Local image, build global name + image->name = calloc(strlen(namespace) + strlen(groups[i]) + 2, sizeof(char)); + sprintf(image->name, "%s/%s", namespace, groups[i]); + } else - printf("ERROR: Image file not found: %s\n", _images[i].file); - - close(fd); - - if (_images[i].cache_file) { - // read cache map from file - _images[i].cache_map = calloc(_images[i].filesize >> 15, sizeof(char)); - memset(_images[i].cache_map, 0, (_images[i].filesize >> 15) * sizeof(char)); - char tmp[strlen(_images[i].cache_file)+4]; - strcpy(tmp, _images[i].cache_file); - strcat(tmp, ".map"); - fd = open(tmp, O_RDONLY); - if (fd > 0) - read(fd, _images[i].cache_map, (_images[i].filesize >> 15) * sizeof(char)); - close(fd); - - // open cache file - fd = open(_images[i].cache_file, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); - if (fd < 1) - printf("ERROR: Could't create cache file\n"); - - if (_images[i].filesize != lseek(fd, 0, SEEK_END)) - fallocate(fd, 0, 0, _images[i].filesize); - - close(fd); + image->name = strdup(groups[i]); } - } - g_strfreev(groups); - g_key_file_free(gkf); -} - -void dnbd3_reload_config(char* config_file_name) -{ - int i, fd; - pthread_spin_lock(&_spinlock); - GSList *iterator = NULL; - for (iterator = _dnbd3_clients; iterator; iterator = iterator->next) - { - dnbd3_client_t *client = iterator->data; - pthread_spin_lock(&client->spinlock); - client->image = NULL; - } - - for (i = 0; i < _num_images; i++) - { - // save cache maps - if (_images[i].cache_file) + if (dnbd3_get_image(image->name, rid, 0)) + { + memlogf("[ERROR] Duplicate image in config: '%s' rid:%d", image->name, rid); + free(image->name); + g_free(image); + continue; + } + + image->low_name = strdup(image->name); + strtolower(image->low_name); + + image->rid = rid; + image->file = g_key_file_get_string(gkf, groups[i], "file", NULL); + char relayed = image->file == NULL || *image->file == '\0'; + if (relayed && image->file) + { + g_free(image->file); + image->file = NULL; + } + + if (relayed) // Image is relayed (this server acts as proxy) + { + if (strchr(groups[i], '.') == NULL) + { + memlogf("[ERROR] Relayed image without global name in config: '%s'", groups[i]); + g_free(image); + continue; + } + image->cache_file = g_key_file_get_string(gkf, groups[i], "cache", NULL); + if (image->cache_file && *image->cache_file == '\0') g_free(image->cache_file); + } + else // Image is a local one, open file to get size { - char tmp[strlen(_images[i].cache_file)+4]; - strcpy(tmp, _images[i].cache_file); - strcat(tmp, ".map"); - fd = open(tmp, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); - - if (fd > 0) - write(fd, _images[i].cache_map, (_images[i].filesize >> 15) * sizeof(char)); + fd = open(image->file, O_RDONLY); + if (fd > 0) { + image->filesize = lseek(fd, 0, SEEK_END); + 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; + } else { + memlogf("[ERROR] Image file not found: '%s'", image->file); + } + } - close(fd); + // A list of servers that are known to also host or relay this image + gsize num_servers; + gchar **servers = g_key_file_get_string_list(gkf, groups[i], "servers", &num_servers, NULL); + if (servers) for (k = 0, j = 0; j < MIN(num_servers, NUMBER_SERVERS); ++j) + { + if (parse_address(servers[j], &(image->servers[k].addrtype), image->servers[k].ipaddr, &(image->servers[k].port))) + { + ++k; continue; + } + image->servers[k].addrtype = 0; } + g_strfreev(servers); - free(_images[i].group); - free(_images[i].file); - free(_images[i].servers); - free(_images[i].serverss); - free(_images[i].cache_file); - free(_images[i].cache_map); + if (image->cache_file) + { + // Determine size of cached image + fd = open(image->cache_file, O_RDONLY); + if (fd > 0) + { + image->filesize = lseek(fd, 0, SEEK_END); + close(fd); + } + if (image->filesize & 4095) + { // Cache files should always be trincated 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 = (image->filesize + (1 << 15) - 1) >> 15; + image->cache_map = calloc(map_len_bytes, sizeof(uint8_t)); + // read cache map from file + // one byte in the map covers 8 4kib blocks, so 32kib per byte + // "+ (1 << 15) - 1" is required to account for the last bit of + // the image that is smaller than 32kib + // this would be the case whenever the image file size is not a + // multiple of 32kib (= the number of blocks is not dividable by 8) + // ie: if the image is 49152 bytes and you do 49152 >> 15 you get 1, + // but you actually need 2 bytes to have a complete cache map + char tmp[strlen(image->cache_file)+4]; + strcpy(tmp, image->cache_file); + strcat(tmp, ".map"); + fd = open(tmp, O_RDONLY); + if (fd > 0) + { + read(fd, image->cache_map, map_len_bytes * sizeof(uint8_t)); + close(fd); + // 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 < k; ++j) last_byte = (last_byte << 1) | 1; + if ((image->cache_map[map_len_bytes-1] & last_byte) != last_byte) + image->working = 0; + else + memlogf("[INFO] Publishing relayed image '%s' because the local cache copy is complete", image->name); + } + + /* + // 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 + pthread_spin_lock(&_spinlock); + _dnbd3_images = g_slist_append(_dnbd3_images, image); + pthread_spin_unlock(&_spinlock); + // DONE IMAGE } - _num_images = 0; - free(_images); - dnbd3_load_config(config_file_name); - for (iterator = _dnbd3_clients; iterator; iterator = iterator->next) - { - dnbd3_client_t *client = iterator->data; - pthread_spin_unlock(&client->spinlock); - } - pthread_spin_unlock(&_spinlock); + g_free(namespace); + g_strfreev(groups); + g_key_file_free(gkf); } int dnbd3_add_image(dnbd3_image_t *image, char *file) { + return ERROR_IMAGE_ALREADY_EXISTS; // TODO: Make it work with image names + /* FILE* f = fopen(image->file,"r"); if (f == NULL) { @@ -198,10 +406,13 @@ int dnbd3_add_image(dnbd3_image_t *image, char *file) printf("ERROR: Config file is not writable: %s\n", file); return ERROR_CONFIG_FILE_PERMISSIONS; } + */ } int dnbd3_del_image(dnbd3_image_t *image, char *file) { + return ERROR_IMAGE_NOT_FOUND; // TODO: Make it work with image names + /* if (image->rid == 0) { printf("ERROR: Delete with rid=0 is not allowed\n"); @@ -254,38 +465,47 @@ int dnbd3_del_image(dnbd3_image_t *image, char *file) printf("ERROR: Config file is not writable: %s\n", file); return ERROR_CONFIG_FILE_PERMISSIONS; } + */ } -dnbd3_image_t* dnbd3_get_image(int vid, int rid) +dnbd3_image_t* dnbd3_get_image(char *name_orig, int rid, const char do_lock) { - int i, max = 0; - dnbd3_image_t *result = NULL; - for (i = 0; i < _num_images; ++i) + dnbd3_image_t *result = NULL, *image; + GSList *iterator; + char name[strlen(name_orig) + 1]; + strcpy(name, name_orig); + strtolower(name); + 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 (_images[i].vid == vid && _images[i].rid == rid) - result = &_images[i]; + if (image->rid == rid && strcmp(name, image->low_name) == 0) + { + result = image; + break; + } } else // search max. rid available { - if (_images[i].vid == vid && _images[i].rid > max) + if (strcmp(name, image->low_name) == 0 && (result == NULL || result->rid < image->rid)) { - result = &_images[i]; - max = _images[i].rid; + result = image; } } } + if (do_lock) pthread_spin_unlock(&_spinlock); return result; } void dnbd3_handle_sigpipe(int signum) { - printf("ERROR: SIGPIPE received!\n"); + memlogf("ERROR: SIGPIPE received!\n"); } void dnbd3_handle_sigterm(int signum) { - printf("INFO: SIGTERM or SIGINT received!\n"); + memlogf("INFO: SIGTERM or SIGINT received!\n"); dnbd3_cleanup(); } |