From 93b9103f7383d400616d222606c294e07b16e1aa Mon Sep 17 00:00:00 2001 From: Lars Müller Date: Sat, 1 Mar 2008 18:30:38 +0000 Subject: Import dnbd* from the former openslx-contrib repo as of revision 92. openslx-contrib is currently read only and will get removed in some days. git-svn-id: http://svn.openslx.org/svn/openslx/contrib/dnbd2/trunk@1592 95ad53e4-c205-0410-b2fa-d234c58c8868 --- server/Makefile | 30 ++++++++ server/config.c | 213 ++++++++++++++++++++++++++++++++++++++++++++++++++++ server/config.h | 13 ++++ server/file.c | 65 ++++++++++++++++ server/file.h | 28 +++++++ server/main.c | 227 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ server/query.c | 74 ++++++++++++++++++ server/query.h | 14 ++++ server/tree.c | 67 +++++++++++++++++ server/tree.h | 37 +++++++++ 10 files changed, 768 insertions(+) create mode 100644 server/Makefile create mode 100644 server/config.c create mode 100644 server/config.h create mode 100644 server/file.c create mode 100644 server/file.h create mode 100644 server/main.c create mode 100644 server/query.c create mode 100644 server/query.h create mode 100644 server/tree.c create mode 100644 server/tree.h (limited to 'server') diff --git a/server/Makefile b/server/Makefile new file mode 100644 index 0000000..a41d6da --- /dev/null +++ b/server/Makefile @@ -0,0 +1,30 @@ +# +# server/Makefile +# + +CPPFLAGS = -I../include +CFLAGS = -Wall -O2 -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 +VPATH = ../include +APP = dnbd2-server + +all: $(APP) + +install: $(APP) + cp $(APP) /usr/local/sbin + +dnbd2-server: main.o config.o query.o tree.o file.o + $(CC) -o $@ $^ + +main.o: config.h query.h tree.h file.h dnbd2.h + +config.o: config.h tree.h file.h dnbd2.h + +query.o: query.h tree.h file.h dnbd2.h + +tree.o: tree.h dnbd2.h + +file.o: file.h + +.PHONY: +clean: + $(RM) *.o *~ $(APP) diff --git a/server/config.c b/server/config.c new file mode 100644 index 0000000..5dec41e --- /dev/null +++ b/server/config.c @@ -0,0 +1,213 @@ +/* + * server/config.c + */ + + +#include +#include +#include +#include +#include +#include "dnbd2.h" +#include "tree.h" +#include "file.h" +#include "config.h" + + +char *get_line(char *buf, FILE *file); +dataset_t *parse_dataset_file(const char *filename); +int parse_server(dnbd2_server_t *server, const char *str); + + +/* + * The config file looks like this: + * + * 192.168.178.120 + * 5000 + * /etc/dnbd2/datasets/debian-3.1 192.168.178.119:5005 + * /etc/dnbd2/datasets/suse-10.2 192.168.178.119:5005 192.168.178.118:5005 + * ... + * + */ +int parse_config_file(const char *filename, struct sockaddr_in *sockaddr, + void **tree) +{ + char buf[LINE_SIZE_MAX]; + FILE *file; + + bzero(sockaddr, sizeof(sockaddr)); + sockaddr->sin_family = AF_INET; + + /* Open file. */ + file = fopen(filename, "r"); + if (!file) { + syslog(LOG_ERR, "Could not open config file %s", filename); + return -1; + } + syslog(LOG_NOTICE, "Config file: %s", filename); + + /* Read IP from file. */ + if (!get_line(buf, file) || !inet_aton(buf, &(sockaddr->sin_addr))) { + syslog(LOG_ERR, "Could not read IP."); + return -1; + } + + /* Read port number from file. */ + if (!get_line(buf, file) || + sscanf(buf, "%hu", &(sockaddr->sin_port)) != 1) { + syslog(LOG_ERR, "Could not read port number."); + return -1; + } + sockaddr->sin_port = htons(sockaddr->sin_port); + + syslog(LOG_NOTICE, "IP = %s, Port = %hu", + inet_ntoa(sockaddr->sin_addr), ntohs(sockaddr->sin_port)); + + int datasets = 0; + char server[ALT_SERVERS_MAX][LINE_SIZE_MAX]; + char dsfile[LINE_SIZE_MAX]; + while (get_line(buf, file)) { + + int ret = sscanf(buf, "%s %s %s %s %s", + dsfile, + server[0], + server[1], + server[2], + server[3]) - 1; + + /* Parse a dataset file and put it into a tree-node. */ + dataset_t *dataset = parse_dataset_file(dsfile); + if (!dataset) + goto out_nodsfile; + node_t *data = (node_t *)malloc(sizeof(node_t)); + if (!data) { + syslog(LOG_ERR, + "Could not allocate memory for new Dataset."); + goto out_nodataset; + } + int fd = file_open(dataset->path); + if (fd == -1) { + syslog(LOG_ERR, + "Could not open file or block device %s", + dataset->path); + goto out_nodata; + } + strncpy(data->path, dsfile, FILE_NAME_MAX); + data->ds = dataset; + data->fd = fd; + + /* Parse the list of alterntive servers. */ + int i; + int cnt = 0; + for (i=0 ; iserver[cnt], server[i]); + data->servers = cnt; + + /* Check if the Volume-ID-Release-ID pair is already in use. */ + node_t *data2 = tree_find(data, tree); + if (data2) { + syslog(LOG_ERR, + "Vol-ID/Rel-ID already used in Dataset file %s", + data2->path); + goto out_nodata; + } + + /* Insert Dataset into tree. */ + if (tree_insert(data, tree) == -1) { + syslog(LOG_ERR, "Could not insert Dataset into tree."); + goto out_nodata; + } + + datasets++; + continue; + + out_nodata: + free(data); + out_nodataset: + free(dataset); + out_nodsfile: + syslog(LOG_ERR, "Problem parsing %s", dsfile); + } + + syslog(LOG_NOTICE, "Loaded %d Dataset(s).", datasets); + + fclose(file); + return datasets; +} + + +/* + * A dataset config file looks like this: + * + * /path/to/file/or/block/device + * Volume-ID + * Release-ID + */ +dataset_t *parse_dataset_file(const char *filename) +{ + char buf[LINE_SIZE_MAX]; + + FILE *file = fopen(filename, "r"); + if (!file) { + syslog(LOG_ERR, "Could not open Dataset file %s", filename); + return NULL; + } + + dataset_t *dataset = (dataset_t *) malloc(sizeof(dataset_t)); + if (!dataset) { + syslog(LOG_ERR, "Could not allocate memory for new Dataset."); + return NULL; + } + + /* Read file- or block device name from file. */ + if (!get_line(dataset->path, file)) { + syslog(LOG_ERR, "Could not read path to file or block device"); + return NULL; + } + + /* Read Volume-ID from file. */ + if (!get_line(buf, file) || sscanf(buf, "%hu", &(dataset->vid)) != 1) { + syslog(LOG_ERR, "Could not read Volume-ID."); + return NULL; + } + + /* Read Release-ID from file. */ + if (!get_line(buf, file) || sscanf(buf, "%hu", &(dataset->rid)) != 1) { + syslog(LOG_ERR, "Could not read Release-ID."); + return NULL; + } + + fclose(file); + return dataset; +} + + +char *get_line(char *buf, FILE *file) +{ + char *ret = fgets(buf, LINE_SIZE_MAX, file); + + /* change \n with \0 */ + if (ret) + buf[strlen(buf)-1] = '\0'; + + return ret; +} + + +int parse_server(dnbd2_server_t *server, const char *str) +{ + char ip[LINE_SIZE_MAX]; + uint16_t port; + struct in_addr tmp; + + if (sscanf(str, "%[^:]:%hu", ip, &port) != 2) + return 0; + + if (!inet_aton(ip, &tmp)) + return 0; + + memcpy(&server->ip, &tmp, sizeof(uint32_t)); + server->port = htons(port); + + return 1; +} diff --git a/server/config.h b/server/config.h new file mode 100644 index 0000000..dc9cff1 --- /dev/null +++ b/server/config.h @@ -0,0 +1,13 @@ +/* + * server/config.h + */ + + +/* + * Parse the server configuration file, create a socket address + * structure and a tree of datasets. + * + * Returns: Number of Datasets loaded or -1 on failure. + */ +int parse_config_file(const char *filename, struct sockaddr_in *sockaddr, + void **tree); diff --git a/server/file.c b/server/file.c new file mode 100644 index 0000000..26c3cd0 --- /dev/null +++ b/server/file.c @@ -0,0 +1,65 @@ +/* + * server/file.c + */ + + +#include +#include +#include +#include "file.h" + + +int file_open(char *filename) +{ + int fd = open(filename, O_RDONLY); + if (fd == -1) + return -1; + + struct stat st; + if (fstat(fd, &st) == -1) + return -1; + + return fd; +} + + +int file_getsize(int fd, off_t *size) +{ + *size = lseek64(fd, 0, SEEK_END); + + if (*size == -1) + return -1; + + return 0; +} + + +int file_read(int fd, void *buf, size_t size, off_t pos) +{ + off_t newpos = lseek(fd, pos, SEEK_SET); + + if (newpos == -1) + return -1; + + size_t nleft = size; + ssize_t nread; + char *ptr = buf; + + while (nleft > 0) { + if ((nread = read(fd, ptr, nleft)) < 0) { + if (errno == EINTR) + continue; + + return -1; + } + + if (nread == 0) { + break; + } + + nleft -= nread; + ptr += nread; + } + + return 0; +} diff --git a/server/file.h b/server/file.h new file mode 100644 index 0000000..720cf9b --- /dev/null +++ b/server/file.h @@ -0,0 +1,28 @@ +/* + * server/file.h - Functions to work with files. + */ + + +/* + * Open a file and test it with stat. + * + * Returns: File descriptor on success or -1 on failure. + */ +int file_open(char *filename); + + +/* + * Store in @size the size in bytes of the file pointed to by @fd. + * + * Returns: 0 on success -1 on failure; + * + */ +int file_getsize(int fd, off_t *size); + + +/* + * Copy @size bytes of @fd, starting at @pos, into @buf. + * + * Returns: 0 on success -1 on failure. + */ +int file_read(int fd, void *buf, size_t size, off_t pos); diff --git a/server/main.c b/server/main.c new file mode 100644 index 0000000..5a923a9 --- /dev/null +++ b/server/main.c @@ -0,0 +1,227 @@ +/* + * server/main.c + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dnbd2.h" +#include "tree.h" +#include "file.h" +#include "config.h" +#include "query.h" + + +char *cfile = NULL; /* path to config file */ +void *tree = NULL; /* tree containing datasets */ +int server_fd; /* file descriptor for UPD communication */ +struct sockaddr_in server_addr; + + +/* + * Flags and signal handlers. + */ +int sig_exit, sig_config; +void exit_handler(int sig); +void config_handler(int sig); + + +/* + * Function to configure server. We call it with LOAD on + * initialization and with RELOAD when SIGHUP is caught. + */ +enum config_action { LOAD, RELOAD }; +int configure(enum config_action action); + + +void print_usage(void); +int daemon_init(void); + + +int main(int argc, char **argv) +{ + int ret; + ssize_t n; + socklen_t len; + dnbd2_data_reply_t reply; + dnbd2_data_request_t request; + struct sockaddr_in client_addr; + struct sigaction exit_act, config_act; + + if (argc != 2) { + print_usage(); + return -1; + } + + cfile = argv[1]; + openlog(argv[0], LOG_PID, LOG_LOCAL2); + syslog(LOG_NOTICE, "Starting DNBD2 Server."); + + /* Load datasets and bind to socket. */ + if (configure(LOAD) == -1) + goto out_no_start; + + /* Daemonize. */ + ret = daemon_init(); + if (ret == -1) { + syslog(LOG_ERR, "Could not fork and background."); + goto out_no_start; + } + + /* Setup signal handlers. */ + sigaction(SIGTERM, NULL, &exit_act); + exit_act.sa_flags &= ~SA_RESTART; + exit_act.sa_handler = exit_handler; + + sigaction(SIGHUP, NULL, &config_act); + config_act.sa_flags &= ~SA_RESTART; + config_act.sa_handler = config_handler; + + sigaction(SIGTERM, &exit_act, NULL); + sigaction(SIGINT, &exit_act, NULL); + sigaction(SIGHUP, &config_act, NULL); + + while (1) { + if (sig_exit) { + syslog(LOG_NOTICE, "Stopping Server."); + exit(0); + } + if (sig_config) { + syslog(LOG_NOTICE, "Reloading configuration."); + if (configure(RELOAD) == -1) + syslog(LOG_ERR, "Not using new configuration."); + sig_config = 0; + } + + /* Receive request. */ + len = sizeof(client_addr); + n = recvfrom(server_fd, &request, sizeof(request), 0, + &client_addr, &len); + + if (n == -1 || n != sizeof(request)) + continue; + + /* Process request. */ + ret = handle_query(&request, &reply, &tree); + if (ret == -1) + continue; + + /* Send reply. */ + sendto(server_fd, &reply, sizeof(reply), 0, &client_addr, len); + } + + return 0; + + out_no_start: + syslog(LOG_ERR, "Server not started."); + fprintf(stderr, "Server not started - " + "consult your syslog for errors.\n"); + return -1; +} + + +void config_handler(int sig) +{ + sig_config = 1; +} + + +void exit_handler(int sig) +{ + sig_exit = 1; +} + + +int cmp_addr(struct sockaddr_in addr1, struct sockaddr_in addr2) +{ + int diff = memcmp(&addr1.sin_addr.s_addr, + &addr2.sin_addr.s_addr, + sizeof(in_addr_t)); + + if (diff) + return diff; + + return memcmp(&addr1.sin_port, + &addr2.sin_port, + sizeof(in_port_t)); +} + + +int configure(enum config_action action) +{ + void *tmp_tree = NULL; + int datasets, ret, tmp_fd = server_fd; + struct sockaddr_in tmp_addr; + + datasets = parse_config_file(cfile, &tmp_addr, &tmp_tree); + if (datasets <= 0) + return -1; + + /* Create a socket and bind to it. */ + if (action == LOAD || cmp_addr(tmp_addr, server_addr)) { + tmp_fd = socket(PF_INET, SOCK_DGRAM, 0); + if (tmp_fd == -1) { + syslog(LOG_ERR, "Could not create socket."); + return -1; + } + + ret = bind(tmp_fd, (struct sockaddr *) &tmp_addr, + sizeof(tmp_addr)); + + if (ret == -1) { + close(tmp_fd); + syslog(LOG_ERR, "Could not assign name to socket."); + return -1; + } + } + + /* Make new socket available and close the old one if necesary. */ + if (action == RELOAD && cmp_addr(tmp_addr, server_addr)) { + if (close(server_fd) == -1 ) { + syslog(LOG_ERR, "Could not close socket."); + } + } + + server_fd = tmp_fd; + server_addr = tmp_addr; + + tree_destroy(tree); + tree = tmp_tree; + return 0; +} + + +void print_usage(void) +{ + printf("usage: dnbd2-dserver config-file\n"); +} + + +int daemon_init(void) +{ + pid_t pid = fork(); + + if (pid == -1) + return pid; + + if (pid != 0) { + /* We are the parent. */ + exit(0); + } + + /* We are the child. */ + setsid(); + chdir("/"); + umask(0); + + return 0; +} diff --git a/server/query.c b/server/query.c new file mode 100644 index 0000000..16f48b6 --- /dev/null +++ b/server/query.c @@ -0,0 +1,74 @@ +/* + * server/query.c + */ + + +#include +#include +#include +#include +#include +#include +#include +#include "dnbd2.h" +#include "query.h" +#include "tree.h" +#include "file.h" + + +int handle_query(dnbd2_data_request_t *request, + dnbd2_data_reply_t *reply, + void **tree) +{ + int fd, i; + node_t node1; + node_t *node2; + dataset_t ds; + off_t size, pos; + uint16_t cmd; + + /* Fetch the right fd for this vid/rid pair. */ + ds.vid = ntohs(request->vid); + ds.rid = ntohs(request->rid); + node1.ds = &ds; + node2 = tree_find(&node1, tree); + if (!node2) + return -1; + fd = node2->fd; + + cmd = ntohs(request->cmd); + switch (cmd) { + case CMD_GET_BLOCK: + reply->num = request->num; + pos = ntohll(request->num); + file_read(fd, reply->payload.data, DNBD2_BLOCK_SIZE, pos); + break; + + case CMD_GET_SIZE: + if (file_getsize(fd, &size) == -1) + return -1; + reply->num = htonll(size); + break; + + case CMD_GET_SERVERS: + /* Fetch a random block to deliver a more realistic RTT. */ + pos = 0; + if (!file_getsize(fd, &size)) + pos = (off_t) (size * (rand() / (RAND_MAX + 1.0))); + file_read(fd, reply->payload.data, DNBD2_BLOCK_SIZE, pos); + reply->num = htonll(node2->servers); + for (i=0 ; iservers ; i++) { + memcpy(&reply->payload.server[i], + &node2->server[i], + sizeof(dnbd2_server_t)); + } + break; + } + + reply->cmd = request->cmd; + reply->time = request->time; + reply->vid = request->vid; + reply->rid = request->rid; + + return 0; +} diff --git a/server/query.h b/server/query.h new file mode 100644 index 0000000..ecf3969 --- /dev/null +++ b/server/query.h @@ -0,0 +1,14 @@ +/* + * server/query.h + */ + + +/* + * Builds a @reply based on @request, using the datasets stored in + * @tree. + * + * Returns: 0 on success or -1 on failure. + */ +int handle_query(dnbd2_data_request_t *request, + dnbd2_data_reply_t *reply, + void **tree); diff --git a/server/tree.c b/server/tree.c new file mode 100644 index 0000000..3daf6df --- /dev/null +++ b/server/tree.c @@ -0,0 +1,67 @@ +/* + * server/tree.c + */ + + +#include +#include +#include +#include "dnbd2.h" +#include "tree.h" + + +int compare_node(const void *node1, const void *node2) +{ + + dataset_t *ds1 = ((node_t *) node1)->ds; + dataset_t *ds2 = ((node_t *) node2)->ds; + + int diff = ds1->vid - ds2->vid; + if (diff != 0) + return diff; + + return ds1->rid - ds2->rid; +} + + +void destroy_node(void *node) +{ + free(((node_t *) node)->ds); + free((node_t *) node); +} + + +int tree_insert(node_t *data, void **tree) +{ + void *tmp = tsearch((void *)data, tree, compare_node); + if (!tmp) + return -1; + + node_t *ret = *(node_t **) tmp; + + /* Check if there is another item + in the tree with the same key. */ + if (ret != data) + return -1; + + return 0; +} + + +node_t *tree_find(node_t *data, void **tree) +{ + void *tmp = tfind((void *)data, tree, compare_node); + + if (!tmp) + return NULL; + + node_t *ret = *(node_t **) tmp; + + return ret; +} + + +void tree_destroy(void **tree) +{ + tdestroy(tree, &destroy_node); +} diff --git a/server/tree.h b/server/tree.h new file mode 100644 index 0000000..e6eea84 --- /dev/null +++ b/server/tree.h @@ -0,0 +1,37 @@ +/* + * server/tree.h - Mechanism to store Datasets in binary trees. + */ + + +/* + * This structure represents a Dataset along with its config file and + * file descriptor. This type of items will be stored in a binary + * tree. The key to this structure is the Dataset's Volume-ID and + * Release-ID (they must be unique in each server instance). + */ +typedef struct node { + dataset_t *ds; + char path[FILE_NAME_MAX]; + int fd; + int servers; + dnbd2_server_t server[ALT_SERVERS_MAX]; +} node_t; + + +/* + * Returns: 0 on success or -1 on failure. + */ +int tree_insert(node_t *data, void **tree); + + +/* + * Returns: Pointer to item on search-hit or NULL on search-miss. + */ +node_t *tree_find(node_t *data, void **tree); + + +/* + * Free all resources used by the tree. Useful when reloading + * datasets. + */ +void tree_destroy(void **tree); -- cgit v1.2.3-55-g7522