From 5b9b2174512d97399c68172bffe91cf928b1400a Mon Sep 17 00:00:00 2001 From: sr Date: Thu, 30 Aug 2012 00:36:54 +0200 Subject: [SERVER] Set _FILE_OFFSET_BITS=64 so that images >4GiB will be handled properly on 32bit [SERVER] Change IPC interface to be able to handle more than 1 request per connection [SERVER] Change IPC interface to use select() so it can handle multiple connections at the same time [SERVER] Re-Implement dnbd3_add_image() to work with image list [SERVER] Add lots of sanity/safety checks and error messages when loading/adding an image --- src/config.h | 2 +- src/server/ipc.c | 843 +++++++++++++++++++++++++++++++++------------------- src/server/ipc.h | 5 +- src/server/server.c | 11 +- src/server/server.h | 10 +- src/server/utils.c | 766 +++++++++++++++++++++++++---------------------- src/server/utils.h | 18 +- 7 files changed, 973 insertions(+), 682 deletions(-) (limited to 'src') diff --git a/src/config.h b/src/config.h index 1cef874..7a68f7a 100644 --- a/src/config.h +++ b/src/config.h @@ -42,7 +42,7 @@ #define SOCKET_TIMEOUT_CLIENT_DATA 2 #define SOCKET_TIMEOUT_CLIENT_DISCOVERY 1 -#define NUMBER_SERVERS 8 +#define NUMBER_SERVERS 8 // Number of alt servers per image/device #define RTT_THRESHOLD_FACTOR(us) (((us) * 2) / 3) // 2/3 = current to best must be 33% worse #define RTT_UNREACHABLE 0x7FFFFFFul // Use this value for timeout/unreachable as RTT. Don't set too high or you might get overflows. 0x7FFFFFF = 134 seconds // This must be a power of two: diff --git a/src/server/ipc.c b/src/server/ipc.c index 98584a6..e7474e8 100644 --- a/src/server/ipc.c +++ b/src/server/ipc.c @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include #include @@ -39,57 +41,76 @@ #include "utils.h" #include "memlog.h" -void* dnbd3_ipc_receive() -{ - GSList *iterator = NULL; +static int server_sock = -1; +static volatile int keep_running = 1; +static char *payload = NULL; - struct tm * timeinfo; - char time_buff[64], rid[20], ipaddr[100]; +static int ipc_receive(int client_sock); +static int get_highest_fd(GSList *sockets); +static int send_reply(int client_sock, void *data_in, int len); +static int recv_data(int client_sock, void *buffer_out, int len); - dnbd3_ipc_t header; - int server_sock, client_sock; +static int get_highest_fd(GSList *sockets) +{ + GSList *iterator; + int max = 0; - struct timeval timeout; - timeout.tv_sec = 2; - timeout.tv_usec = 0; + for (iterator = sockets; iterator; iterator = iterator->next) + { + const int fd = (int)(size_t)iterator->data; + if (fd > max) + max = fd; + } + return max; +} + +void *dnbd3_ipc_mainloop() +{ + payload = malloc(MAX_PAYLOAD); + if (payload == NULL) + { + memlogf("[CRITICAL] Couldn't allocate IPC payload buffer. IPC disabled."); + pthread_exit((void *)0); + return NULL; + } #ifdef IPC_TCP - struct sockaddr_in server, client; - unsigned int len = sizeof(client); - - // Create socket - if ((server_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) - { - perror("ERROR: IPC socket"); - exit(EXIT_FAILURE); - } - - memset(&server, 0, sizeof(server)); - server.sin_family = AF_INET; // IPv4 - server.sin_addr.s_addr = inet_addr("127.0.0.1"); - server.sin_port = htons(IPC_PORT); // set port number - - int optval = 1; - setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); - - // Bind to socket - if (bind(server_sock, (struct sockaddr*) &server, sizeof(server)) < 0) - { - perror("ERROR: IPC bind"); - exit(EXIT_FAILURE); - } - - // Listen on socket - if (listen(server_sock, 5) < 0) - { - perror("ERROR: IPC listen"); - exit(EXIT_FAILURE); - } + struct sockaddr_in server, client; + socklen_t len = sizeof(client); + + // Create socket + if ((server_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) + { + perror("ERROR: IPC socket"); + exit(EXIT_FAILURE); + } + + memset(&server, 0, sizeof(server)); + server.sin_family = AF_INET; // IPv4 + server.sin_addr.s_addr = INADDR_ANY; + server.sin_port = htons(IPC_PORT); // set port number + + const int optval = 1; + setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); + + // Bind to socket + if (bind(server_sock, (struct sockaddr *)&server, sizeof(server)) < 0) + { + perror("ERROR: IPC bind"); + exit(EXIT_FAILURE); + } + + // Listen on socket + if (listen(server_sock, 5) < 0) + { + perror("ERROR: IPC listen"); + exit(EXIT_FAILURE); + } #else - struct sockaddr_un server, client; - unsigned int len = sizeof(client); + struct sockaddr_un server, client; + socklen_t len = sizeof(client); - // Create socket + // Create socket if ((server_sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { perror("ERROR: IPC socket"); @@ -128,288 +149,481 @@ void* dnbd3_ipc_receive() } #endif - while (1) - { - int size; - char* buf; - xmlDocPtr doc; - xmlNodePtr root_node, images_node, clients_node, tmp_node, log_parent_node, log_node; - xmlChar *xmlbuff; - int buffersize; - - // Accept connection - if ((client_sock = accept(server_sock, &client, &len)) < 0) - { - perror("ERROR: IPC accept"); - exit(EXIT_FAILURE); - } - - setsockopt(client_sock, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(timeout)); - setsockopt(client_sock, SOL_SOCKET, SO_SNDTIMEO, (char *) &timeout, sizeof(timeout)); - - recv(client_sock, &header, sizeof(header), MSG_WAITALL); - header.cmd = ntohl(header.cmd); - header.size = ntohl(header.size); - header.error = ntohl(header.error); - - switch (header.cmd) - { - case IPC_EXIT: - memlogf("INFO: Server shutdown...\n"); - header.size = ntohl(0); - header.error = ntohl(0); - send(client_sock, (char *) &header, sizeof(header), MSG_WAITALL); - close(client_sock); - close(server_sock); - dnbd3_cleanup(); - break; - - case IPC_RELOAD: - header.size = ntohl(0); - header.error = ntohl(ERROR_UNKNOWN); - send(client_sock, (char *) &header, sizeof(header), MSG_WAITALL); - close(client_sock); - break; - - case IPC_INFO: - doc = xmlNewDoc(BAD_CAST "1.0"); - root_node = xmlNewNode(NULL, BAD_CAST "info"); - xmlDocSetRootElement(doc, root_node); - - // Images - images_node = xmlNewNode(NULL, BAD_CAST "images"); - xmlAddChild(root_node, images_node); - pthread_spin_lock(&_spinlock); - for (iterator = _dnbd3_images; iterator; iterator = iterator->next) - { - const dnbd3_image_t *image = iterator->data; - sprintf(rid,"%d",image->rid); - timeinfo = localtime(&image->atime); - strftime(time_buff,64,"%d.%m.%y %H:%M:%S",timeinfo); - tmp_node = xmlNewNode(NULL, BAD_CAST "image"); - xmlNewProp(tmp_node, BAD_CAST "name", BAD_CAST image->name); - xmlNewProp(tmp_node, BAD_CAST "atime", BAD_CAST time_buff); - xmlNewProp(tmp_node, BAD_CAST "rid", BAD_CAST rid); - xmlNewProp(tmp_node, BAD_CAST "file", BAD_CAST image->file); - xmlNewProp(tmp_node, BAD_CAST "servers", BAD_CAST "???"); - xmlNewProp(tmp_node, BAD_CAST "cache", BAD_CAST image->cache_file); - xmlAddChild(images_node, tmp_node); + // Run connection-accepting loop + + fd_set all_sockets, readset, exceptset; + + GSList *sockets = NULL, *iterator; + + int client_sock, ret, flags; + int maxfd = server_sock + 1; + int error_count = 0; + + struct timeval client_timeout, select_timeout; + client_timeout.tv_sec = 0; + client_timeout.tv_usec = 500 * 1000; + + FD_ZERO(&all_sockets); + FD_SET(server_sock, &all_sockets); + + // Make listening socket non-blocking + flags = fcntl(server_sock, F_GETFL, 0); + if (flags == -1) + flags = 0; + fcntl(server_sock, F_SETFL, flags | O_NONBLOCK); + + xmlInitParser(); + + while (keep_running) + { + readset = exceptset = all_sockets; + select_timeout.tv_sec = 4; + select_timeout.tv_usec = 0; + ret = select(maxfd, &readset, NULL, &exceptset, &select_timeout); + while (ret > 0) + { + --ret; + if (FD_ISSET(server_sock, &readset)) + { + // Accept connection + if ((client_sock = accept(server_sock, &client, &len)) < 0) + { + if (errno != EAGAIN) + { + memlogf("[ERROR] Error accepting an IPC connection"); + if (++error_count > 10) + goto end_loop; + } + continue; + } + error_count = 0; + // Apply read/write timeout + setsockopt(client_sock, SOL_SOCKET, SO_RCVTIMEO, &client_timeout, sizeof(client_timeout)); + setsockopt(client_sock, SOL_SOCKET, SO_SNDTIMEO, &client_timeout, sizeof(client_timeout)); + // Make new connection blocking + flags = fcntl(client_sock, F_GETFL, 0); + if (flags == -1) + flags = 0; + fcntl(client_sock, F_SETFL, flags & ~(int)O_NONBLOCK); + sockets = g_slist_prepend(sockets, (void *)(size_t)client_sock); + if (client_sock >= maxfd) + maxfd = client_sock + 1; + FD_SET(client_sock, &all_sockets); } - // Clients - clients_node = xmlNewNode(NULL, BAD_CAST "clients"); - log_node = xmlAddChild(root_node, clients_node); - for (iterator = _dnbd3_clients; iterator; iterator = iterator->next) + else if (FD_ISSET(server_sock, &exceptset)) { - dnbd3_client_t *client = iterator->data; - if (client->image) + memlogf("[ERROR] An exception occurred on the IPC listening socket."); + if (++error_count > 10) + goto end_loop; + } + else + { + // Must be an active IPC connection + int del = -1; + for (iterator = sockets; iterator; iterator = iterator->next) + { + if (del != -1) + { + // Delete a previously closed connection from list (delayed, otherwise list might get messed up) + sockets = g_slist_remove(sockets, (void *)(size_t)del); + del = -1; + maxfd = get_highest_fd(sockets) + 1; + } + client_sock = (int)(size_t)iterator->data; + if (FD_ISSET(client_sock, &readset)) + { + // Client sending data + if (!ipc_receive(client_sock)) + { + // Connection has been closed + close(client_sock); + del = client_sock; + FD_CLR(client_sock, &all_sockets); + } + } + else if (FD_ISSET(client_sock, &exceptset)) + { + // Something unexpected happened, just close connection + close(client_sock); + del = client_sock; + FD_CLR(client_sock, &all_sockets); + } + } + if (del != -1) { - tmp_node = xmlNewNode(NULL, BAD_CAST "client"); - *ipaddr = '\0'; - inet_ntop(client->addrtype, client->ipaddr, ipaddr, 100); - xmlNewProp(tmp_node, BAD_CAST "ip", BAD_CAST ipaddr); - xmlNewProp(tmp_node, BAD_CAST "file", BAD_CAST client->image->file); - xmlAddChild(clients_node, tmp_node); + // In case last socket was closed during iteration + sockets = g_slist_remove(sockets, (void *)(size_t)del); + maxfd = get_highest_fd(sockets) + 1; } } + } // End select loop + } // End mainloop + +end_loop: + memlogf("[INFO] Shutting down IPC interface."); + if (server_sock != -1) + { + close(server_sock); + server_sock = -1; + } + + free(payload); + xmlCleanupParser(); + pthread_exit((void *)0); + return NULL; +} + +void dnbd3_ipc_shutdown() +{ + keep_running = 0; + if (server_sock == -1) + return; + close(server_sock); + server_sock = -1; +} + +/** + * Send message to client, return !=0 on success, 0 on failure + */ +static int send_reply(int client_sock, void *data_in, int len) +{ + if (len <= 0) // Nothing to send + return 1; + char *data = data_in; // Needed for pointer arithmetic + int ret, i; + for (i = 0; i < 3; ++i) // Retry at most 3 times, each try takes at most 0.5 seconds (socket timeout) + { + ret = send(client_sock, data, len, 0); + if (ret == 0) // Connection closed + return 0; + if (ret < 0) + { + if (errno != EAGAIN) // Some unexpected error + return 0; + usleep(1000); // 1ms + continue; + } + len -= ret; + if (len <= 0) // Sent everything + return 1; + data += ret; // move target buffer pointer + } + return 0; +} + +/** + * Receive data from client, return !=0 on success, 0 on failure + */ +static int recv_data(int client_sock, void *buffer_out, int len) +{ + if (len <= 0) // Nothing to receive + return 1; + char *data = buffer_out; // Needed for pointer arithmetic + int ret, i; + for (i = 0; i < 3; ++i) // Retry at most 3 times, each try takes at most 0.5 seconds (socket timeout) + { + ret = recv(client_sock, data, len, MSG_WAITALL); + if (ret == 0) // Connection closed + return 0; + if (ret < 0) + { + if (errno != EAGAIN) // Some unexpected error + return 0; + usleep(1000); // 1ms + continue; + } + len -= ret; + if (len <= 0) // Received everything + return 1; + data += ret; // move target buffer pointer + } + return 0; +} + +/** + * Returns !=0 if send/recv successful, 0 on any kind of network failure + */ +static int ipc_receive(int client_sock) +{ + GSList *iterator = NULL; + + struct tm *timeinfo; + char time_buff[64], rid[20], ipaddr[100]; + + dnbd3_ipc_t header; + + uint32_t cmd; + + int ret, locked; + int return_value = 0; + xmlDocPtr doc = NULL; + xmlNodePtr root_node, images_node, clients_node, tmp_node, log_parent_node, log_node; + xmlChar *xmlbuff; + int buffersize; + + ret = recv(client_sock, &header, sizeof(header), MSG_WAITALL); + if (ret != sizeof(header)) + return ((ret < 0 && errno == EAGAIN) ? 1 : 0); + cmd = ntohl(header.cmd); // Leave header.cmd in network byte order for reply + header.size = ntohl(header.size); + + header.error = htonl(ERROR_UNSPECIFIED_ERROR); // Default value of error, so remember to set it for the reply if call succeeded + + if (header.size != 0) + { + // Message has payload, receive it + if (header.size > MAX_PAYLOAD) + { + memlogf("[WARNING] IPC command with payload of %u bytes ignored.", (unsigned int)header.size); + return 0; + } + if (!recv_data(client_sock, payload, header.size)) + return 0; + } + + switch (cmd) + { + case IPC_EXIT: + memlogf("[INFO] Server shutdown by IPC request"); + header.size = ntohl(0); + header.error = ntohl(0); + return_value = send_reply(client_sock, &header, sizeof(header)); + dnbd3_cleanup(); + break; + + case IPC_INFO: + locked = 0; + xmlbuff = NULL; + doc = xmlNewDoc(BAD_CAST "1.0"); + if (doc == NULL) + goto get_info_reply_cleanup; + root_node = xmlNewNode(NULL, BAD_CAST "info"); + if (root_node == NULL) + goto get_info_reply_cleanup; + xmlDocSetRootElement(doc, root_node); + + // Images + images_node = xmlNewNode(NULL, BAD_CAST "images"); + if (images_node == NULL) + goto get_info_reply_cleanup; + xmlAddChild(root_node, images_node); + locked = 1; + pthread_spin_lock(&_spinlock); + for (iterator = _dnbd3_images; iterator; iterator = iterator->next) + { + const dnbd3_image_t *image = iterator->data; + sprintf(rid, "%d", image->rid); + timeinfo = localtime(&image->atime); + strftime(time_buff, 64, "%d.%m.%y %H:%M:%S", timeinfo); + tmp_node = xmlNewNode(NULL, BAD_CAST "image"); + if (tmp_node == NULL) + goto get_info_reply_cleanup; + xmlNewProp(tmp_node, BAD_CAST "name", BAD_CAST image->name); + xmlNewProp(tmp_node, BAD_CAST "atime", BAD_CAST time_buff); + xmlNewProp(tmp_node, BAD_CAST "rid", BAD_CAST rid); + xmlNewProp(tmp_node, BAD_CAST "file", BAD_CAST image->file); + xmlNewProp(tmp_node, BAD_CAST "servers", BAD_CAST "???"); + xmlNewProp(tmp_node, BAD_CAST "cache", BAD_CAST image->cache_file); + xmlAddChild(images_node, tmp_node); + } + // Clients + clients_node = xmlNewNode(NULL, BAD_CAST "clients"); + if (clients_node == NULL) + goto get_info_reply_cleanup; + xmlAddChild(root_node, clients_node); + for (iterator = _dnbd3_clients; iterator; iterator = iterator->next) + { + dnbd3_client_t *client = iterator->data; + if (client->image) + { + tmp_node = xmlNewNode(NULL, BAD_CAST "client"); + if (tmp_node == NULL) + goto get_info_reply_cleanup; + *ipaddr = '\0'; + inet_ntop(client->addrtype, client->ipaddr, ipaddr, 100); + xmlNewProp(tmp_node, BAD_CAST "ip", BAD_CAST ipaddr); + xmlNewProp(tmp_node, BAD_CAST "file", BAD_CAST client->image->file); + xmlAddChild(clients_node, tmp_node); + } + } + pthread_spin_unlock(&_spinlock); + locked = 0; + + // Log + log_parent_node = xmlNewChild(root_node, NULL, BAD_CAST "log", NULL); + if (log_parent_node == NULL) + goto get_info_reply_cleanup; + char *log = fetchlog(0); + if (log == NULL) + log = "LOG IS NULL"; + log_node = xmlNewCDataBlock(doc, BAD_CAST log, strlen(log)); + if (log_node == NULL) + goto get_info_reply_cleanup; + xmlAddChild(log_parent_node, log_node); + + // Dump and send + xmlDocDumpFormatMemory(doc, &xmlbuff, &buffersize, 1); + header.size = htonl(buffersize); + header.error = htonl(0); + +get_info_reply_cleanup: + if (locked) pthread_spin_unlock(&_spinlock); + // Send reply + return_value = send_reply(client_sock, &header, sizeof(header)); + if (return_value && xmlbuff) + return_value = send_reply(client_sock, xmlbuff, buffersize); + // Cleanup + xmlFree(xmlbuff); + xmlFreeDoc(doc); + free(log); + break; + + case IPC_ADDIMG: + case IPC_DELIMG: + if (header.size == 0) + { + header.size = htonl(0); + header.error = htonl(ERROR_MISSING_ARGUMENT); + return_value = send_reply(client_sock, &header, sizeof(header)); + break; + } + doc = xmlReadMemory(payload, header.size, "noname.xml", NULL, 0); - // Log - log_parent_node = xmlNewChild(root_node, NULL, BAD_CAST "log", NULL); - char *log = fetchlog(0); - if (log == NULL) log = "LOG IS NULL"; - log_node = xmlNewCDataBlock(doc, BAD_CAST log, strlen(log)); - xmlAddChild(log_parent_node, log_node); - - // Dump and send - xmlDocDumpFormatMemory(doc, &xmlbuff, &buffersize, 1); - header.size = htonl(buffersize); - header.error = htonl(0); - send(client_sock, (char *) &header, sizeof(header), MSG_WAITALL); - send(client_sock, (char *) xmlbuff, buffersize, MSG_WAITALL); - - // Cleanup - close(client_sock); - xmlFree(xmlbuff); - xmlFreeDoc(doc); - free(log); - break; - - case IPC_ADDIMG: - pthread_spin_lock(&_spinlock); - - // Parse reply - buf = malloc(header.size); - size = recv(client_sock, buf, header.size, MSG_WAITALL); - doc = xmlReadMemory(buf, size, "noname.xml", NULL, 0); - - if (doc) - { -// xmlDocDump(stdout, doc_config); - - xmlXPathContextPtr xpathCtx; - xmlXPathObjectPtr xpathObj; - xmlChar* xpathExpr; - xmlNodeSetPtr nodes; - xmlNodePtr cur; - - xpathExpr = BAD_CAST "/info/images/image"; - xpathCtx = xmlXPathNewContext(doc); - xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx); - - nodes = xpathObj->nodesetval; - cur = nodes->nodeTab[0]; - if(cur->type == XML_ELEMENT_NODE) - { - dnbd3_image_t image; - memset(&image, 0, sizeof(dnbd3_image_t)); - image.name = (char *) xmlGetNoNsProp(cur, BAD_CAST "name"); - image.rid = atoi((char *) xmlGetNoNsProp(cur, BAD_CAST "rid")); - image.file = (char *) xmlGetNoNsProp(cur, BAD_CAST "file"); - image.cache_file = (char *) xmlGetNoNsProp(cur, BAD_CAST "cache"); - header.error = htonl(dnbd3_add_image(&image, _config_file_name)); - } + if (doc) + { + xmlXPathContextPtr xpathCtx = NULL; + xmlXPathObjectPtr xpathObj = NULL; + xmlNodeSetPtr nodes = NULL; + xmlNodePtr cur = NULL; - xmlXPathFreeObject(xpathObj); - xmlXPathFreeContext(xpathCtx); - } - - header.size = htonl(0); - send(client_sock, (char *) &header, sizeof(header), MSG_WAITALL); - - // Cleanup - pthread_spin_unlock(&_spinlock); - close(client_sock); - xmlFreeDoc(doc); - xmlCleanupParser(); - free(buf); - break; - - case IPC_DELIMG: - pthread_spin_lock(&_spinlock); - - // Parse reply - buf = malloc(header.size); - size = recv(client_sock, buf, header.size, MSG_WAITALL); - doc = xmlReadMemory(buf, size, "noname.xml", NULL, 0); - - if (doc) - { -// xmlDocDump(stdout, doc_config); - - xmlXPathContextPtr xpathCtx; - xmlXPathObjectPtr xpathObj; - xmlChar* xpathExpr; - xmlNodeSetPtr nodes; - xmlNodePtr cur; - - xpathExpr = BAD_CAST "/info/images/image"; - xpathCtx = xmlXPathNewContext(doc); - xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx); - - nodes = xpathObj->nodesetval; - cur = nodes->nodeTab[0]; - if(cur->type == XML_ELEMENT_NODE) + xpathCtx = xmlXPathNewContext(doc); + if (xpathCtx == NULL) + goto add_del_cleanup; + xpathObj = xmlXPathEvalExpression(BAD_CAST "/info/images/image", xpathCtx); + if (xpathObj == NULL) + goto add_del_cleanup; + nodes = xpathObj->nodesetval; + if (nodes == NULL || nodes->nodeNr < 1) + goto add_del_cleanup; + cur = nodes->nodeTab[0]; + if (cur->type == XML_ELEMENT_NODE) + { + dnbd3_image_t image; + memset(&image, 0, sizeof(dnbd3_image_t)); + image.name = (char *)xmlGetNoNsProp(cur, BAD_CAST "name"); + char *rid_str = (char *)xmlGetNoNsProp(cur, BAD_CAST "rid"); + image.file = (char *)xmlGetNoNsProp(cur, BAD_CAST "file"); + image.cache_file = (char *)xmlGetNoNsProp(cur, BAD_CAST "cache"); + if (image.name && rid_str && image.file && image.cache_file) { - dnbd3_image_t image; - memset(&image, 0, sizeof(dnbd3_image_t)); - image.name = (char *) xmlGetNoNsProp(cur, BAD_CAST "name"); - image.rid = atoi((char *) xmlGetNoNsProp(cur, BAD_CAST "rid")); - image.file = (char *) xmlGetNoNsProp(cur, BAD_CAST "file"); - image.cache_file = (char *) xmlGetNoNsProp(cur, BAD_CAST "cache"); - header.error = htonl(dnbd3_del_image(&image, _config_file_name)); + image.rid = atoi(rid_str); + if (cmd == IPC_ADDIMG) + header.error = htonl(dnbd3_add_image(&image, _config_file_name)); + else + header.error = htonl(dnbd3_del_image(&image, _config_file_name)); } + else + header.error = htonl(ERROR_MISSING_ARGUMENT); + xmlFree(image.name); + xmlFree(rid_str); + xmlFree(image.file); + xmlFree(image.cache_file); + } + else + header.error = htonl(ERROR_MISSING_ARGUMENT); - xmlXPathFreeObject(xpathObj); - xmlXPathFreeContext(xpathCtx); - } - - header.size = htonl(0); - send(client_sock, (char *) &header, sizeof(header), MSG_WAITALL); - - // Cleanup - pthread_spin_unlock(&_spinlock); - close(client_sock); - xmlFreeDoc(doc); - xmlCleanupParser(); - free(buf); - break; - - default: - memlogf("ERROR: Unknown command: %i\n", header.cmd); - header.size = htonl(0); - header.error = htonl(ERROR_UNKNOWN); - send(client_sock, (char *) &header, sizeof(header), MSG_WAITALL); - close(client_sock); - break; - - } - } - close(server_sock); - pthread_exit((void *) 0); +add_del_cleanup: + xmlXPathFreeObject(xpathObj); + xmlXPathFreeContext(xpathCtx); + xmlFreeDoc(doc); + } + else + header.error = htonl(ERROR_INVALID_XML); + + header.size = htonl(0); + return_value = send_reply(client_sock, &header, sizeof(header)); + break; + + default: + memlogf("[ERROR] Unknown IPC command: %u", (unsigned int)header.cmd); + header.size = htonl(0); + header.error = htonl(ERROR_UNKNOWN_COMMAND); + return_value = send_reply(client_sock, &header, sizeof(header)); + break; + + } + return return_value; } void dnbd3_ipc_send(int cmd) { - int client_sock, size; + int client_sock, size; #ifdef IPC_TCP - struct sockaddr_in server; - - // Create socket - if ((client_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) - { - perror("ERROR: IPC socket"); - exit(EXIT_FAILURE); - } - - memset(&server, 0, sizeof(server)); - server.sin_family = AF_INET; // IPv4 - server.sin_addr.s_addr = inet_addr("127.0.0.1"); - server.sin_port = htons(IPC_PORT); // set port number - - // Connect to server - if (connect(client_sock, (struct sockaddr *) &server, sizeof(server)) < 0) - { - perror("ERROR: IPC connect"); - exit(EXIT_FAILURE); - } + struct sockaddr_in server; + struct timeval client_timeout; + + // Create socket + if ((client_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) + { + perror("ERROR: IPC socket"); + exit(EXIT_FAILURE); + } + + client_timeout.tv_sec = 4; + client_timeout.tv_usec = 0; + setsockopt(client_sock, SOL_SOCKET, SO_RCVTIMEO, &client_timeout, sizeof(client_timeout)); + setsockopt(client_sock, SOL_SOCKET, SO_SNDTIMEO, &client_timeout, sizeof(client_timeout)); + + memset(&server, 0, sizeof(server)); + server.sin_family = AF_INET; // IPv4 + server.sin_addr.s_addr = inet_addr("127.0.0.1"); + server.sin_port = htons(IPC_PORT); // set port number + + // Connect to server + if (connect(client_sock, (struct sockaddr *)&server, sizeof(server)) < 0) + { + perror("ERROR: IPC connect"); + exit(EXIT_FAILURE); + } #else - struct sockaddr_un server; - - // Create socket - if ((client_sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) - { - perror("ERROR: IPC socket"); - exit(EXIT_FAILURE); - } - server.sun_family = AF_UNIX; - strcpy(server.sun_path, UNIX_SOCKET); - - // Connect to server - if (connect(client_sock, &server, sizeof(server.sun_family) + strlen(server.sun_path)) < 0) - { - perror("ERROR: IPC connect"); - exit(EXIT_FAILURE); - } + struct sockaddr_un server; + + // Create socket + if ((client_sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + { + perror("ERROR: IPC socket"); + exit(EXIT_FAILURE); + } + server.sun_family = AF_UNIX; + strcpy(server.sun_path, UNIX_SOCKET); + + // Connect to server + if (connect(client_sock, &server, sizeof(server.sun_family) + strlen(server.sun_path)) < 0) + { + perror("ERROR: IPC connect"); + exit(EXIT_FAILURE); + } #endif - // Send message - dnbd3_ipc_t header; - header.cmd = htonl(cmd); - header.size = 0; - header.error = 0; - send(client_sock, (char *) &header, sizeof(header), MSG_WAITALL); - recv(client_sock, &header, sizeof(header), MSG_WAITALL); - header.cmd = ntohl(header.cmd); - header.size = ntohl(header.size); - header.error = ntohl(header.error); - - if (cmd == IPC_INFO && header.size > 0) - { - char* buf = malloc(header.size+1); - size = recv(client_sock, buf, header.size, MSG_WAITALL); - xmlDocPtr doc = xmlReadMemory(buf, size, "noname.xml", NULL, 0); - buf[header.size] = 0; + // Send message + dnbd3_ipc_t header; + header.cmd = htonl(cmd); + header.size = 0; + header.error = 0; + send(client_sock, (char *)&header, sizeof(header), MSG_WAITALL); + recv(client_sock, &header, sizeof(header), MSG_WAITALL); + header.cmd = ntohl(header.cmd); + header.size = ntohl(header.size); + header.error = ntohl(header.error); + + if (cmd == IPC_INFO && header.size > 0) + { + char *buf = malloc(header.size + 1); + size = recv(client_sock, buf, header.size, MSG_WAITALL); + xmlDocPtr doc = xmlReadMemory(buf, size, "noname.xml", NULL, 0); + buf[header.size] = 0; if (doc) { @@ -417,7 +631,7 @@ void dnbd3_ipc_send(int cmd) xmlXPathContextPtr xpathCtx; xmlXPathObjectPtr xpathObj; - xmlChar* xpathExpr; + xmlChar *xpathExpr; xmlNodeSetPtr nodes; xmlNodePtr cur; @@ -425,7 +639,8 @@ void dnbd3_ipc_send(int cmd) xpathExpr = BAD_CAST "/info/log"; xpathCtx = xmlXPathNewContext(doc); xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx); - if (xpathObj->nodesetval && xpathObj->nodesetval->nodeTab && xpathObj->nodesetval->nodeTab[0]) { + if (xpathObj->nodesetval && xpathObj->nodesetval->nodeTab && xpathObj->nodesetval->nodeTab[0]) + { printf("--- Last log lines ----\n%s\n\n", xmlNodeGetContent(xpathObj->nodesetval->nodeTab[0])); } xmlXPathFreeObject(xpathObj); @@ -439,9 +654,9 @@ void dnbd3_ipc_send(int cmd) printf("========================================\n"); nodes = xpathObj->nodesetval; n = (nodes) ? nodes->nodeNr : 0; - for(i = 0; i < n; ++i) + for (i = 0; i < n; ++i) { - if(nodes->nodeTab[i]->type == XML_ELEMENT_NODE) + if (nodes->nodeTab[i]->type == XML_ELEMENT_NODE) { cur = nodes->nodeTab[i]; xmlChar *atime = xmlGetNoNsProp(cur, BAD_CAST "atime"); @@ -463,9 +678,9 @@ void dnbd3_ipc_send(int cmd) printf("=============================\n"); nodes = xpathObj->nodesetval; n = (nodes) ? nodes->nodeNr : 0; - for(i = 0; i < n; ++i) + for (i = 0; i < n; ++i) { - if(nodes->nodeTab[i]->type == XML_ELEMENT_NODE) + if (nodes->nodeTab[i]->type == XML_ELEMENT_NODE) { cur = nodes->nodeTab[i]; xmlChar *ip = xmlGetNoNsProp(cur, BAD_CAST "ip"); @@ -489,7 +704,7 @@ void dnbd3_ipc_send(int cmd) printf("ERROR: Failed to parse reply\n-----------\n%s\n-------------\n", buf); } - } + } - close(client_sock); + close(client_sock); } diff --git a/src/server/ipc.h b/src/server/ipc.h index 4a405ee..d4ec7db 100644 --- a/src/server/ipc.h +++ b/src/server/ipc.h @@ -27,7 +27,9 @@ #define IPC_ADDIMG 3 #define IPC_DELIMG 4 -void* dnbd3_ipc_receive(); +void* dnbd3_ipc_mainloop(); + +void dnbd3_ipc_shutdown(); void dnbd3_ipc_send(int cmd); @@ -35,6 +37,7 @@ void dnbd3_ipc_send(int cmd); #pragma pack(1) typedef struct { + uint32_t handle;// 4byte uint32_t cmd; // 4byte uint32_t size; // 4byte uint32_t error; // 4byte diff --git a/src/server/server.c b/src/server/server.c index 965c368..10a1d2f 100644 --- a/src/server/server.c +++ b/src/server/server.c @@ -44,7 +44,8 @@ pthread_spinlock_t _spinlock; GSList *_dnbd3_clients = NULL; char *_config_file_name = DEFAULT_SERVER_CONFIG_FILE; -GSList *_dnbd3_images; // of dnbd3_image_t +char *_local_namespace = NULL; +GSList *_dnbd3_images = NULL; // of dnbd3_image_t void dnbd3_print_help(char* argv_0) { @@ -76,6 +77,8 @@ void dnbd3_cleanup() close(_sock); + dnbd3_ipc_shutdown(); + pthread_spin_lock(&_spinlock); GSList *iterator = NULL; for (iterator = _dnbd3_clients; iterator; iterator = iterator->next) @@ -209,9 +212,9 @@ int main(int argc, char* argv[]) timeout.tv_sec = SOCKET_TIMEOUT_SERVER; timeout.tv_usec = 0; - // setup icp + // setup ipc pthread_t thread_ipc; - pthread_create(&(thread_ipc), NULL, dnbd3_ipc_receive, NULL); + pthread_create(&(thread_ipc), NULL, &dnbd3_ipc_mainloop, NULL); memlogf("[INFO] Server is ready..."); @@ -246,7 +249,7 @@ int main(int argc, char* argv[]) // This has to be done before creating the thread, otherwise a race condition might occur when the new thread dies faster than this thread adds the client to the list after creating the thread pthread_spin_lock(&_spinlock); - _dnbd3_clients = g_slist_append(_dnbd3_clients, dnbd3_client); + _dnbd3_clients = g_slist_prepend(_dnbd3_clients, dnbd3_client); pthread_spin_unlock(&_spinlock); if (0 != pthread_create(&(dnbd3_client->thread), NULL, dnbd3_handle_query, (void *) (uintptr_t) dnbd3_client)) diff --git a/src/server/server.h b/src/server/server.h index 7e19f27..875a5af 100644 --- a/src/server/server.h +++ b/src/server/server.h @@ -25,12 +25,12 @@ #include #include -#include "config.h" +#include "../config.h" #include "../types.h" typedef struct { - char *name; // full name of image, eg. "uni-freiburg.ubuntu-12.04" + char *name; // full name of image, eg. "uni-freiburg/rz/ubuntu-12.04" char *low_name; // full name of image, lowercased for comparison int rid; // revision of provided image char *file; // path to image file or device @@ -53,7 +53,7 @@ typedef struct extern GSList *_dnbd3_clients; // of dnbd3_client_t extern pthread_spinlock_t _spinlock; -extern char *_config_file_name; +extern char *_config_file_name, *_local_namespace; extern GSList *_dnbd3_images; // of dnbd3_image_t @@ -63,4 +63,8 @@ extern int _fake_delay; void dnbd3_cleanup(); +#if !defined(_FILE_OFFSET_BITS) || _FILE_OFFSET_BITS != 64 +#error Please set _FILE_OFFSET_BITS to 64 in your makefile/configuration +#endif + #endif /* SERVER_H_ */ diff --git a/src/server/utils.c b/src/server/utils.c index 3fe1900..d6e7a61 100644 --- a/src/server/utils.c +++ b/src/server/utils.c @@ -31,6 +31,12 @@ #include "utils.h" #include "memlog.h" +static char parse_address(char *string, uint8_t *af, uint8_t *addr, uint16_t *port); +static char is_valid_namespace(char *namespace); +static char is_valid_imagename(char *namespace); +static void strtolower(char *string); +static dnbd3_image_t *prepare_image(char *image_name, int rid, char *image_file, char *cache_file, gchar **servers, gsize num_servers); + /** * 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" @@ -65,19 +71,22 @@ static char parse_address(char *string, uint8_t *af, uint8_t *addr, uint16_t *po char *portpos = NULL, *ptr = string; while (*ptr) { - if (*ptr == ':') portpos = ptr; + if (*ptr == ':') + portpos = ptr; ++ptr; } - if (portpos == NULL) return 0; // No port in string + if (portpos == NULL) + return 0; // No port in string // Consider IP being surrounded by [ ] - if (*string == '[' && *(portpos-1) == ']') + if (*string == '[' && *(portpos - 1) == ']') { ++string; - *(portpos-1) = '\0'; + *(portpos - 1) = '\0'; } *portpos++ = '\0'; int p = atoi(portpos); - if (p < 1 || p > 65535) return 0; // Invalid port + if (p < 1 || p > 65535) + return 0; // Invalid port *port = htons((uint16_t)p); // Try IPv4 with port @@ -101,30 +110,33 @@ static char parse_address(char *string, uint8_t *af, uint8_t *addr, uint16_t *po static char is_valid_namespace(char *namespace) { - if (*namespace == '\0' || *namespace == '/') return 0; // Invalid: Length = 0 or starting with a slash + 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; + if (*namespace != '/' && *namespace != '-' && (*namespace < 'a' || *namespace > 'z') + && (*namespace < 'A' || *namespace > 'Z')) + return 0; ++namespace; } - if (*(namespace - 1) == '/') return 0; // Invalid: Ends in a slash + 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 + 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; + 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 + if (*(namespace - 1) == ' ') + return 0; // Invalid: Ends in a space return 1; } @@ -132,380 +144,430 @@ static void strtolower(char *string) { while (*string) { - if (*string >= 'A' && *string <= 'Z') *string += 32; + if (*string >= 'A' && *string <= 'Z') + *string += 32; ++string; } } -void dnbd3_load_config(char *file) +void dnbd3_load_config() { - int fd; - gint i, j, k; - GKeyFile* gkf; - - gkf = g_key_file_new(); - if (!g_key_file_load_from_file(gkf, file, G_KEY_FILE_NONE, NULL)) - { - printf("ERROR: Config file not found: %s\n", 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; - gsize section_count; - groups = g_key_file_get_groups(gkf, §ion_count); - - for (i = 0; i < section_count; i++) - { - // 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 - { - image->name = strdup(groups[i]); - } - - 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 - { - 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); - } - } - - // 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].hostaddrtype), image->servers[k].hostaddr, &(image->servers[k].port))) - { - ++k; continue; - } - image->servers[k].hostaddrtype = 0; - } - g_strfreev(servers); - - 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); - } + gint i; + GKeyFile* gkf; - /* - // 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 (_local_namespace != NULL || _dnbd3_images != NULL) + { + printf("dnbd3_load_config() called more than once\n\n"); + exit(EXIT_FAILURE); + } - if (_images[i].filesize != lseek(fd, 0, SEEK_END)) - fallocate(fd, 0, 0, _images[i].filesize); + gkf = g_key_file_new(); + if (!g_key_file_load_from_file(gkf, _config_file_name, G_KEY_FILE_NONE, NULL)) + { + printf("ERROR: Config file not found: %s\n", _config_file_name); + exit(EXIT_FAILURE); + } - close(fd); - */ - } - } // end cache_file handling - pthread_spin_lock(&_spinlock); - _dnbd3_images = g_slist_append(_dnbd3_images, image); - pthread_spin_unlock(&_spinlock); - // DONE IMAGE - } - - g_free(namespace); - g_strfreev(groups); - g_key_file_free(gkf); -} + _local_namespace = g_key_file_get_string(gkf, "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; + } -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) + gchar **groups = NULL; + gsize section_count; + groups = g_key_file_get_groups(gkf, §ion_count); + + for (i = 0; i < section_count; i++) { - printf("ERROR: Image file not found: %s\n", image->file); - return ERROR_FILE_NOT_FOUND; + // Special group + if (strcmp(groups[i], "settings") == 0 || strcmp(groups[i], "trusted") == 0) + { + continue; + } + + // An actual image definition + + 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; + } + + char *image_file = g_key_file_get_string(gkf, groups[i], "file", NULL); + char *cache_file = g_key_file_get_string(gkf, groups[i], "cache", NULL); + gsize num_servers; + gchar **servers = g_key_file_get_string_list(gkf, groups[i], "servers", &num_servers, NULL); + + pthread_spin_lock(&_spinlock); + dnbd3_image_t *image = prepare_image(groups[i], rid, image_file, cache_file, servers, num_servers); + if (image) + { + _dnbd3_images = g_slist_prepend(_dnbd3_images, image); + } + pthread_spin_unlock(&_spinlock); + + g_free(image_file); + g_free(cache_file); + g_strfreev(servers); } - fclose (f); - dnbd3_image_t* tmp = dnbd3_get_image(image->vid, image->rid); + g_strfreev(groups); + g_key_file_free(gkf); +} + +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) - { - if(tmp) - image->rid = tmp->rid +1; + { // TODO: globalize image->name somewhere for this call + const dnbd3_image_t *latest = dnbd3_get_image(image->name, image->rid, 0); + if (latest) + image->rid = latest->rid + 1; else image->rid = 1; - } else if (tmp) + } + + dnbd3_image_t *newimage = prepare_image(image->name, image->rid, image->file, image->cache_file, NULL, 0); + if (newimage) + { + _dnbd3_images = g_slist_prepend(_dnbd3_images, image); + } + else { - printf("ERROR: Image already exists (%d,%d)\n", image->vid, image->rid); - return ERROR_IMAGE_ALREADY_EXISTS; + pthread_spin_unlock(&_spinlock); + return ERROR_SEE_LOG; } - GKeyFile* gkf; - gkf = g_key_file_new(); - if (!g_key_file_load_from_file(gkf, file, G_KEY_FILE_NONE, NULL)) - { - printf("ERROR: Config file not found: %s\n", file); - exit(EXIT_FAILURE); - } - - g_key_file_set_integer(gkf, image->group, "vid", image->vid); - g_key_file_set_integer(gkf, image->group, "rid", image->rid); - g_key_file_set_string(gkf, image->group, "file", image->file); - g_key_file_set_string(gkf, image->group, "servers", image->serverss); - g_key_file_set_string(gkf, image->group, "cache", image->cache_file); - - gchar* data = g_key_file_to_data(gkf, NULL, NULL); - - f = fopen(file,"w"); - if (f) - { - fputs((char*) data, f); - fclose(f); - g_free(data); - g_key_file_free(gkf); - return 0; - } - else - { - g_free(data); - g_key_file_free(gkf); - printf("ERROR: Config file is not writable: %s\n", file); - return ERROR_CONFIG_FILE_PERMISSIONS; - } - */ + // Adding image was successful, write config file + GKeyFile* gkf; + gkf = g_key_file_new(); + if (!g_key_file_load_from_file(gkf, _config_file_name, G_KEY_FILE_NONE, NULL)) + { + printf("ERROR: Config file not found: %s\n", _config_file_name); + exit(EXIT_FAILURE); + } + + g_key_file_set_integer(gkf, image->name, "rid", image->rid); + g_key_file_set_string(gkf, image->name, "file", image->file); + //g_key_file_set_string(gkf, image->name, "servers", image->serverss); // TODO: Save servers as string + g_key_file_set_string(gkf, image->name, "cache", image->cache_file); + + gchar* data = g_key_file_to_data(gkf, NULL, NULL); + + int f = fopen(_config_file_name, "w"); + if (f >= 0) + { + fputs((char*) data, f); + fclose(f); + pthread_spin_unlock(&_spinlock); + g_free(data); + g_key_file_free(gkf); + memlogf("[INFO] Added new image '%s' (rid %d)", newimage->name, newimage->rid); + return 0; + } + pthread_spin_unlock(&_spinlock); + g_free(data); + g_key_file_free(gkf); + memlogf("[ERROR] Image added, but config file is not writable (%s)", _config_file_name); + return ERROR_SEE_LOG; } 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"); - return ERROR_RID; - } - - dnbd3_image_t* tmp = dnbd3_get_image(image->vid, image->rid); - if (!tmp) - { - printf("ERROR: Image not found: (%d,%d)\n", image->vid, image->rid); - return ERROR_IMAGE_NOT_FOUND; - } - - GSList *iterator = NULL; - for (iterator = _dnbd3_clients; iterator; iterator = iterator->next) - { - dnbd3_client_t *client = iterator->data; - if (tmp == client->image) - { - printf("ERROR: Delete is not allowed, image is in use (%d,%d)\n", tmp->vid, tmp->rid); - return ERROR_IMAGE_IN_USE; - } - } - - GKeyFile* gkf; - gkf = g_key_file_new(); - if (!g_key_file_load_from_file(gkf, file, G_KEY_FILE_NONE, NULL)) - { - printf("ERROR: Config file not found: %s\n", file); - exit(EXIT_FAILURE); - } - - g_key_file_remove_group(gkf, tmp->group, NULL); - gchar* data = g_key_file_to_data(gkf, NULL, NULL); - - FILE* f = fopen(file,"w"); - if (f) - { - fputs((char*) data, f); - fclose(f); - g_free(data); - g_key_file_free(gkf); - // TODO: unlink image file - return 0; - } - else - { - g_free(data); - g_key_file_free(gkf); - printf("ERROR: Config file is not writable: %s\n", file); - return ERROR_CONFIG_FILE_PERMISSIONS; - } - */ + if (image->rid == 0) + { + printf("ERROR: Delete with rid=0 is not allowed\n"); + return ERROR_RID; + } + + dnbd3_image_t* tmp = dnbd3_get_image(image->vid, image->rid); + if (!tmp) + { + printf("ERROR: Image not found: (%d,%d)\n", image->vid, image->rid); + return ERROR_IMAGE_NOT_FOUND; + } + + GSList *iterator = NULL; + for (iterator = _dnbd3_clients; iterator; iterator = iterator->next) + { + dnbd3_client_t *client = iterator->data; + if (tmp == client->image) + { + printf("ERROR: Delete is not allowed, image is in use (%d,%d)\n", tmp->vid, tmp->rid); + return ERROR_IMAGE_IN_USE; + } + } + + GKeyFile* gkf; + gkf = g_key_file_new(); + if (!g_key_file_load_from_file(gkf, file, G_KEY_FILE_NONE, NULL)) + { + printf("ERROR: Config file not found: %s\n", file); + exit(EXIT_FAILURE); + } + + g_key_file_remove_group(gkf, tmp->group, NULL); + gchar* data = g_key_file_to_data(gkf, NULL, NULL); + + FILE* f = fopen(file,"w"); + if (f) + { + fputs((char*) data, f); + fclose(f); + g_free(data); + g_key_file_free(gkf); + // TODO: unlink image file + return 0; + } + else + { + g_free(data); + g_key_file_free(gkf); + printf("ERROR: Config file is not writable: %s\n", file); + return ERROR_CONFIG_FILE_PERMISSIONS; + } + */ } dnbd3_image_t* dnbd3_get_image(char *name_orig, int rid, const char do_lock) { - dnbd3_image_t *result = NULL, *image; + 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 (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; + 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!\n"); + memlogf("ERROR: SIGPIPE received!\n"); } void dnbd3_handle_sigterm(int signum) { - memlogf("INFO: SIGTERM or SIGINT received!\n"); - dnbd3_cleanup(); + memlogf("INFO: SIGTERM or SIGINT received!\n"); + 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, gchar **servers, gsize num_servers) +{ + int j, k; + 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->name = calloc(strlen(_local_namespace) + strlen(image_name) + 2, sizeof(char)); + sprintf(image->name, "%s/%s", _local_namespace, image_name); + } + else + { + image->name = strdup(image_name); + } + + if (dnbd3_get_image(image->name, rid, 0)) + { + memlogf("[ERROR] Duplicate image in config: '%s' rid:%d", image->name, rid); + goto error; + } + + image->low_name = strdup(image->name); + strtolower(image->low_name); + + image->rid = rid; + const char relayed = (image_file == NULL || image_file == '\0'); + + if (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; + } + + // A list of servers that are known to also host or relay this image + if (servers) + for (k = 0, j = 0; j < MIN(num_servers, NUMBER_SERVERS); ++j) + { + if (parse_address(servers[j], &(image->servers[k].hostaddrtype), image->servers[k].hostaddr, + &(image->servers[k].port))) + { + ++k; + continue; + } + image->servers[k].hostaddrtype = 0; + } + + 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 = (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) + 5]; + strcpy(tmp, image->cache_file); + strcat(tmp, ".map"); + fd = open(tmp, O_RDONLY); // TODO: Check if map file has expected size + 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 < blocks_in_last_byte; ++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] Instantly 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 + return image; +error: + // Free stuff. Some pointers might be zero, but calling free() on those is safe. + free(image->cache_map); + free(image->name); + free(image->low_name); + free(image->file); + free(image->cache_file); + g_free(image); + return NULL; } diff --git a/src/server/utils.h b/src/server/utils.h index ec83e64..9f709d5 100644 --- a/src/server/utils.h +++ b/src/server/utils.h @@ -26,13 +26,17 @@ #ifndef UTILS_H_ #define UTILS_H_ -#define ERROR_FILE_NOT_FOUND 1 -#define ERROR_IMAGE_ALREADY_EXISTS 2 -#define ERROR_CONFIG_FILE_PERMISSIONS 3 -#define ERROR_IMAGE_NOT_FOUND 4 -#define ERROR_RID 5 -#define ERROR_IMAGE_IN_USE 6 -#define ERROR_UNKNOWN 10 +#define ERROR_FILE_NOT_FOUND 1 +#define ERROR_IMAGE_ALREADY_EXISTS 2 +#define ERROR_CONFIG_FILE_PERMISSIONS 3 +#define ERROR_IMAGE_NOT_FOUND 4 +#define ERROR_RID 5 +#define ERROR_IMAGE_IN_USE 6 +#define ERROR_MISSING_ARGUMENT 7 +#define ERROR_UNSPECIFIED_ERROR 8 +#define ERROR_INVALID_XML 9 +#define ERROR_UNKNOWN_COMMAND 10 +#define ERROR_SEE_LOG 11 void dnbd3_load_config(char *file); int dnbd3_add_image(dnbd3_image_t *image, char *file); -- cgit v1.2.3-55-g7522